sqlx_ledger/balance/
entity.rs

1use chrono::{DateTime, Utc};
2use rust_decimal::Decimal;
3use serde::{Deserialize, Serialize};
4
5use crate::entry::StagedEntry;
6use crate::primitives::*;
7
8/// Representation of account's balance tracked in 3 distinct layers.
9#[derive(Debug, Clone)]
10pub struct AccountBalance {
11    pub(super) balance_type: DebitOrCredit,
12    pub details: BalanceDetails,
13}
14
15impl AccountBalance {
16    pub fn pending(&self) -> Decimal {
17        if self.balance_type == DebitOrCredit::Credit {
18            self.details.pending_cr_balance - self.details.pending_dr_balance
19        } else {
20            self.details.pending_dr_balance - self.details.pending_cr_balance
21        }
22    }
23
24    pub fn settled(&self) -> Decimal {
25        if self.balance_type == DebitOrCredit::Credit {
26            self.details.settled_cr_balance - self.details.settled_dr_balance
27        } else {
28            self.details.settled_dr_balance - self.details.settled_cr_balance
29        }
30    }
31
32    pub fn encumbered(&self) -> Decimal {
33        if self.balance_type == DebitOrCredit::Credit {
34            self.details.encumbered_cr_balance - self.details.encumbered_dr_balance
35        } else {
36            self.details.encumbered_dr_balance - self.details.encumbered_cr_balance
37        }
38    }
39}
40
41/// Contains the details of the balance and methods to update from new
42/// entries.
43#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct BalanceDetails {
45    pub journal_id: JournalId,
46    pub account_id: AccountId,
47    pub entry_id: EntryId,
48    pub currency: Currency,
49    pub settled_dr_balance: Decimal,
50    pub settled_cr_balance: Decimal,
51    pub settled_entry_id: EntryId,
52    pub settled_modified_at: DateTime<Utc>,
53    pub pending_dr_balance: Decimal,
54    pub pending_cr_balance: Decimal,
55    pub pending_entry_id: EntryId,
56    pub pending_modified_at: DateTime<Utc>,
57    pub encumbered_dr_balance: Decimal,
58    pub encumbered_cr_balance: Decimal,
59    pub encumbered_entry_id: EntryId,
60    pub encumbered_modified_at: DateTime<Utc>,
61    pub version: i32,
62    pub modified_at: DateTime<Utc>,
63    pub created_at: DateTime<Utc>,
64}
65
66impl BalanceDetails {
67    pub(crate) fn update(self, entry: &StagedEntry) -> Self {
68        self.update_inner(entry)
69    }
70
71    pub(crate) fn init(journal_id: JournalId, entry: &StagedEntry) -> Self {
72        Self {
73            journal_id,
74            account_id: entry.account_id,
75            entry_id: entry.entry_id,
76            currency: entry.currency,
77            settled_dr_balance: Decimal::ZERO,
78            settled_cr_balance: Decimal::ZERO,
79            settled_entry_id: entry.entry_id,
80            settled_modified_at: entry.created_at,
81            pending_dr_balance: Decimal::ZERO,
82            pending_cr_balance: Decimal::ZERO,
83            pending_entry_id: entry.entry_id,
84            pending_modified_at: entry.created_at,
85            encumbered_dr_balance: Decimal::ZERO,
86            encumbered_cr_balance: Decimal::ZERO,
87            encumbered_entry_id: entry.entry_id,
88            encumbered_modified_at: entry.created_at,
89            version: 0,
90            modified_at: entry.created_at,
91            created_at: entry.created_at,
92        }
93        .update_inner(entry)
94    }
95
96    fn update_inner(mut self, entry: &StagedEntry) -> Self {
97        self.version += 1;
98        self.modified_at = entry.created_at;
99        self.entry_id = entry.entry_id;
100        match entry.layer {
101            Layer::Settled => {
102                self.settled_entry_id = entry.entry_id;
103                self.settled_modified_at = entry.created_at;
104                match entry.direction {
105                    DebitOrCredit::Debit => {
106                        self.settled_dr_balance += entry.units;
107                    }
108                    DebitOrCredit::Credit => {
109                        self.settled_cr_balance += entry.units;
110                    }
111                }
112            }
113            Layer::Pending => {
114                self.pending_entry_id = entry.entry_id;
115                self.pending_modified_at = entry.created_at;
116                match entry.direction {
117                    DebitOrCredit::Debit => {
118                        self.pending_dr_balance += entry.units;
119                    }
120                    DebitOrCredit::Credit => {
121                        self.pending_cr_balance += entry.units;
122                    }
123                }
124            }
125            Layer::Encumbered => {
126                self.encumbered_entry_id = entry.entry_id;
127                self.encumbered_modified_at = entry.created_at;
128                match entry.direction {
129                    DebitOrCredit::Debit => {
130                        self.encumbered_dr_balance += entry.units;
131                    }
132                    DebitOrCredit::Credit => {
133                        self.encumbered_cr_balance += entry.units;
134                    }
135                }
136            }
137        }
138        self
139    }
140}