1use crate::{balance::Balance, journal::Journal, account::Account};
6
7pub fn report_accounts(journal: &Journal) -> Vec<String> {
13 let accts = journal.master.flatten_account_tree();
14 accts
15 .iter()
16 .map(|account| account.name.to_string())
17 .collect()
18}
19
20fn report_commodities() {
21 todo!()
22}
23
24fn report_payees() {
25 todo!()
26}
27
28pub fn balance_report(journal: &Journal) -> Vec<String> {
32 log::debug!("Running the balance report");
33
34 get_children_lines(&journal.master, journal)
40}
41
42fn get_children_lines<'a>(account: &'a Account, journal: &'a Journal) -> Vec<String> {
44 let mut result = vec![];
45
46 let mut balance_line = String::new();
47 let total = account.total();
48 for amount in total.amounts {
49 balance_line += amount.quantity.to_string().as_str();
50 if amount.get_commodity().is_some() {
51 if let Some(c) = amount.get_commodity() {
52 balance_line += " ";
53 balance_line += c.symbol.as_str();
54 }
55 }
56 }
57 result.push(format!("Account {} has balance {}", account.fullname(), balance_line));
58
59 let mut acct_names: Vec<_> = account.accounts.keys().collect();
61 acct_names.sort();
62
63 for acct_name in acct_names {
65 let acct = account.accounts.get(acct_name).unwrap();
66 result.extend(get_children_lines(acct, journal));
67 }
68
69 result
70}
71
72fn get_account_balances(journal: &Journal) -> Vec<(&str, Balance)> {
77 let mut balances = vec![];
78
79 for acc in journal.master.flatten_account_tree() {
81 let filtered_posts = journal
83 .xacts.iter().flat_map(|x| x.posts.iter())
84 .filter(|post| post.account == acc);
85
86 let mut balance: Balance = Balance::new();
89 for post in filtered_posts {
90 balance.add(&post.amount.as_ref().unwrap());
91 }
92
93 balances.push((acc.fullname(), balance));
94 }
95 balances
96}
97
98fn format_balance_report(mut balances: Vec<(String, Balance)>, journal: &Journal) -> Vec<String> {
100 balances.sort_by(|(acc1, _bal1), (acc2, _bal2)| acc1.cmp(&acc2));
102
103 let mut output = vec![];
104 for (account, balance) in balances {
105 let mut bal_text: String = String::new();
106 for amount in &balance.amounts {
107 let symbol = match amount.get_commodity() {
109 Some(c) => c.symbol.as_str(),
110 None => "",
111 };
112
113 if !bal_text.is_empty() {
114 bal_text += ", ";
115 }
116
117 bal_text += amount.quantity.to_string().as_str();
118
119 if !symbol.is_empty() {
120 bal_text += " ";
121 bal_text += symbol;
122 }
123 }
124 let line = format!("Account {} has balance {}", account, bal_text);
125 output.push(line);
126 }
127 output
128}
129
130fn market(target_commodity: &str) {
136
137}
138
139#[cfg(test)]
140mod tests {
141 use std::io::Cursor;
142
143 use super::balance_report;
144 use crate::{journal::Journal, parser};
145
146 #[test]
147 fn test_balance_report_one_xact() {
148 let src = r#";
1492023-05-05 Payee
150 Expenses 25 EUR
151 Assets
152
153"#;
154 let mut journal = Journal::new();
155 parser::read_into_journal(Cursor::new(src), &mut journal);
156
157 let actual: Vec<String> = balance_report(&journal);
158
159 assert!(!actual.is_empty());
160 assert_eq!(3, actual.len());
161 assert_eq!("Account has balance 0 EUR", actual[0]);
162 assert_eq!("Account Assets has balance -25 EUR", actual[1]);
163 assert_eq!("Account Expenses has balance 25 EUR", actual[2]);
164 }
168
169 #[test]
170 fn test_bal_report_two_commodities() {
171 let src = r#";
1722023-05-05 Payee
173 Expenses 25 EUR
174 Assets
175
1762023-05-05 Payee 2
177 Expenses 13 BAM
178 Assets
179"#;
180 let source = Cursor::new(src);
181 let mut journal = Journal::new();
182 parser::read_into_journal(source, &mut journal);
183
184 let actual: Vec<String> = balance_report(&journal);
186
187 assert!(!actual.is_empty());
189 assert_eq!(3, actual.len());
190 assert_eq!("Account has balance 0 EUR0 BAM", actual[0]);
191 assert_eq!("Account Assets has balance -25 EUR-13 BAM", actual[1]);
192 assert_eq!("Account Expenses has balance 25 EUR13 BAM", actual[2]);
193 }
194
195 #[test]
196 fn test_bal_multiple_commodities_in_the_same_xact() {
197 let src = r#";
1982023-05-05 Payee
199 Assets:Cash EUR -25 EUR
200 Assets:Cash USD 30 USD
201"#;
202 let source = Cursor::new(src);
203 let mut journal = Journal::new();
204 parser::read_into_journal(source, &mut journal);
205
206 let actual: Vec<String> = balance_report(&journal);
208
209 assert!(!actual.is_empty());
211 assert_eq!(4, actual.len());
212 assert_eq!("Account has balance -25 EUR30 USD", actual[0]);
213 assert_eq!("Account Assets has balance -25 EUR30 USD", actual[1]);
214 assert_eq!("Account Assets:Cash EUR has balance -25 EUR", actual[2]);
215 assert_eq!("Account Assets:Cash USD has balance 30 USD", actual[3]);
216 }
217
218 fn test_bal_market_prices() {
220 }
225}