diff --git a/Cargo.lock b/Cargo.lock index f6504e7b..e39183a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8766,6 +8766,8 @@ dependencies = [ "askama", "axum", "axum-extra", + "isocountry", + "jiff", "prost", "prost-types", "rusty-money", diff --git a/common/proto/accounting.proto b/common/proto/accounting.proto index 4003240d..534324ba 100644 --- a/common/proto/accounting.proto +++ b/common/proto/accounting.proto @@ -57,6 +57,13 @@ service Accounting { returns (ListAccountingPeriodsResponse); rpc ListPeriodBalances(ListPeriodBalancesRequest) returns (ListPeriodBalancesResponse); + // Materialize one row per directly transferable source account in a table + // containing an ACCOUNTING_TRANSFER definition. Source fields are frozen + // accounting facts; target fields start as an unchanged same-path copy and + // may then be changed by the table's Steel logic or by the user. + rpc GenerateAccountingTransfer(GenerateAccountingTransferRequest) + returns (GenerateAccountingTransferResponse); + // Administratively map one account in a predecessor profile to one account // in the current profile. Existing activity on the target account is allowed. rpc MapOpeningBalanceAccount(MapOpeningBalanceAccountRequest) @@ -382,8 +389,35 @@ message PeriodBalance { string closing_balance = 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 denomination_currency = 3; + // Signed nets use debit-positive convention and remain in the denomination + // currency without conversion during carry-forward. + string opening_quantity = 4; + string period_debit_quantity = 5; + string period_credit_quantity = 6; + string closing_quantity = 7; +} + message ListPeriodBalancesResponse { repeated PeriodBalance balances = 1; + repeated PeriodDenominationBalance denomination_balances = 2; +} + +message GenerateAccountingTransferRequest { + string profile_name = 1; + string table_name = 2; + int64 source_period_id = 3; + int64 target_period_id = 4; +} + +message GenerateAccountingTransferResponse { + int64 table_definition_id = 1; + int64 generated_row_count = 2; } message MapOpeningBalanceAccountRequest { @@ -431,6 +465,11 @@ message OpeningBalanceAccountMapping { OpeningBalanceStatus status = 12; string created_at = 13; string created_by_user_id = 14; + string denomination_currency = 15; + string denomination_opening_quantity = 16; + string denomination_period_debit_quantity = 17; + string denomination_period_credit_quantity = 18; + string denomination_current_quantity = 19; } // A portion of a predecessor account's value that no mapping carries into the @@ -448,6 +487,16 @@ message UnmappedSourceBalance { OpeningBalanceStatus status = 6; } +message UnmappedSourceDenominationBalance { + int64 source_period_id = 1; + string source_profile_name = 2; + repeated string source_account_segments = 3; + string denomination_currency = 4; + // Exact foreign-currency quantity that no mapping carries forward. + string unmapped_quantity = 5; + OpeningBalanceStatus status = 6; +} + message ListOpeningBalanceAccountsResponse { repeated OpeningBalanceAccountMapping mappings = 1; @@ -456,4 +505,6 @@ message ListOpeningBalanceAccountsResponse { // mapping only, which happens when the predecessor belongs to another // profile or when the period has at least one mapping of its own. repeated UnmappedSourceBalance unmapped_source_balances = 2; + repeated UnmappedSourceDenominationBalance + unmapped_source_denomination_balances = 3; } diff --git a/common/proto/table_definition.proto b/common/proto/table_definition.proto index 38197ffd..8df16a08 100644 --- a/common/proto/table_definition.proto +++ b/common/proto/table_definition.proto @@ -174,6 +174,9 @@ message ColumnDefinition { // account always selects a row from the profile's managed accounts table; // name is limited to 10 characters and one stored row contributes // one line to the profile journal) + // ACCOUNTING_TRANSFER (creates automatic source-period/account/book/ + // denomination fields and writable target equivalents; one + // table may contain only one accounting transfer definition) // INT // BIGINT // DATE diff --git a/common/src/proto/descriptor.bin b/common/src/proto/descriptor.bin index 0f01c031..0cb612c9 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 49894310..15e13f9d 100644 --- a/common/src/proto/komp_ac.accounting.rs +++ b/common/src/proto/komp_ac.accounting.rs @@ -401,10 +401,51 @@ pub struct PeriodBalance { #[prost(string, tag = "7")] pub closing_balance: ::prost::alloc::string::String, } +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct PeriodDenominationBalance { + #[prost(int64, tag = "1")] + 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 = "3")] + pub denomination_currency: ::prost::alloc::string::String, + /// Signed nets use debit-positive convention and remain in the denomination + /// currency without conversion during carry-forward. + #[prost(string, tag = "4")] + pub opening_quantity: ::prost::alloc::string::String, + #[prost(string, tag = "5")] + pub period_debit_quantity: ::prost::alloc::string::String, + #[prost(string, tag = "6")] + pub period_credit_quantity: ::prost::alloc::string::String, + #[prost(string, tag = "7")] + pub closing_quantity: ::prost::alloc::string::String, +} #[derive(Clone, PartialEq, ::prost::Message)] pub struct ListPeriodBalancesResponse { #[prost(message, repeated, tag = "1")] pub balances: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag = "2")] + pub denomination_balances: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct GenerateAccountingTransferRequest { + #[prost(string, tag = "1")] + pub profile_name: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub table_name: ::prost::alloc::string::String, + #[prost(int64, tag = "3")] + pub source_period_id: i64, + #[prost(int64, tag = "4")] + pub target_period_id: i64, +} +#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct GenerateAccountingTransferResponse { + #[prost(int64, tag = "1")] + pub table_definition_id: i64, + #[prost(int64, tag = "2")] + pub generated_row_count: i64, } #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct MapOpeningBalanceAccountRequest { @@ -475,6 +516,16 @@ pub struct OpeningBalanceAccountMapping { pub created_at: ::prost::alloc::string::String, #[prost(string, tag = "14")] pub created_by_user_id: ::prost::alloc::string::String, + #[prost(string, tag = "15")] + pub denomination_currency: ::prost::alloc::string::String, + #[prost(string, tag = "16")] + pub denomination_opening_quantity: ::prost::alloc::string::String, + #[prost(string, tag = "17")] + pub denomination_period_debit_quantity: ::prost::alloc::string::String, + #[prost(string, tag = "18")] + pub denomination_period_credit_quantity: ::prost::alloc::string::String, + #[prost(string, tag = "19")] + pub denomination_current_quantity: ::prost::alloc::string::String, } /// A portion of a predecessor account's value that no mapping carries into the /// target period. Closing the target period drops this value, so each entry must @@ -499,6 +550,24 @@ pub struct UnmappedSourceBalance { #[prost(enumeration = "OpeningBalanceStatus", tag = "6")] pub status: i32, } +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct UnmappedSourceDenominationBalance { + #[prost(int64, tag = "1")] + 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 = "4")] + pub denomination_currency: ::prost::alloc::string::String, + /// Exact foreign-currency quantity that no mapping carries forward. + #[prost(string, tag = "5")] + pub unmapped_quantity: ::prost::alloc::string::String, + #[prost(enumeration = "OpeningBalanceStatus", tag = "6")] + pub status: i32, +} #[derive(Clone, PartialEq, ::prost::Message)] pub struct ListOpeningBalanceAccountsResponse { #[prost(message, repeated, tag = "1")] @@ -509,6 +578,10 @@ pub struct ListOpeningBalanceAccountsResponse { /// profile or when the period has at least one mapping of its own. #[prost(message, repeated, tag = "2")] pub unmapped_source_balances: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag = "3")] + pub unmapped_source_denomination_balances: ::prost::alloc::vec::Vec< + UnmappedSourceDenominationBalance, + >, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] @@ -1205,6 +1278,39 @@ pub mod accounting_client { ); self.inner.unary(req, path, codec).await } + /// Materialize one row per directly transferable source account in a table + /// containing an ACCOUNTING_TRANSFER definition. Source fields are frozen + /// accounting facts; target fields start as an unchanged same-path copy and + /// may then be changed by the table's Steel logic or by the user. + pub async fn generate_accounting_transfer( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic_prost::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/komp_ac.accounting.Accounting/GenerateAccountingTransfer", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new( + "komp_ac.accounting.Accounting", + "GenerateAccountingTransfer", + ), + ); + self.inner.unary(req, path, codec).await + } /// Administratively map one account in a predecessor profile to one account /// in the current profile. Existing activity on the target account is allowed. pub async fn map_opening_balance_account( @@ -1422,6 +1528,17 @@ pub mod accounting_server { tonic::Response, tonic::Status, >; + /// Materialize one row per directly transferable source account in a table + /// containing an ACCOUNTING_TRANSFER definition. Source fields are frozen + /// accounting facts; target fields start as an unchanged same-path copy and + /// may then be changed by the table's Steel logic or by the user. + async fn generate_accounting_transfer( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; /// Administratively map one account in a predecessor profile to one account /// in the current profile. Existing activity on the target account is allowed. async fn map_opening_balance_account( @@ -2308,6 +2425,58 @@ pub mod accounting_server { }; Box::pin(fut) } + "/komp_ac.accounting.Accounting/GenerateAccountingTransfer" => { + #[allow(non_camel_case_types)] + struct GenerateAccountingTransferSvc(pub Arc); + impl< + T: Accounting, + > tonic::server::UnaryService< + super::GenerateAccountingTransferRequest, + > for GenerateAccountingTransferSvc { + type Response = super::GenerateAccountingTransferResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request< + super::GenerateAccountingTransferRequest, + >, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::generate_accounting_transfer( + &inner, + request, + ) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = GenerateAccountingTransferSvc(inner); + let codec = tonic_prost::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } "/komp_ac.accounting.Accounting/MapOpeningBalanceAccount" => { #[allow(non_camel_case_types)] struct MapOpeningBalanceAccountSvc(pub Arc); diff --git a/common/src/proto/komp_ac.table_definition.rs b/common/src/proto/komp_ac.table_definition.rs index a0900e5a..2500097c 100644 --- a/common/src/proto/komp_ac.table_definition.rs +++ b/common/src/proto/komp_ac.table_definition.rs @@ -144,6 +144,9 @@ pub struct ColumnDefinition { /// account always selects a row from the profile's managed accounts table; /// name is limited to 10 characters and one stored row contributes /// one line to the profile journal) + /// ACCOUNTING_TRANSFER (creates automatic source-period/account/book/ + /// denomination fields and writable target equivalents; one + /// table may contain only one accounting transfer definition) /// INT /// BIGINT /// DATE diff --git a/server b/server index 45e95554..5d1a2b21 160000 --- a/server +++ b/server @@ -1 +1 @@ -Subproject commit 45e95554c9a9ba82d6b421bf5599e2a74f966a3e +Subproject commit 5d1a2b2186f3d6099ebc48930d62e5a441aa6ae8