From 1f8b73793d50e95bfc7a6fb4efdd215c8e303f90 Mon Sep 17 00:00:00 2001 From: Priec Date: Thu, 13 Aug 2026 14:41:27 +0200 Subject: [PATCH] aliasing3 --- client | 2 +- common/proto/table_definition.proto | 19 +++--- common/src/proto/descriptor.bin | Bin 180697 -> 180594 bytes common/src/proto/komp_ac.table_definition.rs | 19 +++--- server | 2 +- web/src/pages/add_table/draft.rs | 62 +++++++++++-------- web/src/pages/add_table/state.rs | 15 ++--- web/src/schema/mod.rs | 53 +++++++++++++--- 8 files changed, 110 insertions(+), 62 deletions(-) diff --git a/client b/client index c923d0de..c15c62e8 160000 --- a/client +++ b/client @@ -1 +1 @@ -Subproject commit c923d0de22b3ed20cd9d0fbd322456c18e7aa338 +Subproject commit c15c62e88b4df273095d9a17ad7655b9d30c92c7 diff --git a/common/proto/table_definition.proto b/common/proto/table_definition.proto index c682a75e..9357a7be 100644 --- a/common/proto/table_definition.proto +++ b/common/proto/table_definition.proto @@ -108,18 +108,17 @@ message PostTableDefinitionRequest { // column, though, so it is free to be anything: this is where that choice is // made, instead of a RenameColumnAlias call afterwards. // -// Only ACCOUNTING's columns may be renamed. Renaming a generated column -// requires its relationship to whatever generated it to be recorded, so the -// rest of the system can find it without knowing its name; ACCOUNTING has that -// in table_accounting_definitions, and nothing else does. The ACCOUNTING_ -// TRANSFER connectors and the PHONE and IBAN companions are refused, the -// latter because a row write finds them by rebuilding their names from their -// parent column's name. +// ACCOUNTING, PHONE and IBAN generated columns may be renamed: their +// relationships are recorded by physical column, so the rest of the system can +// find them without knowing their display names. ACCOUNTING_TRANSFER connectors +// are the exception and are refused here exactly as RenameColumnAlias refuses +// them. message GeneratedColumnAlias { // The name the backend would otherwise give the column: one of ACCOUNTING's - // "name", "tax_point_date", "debit", "credit" or "account". Must name a - // column the request really generates -- an alias for anything else is - // rejected rather than ignored, so a typo cannot pass silently. + // "name", "tax_point_date", "debit", "credit" or "account", or a PHONE or + // IBAN companion such as "work_phone_ext". Must name a column the request + // really generates -- an alias for anything else is rejected rather than + // ignored, so a typo cannot pass silently. string generated_name = 1; // What the column should be called instead. Same rules as any column name. diff --git a/common/src/proto/descriptor.bin b/common/src/proto/descriptor.bin index 5d420cabab87030535c547577c0b9d559d02260f..47496694e557c229ad930359a3d8132f57ea6464 100644 GIT binary patch delta 7431 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 delta 7573 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 ZUhn{+vCJ!u{s90&VOh%?f1=%&{}1Tm3m*Ug diff --git a/common/src/proto/komp_ac.table_definition.rs b/common/src/proto/komp_ac.table_definition.rs index 75c11b64..5f7a9cce 100644 --- a/common/src/proto/komp_ac.table_definition.rs +++ b/common/src/proto/komp_ac.table_definition.rs @@ -56,20 +56,19 @@ pub struct PostTableDefinitionRequest { /// column, though, so it is free to be anything: this is where that choice is /// made, instead of a RenameColumnAlias call afterwards. /// -/// Only ACCOUNTING's columns may be renamed. Renaming a generated column -/// requires its relationship to whatever generated it to be recorded, so the -/// rest of the system can find it without knowing its name; ACCOUNTING has that -/// in table_accounting_definitions, and nothing else does. The ACCOUNTING\_ -/// TRANSFER connectors and the PHONE and IBAN companions are refused, the -/// latter because a row write finds them by rebuilding their names from their -/// parent column's name. +/// ACCOUNTING, PHONE and IBAN generated columns may be renamed: their +/// relationships are recorded by physical column, so the rest of the system can +/// find them without knowing their display names. ACCOUNTING_TRANSFER connectors +/// are the exception and are refused here exactly as RenameColumnAlias refuses +/// them. #[derive(serde::Serialize, serde::Deserialize)] #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct GeneratedColumnAlias { /// The name the backend would otherwise give the column: one of ACCOUNTING's - /// "name", "tax_point_date", "debit", "credit" or "account". Must name a - /// column the request really generates -- an alias for anything else is - /// rejected rather than ignored, so a typo cannot pass silently. + /// "name", "tax_point_date", "debit", "credit" or "account", or a PHONE or + /// IBAN companion such as "work_phone_ext". Must name a column the request + /// really generates -- an alias for anything else is rejected rather than + /// ignored, so a typo cannot pass silently. #[prost(string, tag = "1")] pub generated_name: ::prost::alloc::string::String, /// What the column should be called instead. Same rules as any column name. diff --git a/server b/server index 9984e3b2..6e8ac323 160000 --- a/server +++ b/server @@ -1 +1 @@ -Subproject commit 9984e3b272514f51092e049465d040afb0630279 +Subproject commit 6e8ac32389ac7e69556ed0f1afe6fef8c0d68a68 diff --git a/web/src/pages/add_table/draft.rs b/web/src/pages/add_table/draft.rs index e7024140..ccf5ec76 100644 --- a/web/src/pages/add_table/draft.rs +++ b/web/src/pages/add_table/draft.rs @@ -21,6 +21,9 @@ use crate::{ /// 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. +pub(crate) const ACCOUNTING_TRANSFER_FIELD_TYPE: &str = "accounting_transfer"; + /// The virtual field the ACCOUNTING foreign key is written as. It is a column /// like any other once created, so it can be aliased too. pub(crate) const ACCOUNT_API_COLUMN: &str = "account"; @@ -225,18 +228,13 @@ impl TableDraft { /// The generated columns this draft would let the user rename, in the order /// they appear in the column list. /// - /// ACCOUNTING's, and only ACCOUNTING's. A generated column can carry a name - /// of its own once the rest of the system can find it without that name -- - /// which for ACCOUNTING is `table_accounting_definitions`, recording its - /// columns by physical name. The ACCOUNTING_TRANSFER connectors and the - /// PHONE and IBAN companions have no such record: a companion is found on - /// write by rebuilding its name from its parent's, so renaming one would - /// hide it from the write path. The backend refuses those, and this does - /// not offer what the backend refuses. + /// Every generated column whose relationship is recorded independently of + /// its display name. ACCOUNTING_TRANSFER's connectors are the exception: + /// the backend still resolves those by their fixed names. pub(crate) fn aliasable_generated_columns(&self) -> Vec { let mut names = Vec::new(); for (index, column) in self.columns.added.iter().enumerate() { - if column.data_type != ACCOUNTING_FIELD_TYPE { + if column.data_type == ACCOUNTING_TRANSFER_FIELD_TYPE { continue; } names.extend( @@ -245,7 +243,9 @@ impl TableDraft { .iter() .map(|generated| generated.name.clone()), ); - names.push(ACCOUNT_API_COLUMN.to_string()); + if column.data_type == ACCOUNTING_FIELD_TYPE { + names.push(ACCOUNT_API_COLUMN.to_string()); + } } names } @@ -555,24 +555,34 @@ mod tests { ); } - /// Only ACCOUNTING's columns are offered. The transfer connectors are - /// resolved by name by the posting engine, and a phone or IBAN companion is - /// found on write by rebuilding its name from its parent's -- the backend - /// refuses both, so neither is offered here. + /// ACCOUNTING, PHONE and IBAN companions may be aliased. Transfer + /// connectors remain fixed because the posting engine resolves their names. #[test] - fn only_accounting_columns_are_aliasable() { - for (name, data_type) in [ - ("accounting_transfer", "accounting_transfer"), - ("work_phone", "phone"), - ("bank_account", "iban"), - ] { - let draft = draft_with_column(name, data_type); + fn every_recorded_generated_column_is_aliasable() { + let phone = draft_with_column("work_phone", "phone"); + assert_eq!( + phone.aliasable_generated_columns(), + [ + "work_phone_ext", + "work_phone_type", + "work_phone_country", + "work_phone_calling_code", + ] + ); - assert!( - draft.aliasable_generated_columns().is_empty(), - "`{data_type}` should offer no aliases" - ); - } + let iban = draft_with_column("bank_account", "iban"); + assert_eq!( + iban.aliasable_generated_columns(), + [ + "bank_account_country", + "bank_account_bban", + "bank_account_bank_identifier", + "bank_account_branch_identifier", + ] + ); + + let transfer = draft_with_column("accounting_transfer", "accounting_transfer"); + assert!(transfer.aliasable_generated_columns().is_empty()); } #[test] diff --git a/web/src/pages/add_table/state.rs b/web/src/pages/add_table/state.rs index 37caad79..9f7783b7 100644 --- a/web/src/pages/add_table/state.rs +++ b/web/src/pages/add_table/state.rs @@ -13,7 +13,10 @@ use crate::schema::{ColumnCatalog, ColumnDraft, columns_from_rows}; -use super::draft::{ACCOUNT_API_COLUMN, ACCOUNTING_FIELD_TYPE, GeneratedAlias, TableDraft}; +use super::draft::{ + ACCOUNT_API_COLUMN, ACCOUNTING_FIELD_TYPE, ACCOUNTING_TRANSFER_FIELD_TYPE, GeneratedAlias, + TableDraft, +}; /// The `profile_name` option meaning "create a new profile too". pub(crate) const NEW_PROFILE: &str = "__new__"; @@ -223,12 +226,10 @@ impl AddTablePageState { alias: String::new(), }); - // A generated column is named by the backend, but the name is a - // display name, and ACCOUNTING's columns can be given one of the - // user's own in the request that creates them. Only ACCOUNTING's: - // see `TableDraft::aliasable_generated_columns` for why the other - // generated columns cannot be renamed at all. - let aliasable = column.data_type == ACCOUNTING_FIELD_TYPE; + // Every generated column with persisted provenance may be named by + // the request. Transfer connectors are still resolved by fixed + // backend names and remain the exception. + let aliasable = column.data_type != ACCOUNTING_TRANSFER_FIELD_TYPE; for generated in columns.generated_columns_of(index) { let mut tags = vec![format!("generated by {}", column.data_type)]; diff --git a/web/src/schema/mod.rs b/web/src/schema/mod.rs index 7bf36dcf..5298cee3 100644 --- a/web/src/schema/mod.rs +++ b/web/src/schema/mod.rs @@ -664,11 +664,22 @@ impl ColumnDraft { /// compound one. This is what the column list shows underneath it, so the /// columns a definition row brings are visible while the table is still /// being described. - pub(crate) fn generated_columns_of(&self, index: usize) -> &[GeneratedColumn] { - self.added - .get(index) - .map(|column| self.catalog.generated_columns(&column.data_type)) - .unwrap_or_default() + pub(crate) fn generated_columns_of(&self, index: usize) -> Vec { + let Some(column) = self.added.get(index) else { + return Vec::new(); + }; + let generated = self.catalog.generated_columns(&column.data_type); + let default_prefix = format!("{}_", column.data_type); + generated + .iter() + .cloned() + .map(|mut companion| { + if let Some(suffix) = companion.name.strip_prefix(&default_prefix) { + companion.name = format!("{}_{}", column.name, suffix); + } + companion + }) + .collect() } /// Whether a column can be indexed or identify a row. A compound column @@ -1115,7 +1126,23 @@ pub(crate) mod tests { grouped("gtin_12", "gtin"), grouped("gtin_13", "gtin"), grouped("gtin_14", "gtin"), - declarable("iban"), + ColumnType { + generated_columns: vec![ + generated_column("iban_country", "iban_country", false), + generated_column("iban_bban", "iban_bban", false), + generated_column( + "iban_bank_identifier", + "iban_bank_identifier", + false, + ), + generated_column( + "iban_branch_identifier", + "iban_branch_identifier", + false, + ), + ], + ..declarable("iban") + }, ColumnType { declarable: false, ..declarable("iban_bban") @@ -1135,7 +1162,19 @@ pub(crate) mod tests { ..declarable("numeric") }, declarable("period"), - declarable("phone"), + ColumnType { + generated_columns: vec![ + generated_column("phone_ext", "phone_extension", false), + generated_column("phone_type", "phone_type", false), + generated_column("phone_country", "phone_country", false), + generated_column( + "phone_calling_code", + "phone_calling_code", + false, + ), + ], + ..declarable("phone") + }, ColumnType { declarable: false, sql_type: "INTEGER".to_string(),