cala_ledger/
lib.rs

1//! # cala-ledger
2//!
3//! This crate provides a set of primitives for implementing an SQL-compatible
4//! double-entry accounting system. This system is engineered specifically is
5//! for dealing with money and building financial products.
6//!
7//! Visit the [website of the Cala project](https://cala.sh) for more info and
8//! tutorials.
9//!
10//! ## Quick Start
11//!
12//! Here is how to initialize a ledger create a primitive template and post a transaction.
13//! This is a toy example that brings all pieces together end-to-end.
14//! Not recommended for real use.
15//! ```rust
16//! use cala_ledger::{account::*, journal::*, tx_template::*, *};
17//! use rust_decimal::Decimal;
18//! use uuid::uuid;
19//!
20//! async fn init_cala(journal_id: JournalId) -> anyhow::Result<CalaLedger, anyhow::Error> {
21//!     let cala_config = CalaLedgerConfig::builder()
22//!         .pg_con("postgres://user:password@localhost:5432/pg")
23//!         // .exec_migrations(true) # commented out for execution in CI
24//!         .build()?;
25//!     let cala = CalaLedger::init(cala_config).await?;
26//!
27//!     // Initialize the journal - all entities are constructed via builders
28//!     let new_journal = NewJournal::builder()
29//!         .id(journal_id)
30//!         .name("Ledger")
31//!         .build()
32//!         .expect("Couldn't build NewJournal");
33//!     let _ = cala.journals().create(new_journal).await;
34//!
35//!     // Initialize an income omnibus account
36//!     let main_account_id = uuid!("00000000-0000-0000-0000-000000000001");
37//!     let new_account = NewAccount::builder()
38//!         .id(main_account_id)
39//!         .name("Income")
40//!         .code("Income")
41//!         .build()?;
42//!     cala.accounts().create(new_account).await?;
43//!
44//!     // Create the trivial 'income' template
45//!     let params = vec![
46//!         NewParamDefinition::builder()
47//!             .name("sender_account_id")
48//!             .r#type(ParamDataType::Uuid)
49//!             .build()?,
50//!         NewParamDefinition::builder()
51//!             .name("units")
52//!             .r#type(ParamDataType::Decimal)
53//!             .build()?,
54//!     ];
55//!
56//!     let entries = vec![
57//!         NewTxTemplateEntry::builder()
58//!             .entry_type("'INCOME_DR'")
59//!             .account_id("params.sender_account_id")
60//!             .layer("SETTLED")
61//!             .direction("DEBIT")
62//!             .units("params.units")
63//!             .currency("'BTC'")
64//!             .build()?,
65//!         NewTxTemplateEntry::builder()
66//!             .entry_type("'INCOME_CR'")
67//!             .account_id(format!("uuid('{}')", main_account_id))
68//!             .layer("SETTLED")
69//!             .direction("CREDIT")
70//!             .units("params.units")
71//!             .currency("'BTC'")
72//!             .build()?,
73//!     ];
74//!
75//!     let tx_code = "GENERAL_INCOME";
76//!     let new_template = NewTxTemplate::builder()
77//!         .id(uuid::Uuid::now_v7())
78//!         .code(tx_code)
79//!         .params(params)
80//!         .transaction(
81//!             NewTxTemplateTransaction::builder()
82//!                 .effective("date()")
83//!                 .journal_id(format!("uuid('{}')", journal_id))
84//!                 .build()?,
85//!         )
86//!         .entries(entries)
87//!         .build()?;
88//!
89//!     cala.tx_templates().create(new_template).await?;
90//!     Ok(cala)
91//! }
92//!
93//! #[tokio::main]
94//! async fn main() -> anyhow::Result<()> {
95//!     let journal_id = JournalId::from(uuid!("00000000-0000-0000-0000-000000000001"));
96//!     let cala = init_cala(journal_id).await?;
97//!     // The account that is sending to the general income account
98//!     let sender_account_id = AccountId::new();
99//!     let sender_account = NewAccount::builder()
100//!         .id(sender_account_id)
101//!         .name(format!("Sender-{}", sender_account_id))
102//!         .code(format!("Sender-{}", sender_account_id))
103//!         .build()?;
104//!     cala.accounts().create(sender_account).await?;
105//!     // Prepare the input parameters that the template requires
106//!     let mut params = Params::new();
107//!     params.insert("sender_account_id", sender_account_id);
108//!     params.insert("units", Decimal::ONE);
109//!     // Create the transaction via the template
110//!     cala.post_transaction(TransactionId::new(), "GENERAL_INCOME", params)
111//!         .await?;
112//!
113//!     let account_balance = cala
114//!         .balances()
115//!         .find(journal_id, sender_account_id, "BTC".parse()?)
116//!         .await?;
117//!
118//!     let expected_balance = Decimal::new(-1, 0); // Define the expected balance
119//!     assert_eq!(account_balance.settled(), expected_balance);
120//!     Ok(())
121//! }
122//! ```
123
124#![cfg_attr(feature = "fail-on-warnings", deny(warnings))]
125#![cfg_attr(feature = "fail-on-warnings", deny(clippy::all))]
126
127mod cel_context;
128mod param;
129
130pub mod account;
131pub mod account_set;
132pub mod balance;
133pub mod entry;
134pub mod journal;
135pub mod migrate;
136pub mod transaction;
137pub mod tx_template;
138pub mod velocity;
139
140pub use es_entity;
141
142mod ledger;
143pub mod outbox;
144
145pub use ledger::*;
146
147pub mod primitives {
148    pub use cala_types::primitives::*;
149}
150pub use primitives::*;