envelope_cli/display/
account.rs1use crate::models::Account;
6use crate::services::account::AccountSummary;
7
8pub fn format_account_list(summaries: &[AccountSummary]) -> String {
10 if summaries.is_empty() {
11 return "No accounts found.".to_string();
12 }
13
14 let name_width = summaries
16 .iter()
17 .map(|s| s.account.name.len())
18 .max()
19 .unwrap_or(4)
20 .max(4);
21
22 let type_width = summaries
23 .iter()
24 .map(|s| s.account.account_type.to_string().len())
25 .max()
26 .unwrap_or(4)
27 .max(4);
28
29 let mut output = String::new();
31 output.push_str(&format!(
32 "{:<name_width$} {:<type_width$} {:>12} {:>12} {}\n",
33 "Name",
34 "Type",
35 "Balance",
36 "Cleared",
37 "Status",
38 name_width = name_width,
39 type_width = type_width,
40 ));
41
42 output.push_str(&format!(
44 "{:-<name_width$} {:-<type_width$} {:->12} {:->12} {:-<10}\n",
45 "",
46 "",
47 "",
48 "",
49 "",
50 name_width = name_width,
51 type_width = type_width,
52 ));
53
54 for summary in summaries {
56 let status = if summary.account.archived {
57 "Archived"
58 } else if !summary.account.on_budget {
59 "Off-Budget"
60 } else if summary.uncleared_count > 0 {
61 &format!("{} pending", summary.uncleared_count)
62 } else {
63 ""
64 };
65
66 output.push_str(&format!(
67 "{:<name_width$} {:<type_width$} {:>12} {:>12} {}\n",
68 summary.account.name,
69 summary.account.account_type,
70 summary.balance.to_string(),
71 summary.cleared_balance.to_string(),
72 status,
73 name_width = name_width,
74 type_width = type_width,
75 ));
76 }
77
78 let total_balance: crate::models::Money = summaries.iter().map(|s| s.balance).sum();
80 let total_cleared: crate::models::Money = summaries.iter().map(|s| s.cleared_balance).sum();
81
82 output.push_str(&format!(
83 "{:-<name_width$} {:-<type_width$} {:->12} {:->12} {:-<10}\n",
84 "",
85 "",
86 "",
87 "",
88 "",
89 name_width = name_width,
90 type_width = type_width,
91 ));
92
93 output.push_str(&format!(
94 "{:<name_width$} {:<type_width$} {:>12} {:>12}\n",
95 "TOTAL",
96 "",
97 total_balance.to_string(),
98 total_cleared.to_string(),
99 name_width = name_width,
100 type_width = type_width,
101 ));
102
103 output
104}
105
106pub fn format_account_details(summary: &AccountSummary) -> String {
108 let account = &summary.account;
109
110 let mut output = String::new();
111
112 output.push_str(&format!("Account: {}\n", account.name));
113 output.push_str(&format!(" Type: {}\n", account.account_type));
114 output.push_str(&format!(" ID: {}\n", account.id));
115 output.push_str(&format!(
116 " On Budget: {}\n",
117 if account.on_budget { "Yes" } else { "No" }
118 ));
119 output.push_str(&format!(
120 " Archived: {}\n",
121 if account.archived { "Yes" } else { "No" }
122 ));
123 output.push('\n');
124 output.push_str(&format!(
125 " Starting Balance: {}\n",
126 account.starting_balance
127 ));
128 output.push_str(&format!(" Current Balance: {}\n", summary.balance));
129 output.push_str(&format!(
130 " Cleared Balance: {}\n",
131 summary.cleared_balance
132 ));
133 output.push_str(&format!(
134 " Uncleared Count: {}\n",
135 summary.uncleared_count
136 ));
137
138 if let Some(date) = account.last_reconciled_date {
139 output.push('\n');
140 output.push_str(&format!(" Last Reconciled: {}\n", date));
141 if let Some(balance) = account.last_reconciled_balance {
142 output.push_str(&format!(" Reconciled Balance: {}\n", balance));
143 }
144 }
145
146 if !account.notes.is_empty() {
147 output.push('\n');
148 output.push_str(&format!(" Notes: {}\n", account.notes));
149 }
150
151 output.push('\n');
152 output.push_str(&format!(
153 " Created: {}\n",
154 account.created_at.format("%Y-%m-%d %H:%M UTC")
155 ));
156 output.push_str(&format!(
157 " Modified: {}\n",
158 account.updated_at.format("%Y-%m-%d %H:%M UTC")
159 ));
160
161 output
162}
163
164pub fn format_account_list_simple(accounts: &[Account]) -> String {
166 if accounts.is_empty() {
167 return "No accounts found.".to_string();
168 }
169
170 let mut output = String::new();
171 for account in accounts {
172 let status = if account.archived { " (archived)" } else { "" };
173 output.push_str(&format!(
174 " {} - {}{}\n",
175 account.name, account.account_type, status
176 ));
177 }
178 output
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184 use crate::models::{AccountType, Money};
185
186 fn create_test_summary(name: &str, balance: i64, cleared: i64) -> AccountSummary {
187 let account =
188 Account::with_starting_balance(name, AccountType::Checking, Money::from_cents(0));
189 AccountSummary {
190 account,
191 balance: Money::from_cents(balance),
192 cleared_balance: Money::from_cents(cleared),
193 uncleared_count: if balance != cleared { 1 } else { 0 },
194 }
195 }
196
197 #[test]
198 fn test_format_account_list() {
199 let summaries = vec![
200 create_test_summary("Checking", 100000, 95000),
201 create_test_summary("Savings", 500000, 500000),
202 ];
203
204 let output = format_account_list(&summaries);
205 assert!(output.contains("Checking"));
206 assert!(output.contains("Savings"));
207 assert!(output.contains("TOTAL"));
208 assert!(
209 output.contains("$1,000.00")
210 || output.contains("$6000.00")
211 || output.contains("$6,000.00")
212 );
213 }
214
215 #[test]
216 fn test_format_empty_list() {
217 let output = format_account_list(&[]);
218 assert!(output.contains("No accounts found"));
219 }
220
221 #[test]
222 fn test_format_account_details() {
223 let summary = create_test_summary("My Account", 100000, 90000);
224 let output = format_account_details(&summary);
225
226 assert!(output.contains("My Account"));
227 assert!(output.contains("Checking"));
228 assert!(output.contains("Current Balance"));
229 assert!(output.contains("Cleared Balance"));
230 }
231}