web aliasing of accounting and others
This commit is contained in:
@@ -17,8 +17,8 @@ use crate::{
|
||||
};
|
||||
|
||||
/// The compound type that also brings a system column with it. Which columns
|
||||
/// it generates is the catalog's answer; the account foreign key is not one of
|
||||
/// them, and [`TableDraft::preview_rows`] is where that is explained.
|
||||
/// it generates is the catalog's answer; the physical `account_id` is not one
|
||||
/// of them, and [`TableDraft::preview_rows`] is where that is explained.
|
||||
pub(crate) const ACCOUNTING_FIELD_TYPE: &str = "accounting";
|
||||
|
||||
/// The compound type whose connectors remain tied to their backend names.
|
||||
@@ -237,13 +237,19 @@ impl TableDraft {
|
||||
if column.data_type == ACCOUNTING_TRANSFER_FIELD_TYPE {
|
||||
continue;
|
||||
}
|
||||
names.extend(
|
||||
self.columns
|
||||
.generated_columns_of(index)
|
||||
.iter()
|
||||
.map(|generated| generated.name.clone()),
|
||||
);
|
||||
if column.data_type == ACCOUNTING_FIELD_TYPE {
|
||||
let generated: Vec<String> = self
|
||||
.columns
|
||||
.generated_columns_of(index)
|
||||
.iter()
|
||||
.map(|generated| generated.name.clone())
|
||||
.collect();
|
||||
// A backend that reports `account` with the rest of ACCOUNTING's
|
||||
// companions has already named it here; offering it a second time
|
||||
// for the foreign key would put two fields on one column, and only
|
||||
// the first of the two would be read.
|
||||
let account_reported = generated.iter().any(|name| name == ACCOUNT_API_COLUMN);
|
||||
names.extend(generated);
|
||||
if column.data_type == ACCOUNTING_FIELD_TYPE && !account_reported {
|
||||
names.push(ACCOUNT_API_COLUMN.to_string());
|
||||
}
|
||||
}
|
||||
@@ -831,8 +837,10 @@ mod tests {
|
||||
"tax_point_date",
|
||||
"debit",
|
||||
"credit",
|
||||
// The account foreign key, which the catalog does not report
|
||||
// because it is a system column rather than a generated one.
|
||||
// The foreign key to the profile's accounts, once as the
|
||||
// column the backend reports and once as the physical column
|
||||
// it is stored in.
|
||||
"account",
|
||||
"account_id",
|
||||
"created_at",
|
||||
]
|
||||
|
||||
@@ -231,7 +231,15 @@ impl AddTablePageState {
|
||||
// backend names and remain the exception.
|
||||
let aliasable = column.data_type != ACCOUNTING_TRANSFER_FIELD_TYPE;
|
||||
|
||||
for generated in columns.generated_columns_of(index) {
|
||||
let generated_columns = columns.generated_columns_of(index);
|
||||
// Whether the backend already reports `account` as one of them, in
|
||||
// which case the foreign-key row below is a second view of a column
|
||||
// that is named once, not a column of its own to name again.
|
||||
let account_reported = generated_columns
|
||||
.iter()
|
||||
.any(|generated| generated.name == ACCOUNT_API_COLUMN);
|
||||
|
||||
for generated in generated_columns {
|
||||
let mut tags = vec![format!("generated by {}", column.data_type)];
|
||||
if generated.inherits_currency {
|
||||
tags.push(format!("{}, {}", column.currency, column.money_mode.label()));
|
||||
@@ -250,9 +258,10 @@ impl AddTablePageState {
|
||||
});
|
||||
}
|
||||
|
||||
// The account foreign key is a system column rather than a
|
||||
// generated user column, so the catalog does not report it; the
|
||||
// same explanation as in `TableDraft::preview_rows`.
|
||||
// The physical column the account foreign key is stored in. It is
|
||||
// a system column, so it is listed whether or not the catalog
|
||||
// reports `account` among the generated ones — but it is named
|
||||
// there when it does, never twice.
|
||||
if column.data_type == ACCOUNTING_FIELD_TYPE {
|
||||
rows.push(ColumnRow {
|
||||
index: None,
|
||||
@@ -269,7 +278,7 @@ impl AddTablePageState {
|
||||
self.draft.generated_display_name(ACCOUNT_API_COLUMN)
|
||||
),
|
||||
],
|
||||
alias_source: Some(ACCOUNT_API_COLUMN.to_string()),
|
||||
alias_source: (!account_reported).then(|| ACCOUNT_API_COLUMN.to_string()),
|
||||
alias: self.draft.alias_for(ACCOUNT_API_COLUMN).to_string(),
|
||||
});
|
||||
}
|
||||
@@ -277,6 +286,33 @@ impl AddTablePageState {
|
||||
rows
|
||||
}
|
||||
|
||||
/// The generated columns that may be renamed, in the order they are
|
||||
/// listed.
|
||||
///
|
||||
/// Renaming lives in a section of its own rather than in the column list:
|
||||
/// the list is where the table is read, and a text box in every other row
|
||||
/// of it turns reading into scanning past inputs.
|
||||
/// One field per generated column and no more: a source named twice would
|
||||
/// be two fields over one column, and the draft reads the first of them,
|
||||
/// so whatever was typed in the second would be dropped without a word.
|
||||
pub(crate) fn alias_rows(&self) -> Vec<AliasRow> {
|
||||
let mut rows: Vec<AliasRow> = Vec::new();
|
||||
for row in self.column_rows() {
|
||||
let Some(source) = row.alias_source else {
|
||||
continue;
|
||||
};
|
||||
if rows.iter().any(|existing| existing.source == source) {
|
||||
continue;
|
||||
}
|
||||
rows.push(AliasRow {
|
||||
source,
|
||||
alias: row.alias,
|
||||
data_type: row.data_type,
|
||||
});
|
||||
}
|
||||
rows
|
||||
}
|
||||
|
||||
/// Row-display candidates: `id` first, then every column, matching the
|
||||
/// client's candidate list.
|
||||
pub(crate) fn row_display_candidates(&self) -> Vec<RowDisplayCandidate> {
|
||||
@@ -331,6 +367,16 @@ pub(crate) struct ColumnRow {
|
||||
pub alias: String,
|
||||
}
|
||||
|
||||
/// One line of the "Rename generated columns" section: a generated column the
|
||||
/// request may name, and the name asked for it so far.
|
||||
pub(crate) struct AliasRow {
|
||||
/// The generated column's own name, which is what the rename asks for.
|
||||
pub source: String,
|
||||
/// The alias typed for it, empty when the backend's name is being kept.
|
||||
pub alias: String,
|
||||
pub data_type: String,
|
||||
}
|
||||
|
||||
pub(crate) struct RowDisplayCandidate {
|
||||
pub index: usize,
|
||||
pub name: String,
|
||||
@@ -449,6 +495,7 @@ mod tests {
|
||||
"tax_point_date",
|
||||
"debit",
|
||||
"credit",
|
||||
"account",
|
||||
"account_id",
|
||||
]
|
||||
);
|
||||
@@ -504,7 +551,11 @@ mod tests {
|
||||
let row = |name: &str| rows.iter().find(|row| row.name == name).unwrap();
|
||||
assert_eq!(row("debit").alias_source.as_deref(), Some("debit"));
|
||||
assert_eq!(row("debit").alias, "md");
|
||||
assert_eq!(row("account_id").alias_source.as_deref(), Some("account"));
|
||||
// `account` is reported by the catalog, so it is named there and the
|
||||
// physical column it lands in is not offered a second field.
|
||||
assert_eq!(row("account").alias_source.as_deref(), Some("account"));
|
||||
assert_eq!(row("account").alias, "ucet");
|
||||
assert!(row("account_id").alias_source.is_none());
|
||||
assert!(row("account_id").tags.contains(&"written as ucet".to_string()));
|
||||
assert!(
|
||||
row("accounting").alias_source.is_none(),
|
||||
|
||||
@@ -126,7 +126,8 @@ mod tests {
|
||||
}
|
||||
|
||||
/// The columns an ACCOUNTING row generates get a name field of their own,
|
||||
/// which is what makes them aliasable at creation time.
|
||||
/// which is what makes them aliasable at creation time — in a section of
|
||||
/// their own, not in the column list, which is there to be read.
|
||||
#[test]
|
||||
fn generated_accounting_columns_get_an_alias_field() {
|
||||
let mut page = page();
|
||||
@@ -151,6 +152,38 @@ mod tests {
|
||||
);
|
||||
}
|
||||
assert!(html.contains(r#"name="generated_alias_names""#));
|
||||
// The column list itself carries no input at all.
|
||||
let list = html
|
||||
.split("<h2>Columns")
|
||||
.nth(1)
|
||||
.and_then(|rest| rest.split("</table>").next())
|
||||
.expect("the column list should be rendered");
|
||||
assert!(!list.contains("<input"), "{list}");
|
||||
assert!(html.contains("<summary>Rename generated columns</summary>"));
|
||||
}
|
||||
|
||||
/// A rename is reported where the column is listed, so the list still says
|
||||
/// what the table will look like without the section being opened.
|
||||
#[test]
|
||||
fn a_renamed_generated_column_says_so_in_the_column_list() {
|
||||
let mut page = page();
|
||||
page.draft.columns.added.push(ColumnDefinition {
|
||||
name: "accounting".to_string(),
|
||||
data_type: "accounting".to_string(),
|
||||
indexed: false,
|
||||
quantity_ledger: false,
|
||||
money_mode: MoneyMode::Exact,
|
||||
currency: "EUR".to_string(),
|
||||
});
|
||||
page.draft.generated_aliases = vec![crate::pages::add_table::draft::GeneratedAlias {
|
||||
source: "debit".to_string(),
|
||||
alias: "md".to_string(),
|
||||
}];
|
||||
|
||||
let html = render_builder(&page);
|
||||
|
||||
assert!(html.contains(r#"<span class="tag">renamed to md</span>"#), "{html}");
|
||||
assert!(html.contains(r#"name="generated_alias_names" value="md""#));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user