diff --git a/client b/client index da1557a2..827e6a40 160000 --- a/client +++ b/client @@ -1 +1 @@ -Subproject commit da1557a2d4a08be07f7c3af93529aa240a060744 +Subproject commit 827e6a408a9f3df5f306461d3ade22f08e957c62 diff --git a/common/proto/accounting.proto b/common/proto/accounting.proto index 534324ba..ae298c70 100644 --- a/common/proto/accounting.proto +++ b/common/proto/accounting.proto @@ -76,7 +76,8 @@ service Accounting { message EnsureAccountRequest { string profile_name = 1; - repeated string segments = 2; + // Slash-delimited root-to-leaf account path, for example "123/12/1". + string account = 2; // Currency accepted by source postings to the leaf account. Empty uses the // profile accounting currency. Missing ancestors are created in the profile // accounting currency. @@ -87,9 +88,8 @@ message Account { int64 id = 1; optional int64 parent_account_id = 2; string segment = 3; - // Root-to-leaf path of this account. Same shape as the request that created - // it; the server never joins segments into a delimited code. - repeated string segments = 4; + // Slash-delimited root-to-leaf account path. + string account = 4; string denomination_currency = 5; } @@ -161,9 +161,8 @@ message PostJournalRequest { message JournalLineInput { JournalSide side = 1; - // Parsed account path segments. The client owns presentation parsing; the - // server accepts no delimiter-encoded account string. - repeated string account_segments = 2; + // Slash-delimited root-to-leaf account path. + string account = 2; string amount = 3; string description = 4; } @@ -281,8 +280,8 @@ message JournalLine { int64 id = 1; int32 line_number = 2; JournalSide side = 3; - // Root-to-leaf path of the posted account, as sent on JournalLineInput. - repeated string account_segments = 4; + // Slash-delimited root-to-leaf path of the posted account. + string account = 4; string amount = 5; string description = 6; bool deleted = 7; @@ -379,8 +378,8 @@ message ListPeriodBalancesRequest { message PeriodBalance { int64 period_id = 1; - // Root-to-leaf path of the account this frozen balance belongs to. - repeated string account_segments = 2; + // Slash-delimited root-to-leaf path of this frozen balance's account. + string account = 2; string currency = 3; // Signed nets use debit-positive convention. string opening_balance = 4; @@ -393,7 +392,7 @@ message PeriodDenominationBalance { int64 period_id = 1; // Exact denominated account. Unlike book balances, this quantity is not // rolled up into parent accounts with a different denomination. - repeated string account_segments = 2; + string account = 2; string denomination_currency = 3; // Signed nets use debit-positive convention and remain in the denomination // currency without conversion during carry-forward. @@ -424,13 +423,13 @@ message MapOpeningBalanceAccountRequest { // Open period whose profile receives the opening balance. Its configured // previous_period_id identifies the source profile and period. int64 target_period_id = 1; - repeated string source_account_segments = 2; - repeated string target_account_segments = 3; + string source_account = 2; + string target_account = 3; } message UnmapOpeningBalanceAccountRequest { int64 target_period_id = 1; - repeated string target_account_segments = 2; + string target_account = 2; } message UnmapOpeningBalanceAccountResponse { @@ -453,9 +452,9 @@ message OpeningBalanceAccountMapping { int64 target_period_id = 1; int64 source_period_id = 2; string source_profile_name = 3; - repeated string source_account_segments = 4; + string source_account = 4; string target_profile_name = 5; - repeated string target_account_segments = 6; + string target_account = 6; string currency = 7; // Signed balances use the debit-positive convention. string opening_balance = 8; @@ -478,7 +477,7 @@ message OpeningBalanceAccountMapping { message UnmappedSourceBalance { int64 source_period_id = 1; string source_profile_name = 2; - repeated string source_account_segments = 3; + string source_account = 3; string currency = 4; // Signed portion not covered by a mapping, using the debit-positive // convention. This is never zero and may be less than the account's @@ -490,7 +489,7 @@ message UnmappedSourceBalance { message UnmappedSourceDenominationBalance { int64 source_period_id = 1; string source_profile_name = 2; - repeated string source_account_segments = 3; + string source_account = 3; string denomination_currency = 4; // Exact foreign-currency quantity that no mapping carries forward. string unmapped_quantity = 5; diff --git a/common/proto/table_structure.proto b/common/proto/table_structure.proto index 70ea77b7..df653545 100644 --- a/common/proto/table_structure.proto +++ b/common/proto/table_structure.proto @@ -46,14 +46,14 @@ message GetTableStructureResponse { message TableStructureResponse { // Columns of the physical table, including system columns (id, deleted, // created_at, row_revision), user-defined columns, and any foreign-key columns such as - // named by whoever declared each link, plus the dedicated "account_id" on + // named by whoever declared each link, plus the dedicated "account" on // ACCOUNTING-enabled tables. May be empty if the physical table is missing. repeated TableColumn columns = 1; } // One physical column entry as reported by information_schema. message TableColumn { - // Column name exactly as defined in PostgreSQL. + // Public column name. Physical account_id columns are exposed as account. string name = 1; // Normalized data type string derived from information_schema: diff --git a/common/proto/tables_data.proto b/common/proto/tables_data.proto index 2d95c3fe..968851bb 100644 --- a/common/proto/tables_data.proto +++ b/common/proto/tables_data.proto @@ -62,7 +62,7 @@ service TablesData { // - Validates profile and table definition // - Returns all columns as strings (COALESCE(col::TEXT, '') AS col) // including: id, deleted, row_revision, all user-defined columns, and FK columns - // named by whoever declared each link, plus "account_id" on + // named by whoever declared each link, plus "account" on // ACCOUNTING-enabled tables // - Fails with NOT_FOUND if record does not exist or is soft-deleted // - If the physical table is missing but the definition exists, returns INTERNAL @@ -102,7 +102,7 @@ message PostTableDataRequest { // - System/FK columns: // • "deleted" (BOOLEAN), optional; default FALSE if not provided // • one BIGINT per link, named by whoever declared it - // • "account_id" (BIGINT) on ACCOUNTING-enabled tables + // • "account" (slash-delimited account string) on ACCOUNTING-enabled tables // // Type expectations by SQL type: // - TEXT: string value; empty string is treated as NULL @@ -251,7 +251,7 @@ message GetTableDataResponse { // - id, deleted // - all user-defined columns from the table definition // - one column per link, named by whoever declared it - // - account_id for ACCOUNTING-enabled tables + // - account for ACCOUNTING-enabled tables // // All values are returned as TEXT via col::TEXT and COALESCEed to empty string // (NULL becomes ""). The row is returned only if deleted = FALSE. diff --git a/common/src/proto/descriptor.bin b/common/src/proto/descriptor.bin index a0411d5e..e106dc3f 100644 Binary files a/common/src/proto/descriptor.bin and b/common/src/proto/descriptor.bin differ diff --git a/common/src/proto/komp_ac.accounting.rs b/common/src/proto/komp_ac.accounting.rs index 15e13f9d..f0278312 100644 --- a/common/src/proto/komp_ac.accounting.rs +++ b/common/src/proto/komp_ac.accounting.rs @@ -3,8 +3,9 @@ pub struct EnsureAccountRequest { #[prost(string, tag = "1")] pub profile_name: ::prost::alloc::string::String, - #[prost(string, repeated, tag = "2")] - pub segments: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + /// Slash-delimited root-to-leaf account path, for example "123/12/1". + #[prost(string, tag = "2")] + pub account: ::prost::alloc::string::String, /// Currency accepted by source postings to the leaf account. Empty uses the /// profile accounting currency. Missing ancestors are created in the profile /// accounting currency. @@ -19,10 +20,9 @@ pub struct Account { pub parent_account_id: ::core::option::Option, #[prost(string, tag = "3")] pub segment: ::prost::alloc::string::String, - /// Root-to-leaf path of this account. Same shape as the request that created - /// it; the server never joins segments into a delimited code. - #[prost(string, repeated, tag = "4")] - pub segments: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + /// Slash-delimited root-to-leaf account path. + #[prost(string, tag = "4")] + pub account: ::prost::alloc::string::String, #[prost(string, tag = "5")] pub denomination_currency: ::prost::alloc::string::String, } @@ -68,10 +68,9 @@ pub struct PostJournalRequest { pub struct JournalLineInput { #[prost(enumeration = "JournalSide", tag = "1")] pub side: i32, - /// Parsed account path segments. The client owns presentation parsing; the - /// server accepts no delimiter-encoded account string. - #[prost(string, repeated, tag = "2")] - pub account_segments: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + /// Slash-delimited root-to-leaf account path. + #[prost(string, tag = "2")] + pub account: ::prost::alloc::string::String, #[prost(string, tag = "3")] pub amount: ::prost::alloc::string::String, #[prost(string, tag = "4")] @@ -238,9 +237,9 @@ pub struct JournalLine { pub line_number: i32, #[prost(enumeration = "JournalSide", tag = "3")] pub side: i32, - /// Root-to-leaf path of the posted account, as sent on JournalLineInput. - #[prost(string, repeated, tag = "4")] - pub account_segments: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + /// Slash-delimited root-to-leaf path of the posted account. + #[prost(string, tag = "4")] + pub account: ::prost::alloc::string::String, #[prost(string, tag = "5")] pub amount: ::prost::alloc::string::String, #[prost(string, tag = "6")] @@ -386,9 +385,9 @@ pub struct ListPeriodBalancesRequest { pub struct PeriodBalance { #[prost(int64, tag = "1")] pub period_id: i64, - /// Root-to-leaf path of the account this frozen balance belongs to. - #[prost(string, repeated, tag = "2")] - pub account_segments: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + /// Slash-delimited root-to-leaf path of this frozen balance's account. + #[prost(string, tag = "2")] + pub account: ::prost::alloc::string::String, #[prost(string, tag = "3")] pub currency: ::prost::alloc::string::String, /// Signed nets use debit-positive convention. @@ -407,8 +406,8 @@ pub struct PeriodDenominationBalance { pub period_id: i64, /// Exact denominated account. Unlike book balances, this quantity is not /// rolled up into parent accounts with a different denomination. - #[prost(string, repeated, tag = "2")] - pub account_segments: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(string, tag = "2")] + pub account: ::prost::alloc::string::String, #[prost(string, tag = "3")] pub denomination_currency: ::prost::alloc::string::String, /// Signed nets use debit-positive convention and remain in the denomination @@ -453,23 +452,17 @@ pub struct MapOpeningBalanceAccountRequest { /// previous_period_id identifies the source profile and period. #[prost(int64, tag = "1")] pub target_period_id: i64, - #[prost(string, repeated, tag = "2")] - pub source_account_segments: ::prost::alloc::vec::Vec< - ::prost::alloc::string::String, - >, - #[prost(string, repeated, tag = "3")] - pub target_account_segments: ::prost::alloc::vec::Vec< - ::prost::alloc::string::String, - >, + #[prost(string, tag = "2")] + pub source_account: ::prost::alloc::string::String, + #[prost(string, tag = "3")] + pub target_account: ::prost::alloc::string::String, } #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct UnmapOpeningBalanceAccountRequest { #[prost(int64, tag = "1")] pub target_period_id: i64, - #[prost(string, repeated, tag = "2")] - pub target_account_segments: ::prost::alloc::vec::Vec< - ::prost::alloc::string::String, - >, + #[prost(string, tag = "2")] + pub target_account: ::prost::alloc::string::String, } #[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] pub struct UnmapOpeningBalanceAccountResponse { @@ -489,16 +482,12 @@ pub struct OpeningBalanceAccountMapping { pub source_period_id: i64, #[prost(string, tag = "3")] pub source_profile_name: ::prost::alloc::string::String, - #[prost(string, repeated, tag = "4")] - pub source_account_segments: ::prost::alloc::vec::Vec< - ::prost::alloc::string::String, - >, + #[prost(string, tag = "4")] + pub source_account: ::prost::alloc::string::String, #[prost(string, tag = "5")] pub target_profile_name: ::prost::alloc::string::String, - #[prost(string, repeated, tag = "6")] - pub target_account_segments: ::prost::alloc::vec::Vec< - ::prost::alloc::string::String, - >, + #[prost(string, tag = "6")] + pub target_account: ::prost::alloc::string::String, #[prost(string, tag = "7")] pub currency: ::prost::alloc::string::String, /// Signed balances use the debit-positive convention. @@ -536,10 +525,8 @@ pub struct UnmappedSourceBalance { pub source_period_id: i64, #[prost(string, tag = "2")] pub source_profile_name: ::prost::alloc::string::String, - #[prost(string, repeated, tag = "3")] - pub source_account_segments: ::prost::alloc::vec::Vec< - ::prost::alloc::string::String, - >, + #[prost(string, tag = "3")] + pub source_account: ::prost::alloc::string::String, #[prost(string, tag = "4")] pub currency: ::prost::alloc::string::String, /// Signed portion not covered by a mapping, using the debit-positive @@ -556,10 +543,8 @@ pub struct UnmappedSourceDenominationBalance { pub source_period_id: i64, #[prost(string, tag = "2")] pub source_profile_name: ::prost::alloc::string::String, - #[prost(string, repeated, tag = "3")] - pub source_account_segments: ::prost::alloc::vec::Vec< - ::prost::alloc::string::String, - >, + #[prost(string, tag = "3")] + pub source_account: ::prost::alloc::string::String, #[prost(string, tag = "4")] pub denomination_currency: ::prost::alloc::string::String, /// Exact foreign-currency quantity that no mapping carries forward. diff --git a/common/src/proto/komp_ac.table_structure.rs b/common/src/proto/komp_ac.table_structure.rs index 1d17cade..6f49055c 100644 --- a/common/src/proto/komp_ac.table_structure.rs +++ b/common/src/proto/komp_ac.table_structure.rs @@ -26,7 +26,7 @@ pub struct GetTableStructureResponse { pub struct TableStructureResponse { /// Columns of the physical table, including system columns (id, deleted, /// created_at, row_revision), user-defined columns, and any foreign-key columns such as - /// named by whoever declared each link, plus the dedicated "account_id" on + /// named by whoever declared each link, plus the dedicated "account" on /// ACCOUNTING-enabled tables. May be empty if the physical table is missing. #[prost(message, repeated, tag = "1")] pub columns: ::prost::alloc::vec::Vec, @@ -34,7 +34,7 @@ pub struct TableStructureResponse { /// One physical column entry as reported by information_schema. #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct TableColumn { - /// Column name exactly as defined in PostgreSQL. + /// Public column name. Physical account_id columns are exposed as account. #[prost(string, tag = "1")] pub name: ::prost::alloc::string::String, /// Normalized data type string derived from information_schema: diff --git a/common/src/proto/komp_ac.tables_data.rs b/common/src/proto/komp_ac.tables_data.rs index ef531db7..deebf2d7 100644 --- a/common/src/proto/komp_ac.tables_data.rs +++ b/common/src/proto/komp_ac.tables_data.rs @@ -18,7 +18,7 @@ pub struct PostTableDataRequest { /// * System/FK columns: /// • "deleted" (BOOLEAN), optional; default FALSE if not provided /// • one BIGINT per link, named by whoever declared it - /// • "account_id" (BIGINT) on ACCOUNTING-enabled tables + /// • "account" (slash-delimited account string) on ACCOUNTING-enabled tables /// /// Type expectations by SQL type: /// @@ -191,7 +191,7 @@ pub struct GetTableDataResponse { /// * id, deleted /// * all user-defined columns from the table definition /// * one column per link, named by whoever declared it - /// * account_id for ACCOUNTING-enabled tables + /// * account for ACCOUNTING-enabled tables /// /// All values are returned as TEXT via col::TEXT and COALESCEed to empty string /// (NULL becomes ""). The row is returned only if deleted = FALSE. @@ -482,7 +482,7 @@ pub mod tables_data_client { /// * Validates profile and table definition /// * Returns all columns as strings (COALESCE(col::TEXT, '') AS col) /// including: id, deleted, row_revision, all user-defined columns, and FK columns - /// named by whoever declared each link, plus "account_id" on + /// named by whoever declared each link, plus "account" on /// ACCOUNTING-enabled tables /// * Fails with NOT_FOUND if record does not exist or is soft-deleted /// * If the physical table is missing but the definition exists, returns INTERNAL @@ -675,7 +675,7 @@ pub mod tables_data_server { /// * Validates profile and table definition /// * Returns all columns as strings (COALESCE(col::TEXT, '') AS col) /// including: id, deleted, row_revision, all user-defined columns, and FK columns - /// named by whoever declared each link, plus "account_id" on + /// named by whoever declared each link, plus "account" on /// ACCOUNTING-enabled tables /// * Fails with NOT_FOUND if record does not exist or is soft-deleted /// * If the physical table is missing but the definition exists, returns INTERNAL diff --git a/common/src/system_column.rs b/common/src/system_column.rs index 0a09c3cb..23171634 100644 --- a/common/src/system_column.rs +++ b/common/src/system_column.rs @@ -43,6 +43,7 @@ pub const TRAILING_SYSTEM_COLUMNS: [SystemColumn; 1] = [SystemColumn { /// Its declaration names that profile's schema, so it is built where the /// profile is known rather than spelled out here. pub const ACCOUNT_REFERENCE_COLUMN: &str = "account_id"; +pub const ACCOUNT_API_COLUMN: &str = "account"; /// Every system column name, whether or not the column is on a given table. /// @@ -59,12 +60,13 @@ pub fn system_column_names() -> impl Iterator { /// Whether `name` is a system column: a name that is safe to show a client /// as-is, and that a user may not claim for a column alias or a table. pub fn is_system_column(name: &str) -> bool { - system_column_names().any(|system_name| system_name == name) + name == ACCOUNT_API_COLUMN || system_column_names().any(|system_name| system_name == name) } /// The system column names in a message, as `'id', 'deleted', ...`. pub fn system_column_name_list() -> String { system_column_names() + .chain(std::iter::once(ACCOUNT_API_COLUMN)) .map(|name| format!("'{name}'")) .collect::>() .join(", ") @@ -73,7 +75,8 @@ pub fn system_column_name_list() -> String { #[cfg(test)] mod tests { use super::{ - ACCOUNT_REFERENCE_COLUMN, is_system_column, system_column_name_list, system_column_names, + ACCOUNT_API_COLUMN, ACCOUNT_REFERENCE_COLUMN, is_system_column, system_column_name_list, + system_column_names, }; #[test] @@ -86,6 +89,7 @@ mod tests { #[test] fn a_conditional_column_is_reserved_on_every_table() { assert!(is_system_column(ACCOUNT_REFERENCE_COLUMN)); + assert!(is_system_column(ACCOUNT_API_COLUMN)); } #[test] @@ -98,7 +102,7 @@ mod tests { fn the_name_list_reads_as_a_sentence_fragment() { assert_eq!( system_column_name_list(), - "'id', 'deleted', 'row_revision', 'created_at', 'account_id'" + "'id', 'deleted', 'row_revision', 'created_at', 'account_id', 'account'" ); } } diff --git a/server b/server index 1fe9ffa7..d29f1305 160000 --- a/server +++ b/server @@ -1 +1 @@ -Subproject commit 1fe9ffa75942f81d511506d4855f66e945071d69 +Subproject commit d29f130523766cf7beb44e478950aca558fa2b23