diff --git a/Cargo.lock b/Cargo.lock index b7c732dd9d..e7eaebf4dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -690,7 +690,7 @@ checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0" [[package]] name = "cala-cel-interpreter" version = "0.3.3-dev" -source = "git+https://github.com/galoymoney/cala.git?branch=main#0a16445d5aece3fe7357cd4dfcf16801740defbd" +source = "git+https://github.com/galoymoney/cala.git?branch=main#009c41fa4ffc270024606ca345378b25d4079e0f" dependencies = [ "cala-cel-parser", "chrono", @@ -705,7 +705,7 @@ dependencies = [ [[package]] name = "cala-cel-parser" version = "0.3.3-dev" -source = "git+https://github.com/galoymoney/cala.git?branch=main#0a16445d5aece3fe7357cd4dfcf16801740defbd" +source = "git+https://github.com/galoymoney/cala.git?branch=main#009c41fa4ffc270024606ca345378b25d4079e0f" dependencies = [ "lalrpop", "lalrpop-util", @@ -714,7 +714,7 @@ dependencies = [ [[package]] name = "cala-ledger" version = "0.3.3-dev" -source = "git+https://github.com/galoymoney/cala.git?branch=main#0a16445d5aece3fe7357cd4dfcf16801740defbd" +source = "git+https://github.com/galoymoney/cala.git?branch=main#009c41fa4ffc270024606ca345378b25d4079e0f" dependencies = [ "cached 0.51.4", "cala-cel-interpreter", @@ -746,7 +746,7 @@ dependencies = [ [[package]] name = "cala-ledger-core-types" version = "0.3.3-dev" -source = "git+https://github.com/galoymoney/cala.git?branch=main#0a16445d5aece3fe7357cd4dfcf16801740defbd" +source = "git+https://github.com/galoymoney/cala.git?branch=main#009c41fa4ffc270024606ca345378b25d4079e0f" dependencies = [ "cala-cel-interpreter", "chrono", @@ -763,7 +763,7 @@ dependencies = [ [[package]] name = "cala-tracing" version = "0.3.3-dev" -source = "git+https://github.com/galoymoney/cala.git?branch=main#0a16445d5aece3fe7357cd4dfcf16801740defbd" +source = "git+https://github.com/galoymoney/cala.git?branch=main#009c41fa4ffc270024606ca345378b25d4079e0f" dependencies = [ "anyhow", "axum-extra", @@ -1370,7 +1370,7 @@ dependencies = [ [[package]] name = "es-entity" version = "0.3.3-dev" -source = "git+https://github.com/galoymoney/cala.git?branch=main#0a16445d5aece3fe7357cd4dfcf16801740defbd" +source = "git+https://github.com/galoymoney/cala.git?branch=main#009c41fa4ffc270024606ca345378b25d4079e0f" dependencies = [ "async-graphql", "async-trait", @@ -1389,7 +1389,7 @@ dependencies = [ [[package]] name = "es-entity-macros" version = "0.3.3-dev" -source = "git+https://github.com/galoymoney/cala.git?branch=main#0a16445d5aece3fe7357cd4dfcf16801740defbd" +source = "git+https://github.com/galoymoney/cala.git?branch=main#009c41fa4ffc270024606ca345378b25d4079e0f" dependencies = [ "convert_case", "darling", @@ -3225,7 +3225,7 @@ version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0f3e5beed80eb580c68e2c600937ac2c4eedabdfd5ef1e5b7ea4f3fba84497b" dependencies = [ - "heck 0.5.0", + "heck 0.4.1", "itertools 0.13.0", "log", "multimap", @@ -3284,7 +3284,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a669d5acbe719010c6f62a64e6d7d88fdedc1fe46e419747949ecb6312e9b14" dependencies = [ - "heck 0.5.0", + "heck 0.4.1", "prost", "prost-build", "prost-types", @@ -4156,7 +4156,7 @@ dependencies = [ [[package]] name = "sim-time" version = "0.3.3-dev" -source = "git+https://github.com/galoymoney/cala.git?branch=main#0a16445d5aece3fe7357cd4dfcf16801740defbd" +source = "git+https://github.com/galoymoney/cala.git?branch=main#009c41fa4ffc270024606ca345378b25d4079e0f" dependencies = [ "chrono", "serde", diff --git a/Cargo.toml b/Cargo.toml index 2f5843a8ff..32395ca701 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,16 +33,34 @@ cala-ledger = { git = "https://github.com/galoymoney/cala.git", branch = "main" # cala-types = "0.3.1" anyhow = "1.0.95" -async-graphql = { version = "7.0.11", default-features = false, features = ["dataloader", "tracing", "chrono", "playground"] } +async-graphql = { version = "7.0.11", default-features = false, features = [ + "dataloader", + "tracing", + "chrono", + "playground", +] } async-graphql-axum = "7.0.9" async-trait = "0.1.80" axum = { version = "0.7.9", features = ["macros"] } -axum-extra = { version = "0.9.6", default-features = false, features = ["tracing", "typed-header"] } -chrono = { version = "0.4.31", features = ["clock", "serde"], default-features = false } +axum-extra = { version = "0.9.6", default-features = false, features = [ + "tracing", + "typed-header", +] } +chrono = { version = "0.4.31", features = [ + "clock", + "serde", +], default-features = false } clap = { version = "4.5", features = ["derive", "env"] } derive_builder = "0.20.0" -graphql_client = {version = "0.14.0", features = ["reqwest-rustls"]} -sqlx = { version = "0.8.2", features = [ "runtime-tokio-rustls", "postgres", "rust_decimal", "uuid", "chrono", "json" ] } +graphql_client = { version = "0.14.0", features = ["reqwest-rustls"] } +sqlx = { version = "0.8.2", features = [ + "runtime-tokio-rustls", + "postgres", + "rust_decimal", + "uuid", + "chrono", + "json", +] } thiserror = "1.0.69" uuid = { version = "1.11", features = ["serde", "v4"] } tracing = "0.1.40" @@ -50,10 +68,18 @@ tracing-opentelemetry = "0.25.0" tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json"] } opentelemetry = { version = "0.24.0" } opentelemetry_sdk = { version = "0.24.1", features = ["rt-tokio"] } -opentelemetry-otlp = { version = "0.17.0", features = ["http-proto", "reqwest-client"] } +opentelemetry-otlp = { version = "0.17.0", features = [ + "http-proto", + "reqwest-client", +] } opentelemetry-http = "0.13" opentelemetry-semantic-conventions = "0.16.0" -tokio = { version = "1.43", features = ["rt-multi-thread", "macros", "time", "sync"] } +tokio = { version = "1.43", features = [ + "rt-multi-thread", + "macros", + "time", + "sync", +] } tokio-stream = { version = "0.1.16", features = ["sync"] } strum = { version = "0.26", features = ["derive"] } serde = { version = "1.0.214", features = ["derive"] } @@ -61,7 +87,10 @@ serde_yaml = "0.9.32" serde_json = "1.0.133" serde_with = "3.8.1" futures = "0.3.29" -reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } +reqwest = { version = "0.12", default-features = false, features = [ + "json", + "rustls-tls", +] } rust_decimal_macros = "1.34.2" rust_decimal = "1.35.0" rusty-money = { version = "0.4", features = ["iso", "crypto"] } @@ -71,12 +100,21 @@ hex = "0.4.3" hmac = "0.12.1" sha2 = "0.10.8" tower-http = { version = "0.5.2", features = ["cors"] } -sqlx-adapter = { version = "1.6.0", default-features = false, features = ["postgres", "runtime-tokio-rustls"]} +sqlx-adapter = { version = "1.6.0", default-features = false, features = [ + "postgres", + "runtime-tokio-rustls", +] } ory-kratos-client = "1.1.0" cached = { version = "0.53.1", features = ["async"] } gcp_auth = { version = "0.12.2", features = ["webpki-roots"] } -gcp-bigquery-client = { git = "https://github.com/bodymindarts/gcp-bigquery-client.git", branch = "feat--webpki", features = ["rust-tls"] } -cloud-storage = { version = "0.11.1",default-features = false, features = ["rustls-tls", "sync", "global-client"] } +gcp-bigquery-client = { git = "https://github.com/bodymindarts/gcp-bigquery-client.git", branch = "feat--webpki", features = [ + "rust-tls", +] } +cloud-storage = { version = "0.11.1", default-features = false, features = [ + "rustls-tls", + "sync", + "global-client", +] } rand = "0.8" [profile.release] diff --git a/core/chart-of-accounts/src/chart_of_accounts/entity.rs b/core/chart-of-accounts/src/chart_of_accounts/entity.rs index c549fa8d84..8e2ab5b1c6 100644 --- a/core/chart-of-accounts/src/chart_of_accounts/entity.rs +++ b/core/chart-of-accounts/src/chart_of_accounts/entity.rs @@ -8,7 +8,7 @@ use es_entity::*; use crate::{ path::*, primitives::{ChartId, LedgerAccountSetId}, - ControlSubAccountDetails, + ControlAccountDetails, ControlSubAccountDetails, }; pub use super::error::*; @@ -25,6 +25,7 @@ pub enum ChartEvent { audit_info: AuditInfo, }, ControlAccountAdded { + id: LedgerAccountSetId, encoded_path: String, path: ControlAccountPath, name: String, @@ -85,11 +86,12 @@ impl Chart { pub fn create_control_account( &mut self, + id: LedgerAccountSetId, category: ChartCategory, name: String, reference: String, audit_info: AuditInfo, - ) -> Result { + ) -> Result { if self .find_control_account_by_reference(reference.to_string()) .is_some() @@ -99,14 +101,20 @@ impl Chart { let path = self.next_control_account(category)?; self.events.push(ChartEvent::ControlAccountAdded { + id, encoded_path: path.path_encode(self.id), path, - name, - reference, + name: name.to_string(), + reference: reference.to_string(), audit_info, }); - Ok(path) + Ok(ControlAccountDetails { + path, + account_set_id: id, + name, + reference, + }) } fn next_control_sub_account( @@ -283,8 +291,12 @@ mod tests { #[test] fn test_create_control_account() { let mut chart = init_chart_of_events(); - let ControlAccountPath { category, index } = chart + let ControlAccountDetails { + path: ControlAccountPath { category, index }, + .. + } = chart .create_control_account( + LedgerAccountSetId::new(), ChartCategory::Assets, "Assets".to_string(), "assets".to_string(), @@ -300,6 +312,7 @@ mod tests { let mut chart = init_chart_of_events(); chart .create_control_account( + LedgerAccountSetId::new(), ChartCategory::Assets, "Assets #1".to_string(), "assets".to_string(), @@ -308,6 +321,7 @@ mod tests { .unwrap(); match chart.create_control_account( + LedgerAccountSetId::new(), ChartCategory::Assets, "Assets #2".to_string(), "assets".to_string(), @@ -327,6 +341,7 @@ mod tests { let mut chart = init_chart_of_events(); let control_account = chart .create_control_account( + LedgerAccountSetId::new(), ChartCategory::Assets, "Assets".to_string(), "assets".to_string(), @@ -345,7 +360,7 @@ mod tests { } = chart .create_control_sub_account( LedgerAccountSetId::new(), - control_account, + control_account.path, "Current Assets".to_string(), "current-assets".to_string(), dummy_audit_info(), @@ -361,6 +376,7 @@ mod tests { let mut chart = init_chart_of_events(); let control_account = chart .create_control_account( + LedgerAccountSetId::new(), ChartCategory::Assets, "Assets".to_string(), "assets".to_string(), @@ -370,7 +386,7 @@ mod tests { chart .create_control_sub_account( LedgerAccountSetId::new(), - control_account, + control_account.path, "Current Assets #1".to_string(), "current-assets".to_string(), dummy_audit_info(), @@ -379,7 +395,7 @@ mod tests { match chart.create_control_sub_account( LedgerAccountSetId::new(), - control_account, + control_account.path, "Current Assets #2".to_string(), "current-assets".to_string(), dummy_audit_info(), @@ -402,6 +418,7 @@ mod tests { chart .create_control_account( + LedgerAccountSetId::new(), ChartCategory::Assets, "First".to_string(), "assets-01".to_string(), @@ -409,8 +426,12 @@ mod tests { ) .unwrap(); - let ControlAccountPath { category, index } = chart + let ControlAccountDetails { + path: ControlAccountPath { category, index }, + .. + } = chart .create_control_account( + LedgerAccountSetId::new(), ChartCategory::Assets, "Second".to_string(), "assets-02".to_string(), @@ -426,6 +447,7 @@ mod tests { let mut chart = init_chart_of_events(); let control_account = chart .create_control_account( + LedgerAccountSetId::new(), ChartCategory::Assets, "Assets".to_string(), "assets".to_string(), @@ -436,7 +458,7 @@ mod tests { chart .create_control_sub_account( LedgerAccountSetId::new(), - control_account, + control_account.path, "First".to_string(), "first-asset".to_string(), dummy_audit_info(), @@ -454,7 +476,7 @@ mod tests { } = chart .create_control_sub_account( LedgerAccountSetId::new(), - control_account, + control_account.path, "Second".to_string(), "second-asset".to_string(), dummy_audit_info(), diff --git a/core/chart-of-accounts/src/chart_of_accounts/tree.rs b/core/chart-of-accounts/src/chart_of_accounts/tree.rs index 9f79785ea5..787f9b9edd 100644 --- a/core/chart-of-accounts/src/chart_of_accounts/tree.rs +++ b/core/chart-of-accounts/src/chart_of_accounts/tree.rs @@ -165,6 +165,7 @@ mod tests { { let control_account = chart .create_control_account( + LedgerAccountSetId::new(), ChartCategory::Assets, "Loans Receivable".to_string(), "loans-receivable".to_string(), @@ -174,7 +175,7 @@ mod tests { chart .create_control_sub_account( LedgerAccountSetId::new(), - control_account, + control_account.path, "Fixed Loans Receivable".to_string(), "fixed-loans-receivable".to_string(), dummy_audit_info(), @@ -189,6 +190,7 @@ mod tests { { let control_account = chart .create_control_account( + LedgerAccountSetId::new(), ChartCategory::Liabilities, "User Checking".to_string(), "user-checking".to_string(), @@ -198,7 +200,7 @@ mod tests { chart .create_control_sub_account( LedgerAccountSetId::new(), - control_account, + control_account.path, "User Checking".to_string(), "sub-user-checking".to_string(), dummy_audit_info(), @@ -213,6 +215,7 @@ mod tests { { let control_account = chart .create_control_account( + LedgerAccountSetId::new(), ChartCategory::Equity, "Shareholder Equity".to_string(), "shareholder-equity".to_string(), @@ -222,7 +225,7 @@ mod tests { chart .create_control_sub_account( LedgerAccountSetId::new(), - control_account, + control_account.path, "Shareholder Equity".to_string(), "sub-shareholder-equity".to_string(), dummy_audit_info(), @@ -237,6 +240,7 @@ mod tests { { chart .create_control_account( + LedgerAccountSetId::new(), ChartCategory::Revenues, "Interest Revenue".to_string(), "interest-revenue".to_string(), diff --git a/core/chart-of-accounts/src/lib.rs b/core/chart-of-accounts/src/lib.rs index 17d0b9956a..3d38f8aa08 100644 --- a/core/chart-of-accounts/src/lib.rs +++ b/core/chart-of-accounts/src/lib.rs @@ -181,11 +181,13 @@ where pub async fn create_control_account( &self, + id: impl Into + std::fmt::Debug, chart_id: impl Into, category: ChartCategory, name: String, reference: String, - ) -> Result { + ) -> Result { + let id = id.into(); let chart_id = chart_id.into(); let mut op = self.repo.begin_op().await?; @@ -202,13 +204,28 @@ where let mut chart = self.repo.find_by_id(chart_id).await?; - let path = chart.create_control_account(category, name, reference, audit_info)?; + let control_account = + chart.create_control_account(id, category, name, reference, audit_info)?; self.repo.update_in_op(&mut op, &mut chart).await?; + let mut op = self.cala.ledger_operation_from_db_op(op); + let new_account_set = NewAccountSet::builder() + .id(control_account.account_set_id) + .journal_id(self.journal_id) + .name(control_account.name.to_string()) + .description(control_account.name.to_string()) + .normal_balance_type(control_account.path.normal_balance_type()) + .build() + .expect("Could not build new account set"); + self.cala + .account_sets() + .create_in_op(&mut op, new_account_set) + .await?; + op.commit().await?; - Ok(path) + Ok(control_account) } pub async fn find_control_sub_account_by_reference( @@ -238,7 +255,7 @@ where &self, id: impl Into + std::fmt::Debug, chart_id: impl Into + std::fmt::Debug, - control_account: ControlAccountPath, + control_account: ControlAccountDetails, name: String, reference: String, ) -> Result { @@ -259,28 +276,40 @@ where let mut chart = self.repo.find_by_id(chart_id).await?; - let account_set_details = - chart.create_control_sub_account(id, control_account, name, reference, audit_info)?; + let control_sub_account = chart.create_control_sub_account( + id, + control_account.path, + name, + reference, + audit_info, + )?; - let mut op = self.repo.begin_op().await?; self.repo.update_in_op(&mut op, &mut chart).await?; let mut op = self.cala.ledger_operation_from_db_op(op); let new_account_set = NewAccountSet::builder() - .id(account_set_details.account_set_id) + .id(control_sub_account.account_set_id) .journal_id(self.journal_id) - .name(account_set_details.name.to_string()) - .description(account_set_details.name.to_string()) - .normal_balance_type(account_set_details.path.normal_balance_type()) + .name(control_sub_account.name.to_string()) + .description(control_sub_account.name.to_string()) + .normal_balance_type(control_sub_account.path.normal_balance_type()) .build() .expect("Could not build new account set"); self.cala .account_sets() .create_in_op(&mut op, new_account_set) .await?; + self.cala + .account_sets() + .add_member_in_op( + &mut op, + control_account.account_set_id, + control_sub_account.account_set_id, + ) + .await?; op.commit().await?; - Ok(account_set_details) + Ok(control_sub_account) } } diff --git a/core/chart-of-accounts/src/path/mod.rs b/core/chart-of-accounts/src/path/mod.rs index 4dc53725ae..1ac198983b 100644 --- a/core/chart-of-accounts/src/path/mod.rs +++ b/core/chart-of-accounts/src/path/mod.rs @@ -100,6 +100,13 @@ impl ControlAccountPath { format!("{}::{}", chart_id, self) } + pub fn normal_balance_type(&self) -> DebitOrCredit { + match self.category { + ChartCategory::Assets | ChartCategory::Expenses => DebitOrCredit::Debit, + _ => DebitOrCredit::Credit, + } + } + pub const fn first_control_sub_account(&self) -> ControlSubAccountPath { ControlSubAccountPath { category: self.category, diff --git a/core/chart-of-accounts/src/primitives.rs b/core/chart-of-accounts/src/primitives.rs index fd5a5c8077..51a5333bb7 100644 --- a/core/chart-of-accounts/src/primitives.rs +++ b/core/chart-of-accounts/src/primitives.rs @@ -9,7 +9,7 @@ pub use cala_ledger::{ }; pub use crate::path::ChartCategory; -use crate::path::ControlSubAccountPath; +use crate::path::{ControlAccountPath, ControlSubAccountPath}; es_entity::entity_id! { ChartId, @@ -122,19 +122,19 @@ impl From for CoreChartOfAccountsAction { } #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ChartAccountDetails { - pub account_id: LedgerAccountId, - pub encoded_path: String, +pub struct ControlAccountCreationDetails { + pub category: ChartCategory, + pub account_set_id: LedgerAccountSetId, pub name: String, - pub description: String, + pub reference: String, } #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ChartCreationDetails { - pub control_sub_account: ControlSubAccountPath, - pub account_id: LedgerAccountId, +pub struct ControlAccountDetails { + pub path: ControlAccountPath, + pub account_set_id: LedgerAccountSetId, pub name: String, - pub description: String, + pub reference: String, } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/core/chart-of-accounts/tests/chart_of_accounts.rs b/core/chart-of-accounts/tests/chart_of_accounts.rs index 75fa9e1e46..dd195a1c7f 100644 --- a/core/chart-of-accounts/tests/chart_of_accounts.rs +++ b/core/chart-of-accounts/tests/chart_of_accounts.rs @@ -53,8 +53,9 @@ async fn create_and_populate() -> anyhow::Result<()> { let charts = chart_of_accounts.list_charts(&DummySubject).await?; assert!(charts.iter().any(|chart| chart.id == chart_id)); - let control_account_code = chart_of_accounts + let control_account = chart_of_accounts .create_control_account( + LedgerAccountSetId::new(), chart_id, ChartCategory::Assets, "Credit Facilities Receivable".to_string(), @@ -63,18 +64,18 @@ async fn create_and_populate() -> anyhow::Result<()> { .await?; let control_sub_account_name = "Fixed-Term Credit Facilities Receivable"; - let control_sub_account_code = chart_of_accounts + let control_sub_account = chart_of_accounts .create_control_sub_account( LedgerAccountSetId::new(), chart_id, - control_account_code, + control_account.clone(), control_sub_account_name.to_string(), "fixed-term-credit-facilities-receivable".to_string(), ) .await?; assert_eq!( - control_sub_account_code.path.control_account(), - control_account_code + control_sub_account.path.control_account(), + control_account.path ); Ok(()) diff --git a/core/deposit/tests/deposit.rs b/core/deposit/tests/deposit.rs index 92b463cbed..54bbcb3264 100644 --- a/core/deposit/tests/deposit.rs +++ b/core/deposit/tests/deposit.rs @@ -39,24 +39,25 @@ async fn deposit() -> anyhow::Result<()> { ) .await?; - let control_account_path = chart_of_accounts + let control_account = chart_of_accounts .create_control_account( + LedgerAccountSetId::new(), chart_id, ChartCategory::Liabilities, "Deposits".to_string(), "deposits".to_string(), ) .await?; - let control_sub_account_path = chart_of_accounts + let control_sub_account = chart_of_accounts .create_control_sub_account( LedgerAccountSetId::new(), chart_id, - control_account_path, + control_account, "User Deposits".to_string(), "user-deposits".to_string(), ) .await?; - let factory = chart_of_accounts.transaction_account_factory(control_sub_account_path); + let factory = chart_of_accounts.transaction_account_factory(control_sub_account); let deposit = CoreDeposit::init( &pool, diff --git a/core/deposit/tests/withdraw.rs b/core/deposit/tests/withdraw.rs index 18686388f6..b9ba5fce3d 100644 --- a/core/deposit/tests/withdraw.rs +++ b/core/deposit/tests/withdraw.rs @@ -40,24 +40,25 @@ async fn overdraw_and_cancel_withdrawal() -> anyhow::Result<()> { ) .await?; - let control_account_path = chart_of_accounts + let control_account = chart_of_accounts .create_control_account( + LedgerAccountSetId::new(), chart_id, ChartCategory::Liabilities, "Deposits".to_string(), "deposits".to_string(), ) .await?; - let control_sub_account_path = chart_of_accounts + let control_sub_account = chart_of_accounts .create_control_sub_account( LedgerAccountSetId::new(), chart_id, - control_account_path, + control_account, "User Deposits".to_string(), "user-deposits".to_string(), ) .await?; - let factory = chart_of_accounts.transaction_account_factory(control_sub_account_path); + let factory = chart_of_accounts.transaction_account_factory(control_sub_account); let deposit = CoreDeposit::init( &pool, diff --git a/lana/admin-server/src/graphql/account.rs b/lana/admin-server/src/graphql/account.rs index ad6423d605..52b3c2026d 100644 --- a/lana/admin-server/src/graphql/account.rs +++ b/lana/admin-server/src/graphql/account.rs @@ -27,16 +27,16 @@ struct BtcAccountAmounts { net_credit: SignedSatoshis, } -// impl From for BtcAccountAmounts { -// fn from(balance: lana_app::ledger::account::BtcAccountBalance) -> Self { -// BtcAccountAmounts { -// debit: balance.debit, -// credit: balance.credit, -// net_debit: balance.net_debit, -// net_credit: balance.net_credit, -// } -// } -// } +impl From for BtcAccountAmounts { + fn from(balance: lana_app::trial_balance::BtcStatementBalanceAmount) -> Self { + BtcAccountAmounts { + debit: balance.dr_balance, + credit: balance.cr_balance, + net_debit: balance.net_dr_balance.into(), + net_credit: balance.net_cr_balance.into(), + } + } +} #[derive(SimpleObject)] struct UsdAccountAmounts { @@ -46,16 +46,16 @@ struct UsdAccountAmounts { net_credit: SignedUsdCents, } -// impl From for UsdAccountAmounts { -// fn from(balance: lana_app::ledger::account::UsdAccountBalance) -> Self { -// UsdAccountAmounts { -// debit: balance.debit, -// credit: balance.credit, -// net_debit: balance.net_debit, -// net_credit: balance.net_credit, -// } -// } -// } +impl From for UsdAccountAmounts { + fn from(balance: lana_app::trial_balance::UsdStatementBalanceAmount) -> Self { + UsdAccountAmounts { + debit: balance.dr_balance, + credit: balance.cr_balance, + net_debit: balance.net_dr_balance.into(), + net_credit: balance.net_cr_balance.into(), + } + } +} #[derive(SimpleObject)] struct LayeredBtcAccountAmounts { @@ -65,16 +65,16 @@ struct LayeredBtcAccountAmounts { encumbrance: BtcAccountAmounts, } -// impl From for LayeredBtcAccountAmounts { -// fn from(balances: lana_app::ledger::account::LayeredBtcAccountBalances) -> Self { -// LayeredBtcAccountAmounts { -// all: balances.all_layers.into(), -// settled: balances.settled.into(), -// pending: balances.pending.into(), -// encumbrance: balances.encumbrance.into(), -// } -// } -// } +impl From for LayeredBtcAccountAmounts { + fn from(balances: lana_app::trial_balance::BtcStatementAccountSetBalance) -> Self { + LayeredBtcAccountAmounts { + all: balances.all.into(), + settled: balances.settled.into(), + pending: balances.pending.into(), + encumbrance: balances.encumbrance.into(), + } + } +} #[derive(SimpleObject)] struct LayeredUsdAccountAmounts { @@ -84,16 +84,16 @@ struct LayeredUsdAccountAmounts { encumbrance: UsdAccountAmounts, } -// impl From for LayeredUsdAccountAmounts { -// fn from(balances: lana_app::ledger::account::LayeredUsdAccountBalances) -> Self { -// LayeredUsdAccountAmounts { -// all: balances.all_layers.into(), -// settled: balances.settled.into(), -// pending: balances.pending.into(), -// encumbrance: balances.encumbrance.into(), -// } -// } -// } +impl From for LayeredUsdAccountAmounts { + fn from(balances: lana_app::trial_balance::UsdStatementAccountSetBalance) -> Self { + LayeredUsdAccountAmounts { + all: balances.all.into(), + settled: balances.settled.into(), + pending: balances.pending.into(), + encumbrance: balances.encumbrance.into(), + } + } +} #[derive(SimpleObject)] pub struct BtcAccountAmountsInPeriod { @@ -102,15 +102,16 @@ pub struct BtcAccountAmountsInPeriod { amount: LayeredBtcAccountAmounts, } -// impl From for BtcAccountAmountsInPeriod { -// fn from(balances: lana_app::ledger::account::RangedBtcAccountBalances) -> Self { -// BtcAccountAmountsInPeriod { -// opening_balance: balances.start.into(), -// closing_balance: balances.end.into(), -// amount: balances.diff.into(), -// } -// } -// } +// FIXME: Adjust for ranged balance from domain +impl From for BtcAccountAmountsInPeriod { + fn from(balances: lana_app::trial_balance::BtcStatementAccountSetBalance) -> Self { + BtcAccountAmountsInPeriod { + opening_balance: balances.clone().into(), + closing_balance: balances.clone().into(), + amount: balances.into(), + } + } +} #[derive(SimpleObject)] pub struct UsdAccountAmountsInPeriod { @@ -119,15 +120,16 @@ pub struct UsdAccountAmountsInPeriod { amount: LayeredUsdAccountAmounts, } -// impl From for UsdAccountAmountsInPeriod { -// fn from(balances: lana_app::ledger::account::RangedUsdAccountBalances) -> Self { -// UsdAccountAmountsInPeriod { -// opening_balance: balances.start.into(), -// closing_balance: balances.end.into(), -// amount: balances.diff.into(), -// } -// } -// } +// FIXME: Adjust for ranged balance from domain +impl From for UsdAccountAmountsInPeriod { + fn from(balances: lana_app::trial_balance::UsdStatementAccountSetBalance) -> Self { + UsdAccountAmountsInPeriod { + opening_balance: balances.clone().into(), + closing_balance: balances.clone().into(), + amount: balances.into(), + } + } +} #[derive(SimpleObject)] pub struct AccountAmountsByCurrency { @@ -135,11 +137,20 @@ pub struct AccountAmountsByCurrency { usd: UsdAccountAmountsInPeriod, } -// impl From for AccountAmountsByCurrency { -// fn from(balances: lana_app::ledger::account::LedgerAccountBalancesByCurrency) -> Self { -// AccountAmountsByCurrency { -// btc: balances.btc.into(), -// usd: balances.usd.into(), -// } -// } -// } +impl From for AccountAmountsByCurrency { + fn from(balances: lana_app::trial_balance::TrialBalance) -> Self { + AccountAmountsByCurrency { + btc: balances.btc_balance.into(), + usd: balances.usd_balance.into(), + } + } +} + +impl From for AccountAmountsByCurrency { + fn from(balances: lana_app::trial_balance::StatementAccountSet) -> Self { + AccountAmountsByCurrency { + btc: balances.btc_balance.into(), + usd: balances.usd_balance.into(), + } + } +} diff --git a/lana/admin-server/src/graphql/financials/account_set.rs b/lana/admin-server/src/graphql/financials/account_set.rs index 307e1e0565..b8159d4abf 100644 --- a/lana/admin-server/src/graphql/financials/account_set.rs +++ b/lana/admin-server/src/graphql/financials/account_set.rs @@ -15,16 +15,16 @@ pub struct AccountSet { has_sub_accounts: bool, } -// impl From for AccountSet { -// fn from(line_item: lana_app::ledger::account_set::LedgerAccountSetWithBalance) -> Self { -// AccountSet { -// id: line_item.id.into(), -// name: line_item.name, -// amounts: line_item.balance.into(), -// has_sub_accounts: line_item.page_info.start_cursor.is_some(), -// } -// } -// } +impl From for AccountSet { + fn from(line_item: lana_app::trial_balance::StatementAccountSet) -> Self { + AccountSet { + id: line_item.id.into(), + name: line_item.name.to_string(), + amounts: line_item.into(), + has_sub_accounts: false, // FIXME: evaluate if still needed + } + } +} #[derive(Union)] pub enum AccountSetSubAccount { @@ -32,6 +32,12 @@ pub enum AccountSetSubAccount { AccountSet(AccountSet), } +impl From for AccountSetSubAccount { + fn from(member: lana_app::trial_balance::StatementAccountSet) -> Self { + AccountSetSubAccount::AccountSet(AccountSet::from(member)) + } +} + // impl From // for AccountSetSubAccount // { diff --git a/lana/admin-server/src/graphql/financials/trial_balance.rs b/lana/admin-server/src/graphql/financials/trial_balance.rs index 29db8837f0..102e28f911 100644 --- a/lana/admin-server/src/graphql/financials/trial_balance.rs +++ b/lana/admin-server/src/graphql/financials/trial_balance.rs @@ -10,16 +10,16 @@ pub struct TrialBalance { sub_accounts: Vec, } -// impl From for TrialBalance { -// fn from(trial_balance: lana_app::ledger::account_set::LedgerTrialBalance) -> Self { -// TrialBalance { -// name: trial_balance.name, -// total: trial_balance.balance.into(), -// sub_accounts: trial_balance -// .accounts -// .into_iter() -// .map(AccountSetSubAccount::from) -// .collect(), -// } -// } -// } +impl From for TrialBalance { + fn from(trial_balance: lana_app::trial_balance::TrialBalance) -> Self { + TrialBalance { + name: trial_balance.name.to_string(), + total: trial_balance.clone().into(), + sub_accounts: trial_balance + .accounts + .into_iter() + .map(AccountSetSubAccount::from) + .collect(), + } + } +} diff --git a/lana/admin-server/src/graphql/schema.rs b/lana/admin-server/src/graphql/schema.rs index ac2ee4d7bf..0e18589463 100644 --- a/lana/admin-server/src/graphql/schema.rs +++ b/lana/admin-server/src/graphql/schema.rs @@ -1,7 +1,9 @@ use async_graphql::{types::connection::*, Context, Object}; use lana_app::{ - accounting_init::constants::{CHART_REF, OBS_CHART_REF}, + accounting_init::constants::{ + CHART_REF, OBS_CHART_REF, OBS_TRIAL_BALANCE_STATEMENT_NAME, TRIAL_BALANCE_STATEMENT_NAME, + }, app::LanaApp, }; @@ -390,6 +392,7 @@ impl Query { maybe_fetch_one!(Document, ctx, app.documents().find_by_id(sub, id)) } + // TODO: remove Option from return type #[allow(unused_variables)] async fn trial_balance( &self, @@ -397,13 +400,12 @@ impl Query { from: Timestamp, until: Option, ) -> async_graphql::Result> { - unimplemented!() - // let (app, sub) = app_and_sub_from_ctx!(ctx); - // let account_summary = app - // .ledger() - // .trial_balance(sub, from.into_inner(), until.map(|t| t.into_inner())) - // .await?; - // Ok(account_summary.map(TrialBalance::from)) + let (app, sub) = app_and_sub_from_ctx!(ctx); + let account_summary = app + .trial_balances() + .trial_balance(sub, TRIAL_BALANCE_STATEMENT_NAME.to_string()) + .await?; + Ok(Some(TrialBalance::from(account_summary))) } #[allow(unused_variables)] @@ -413,13 +415,12 @@ impl Query { from: Timestamp, until: Option, ) -> async_graphql::Result> { - unimplemented!() - // let (app, sub) = app_and_sub_from_ctx!(ctx); - // let account_summary = app - // .ledger() - // .obs_trial_balance(sub, from.into_inner(), until.map(|t| t.into_inner())) - // .await?; - // Ok(account_summary.map(TrialBalance::from)) + let (app, sub) = app_and_sub_from_ctx!(ctx); + let account_summary = app + .trial_balances() + .trial_balance(sub, OBS_TRIAL_BALANCE_STATEMENT_NAME.to_string()) + .await?; + Ok(Some(TrialBalance::from(account_summary))) } async fn chart_of_accounts(&self, ctx: &Context<'_>) -> async_graphql::Result { diff --git a/lana/app/Cargo.toml b/lana/app/Cargo.toml index 169782d616..3ec89958a8 100644 --- a/lana/app/Cargo.toml +++ b/lana/app/Cargo.toml @@ -4,7 +4,7 @@ version = "0.3.7-dev" edition = "2021" [features] -sim-time = ["dep:sim-time", "es-entity/sim-time" ] +sim-time = ["dep:sim-time", "es-entity/sim-time"] fail-on-warnings = [] @@ -68,4 +68,4 @@ cloud-storage = { workspace = true } [dev-dependencies] rand = { workspace = true } -serial_test = { version = "3.1.1", features = [ "file_locks" ] } +serial_test = { version = "3.1.1", features = ["file_locks"] } diff --git a/lana/app/src/accounting_init/constants.rs b/lana/app/src/accounting_init/constants.rs deleted file mode 100644 index 1cb627b4bd..0000000000 --- a/lana/app/src/accounting_init/constants.rs +++ /dev/null @@ -1,66 +0,0 @@ -pub(super) const LANA_JOURNAL_CODE: &str = "LANA_BANK_JOURNAL"; - -pub const CHART_REF: &str = "primary-chart"; -pub(super) const CHART_NAME: &str = "Chart of Accounts"; - -pub(super) const DEPOSITS_CONTROL_ACCOUNT_REF: &str = "deposits"; -pub(super) const DEPOSITS_CONTROL_ACCOUNT_NAME: &str = "Deposits"; -pub(super) const DEPOSITS_CONTROL_SUB_ACCOUNT_REF: &str = "deposits-user"; -pub(super) const DEPOSITS_CONTROL_SUB_ACCOUNT_NAME: &str = "User Deposits"; - -pub(super) const CREDIT_FACILITIES_DISBURSED_RECEIVABLE_CONTROL_ACCOUNT_REF: &str = - "credit-facilities-disbursed-receivable"; -pub(super) const CREDIT_FACILITIES_DISBURSED_RECEIVABLE_CONTROL_ACCOUNT_NAME: &str = - "Credit Facilities Disbursed Receivable"; -pub(super) const CREDIT_FACILITIES_DISBURSED_RECEIVABLE_CONTROL_SUB_ACCOUNT_REF: &str = - "fixed-term-credit-facilities-disbursed-receivable"; -pub(super) const CREDIT_FACILITIES_DISBURSED_RECEIVABLE_CONTROL_SUB_ACCOUNT_NAME: &str = - "Fixed Term Credit Facilities Disbursed Receivable"; // Assets - -pub(super) const CREDIT_FACILITIES_INTEREST_RECEIVABLE_CONTROL_ACCOUNT_REF: &str = - "credit-facilities-interest-receivable"; -pub(super) const CREDIT_FACILITIES_INTEREST_RECEIVABLE_CONTROL_ACCOUNT_NAME: &str = - "Credit Facilities Interest Receivable"; -pub(super) const CREDIT_FACILITIES_INTEREST_RECEIVABLE_CONTROL_SUB_ACCOUNT_REF: &str = - "fixed-term-credit-facilities-interest-receivable"; -pub(super) const CREDIT_FACILITIES_INTEREST_RECEIVABLE_CONTROL_SUB_ACCOUNT_NAME: &str = - "Fixed Term Credit Facilities Interest Receivable"; // Assets - -pub(super) const CREDIT_FACILITIES_INTEREST_INCOME_CONTROL_ACCOUNT_REF: &str = - "credit-facilities-interest-income"; -pub(super) const CREDIT_FACILITIES_INTEREST_INCOME_CONTROL_ACCOUNT_NAME: &str = - "Credit Facilities Interest Income"; -pub(super) const CREDIT_FACILITIES_INTEREST_INCOME_CONTROL_SUB_ACCOUNT_REF: &str = - "fixed-term-credit-facilities-interest-income"; -pub(super) const CREDIT_FACILITIES_INTEREST_INCOME_CONTROL_SUB_ACCOUNT_NAME: &str = - "Fixed Term Credit Facilities Interest Income"; // Revenue - -pub(super) const CREDIT_FACILITIES_FEE_INCOME_CONTROL_ACCOUNT_REF: &str = - "credit-facilities-fee-income"; -pub(super) const CREDIT_FACILITIES_FEE_INCOME_CONTROL_ACCOUNT_NAME: &str = - "Credit Facilities Fee Income"; -pub(super) const CREDIT_FACILITIES_FEE_INCOME_CONTROL_SUB_ACCOUNT_REF: &str = - "fixed-term-credit-facilities-fee-income"; -pub(super) const CREDIT_FACILITIES_FEE_INCOME_CONTROL_SUB_ACCOUNT_NAME: &str = - "Fixed Term Credit Facilities Fee Income"; // Revenue - -pub const OBS_CHART_REF: &str = "off-balance-sheet-chart"; -pub(super) const OBS_CHART_NAME: &str = "Off-Balance-Sheet Chart of Accounts"; - -pub(super) const CREDIT_FACILITIES_COLLATERAL_CONTROL_ACCOUNT_REF: &str = - "credit-facilities-collateral"; -pub(super) const CREDIT_FACILITIES_COLLATERAL_CONTROL_ACCOUNT_NAME: &str = - "Credit Facilities Collateral"; // Liabilities -pub(super) const CREDIT_FACILITIES_COLLATERAL_CONTROL_SUB_ACCOUNT_REF: &str = - "fixed-term-credit-facilities-collateral"; -pub(super) const CREDIT_FACILITIES_COLLATERAL_CONTROL_SUB_ACCOUNT_NAME: &str = - "Fixed Term Credit Facilities Collateral"; // OBS Liabilities - -pub(super) const CREDIT_FACILITIES_FACILITY_CONTROL_ACCOUNT_REF: &str = - "credit-facilities-facility"; -pub(super) const CREDIT_FACILITIES_FACILITY_CONTROL_ACCOUNT_NAME: &str = - "Credit Facilities Facility Available"; -pub(super) const CREDIT_FACILITIES_FACILITY_CONTROL_SUB_ACCOUNT_REF: &str = - "fixed-term-credit-facilities-facility"; -pub(super) const CREDIT_FACILITIES_FACILITY_CONTROL_SUB_ACCOUNT_NAME: &str = - "Fixed Term Credit Facilities Facility Available"; // OBS Assets diff --git a/lana/app/src/accounting_init/constants/credit_facilities.rs b/lana/app/src/accounting_init/constants/credit_facilities.rs new file mode 100644 index 0000000000..41c259a01d --- /dev/null +++ b/lana/app/src/accounting_init/constants/credit_facilities.rs @@ -0,0 +1,48 @@ +pub const CREDIT_FACILITIES_DISBURSED_RECEIVABLE_CONTROL_ACCOUNT_REF: &str = + "credit-facilities-disbursed-receivable"; +pub const CREDIT_FACILITIES_DISBURSED_RECEIVABLE_CONTROL_ACCOUNT_NAME: &str = + "Credit Facilities Disbursed Receivable"; +pub const CREDIT_FACILITIES_DISBURSED_RECEIVABLE_CONTROL_SUB_ACCOUNT_REF: &str = + "fixed-term-credit-facilities-disbursed-receivable"; +pub const CREDIT_FACILITIES_DISBURSED_RECEIVABLE_CONTROL_SUB_ACCOUNT_NAME: &str = + "Fixed Term Credit Facilities Disbursed Receivable"; // Assets + +pub const CREDIT_FACILITIES_INTEREST_RECEIVABLE_CONTROL_ACCOUNT_REF: &str = + "credit-facilities-interest-receivable"; +pub const CREDIT_FACILITIES_INTEREST_RECEIVABLE_CONTROL_ACCOUNT_NAME: &str = + "Credit Facilities Interest Receivable"; +pub const CREDIT_FACILITIES_INTEREST_RECEIVABLE_CONTROL_SUB_ACCOUNT_REF: &str = + "fixed-term-credit-facilities-interest-receivable"; +pub const CREDIT_FACILITIES_INTEREST_RECEIVABLE_CONTROL_SUB_ACCOUNT_NAME: &str = + "Fixed Term Credit Facilities Interest Receivable"; // Assets + +pub const CREDIT_FACILITIES_INTEREST_INCOME_CONTROL_ACCOUNT_REF: &str = + "credit-facilities-interest-income"; +pub const CREDIT_FACILITIES_INTEREST_INCOME_CONTROL_ACCOUNT_NAME: &str = + "Credit Facilities Interest Income"; +pub const CREDIT_FACILITIES_INTEREST_INCOME_CONTROL_SUB_ACCOUNT_REF: &str = + "fixed-term-credit-facilities-interest-income"; +pub const CREDIT_FACILITIES_INTEREST_INCOME_CONTROL_SUB_ACCOUNT_NAME: &str = + "Fixed Term Credit Facilities Interest Income"; // Revenue + +pub const CREDIT_FACILITIES_FEE_INCOME_CONTROL_ACCOUNT_REF: &str = "credit-facilities-fee-income"; +pub const CREDIT_FACILITIES_FEE_INCOME_CONTROL_ACCOUNT_NAME: &str = "Credit Facilities Fee Income"; +pub const CREDIT_FACILITIES_FEE_INCOME_CONTROL_SUB_ACCOUNT_REF: &str = + "fixed-term-credit-facilities-fee-income"; +pub const CREDIT_FACILITIES_FEE_INCOME_CONTROL_SUB_ACCOUNT_NAME: &str = + "Fixed Term Credit Facilities Fee Income"; // Revenue + +pub const CREDIT_FACILITIES_COLLATERAL_CONTROL_ACCOUNT_REF: &str = "credit-facilities-collateral"; +pub const CREDIT_FACILITIES_COLLATERAL_CONTROL_ACCOUNT_NAME: &str = "Credit Facilities Collateral"; // Liabilities +pub const CREDIT_FACILITIES_COLLATERAL_CONTROL_SUB_ACCOUNT_REF: &str = + "fixed-term-credit-facilities-collateral"; +pub const CREDIT_FACILITIES_COLLATERAL_CONTROL_SUB_ACCOUNT_NAME: &str = + "Fixed Term Credit Facilities Collateral"; // OBS Liabilities + +pub const CREDIT_FACILITIES_FACILITY_CONTROL_ACCOUNT_REF: &str = "credit-facilities-facility"; +pub const CREDIT_FACILITIES_FACILITY_CONTROL_ACCOUNT_NAME: &str = + "Credit Facilities Facility Available"; +pub const CREDIT_FACILITIES_FACILITY_CONTROL_SUB_ACCOUNT_REF: &str = + "fixed-term-credit-facilities-facility"; +pub const CREDIT_FACILITIES_FACILITY_CONTROL_SUB_ACCOUNT_NAME: &str = + "Fixed Term Credit Facilities Facility Available"; // OBS Assets diff --git a/lana/app/src/accounting_init/constants/deposits.rs b/lana/app/src/accounting_init/constants/deposits.rs new file mode 100644 index 0000000000..1b9cba1f61 --- /dev/null +++ b/lana/app/src/accounting_init/constants/deposits.rs @@ -0,0 +1,4 @@ +pub const DEPOSITS_CONTROL_ACCOUNT_REF: &str = "deposits"; +pub const DEPOSITS_CONTROL_ACCOUNT_NAME: &str = "Deposits"; +pub const DEPOSITS_CONTROL_SUB_ACCOUNT_REF: &str = "deposits-user"; +pub const DEPOSITS_CONTROL_SUB_ACCOUNT_NAME: &str = "User Deposits"; diff --git a/lana/app/src/accounting_init/constants/mod.rs b/lana/app/src/accounting_init/constants/mod.rs new file mode 100644 index 0000000000..f4408f25a2 --- /dev/null +++ b/lana/app/src/accounting_init/constants/mod.rs @@ -0,0 +1,17 @@ +mod credit_facilities; +mod deposits; + +pub(super) use credit_facilities::*; +pub(super) use deposits::*; + +pub(super) const LANA_JOURNAL_CODE: &str = "LANA_BANK_JOURNAL"; + +pub const CHART_REF: &str = "primary-chart"; +pub(super) const CHART_NAME: &str = "Chart of Accounts"; + +pub const OBS_CHART_REF: &str = "off-balance-sheet-chart"; +pub(super) const OBS_CHART_NAME: &str = "Off-Balance-Sheet Chart of Accounts"; + +pub const TRIAL_BALANCE_STATEMENT_NAME: &str = "Trial Balance"; + +pub const OBS_TRIAL_BALANCE_STATEMENT_NAME: &str = "Off-Balance-Sheet Trial Balance"; diff --git a/lana/app/src/accounting_init/error.rs b/lana/app/src/accounting_init/error.rs index fb6e65c2aa..ab09c7ee7e 100644 --- a/lana/app/src/accounting_init/error.rs +++ b/lana/app/src/accounting_init/error.rs @@ -6,4 +6,6 @@ pub enum AccountingInitError { CoreChartOfAccountsError(#[from] chart_of_accounts::error::CoreChartOfAccountsError), #[error("ApplicationError - JournalError: {0}")] JournalError(#[from] cala_ledger::journal::error::JournalError), + #[error("ApplicationError - TrialBalanceError: {0}")] + TrialBalanceError(#[from] crate::trial_balance::error::TrialBalanceError), } diff --git a/lana/app/src/accounting_init/mod.rs b/lana/app/src/accounting_init/mod.rs index 648b240423..6eb6dbf884 100644 --- a/lana/app/src/accounting_init/mod.rs +++ b/lana/app/src/accounting_init/mod.rs @@ -6,13 +6,13 @@ pub mod error; use chart_of_accounts::ChartId; -use crate::chart_of_accounts::ChartOfAccounts; +use crate::{chart_of_accounts::ChartOfAccounts, trial_balance::TrialBalances}; use cala_ledger::CalaLedger; use error::*; pub use primitives::CreditFacilitiesAccountPaths; -use primitives::{ChartIds, DepositsAccountPaths, LedgerJournalId}; +use primitives::*; #[derive(Clone)] pub struct JournalInit { @@ -21,7 +21,17 @@ pub struct JournalInit { impl JournalInit { pub async fn journal(cala: &CalaLedger) -> Result { - seed::journal(cala).await + seed::journal::init(cala).await + } +} + +#[derive(Clone)] +pub struct StatementsInit; + +impl StatementsInit { + pub async fn statements(trial_balances: &TrialBalances) -> Result<(), AccountingInitError> { + seed::statements::init(trial_balances).await?; + Ok(()) } } @@ -34,8 +44,9 @@ pub struct ChartsInit { impl ChartsInit { pub async fn charts_of_accounts( + trial_balances: &TrialBalances, chart_of_accounts: &ChartOfAccounts, ) -> Result { - seed::charts_of_accounts(chart_of_accounts).await + seed::charts_of_accounts::init(trial_balances, chart_of_accounts).await } } diff --git a/lana/app/src/accounting_init/primitives.rs b/lana/app/src/accounting_init/primitives.rs index 0585af3f47..53aa9bddf9 100644 --- a/lana/app/src/accounting_init/primitives.rs +++ b/lana/app/src/accounting_init/primitives.rs @@ -2,6 +2,8 @@ pub use cala_ledger::primitives::JournalId as LedgerJournalId; use chart_of_accounts::{ChartId, ControlSubAccountDetails}; +pub use crate::primitives::TrialBalanceId; + #[derive(Clone, Copy)] pub struct ChartIds { pub primary: ChartId, diff --git a/lana/app/src/accounting_init/seed.rs b/lana/app/src/accounting_init/seed.rs deleted file mode 100644 index 77593c7e50..0000000000 --- a/lana/app/src/accounting_init/seed.rs +++ /dev/null @@ -1,233 +0,0 @@ -use chart_of_accounts::{ChartCategory, ControlSubAccountDetails}; - -use crate::primitives::LedgerAccountSetId; - -use super::{constants::*, *}; - -pub(super) async fn journal(cala: &CalaLedger) -> Result { - use cala_ledger::journal::*; - - let new_journal = NewJournal::builder() - .id(JournalId::new()) - .name("General Ledger") - .description("General ledger for Lana") - .code(LANA_JOURNAL_CODE) - .build() - .expect("new journal"); - - match cala.journals().create(new_journal).await { - Err(cala_ledger::journal::error::JournalError::CodeAlreadyExists) => { - let journal = cala - .journals() - .find_by_code(LANA_JOURNAL_CODE.to_string()) - .await?; - Ok(JournalInit { - journal_id: journal.id, - }) - } - Err(e) => Err(e.into()), - Ok(journal) => Ok(JournalInit { - journal_id: journal.id, - }), - } -} - -pub(super) async fn charts_of_accounts( - chart_of_accounts: &ChartOfAccounts, -) -> Result { - let chart_ids = &create_charts_of_accounts(chart_of_accounts).await?; - - let deposits = create_deposits_account_paths(chart_of_accounts, chart_ids).await?; - - let credit_facilities = - create_credit_facilities_account_paths(chart_of_accounts, chart_ids).await?; - - Ok(ChartsInit { - chart_ids: *chart_ids, - deposits, - credit_facilities, - }) -} - -async fn create_charts_of_accounts( - chart_of_accounts: &ChartOfAccounts, -) -> Result { - let primary = match chart_of_accounts - .find_by_reference(CHART_REF.to_string()) - .await? - { - Some(chart) => chart, - None => { - chart_of_accounts - .create_chart( - ChartId::new(), - CHART_NAME.to_string(), - CHART_REF.to_string(), - ) - .await? - } - }; - - let off_balance_sheet = match chart_of_accounts - .find_by_reference(OBS_CHART_REF.to_string()) - .await? - { - Some(chart) => chart, - None => { - chart_of_accounts - .create_chart( - ChartId::new(), - OBS_CHART_NAME.to_string(), - OBS_CHART_REF.to_string(), - ) - .await? - } - }; - Ok(ChartIds { - primary: primary.id, - off_balance_sheet: off_balance_sheet.id, - }) -} - -#[allow(clippy::too_many_arguments)] -async fn create_control_sub_account( - chart_of_accounts: &ChartOfAccounts, - id: LedgerAccountSetId, - chart_id: ChartId, - category: ChartCategory, - control_name: String, - control_reference: String, - sub_name: String, - sub_reference: String, -) -> Result { - let control_path = match chart_of_accounts - .find_control_account_by_reference(chart_id, control_reference.clone()) - .await? - { - Some(path) => path, - None => { - chart_of_accounts - .create_control_account(chart_id, category, control_name, control_reference) - .await? - } - }; - - let control_sub_account = match chart_of_accounts - .find_control_sub_account_by_reference(chart_id, sub_reference.clone()) - .await? - { - Some(account_details) => account_details, - None => { - chart_of_accounts - .create_control_sub_account(id, chart_id, control_path, sub_name, sub_reference) - .await? - } - }; - - Ok(control_sub_account) -} - -async fn create_deposits_account_paths( - chart_of_accounts: &ChartOfAccounts, - chart_ids: &ChartIds, -) -> Result { - let deposits = create_control_sub_account( - chart_of_accounts, - LedgerAccountSetId::new(), - chart_ids.primary, - chart_of_accounts::ChartCategory::Liabilities, - DEPOSITS_CONTROL_ACCOUNT_NAME.to_string(), - DEPOSITS_CONTROL_ACCOUNT_REF.to_string(), - DEPOSITS_CONTROL_SUB_ACCOUNT_NAME.to_string(), - DEPOSITS_CONTROL_SUB_ACCOUNT_REF.to_string(), - ) - .await?; - - Ok(DepositsAccountPaths { deposits }) -} - -async fn create_credit_facilities_account_paths( - chart_of_accounts: &ChartOfAccounts, - chart_ids: &ChartIds, -) -> Result { - let collateral = create_control_sub_account( - chart_of_accounts, - LedgerAccountSetId::new(), - chart_ids.off_balance_sheet, - chart_of_accounts::ChartCategory::Liabilities, - CREDIT_FACILITIES_COLLATERAL_CONTROL_ACCOUNT_NAME.to_string(), - CREDIT_FACILITIES_COLLATERAL_CONTROL_ACCOUNT_REF.to_string(), - CREDIT_FACILITIES_COLLATERAL_CONTROL_SUB_ACCOUNT_NAME.to_string(), - CREDIT_FACILITIES_COLLATERAL_CONTROL_SUB_ACCOUNT_REF.to_string(), - ) - .await?; - - let facility = create_control_sub_account( - chart_of_accounts, - LedgerAccountSetId::new(), - chart_ids.off_balance_sheet, - chart_of_accounts::ChartCategory::Assets, - CREDIT_FACILITIES_FACILITY_CONTROL_ACCOUNT_NAME.to_string(), - CREDIT_FACILITIES_FACILITY_CONTROL_ACCOUNT_REF.to_string(), - CREDIT_FACILITIES_FACILITY_CONTROL_SUB_ACCOUNT_NAME.to_string(), - CREDIT_FACILITIES_FACILITY_CONTROL_SUB_ACCOUNT_REF.to_string(), - ) - .await?; - - let disbursed_receivable = create_control_sub_account( - chart_of_accounts, - LedgerAccountSetId::new(), - chart_ids.primary, - chart_of_accounts::ChartCategory::Assets, - CREDIT_FACILITIES_DISBURSED_RECEIVABLE_CONTROL_ACCOUNT_NAME.to_string(), - CREDIT_FACILITIES_DISBURSED_RECEIVABLE_CONTROL_ACCOUNT_REF.to_string(), - CREDIT_FACILITIES_DISBURSED_RECEIVABLE_CONTROL_SUB_ACCOUNT_NAME.to_string(), - CREDIT_FACILITIES_DISBURSED_RECEIVABLE_CONTROL_SUB_ACCOUNT_REF.to_string(), - ) - .await?; - - let interest_receivable = create_control_sub_account( - chart_of_accounts, - LedgerAccountSetId::new(), - chart_ids.primary, - chart_of_accounts::ChartCategory::Assets, - CREDIT_FACILITIES_INTEREST_RECEIVABLE_CONTROL_ACCOUNT_NAME.to_string(), - CREDIT_FACILITIES_INTEREST_RECEIVABLE_CONTROL_ACCOUNT_REF.to_string(), - CREDIT_FACILITIES_INTEREST_RECEIVABLE_CONTROL_SUB_ACCOUNT_NAME.to_string(), - CREDIT_FACILITIES_INTEREST_RECEIVABLE_CONTROL_SUB_ACCOUNT_REF.to_string(), - ) - .await?; - - let interest_income = create_control_sub_account( - chart_of_accounts, - LedgerAccountSetId::new(), - chart_ids.primary, - chart_of_accounts::ChartCategory::Revenues, - CREDIT_FACILITIES_INTEREST_INCOME_CONTROL_ACCOUNT_NAME.to_string(), - CREDIT_FACILITIES_INTEREST_INCOME_CONTROL_ACCOUNT_REF.to_string(), - CREDIT_FACILITIES_INTEREST_INCOME_CONTROL_SUB_ACCOUNT_NAME.to_string(), - CREDIT_FACILITIES_INTEREST_INCOME_CONTROL_SUB_ACCOUNT_REF.to_string(), - ) - .await?; - - let fee_income = create_control_sub_account( - chart_of_accounts, - LedgerAccountSetId::new(), - chart_ids.primary, - chart_of_accounts::ChartCategory::Revenues, - CREDIT_FACILITIES_FEE_INCOME_CONTROL_ACCOUNT_NAME.to_string(), - CREDIT_FACILITIES_FEE_INCOME_CONTROL_ACCOUNT_REF.to_string(), - CREDIT_FACILITIES_FEE_INCOME_CONTROL_SUB_ACCOUNT_NAME.to_string(), - CREDIT_FACILITIES_FEE_INCOME_CONTROL_SUB_ACCOUNT_REF.to_string(), - ) - .await?; - - Ok(CreditFacilitiesAccountPaths { - collateral, - facility, - disbursed_receivable, - interest_receivable, - interest_income, - fee_income, - }) -} diff --git a/lana/app/src/accounting_init/seed/charts_of_accounts.rs b/lana/app/src/accounting_init/seed/charts_of_accounts.rs new file mode 100644 index 0000000000..121e10cc2e --- /dev/null +++ b/lana/app/src/accounting_init/seed/charts_of_accounts.rs @@ -0,0 +1,293 @@ +use chart_of_accounts::{ + ControlAccountCreationDetails, ControlAccountDetails, ControlSubAccountDetails, +}; + +use crate::{ + accounting_init::{constants::*, *}, + primitives::LedgerAccountSetId, +}; + +pub(crate) async fn init( + trial_balances: &TrialBalances, + chart_of_accounts: &ChartOfAccounts, +) -> Result { + let chart_ids = &create_charts_of_accounts(chart_of_accounts).await?; + + let deposits = + create_deposits_account_paths(trial_balances, chart_of_accounts, chart_ids).await?; + + let credit_facilities = + create_credit_facilities_account_paths(trial_balances, chart_of_accounts, chart_ids) + .await?; + + Ok(ChartsInit { + chart_ids: *chart_ids, + deposits, + credit_facilities, + }) +} + +async fn create_charts_of_accounts( + chart_of_accounts: &ChartOfAccounts, +) -> Result { + let primary = match chart_of_accounts + .find_by_reference(CHART_REF.to_string()) + .await? + { + Some(chart) => chart, + None => { + chart_of_accounts + .create_chart( + ChartId::new(), + CHART_NAME.to_string(), + CHART_REF.to_string(), + ) + .await? + } + }; + + let off_balance_sheet = match chart_of_accounts + .find_by_reference(OBS_CHART_REF.to_string()) + .await? + { + Some(chart) => chart, + None => { + chart_of_accounts + .create_chart( + ChartId::new(), + OBS_CHART_NAME.to_string(), + OBS_CHART_REF.to_string(), + ) + .await? + } + }; + + Ok(ChartIds { + primary: primary.id, + off_balance_sheet: off_balance_sheet.id, + }) +} + +async fn create_control_sub_account( + chart_of_accounts: &ChartOfAccounts, + id: LedgerAccountSetId, + chart_id: ChartId, + control_account: ControlAccountCreationDetails, + sub_name: String, + sub_reference: String, +) -> Result<(ControlAccountDetails, ControlSubAccountDetails), AccountingInitError> { + let control_account = match chart_of_accounts + .find_control_account_by_reference(chart_id, control_account.reference.to_string()) + .await? + { + Some(path) => ControlAccountDetails { + path, + account_set_id: control_account.account_set_id, + name: control_account.name.to_string(), + reference: control_account.reference.to_string(), + }, + None => { + chart_of_accounts + .create_control_account( + control_account.account_set_id, + chart_id, + control_account.category, + control_account.name, + control_account.reference, + ) + .await? + } + }; + + let control_sub_account = match chart_of_accounts + .find_control_sub_account_by_reference(chart_id, sub_reference.to_string()) + .await? + { + Some(account_details) => account_details, + None => { + chart_of_accounts + .create_control_sub_account( + id, + chart_id, + control_account.clone(), + sub_name, + sub_reference, + ) + .await? + } + }; + + Ok((control_account, control_sub_account)) +} + +async fn create_deposits_account_paths( + trial_balances: &TrialBalances, + chart_of_accounts: &ChartOfAccounts, + chart_ids: &ChartIds, +) -> Result { + let trial_balance_id = trial_balances + .find_by_name(TRIAL_BALANCE_STATEMENT_NAME.to_string()) + .await? + .unwrap_or_else(|| { + panic!( + "Trial balance for name '{}' not found", + TRIAL_BALANCE_STATEMENT_NAME + ) + }); + + let (deposits_control, deposits) = create_control_sub_account( + chart_of_accounts, + LedgerAccountSetId::new(), + chart_ids.primary, + ControlAccountCreationDetails { + account_set_id: LedgerAccountSetId::new(), + category: chart_of_accounts::ChartCategory::Liabilities, + name: DEPOSITS_CONTROL_ACCOUNT_NAME.to_string(), + reference: DEPOSITS_CONTROL_ACCOUNT_REF.to_string(), + }, + DEPOSITS_CONTROL_SUB_ACCOUNT_NAME.to_string(), + DEPOSITS_CONTROL_SUB_ACCOUNT_REF.to_string(), + ) + .await?; + trial_balances + .add_to_trial_balance(trial_balance_id, deposits_control.account_set_id) + .await?; + + Ok(DepositsAccountPaths { deposits }) +} + +async fn create_credit_facilities_account_paths( + trial_balances: &TrialBalances, + chart_of_accounts: &ChartOfAccounts, + chart_ids: &ChartIds, +) -> Result { + let trial_balance_id = trial_balances + .find_by_name(TRIAL_BALANCE_STATEMENT_NAME.to_string()) + .await? + .unwrap_or_else(|| { + panic!( + "Trial balance for reference '{}' not found", + TRIAL_BALANCE_STATEMENT_NAME + ) + }); + + let (collateral_control, collateral) = create_control_sub_account( + chart_of_accounts, + LedgerAccountSetId::new(), + chart_ids.off_balance_sheet, + ControlAccountCreationDetails { + account_set_id: LedgerAccountSetId::new(), + category: chart_of_accounts::ChartCategory::Liabilities, + name: CREDIT_FACILITIES_COLLATERAL_CONTROL_ACCOUNT_NAME.to_string(), + reference: CREDIT_FACILITIES_COLLATERAL_CONTROL_ACCOUNT_REF.to_string(), + }, + CREDIT_FACILITIES_COLLATERAL_CONTROL_SUB_ACCOUNT_NAME.to_string(), + CREDIT_FACILITIES_COLLATERAL_CONTROL_SUB_ACCOUNT_REF.to_string(), + ) + .await?; + trial_balances + .add_to_trial_balance(trial_balance_id, collateral_control.account_set_id) + .await?; + + let (facility_control, facility) = create_control_sub_account( + chart_of_accounts, + LedgerAccountSetId::new(), + chart_ids.off_balance_sheet, + ControlAccountCreationDetails { + account_set_id: LedgerAccountSetId::new(), + category: chart_of_accounts::ChartCategory::Assets, + name: CREDIT_FACILITIES_FACILITY_CONTROL_ACCOUNT_NAME.to_string(), + reference: CREDIT_FACILITIES_FACILITY_CONTROL_ACCOUNT_REF.to_string(), + }, + CREDIT_FACILITIES_FACILITY_CONTROL_SUB_ACCOUNT_NAME.to_string(), + CREDIT_FACILITIES_FACILITY_CONTROL_SUB_ACCOUNT_REF.to_string(), + ) + .await?; + trial_balances + .add_to_trial_balance(trial_balance_id, facility_control.account_set_id) + .await?; + + let (disbursed_receivable_control, disbursed_receivable) = create_control_sub_account( + chart_of_accounts, + LedgerAccountSetId::new(), + chart_ids.primary, + ControlAccountCreationDetails { + account_set_id: LedgerAccountSetId::new(), + category: chart_of_accounts::ChartCategory::Assets, + name: CREDIT_FACILITIES_DISBURSED_RECEIVABLE_CONTROL_ACCOUNT_NAME.to_string(), + reference: CREDIT_FACILITIES_DISBURSED_RECEIVABLE_CONTROL_ACCOUNT_REF.to_string(), + }, + CREDIT_FACILITIES_DISBURSED_RECEIVABLE_CONTROL_SUB_ACCOUNT_NAME.to_string(), + CREDIT_FACILITIES_DISBURSED_RECEIVABLE_CONTROL_SUB_ACCOUNT_REF.to_string(), + ) + .await?; + trial_balances + .add_to_trial_balance( + trial_balance_id, + disbursed_receivable_control.account_set_id, + ) + .await?; + + let (interest_receivable_control, interest_receivable) = create_control_sub_account( + chart_of_accounts, + LedgerAccountSetId::new(), + chart_ids.primary, + ControlAccountCreationDetails { + account_set_id: LedgerAccountSetId::new(), + category: chart_of_accounts::ChartCategory::Assets, + name: CREDIT_FACILITIES_INTEREST_RECEIVABLE_CONTROL_ACCOUNT_NAME.to_string(), + reference: CREDIT_FACILITIES_INTEREST_RECEIVABLE_CONTROL_ACCOUNT_REF.to_string(), + }, + CREDIT_FACILITIES_INTEREST_RECEIVABLE_CONTROL_SUB_ACCOUNT_NAME.to_string(), + CREDIT_FACILITIES_INTEREST_RECEIVABLE_CONTROL_SUB_ACCOUNT_REF.to_string(), + ) + .await?; + trial_balances + .add_to_trial_balance(trial_balance_id, interest_receivable_control.account_set_id) + .await?; + + let (interest_income_control, interest_income) = create_control_sub_account( + chart_of_accounts, + LedgerAccountSetId::new(), + chart_ids.primary, + ControlAccountCreationDetails { + account_set_id: LedgerAccountSetId::new(), + category: chart_of_accounts::ChartCategory::Revenues, + name: CREDIT_FACILITIES_INTEREST_INCOME_CONTROL_ACCOUNT_NAME.to_string(), + reference: CREDIT_FACILITIES_INTEREST_INCOME_CONTROL_ACCOUNT_REF.to_string(), + }, + CREDIT_FACILITIES_INTEREST_INCOME_CONTROL_SUB_ACCOUNT_NAME.to_string(), + CREDIT_FACILITIES_INTEREST_INCOME_CONTROL_SUB_ACCOUNT_REF.to_string(), + ) + .await?; + trial_balances + .add_to_trial_balance(trial_balance_id, interest_income_control.account_set_id) + .await?; + + let (fee_income_control, fee_income) = create_control_sub_account( + chart_of_accounts, + LedgerAccountSetId::new(), + chart_ids.primary, + ControlAccountCreationDetails { + account_set_id: LedgerAccountSetId::new(), + category: chart_of_accounts::ChartCategory::Revenues, + name: CREDIT_FACILITIES_FEE_INCOME_CONTROL_ACCOUNT_NAME.to_string(), + reference: CREDIT_FACILITIES_FEE_INCOME_CONTROL_ACCOUNT_REF.to_string(), + }, + CREDIT_FACILITIES_FEE_INCOME_CONTROL_SUB_ACCOUNT_NAME.to_string(), + CREDIT_FACILITIES_FEE_INCOME_CONTROL_SUB_ACCOUNT_REF.to_string(), + ) + .await?; + trial_balances + .add_to_trial_balance(trial_balance_id, fee_income_control.account_set_id) + .await?; + + Ok(CreditFacilitiesAccountPaths { + collateral, + facility, + disbursed_receivable, + interest_receivable, + interest_income, + fee_income, + }) +} diff --git a/lana/app/src/accounting_init/seed/journal.rs b/lana/app/src/accounting_init/seed/journal.rs new file mode 100644 index 0000000000..49f7510dfc --- /dev/null +++ b/lana/app/src/accounting_init/seed/journal.rs @@ -0,0 +1,29 @@ +use crate::accounting_init::{constants::*, *}; + +pub(crate) async fn init(cala: &CalaLedger) -> Result { + use cala_ledger::journal::*; + + let new_journal = NewJournal::builder() + .id(JournalId::new()) + .name("General Ledger") + .description("General ledger for Lana") + .code(LANA_JOURNAL_CODE) + .build() + .expect("new journal"); + + match cala.journals().create(new_journal).await { + Err(cala_ledger::journal::error::JournalError::CodeAlreadyExists) => { + let journal = cala + .journals() + .find_by_code(LANA_JOURNAL_CODE.to_string()) + .await?; + Ok(JournalInit { + journal_id: journal.id, + }) + } + Err(e) => Err(e.into()), + Ok(journal) => Ok(JournalInit { + journal_id: journal.id, + }), + } +} diff --git a/lana/app/src/accounting_init/seed/mod.rs b/lana/app/src/accounting_init/seed/mod.rs new file mode 100644 index 0000000000..9e5be1c7b7 --- /dev/null +++ b/lana/app/src/accounting_init/seed/mod.rs @@ -0,0 +1,3 @@ +pub(crate) mod charts_of_accounts; +pub(crate) mod journal; +pub(crate) mod statements; diff --git a/lana/app/src/accounting_init/seed/statements.rs b/lana/app/src/accounting_init/seed/statements.rs new file mode 100644 index 0000000000..94325c312e --- /dev/null +++ b/lana/app/src/accounting_init/seed/statements.rs @@ -0,0 +1,45 @@ +use constants::{OBS_TRIAL_BALANCE_STATEMENT_NAME, TRIAL_BALANCE_STATEMENT_NAME}; + +use crate::accounting_init::*; + +pub(crate) async fn init( + trial_balances: &TrialBalances, +) -> Result { + create_trial_balances(trial_balances).await?; + + Ok(StatementsInit) +} + +async fn create_trial_balances(trial_balances: &TrialBalances) -> Result<(), AccountingInitError> { + let _primary_id = match trial_balances + .find_by_name(TRIAL_BALANCE_STATEMENT_NAME.to_string()) + .await? + { + Some(trial_balance_id) => trial_balance_id, + None => { + trial_balances + .create_trial_balance_statement( + TrialBalanceId::new(), + TRIAL_BALANCE_STATEMENT_NAME.to_string(), + ) + .await? + } + }; + + let _off_balance_sheet_id = match trial_balances + .find_by_name(OBS_TRIAL_BALANCE_STATEMENT_NAME.to_string()) + .await? + { + Some(chart) => chart, + None => { + trial_balances + .create_trial_balance_statement( + TrialBalanceId::new(), + OBS_TRIAL_BALANCE_STATEMENT_NAME.to_string(), + ) + .await? + } + }; + + Ok(()) +} diff --git a/lana/app/src/app/error.rs b/lana/app/src/app/error.rs index 1d24234d6f..9d3edb2829 100644 --- a/lana/app/src/app/error.rs +++ b/lana/app/src/app/error.rs @@ -12,6 +12,8 @@ pub enum ApplicationError { CustomerError(#[from] crate::customer::error::CustomerError), #[error("ApplicationError - CreditFacilityError: {0}")] CreditFacilityError(#[from] crate::credit_facility::error::CreditFacilityError), + #[error("ApplicationError - TrialBalanceError: {0}")] + TrialBalanceError(#[from] crate::trial_balance::error::TrialBalanceError), #[error("ApplicationError - UserError: {0}")] UserError(#[from] crate::user::error::UserError), #[error("ApplicationError - AuthorizationError: {0}")] diff --git a/lana/app/src/app/mod.rs b/lana/app/src/app/mod.rs index 533320190e..17f6c3df46 100644 --- a/lana/app/src/app/mod.rs +++ b/lana/app/src/app/mod.rs @@ -7,7 +7,7 @@ use tracing::instrument; use authz::PermissionCheck; use crate::{ - accounting_init::{ChartsInit, JournalInit}, + accounting_init::{ChartsInit, JournalInit, StatementsInit}, applicant::Applicants, audit::{Audit, AuditCursor, AuditEntry}, authorization::{init as init_authz, AppAction, AppObject, AuditAction, Authorization}, @@ -25,6 +25,7 @@ use crate::{ report::Reports, storage::Storage, terms_template::TermsTemplates, + trial_balance::TrialBalances, user::Users, }; @@ -43,6 +44,7 @@ pub struct LanaApp { applicants: Applicants, users: Users, credit_facilities: CreditFacilities, + trial_balances: TrialBalances, price: Price, report: Reports, terms_templates: TermsTemplates, @@ -75,9 +77,13 @@ impl LanaApp { .expect("cala config"); let cala = cala_ledger::CalaLedger::init(cala_config).await?; let journal_init = JournalInit::journal(&cala).await?; + let trial_balances = + TrialBalances::init(&pool, &authz, &cala, journal_init.journal_id).await?; + StatementsInit::statements(&trial_balances).await?; let chart_of_accounts = ChartOfAccounts::init(&pool, &authz, &cala, journal_init.journal_id).await?; - let charts_init = ChartsInit::charts_of_accounts(&chart_of_accounts).await?; + let charts_init = + ChartsInit::charts_of_accounts(&trial_balances, &chart_of_accounts).await?; let deposits_factory = chart_of_accounts.transaction_account_factory(charts_init.deposits.deposits); @@ -128,6 +134,7 @@ impl LanaApp { price, report, credit_facilities, + trial_balances, terms_templates, documents, _outbox: outbox, @@ -191,6 +198,10 @@ impl LanaApp { &self.credit_facilities } + pub fn trial_balances(&self) -> &TrialBalances { + &self.trial_balances + } + pub fn users(&self) -> &Users { &self.users } diff --git a/lana/app/src/authorization/seed.rs b/lana/app/src/authorization/seed.rs index 52286815dc..1d23d534e3 100644 --- a/lana/app/src/authorization/seed.rs +++ b/lana/app/src/authorization/seed.rs @@ -343,6 +343,15 @@ async fn add_permissions_for_bank_manager(authz: &Authorization) -> Result<(), A CreditFacilityAction::Complete, ) .await?; + authz + .add_permission_to_role(&role, Object::TrialBalance, TrialBalanceAction::Read) + .await?; + authz + .add_permission_to_role(&role, Object::TrialBalance, TrialBalanceAction::Create) + .await?; + authz + .add_permission_to_role(&role, Object::TrialBalance, TrialBalanceAction::Update) + .await?; authz .add_permission_to_role( &role, diff --git a/lana/app/src/lib.rs b/lana/app/src/lib.rs index a448da2bfe..af648d1f64 100644 --- a/lana/app/src/lib.rs +++ b/lana/app/src/lib.rs @@ -16,6 +16,7 @@ pub mod storage; pub mod terms; pub mod terms_template; mod time; +pub mod trial_balance; pub mod outbox { pub type Outbox = outbox::Outbox; diff --git a/lana/app/src/trial_balance/error.rs b/lana/app/src/trial_balance/error.rs new file mode 100644 index 0000000000..56c5a57287 --- /dev/null +++ b/lana/app/src/trial_balance/error.rs @@ -0,0 +1,17 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum TrialBalanceError { + #[error("TrialBalanceError - Sqlx: {0}")] + Sqlx(#[from] sqlx::Error), + #[error("TrialBalanceError - AuditError: {0}")] + AuditError(#[from] audit::error::AuditError), + #[error("TrialBalanceError - AuthorizationError: {0}")] + AuthorizationError(#[from] authz::error::AuthorizationError), + #[error("TrialBalanceError - TrialBalanceLedgerError: {0}")] + TrialBalanceLedgerError(#[from] super::ledger::error::TrialBalanceLedgerError), + #[error("TrialBalanceError - MultipleFound: {0}")] + MultipleFound(String), + #[error("TrialBalanceError - NotFound: {0}")] + NotFound(String), +} diff --git a/lana/app/src/trial_balance/ledger/error.rs b/lana/app/src/trial_balance/ledger/error.rs new file mode 100644 index 0000000000..23e856ad75 --- /dev/null +++ b/lana/app/src/trial_balance/ledger/error.rs @@ -0,0 +1,17 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum TrialBalanceLedgerError { + #[error("TrialBalanceLedgerError - Sqlx: {0}")] + Sqlx(#[from] sqlx::Error), + #[error("TrialBalanceLedgerError - CalaLedger: {0}")] + CalaLedger(#[from] cala_ledger::error::LedgerError), + #[error("TrialBalanceLedgerError - CalaAccountSet: {0}")] + CalaAccountSet(#[from] cala_ledger::account_set::error::AccountSetError), + #[error("TrialBalanceLedgerError - CalaBalance: {0}")] + CalaBalance(#[from] cala_ledger::balance::error::BalanceError), + #[error("TrialBalanceError - ConversionError: {0}")] + ConversionError(#[from] core_money::ConversionError), + #[error("TrialBalanceLedgerError - NonAccountSetMemberTypeFound")] + NonAccountSetMemberTypeFound, +} diff --git a/lana/app/src/trial_balance/ledger/mod.rs b/lana/app/src/trial_balance/ledger/mod.rs new file mode 100644 index 0000000000..b8c2bc690d --- /dev/null +++ b/lana/app/src/trial_balance/ledger/mod.rs @@ -0,0 +1,177 @@ +pub mod error; +pub mod statement; + +use cala_ledger::{ + account_set::{AccountSet, AccountSetMemberId, AccountSetsByCreatedAtCursor, NewAccountSet}, + balance::error::BalanceError, + AccountSetId, CalaLedger, Currency, DebitOrCredit, JournalId, LedgerOperation, +}; + +use error::*; +use statement::*; + +#[derive(Clone)] +pub struct TrialBalanceLedger { + cala: CalaLedger, + journal_id: JournalId, +} + +impl TrialBalanceLedger { + pub fn new(cala: &CalaLedger, journal_id: JournalId) -> Self { + Self { + cala: cala.clone(), + journal_id, + } + } + + pub async fn create( + &self, + op: es_entity::DbOp<'_>, + statement_id: impl Into, + name: &str, + ) -> Result<(), TrialBalanceLedgerError> { + let mut op = self.cala.ledger_operation_from_db_op(op); + + let new_account_set = NewAccountSet::builder() + .id(statement_id) + .journal_id(self.journal_id) + .name(name) + .description(name) + .normal_balance_type(DebitOrCredit::Debit) + .build() + .expect("Could not build new account set"); + self.cala + .account_sets() + .create_in_op(&mut op, new_account_set) + .await?; + + op.commit().await?; + Ok(()) + } + + pub async fn list_for_name( + &self, + name: String, + args: es_entity::PaginatedQueryArgs, + ) -> Result< + es_entity::PaginatedQueryRet, + TrialBalanceLedgerError, + > { + Ok(self.cala.account_sets().list_for_name(name, args).await?) + } + + pub async fn add_member( + &self, + op: es_entity::DbOp<'_>, + statement_id: impl Into, + member: AccountSetId, + ) -> Result<(), TrialBalanceLedgerError> { + let statement_id = statement_id.into(); + + let mut op = self.cala.ledger_operation_from_db_op(op); + self.cala + .account_sets() + .add_member_in_op(&mut op, statement_id, member) + .await?; + + op.commit().await?; + Ok(()) + } + + async fn get_account_set( + &self, + op: &mut LedgerOperation<'_>, + id: impl Into + Copy, + ) -> Result { + let id = id.into(); + + let values = self.cala.account_sets().find(id).await?.into_values(); + + let btc_currency = + Currency::try_from("BTC".to_string()).expect("Cannot deserialize 'BTC' as Currency"); + let btc_balance = match self + .cala + .balances() + .find_in_op(op, self.journal_id, id, btc_currency) + .await + { + Ok(balance) => balance.try_into()?, + Err(BalanceError::NotFound(_, _, _)) => BtcStatementAccountSetBalance::ZERO, + Err(e) => return Err(e.into()), + }; + + let usd_currency = + Currency::try_from("USD".to_string()).expect("Cannot deserialize 'USD' as Currency"); + let usd_balance = match self + .cala + .balances() + .find_in_op(op, self.journal_id, id, usd_currency) + .await + { + Ok(balance) => balance.try_into()?, + Err(BalanceError::NotFound(_, _, _)) => UsdStatementAccountSetBalance::ZERO, + Err(e) => return Err(e.into()), + }; + + Ok(StatementAccountSet { + id: values.id, + name: values.name, + description: values.description, + btc_balance, + usd_balance, + }) + } + + async fn get_member_account_sets( + &self, + op: &mut LedgerOperation<'_>, + id: impl Into + Copy, + ) -> Result, TrialBalanceLedgerError> { + let id = id.into(); + + let member_ids = self + .cala + .account_sets() + .list_members_in_op(op, id, Default::default()) + .await? + .entities + .into_iter() + .map(|m| match m.id { + AccountSetMemberId::AccountSet(id) => Ok(id), + _ => Err(TrialBalanceLedgerError::NonAccountSetMemberTypeFound), + }) + .collect::, TrialBalanceLedgerError>>()?; + + let mut accounts: Vec = vec![]; + for id in member_ids { + accounts.push(self.get_account_set(op, id).await?); + } + + Ok(accounts) + } + + pub async fn get_trial_balance( + &self, + id: impl Into + Copy, + ) -> Result { + let mut op = self.cala.begin_operation().await?; + + let trial_balance_set = self.get_account_set(&mut op, id).await?; + + let accounts = self.get_member_account_sets(&mut op, id).await?; + + op.commit().await?; + + Ok(StatementAccountSetWithAccounts { + id: trial_balance_set.id, + name: trial_balance_set.name, + description: trial_balance_set.description, + btc_balance: trial_balance_set.btc_balance, + usd_balance: trial_balance_set.usd_balance, + accounts: accounts + .into_iter() + .map(StatementAccountSet::from) + .collect(), + }) + } +} diff --git a/lana/app/src/trial_balance/ledger/statement.rs b/lana/app/src/trial_balance/ledger/statement.rs new file mode 100644 index 0000000000..46d2672e1f --- /dev/null +++ b/lana/app/src/trial_balance/ledger/statement.rs @@ -0,0 +1,210 @@ +use cala_ledger::balance::AccountBalance; + +use crate::primitives::{LedgerAccountSetId, Satoshis, UsdCents}; + +use super::TrialBalanceLedgerError; + +#[derive(Clone)] +pub struct StatementAccountSet { + pub id: LedgerAccountSetId, + pub name: String, + pub description: Option, + pub btc_balance: BtcStatementAccountSetBalance, + pub usd_balance: UsdStatementAccountSetBalance, +} + +#[derive(Clone)] +pub struct StatementAccountSetWithAccounts { + pub id: LedgerAccountSetId, + pub name: String, + pub description: Option, + pub btc_balance: BtcStatementAccountSetBalance, + pub usd_balance: UsdStatementAccountSetBalance, + pub accounts: Vec, +} + +#[derive(Clone)] +pub struct BtcStatementAccountSetBalance { + pub all: BtcStatementBalanceAmount, + pub settled: BtcStatementBalanceAmount, + pub pending: BtcStatementBalanceAmount, + pub encumbrance: BtcStatementBalanceAmount, +} + +impl TryFrom for BtcStatementAccountSetBalance { + type Error = TrialBalanceLedgerError; + + fn try_from(balance: AccountBalance) -> Result { + let all_details = balance.details.available(cala_ledger::Layer::Encumbrance); + + Ok(Self { + all: BtcStatementBalanceAmount { + normal_balance: Satoshis::try_from_btc( + balance.available(cala_ledger::Layer::Encumbrance), + )?, + dr_balance: Satoshis::try_from_btc(all_details.dr_balance)?, + cr_balance: Satoshis::try_from_btc(all_details.cr_balance)?, + net_dr_balance: Satoshis::try_from_btc( + all_details.dr_balance - all_details.cr_balance, + )?, + net_cr_balance: Satoshis::try_from_btc( + all_details.cr_balance - all_details.dr_balance, + )?, + }, + settled: BtcStatementBalanceAmount { + normal_balance: Satoshis::try_from_btc(balance.settled())?, + dr_balance: Satoshis::try_from_btc(balance.details.settled.dr_balance)?, + cr_balance: Satoshis::try_from_btc(balance.details.settled.cr_balance)?, + net_dr_balance: Satoshis::try_from_btc( + balance.details.settled.dr_balance - balance.details.settled.cr_balance, + )?, + net_cr_balance: Satoshis::try_from_btc( + balance.details.settled.cr_balance - balance.details.settled.dr_balance, + )?, + }, + pending: BtcStatementBalanceAmount { + normal_balance: Satoshis::try_from_btc(balance.pending())?, + dr_balance: Satoshis::try_from_btc(balance.details.pending.dr_balance)?, + cr_balance: Satoshis::try_from_btc(balance.details.pending.cr_balance)?, + net_dr_balance: Satoshis::try_from_btc( + balance.details.pending.dr_balance - balance.details.pending.cr_balance, + )?, + net_cr_balance: Satoshis::try_from_btc( + balance.details.pending.cr_balance - balance.details.pending.dr_balance, + )?, + }, + encumbrance: BtcStatementBalanceAmount { + normal_balance: Satoshis::try_from_btc(balance.encumbrance())?, + dr_balance: Satoshis::try_from_btc(balance.details.encumbrance.dr_balance)?, + cr_balance: Satoshis::try_from_btc(balance.details.encumbrance.cr_balance)?, + net_dr_balance: Satoshis::try_from_btc( + balance.details.encumbrance.dr_balance - balance.details.encumbrance.cr_balance, + )?, + net_cr_balance: Satoshis::try_from_btc( + balance.details.encumbrance.cr_balance - balance.details.encumbrance.dr_balance, + )?, + }, + }) + } +} + +impl BtcStatementAccountSetBalance { + pub const ZERO: Self = Self { + all: BtcStatementBalanceAmount::ZERO, + settled: BtcStatementBalanceAmount::ZERO, + pending: BtcStatementBalanceAmount::ZERO, + encumbrance: BtcStatementBalanceAmount::ZERO, + }; +} + +#[derive(Clone)] +pub struct UsdStatementAccountSetBalance { + pub all: UsdStatementBalanceAmount, + pub settled: UsdStatementBalanceAmount, + pub pending: UsdStatementBalanceAmount, + pub encumbrance: UsdStatementBalanceAmount, +} + +impl TryFrom for UsdStatementAccountSetBalance { + type Error = TrialBalanceLedgerError; + + fn try_from(balance: AccountBalance) -> Result { + let all_details = balance.details.available(cala_ledger::Layer::Encumbrance); + + Ok(Self { + all: UsdStatementBalanceAmount { + normal_balance: UsdCents::try_from_usd( + balance.available(cala_ledger::Layer::Encumbrance), + )?, + dr_balance: UsdCents::try_from_usd(all_details.dr_balance)?, + cr_balance: UsdCents::try_from_usd(all_details.cr_balance)?, + net_dr_balance: UsdCents::try_from_usd( + all_details.dr_balance - all_details.cr_balance, + )?, + net_cr_balance: UsdCents::try_from_usd( + all_details.cr_balance - all_details.dr_balance, + )?, + }, + settled: UsdStatementBalanceAmount { + normal_balance: UsdCents::try_from_usd(balance.settled())?, + dr_balance: UsdCents::try_from_usd(balance.details.settled.dr_balance)?, + cr_balance: UsdCents::try_from_usd(balance.details.settled.cr_balance)?, + net_dr_balance: UsdCents::try_from_usd( + balance.details.settled.dr_balance - balance.details.settled.cr_balance, + )?, + net_cr_balance: UsdCents::try_from_usd( + balance.details.settled.cr_balance - balance.details.settled.dr_balance, + )?, + }, + pending: UsdStatementBalanceAmount { + normal_balance: UsdCents::try_from_usd(balance.pending())?, + dr_balance: UsdCents::try_from_usd(balance.details.pending.dr_balance)?, + cr_balance: UsdCents::try_from_usd(balance.details.pending.cr_balance)?, + net_dr_balance: UsdCents::try_from_usd( + balance.details.pending.dr_balance - balance.details.pending.cr_balance, + )?, + net_cr_balance: UsdCents::try_from_usd( + balance.details.pending.cr_balance - balance.details.pending.dr_balance, + )?, + }, + encumbrance: UsdStatementBalanceAmount { + normal_balance: UsdCents::try_from_usd(balance.encumbrance())?, + dr_balance: UsdCents::try_from_usd(balance.details.encumbrance.dr_balance)?, + cr_balance: UsdCents::try_from_usd(balance.details.encumbrance.cr_balance)?, + net_dr_balance: UsdCents::try_from_usd( + balance.details.encumbrance.dr_balance - balance.details.encumbrance.cr_balance, + )?, + net_cr_balance: UsdCents::try_from_usd( + balance.details.encumbrance.cr_balance - balance.details.encumbrance.dr_balance, + )?, + }, + }) + } +} + +impl UsdStatementAccountSetBalance { + pub const ZERO: Self = Self { + all: UsdStatementBalanceAmount::ZERO, + settled: UsdStatementBalanceAmount::ZERO, + pending: UsdStatementBalanceAmount::ZERO, + encumbrance: UsdStatementBalanceAmount::ZERO, + }; +} + +#[derive(Clone)] +pub struct BtcStatementBalanceAmount { + pub normal_balance: Satoshis, + pub dr_balance: Satoshis, + pub cr_balance: Satoshis, + pub net_dr_balance: Satoshis, + pub net_cr_balance: Satoshis, +} + +impl BtcStatementBalanceAmount { + pub const ZERO: Self = Self { + normal_balance: Satoshis::ZERO, + dr_balance: Satoshis::ZERO, + cr_balance: Satoshis::ZERO, + net_dr_balance: Satoshis::ZERO, + net_cr_balance: Satoshis::ZERO, + }; +} + +#[derive(Clone)] +pub struct UsdStatementBalanceAmount { + pub normal_balance: UsdCents, + pub dr_balance: UsdCents, + pub cr_balance: UsdCents, + pub net_dr_balance: UsdCents, + pub net_cr_balance: UsdCents, +} + +impl UsdStatementBalanceAmount { + pub const ZERO: Self = Self { + normal_balance: UsdCents::ZERO, + dr_balance: UsdCents::ZERO, + cr_balance: UsdCents::ZERO, + net_dr_balance: UsdCents::ZERO, + net_cr_balance: UsdCents::ZERO, + }; +} diff --git a/lana/app/src/trial_balance/mod.rs b/lana/app/src/trial_balance/mod.rs new file mode 100644 index 0000000000..27d61d43be --- /dev/null +++ b/lana/app/src/trial_balance/mod.rs @@ -0,0 +1,150 @@ +pub mod error; +pub mod ledger; + +use audit::AuditSvc; +use authz::PermissionCheck; +use cala_ledger::CalaLedger; +use rbac_types::{Subject, TrialBalanceAction}; + +use crate::{ + authorization::{Authorization, Object}, + primitives::{LedgerAccountSetId, TrialBalanceId}, +}; + +use error::*; +use ledger::*; +pub use statement::*; + +#[derive(Clone)] +pub struct TrialBalances { + pool: sqlx::PgPool, + authz: Authorization, + trial_balance_ledger: TrialBalanceLedger, +} + +impl TrialBalances { + pub async fn init( + pool: &sqlx::PgPool, + authz: &Authorization, + cala: &CalaLedger, + journal_id: cala_ledger::JournalId, + ) -> Result { + let trial_balance_ledger = TrialBalanceLedger::new(cala, journal_id); + + Ok(Self { + pool: pool.clone(), + trial_balance_ledger, + authz: authz.clone(), + }) + } + + pub async fn create_trial_balance_statement( + &self, + id: impl Into, + name: String, + ) -> Result { + let account_set_id: LedgerAccountSetId = id.into().into(); + + let mut op = es_entity::DbOp::init(&self.pool).await?; + + self.authz + .audit() + .record_system_entry_in_tx(op.tx(), Object::TrialBalance, TrialBalanceAction::Create) + .await?; + + self.trial_balance_ledger + .create(op, account_set_id, &name) + .await?; + + Ok(account_set_id) + } + + pub async fn find_by_name( + &self, + name: String, + ) -> Result, TrialBalanceError> { + self.authz + .audit() + .record_system_entry(Object::TrialBalance, TrialBalanceAction::Read) + .await?; + + let trial_balances = self + .trial_balance_ledger + .list_for_name(name.to_string(), Default::default()) + .await? + .entities; + + match trial_balances.len() { + 0 => Ok(None), + 1 => Ok(Some(trial_balances[0].id)), + _ => Err(TrialBalanceError::MultipleFound(name)), + } + } + + pub async fn add_to_trial_balance( + &self, + trial_balance_id: impl Into, + member_id: impl Into, + ) -> Result<(), TrialBalanceError> { + let trial_balance_id = trial_balance_id.into(); + let member_id = member_id.into(); + + let mut op = es_entity::DbOp::init(&self.pool).await?; + + self.authz + .audit() + .record_system_entry_in_tx(op.tx(), Object::TrialBalance, TrialBalanceAction::Update) + .await?; + + self.trial_balance_ledger + .add_member(op, trial_balance_id, member_id) + .await?; + + Ok(()) + } + + pub async fn trial_balance( + &self, + sub: &Subject, + name: String, + ) -> Result { + self.authz + .enforce_permission(sub, Object::TrialBalance, TrialBalanceAction::Read) + .await?; + + let trial_balance_id = self + .find_by_name(name.to_string()) + .await? + .ok_or(TrialBalanceError::NotFound(name))?; + + let trial_balance_details = self + .trial_balance_ledger + .get_trial_balance(trial_balance_id) + .await?; + + Ok(TrialBalance::from(trial_balance_details)) + } +} + +#[derive(Clone)] +pub struct TrialBalance { + pub id: TrialBalanceId, + pub name: String, + pub description: Option, + pub btc_balance: BtcStatementAccountSetBalance, + pub usd_balance: UsdStatementAccountSetBalance, + pub accounts: Vec, +} + +impl From for TrialBalance { + fn from(details: StatementAccountSetWithAccounts) -> Self { + Self { + id: details.id.into(), + name: details.name, + description: details.description, + btc_balance: details.btc_balance, + usd_balance: details.usd_balance, + accounts: details.accounts, + } + } +} diff --git a/lana/ids/src/lib.rs b/lana/ids/src/lib.rs index 65c46f5f89..bb4622aa84 100644 --- a/lana/ids/src/lib.rs +++ b/lana/ids/src/lib.rs @@ -1,4 +1,6 @@ -use cala_ledger::primitives::TransactionId as LedgerTransactionId; +use cala_ledger::primitives::{ + AccountSetId as LedgerAccountSetId, TransactionId as LedgerTransactionId, +}; es_entity::entity_id! { CustomerId, @@ -7,14 +9,17 @@ es_entity::entity_id! { DisbursalId, InterestAccrualId, TermsTemplateId, + TrialBalanceId, ReportId; CreditFacilityId => governance::ApprovalProcessId, DisbursalId => governance::ApprovalProcessId, - DisbursalId => LedgerTransactionId, ReportId => job::JobId, CreditFacilityId => job::JobId, InterestAccrualId => job::JobId, + + DisbursalId => LedgerTransactionId, CustomerId => deposit::DepositAccountHolderId, + TrialBalanceId => LedgerAccountSetId, } diff --git a/lana/rbac-types/src/action.rs b/lana/rbac-types/src/action.rs index 137dbb6ab0..f54d544f2a 100644 --- a/lana/rbac-types/src/action.rs +++ b/lana/rbac-types/src/action.rs @@ -108,6 +108,7 @@ pub enum AppAction { Audit(AuditAction), Ledger(LedgerAction), CreditFacility(CreditFacilityAction), + TrialBalance(TrialBalanceAction), Document(DocumentAction), } @@ -122,6 +123,7 @@ impl Display for AppAction { Audit(action) => action.fmt(f), Ledger(action) => action.fmt(f), CreditFacility(action) => action.fmt(f), + TrialBalance(action) => action.fmt(f), Document(action) => action.fmt(f), } } @@ -142,6 +144,7 @@ impl FromStr for AppAction { Audit => AppAction::from(action.parse::()?), Ledger => AppAction::from(action.parse::()?), CreditFacility => AppAction::from(action.parse::()?), + TrialBalance => AppAction::from(action.parse::()?), Document => AppAction::from(action.parse::()?), }; Ok(res) @@ -169,6 +172,16 @@ pub enum CreditFacilityAction { impl_trivial_action!(CreditFacilityAction, CreditFacility); +#[derive(PartialEq, Clone, Copy, Debug, strum::Display, strum::EnumString)] +#[strum(serialize_all = "kebab-case")] +pub enum TrialBalanceAction { + Create, + Update, + Read, +} + +impl_trivial_action!(TrialBalanceAction, TrialBalance); + #[derive(PartialEq, Clone, Copy, Debug, strum::Display, strum::EnumString)] #[strum(serialize_all = "kebab-case")] pub enum TermsTemplateAction { diff --git a/lana/rbac-types/src/object.rs b/lana/rbac-types/src/object.rs index 68da96ca0a..c3b6c97a7f 100644 --- a/lana/rbac-types/src/object.rs +++ b/lana/rbac-types/src/object.rs @@ -102,6 +102,7 @@ pub enum AppObject { Audit, Ledger, CreditFacility, + TrialBalance, } impl Display for AppObject { @@ -141,6 +142,7 @@ impl FromStr for AppObject { Audit => AppObject::Audit, Ledger => AppObject::Ledger, CreditFacility => AppObject::CreditFacility, + TrialBalance => AppObject::TrialBalance, Document => AppObject::Document, }; Ok(res)