1use crate::cli::setup_domain;
2use crate::client::accounting::AccountingClient;
3use crate::client::accounting_info::AccountingInfoClient;
4use crate::config::Config;
5use crate::error::YukiError;
6use crate::output::{
7 ListOptions, OutputFormat, apply_pagination, format_json, format_table, is_tty, select_fields,
8};
9use crate::period::parse_period;
10
11pub async fn balance(
12 config: &Config,
13 admin: Option<&str>,
14 account: Option<&str>,
15 period: Option<&str>,
16 format: Option<&str>,
17) -> Result<(), YukiError> {
18 let (start, _end) = resolve_period(period)?;
19 let (client, entry) = setup_domain(config, admin).await?;
20 let mut balances = client.gl_account_balances(&entry.admin_id, &start).await?;
21 if let Some(code) = account {
23 balances.retain(|b| b.code == code);
24 }
25
26 let headers = vec!["Account".into(), "Description".into(), "Balance".into()];
27 let rows: Vec<Vec<String>> = balances
28 .iter()
29 .map(|b| vec![b.code.clone(), b.description.clone(), b.amount.clone()])
30 .collect();
31
32 let fmt = OutputFormat::from_flag(format, is_tty());
33 match fmt {
34 OutputFormat::Table => println!("{}", format_table(&headers, &rows)),
35 OutputFormat::Json => println!("{}", format_json(&headers, &rows)),
36 }
37 Ok(())
38}
39
40pub async fn transactions(
41 config: &Config,
42 admin: Option<&str>,
43 account: Option<&str>,
44 period: Option<&str>,
45 format: Option<&str>,
46 opts: ListOptions<'_>,
47) -> Result<(), YukiError> {
48 let (start, end) = resolve_period(period)?;
49 let gl_code = account.unwrap_or("");
50 let (client, entry) = setup_domain(config, admin).await?;
51 let xml = client
52 .gl_account_transactions(&entry.admin_id, gl_code, &start, &end)
53 .await?;
54 let transactions = AccountingClient::parse_gl_transactions(&xml)?;
55
56 let mut headers = vec![
57 "ID".into(),
58 "Date".into(),
59 "Amount".into(),
60 "Description".into(),
61 ];
62 let mut rows: Vec<Vec<String>> = transactions
63 .iter()
64 .map(|t| {
65 vec![
66 t.id.clone(),
67 t.date.clone(),
68 t.amount.clone(),
69 t.description.clone(),
70 ]
71 })
72 .collect();
73 apply_pagination(&mut rows, &opts);
74 select_fields(&mut headers, &mut rows, &opts)?;
75
76 let fmt = OutputFormat::from_flag(format, is_tty());
77 match fmt {
78 OutputFormat::Table => println!("{}", format_table(&headers, &rows)),
79 OutputFormat::Json => println!("{}", format_json(&headers, &rows)),
80 }
81 Ok(())
82}
83
84pub async fn scheme(
85 config: &Config,
86 admin: Option<&str>,
87 format: Option<&str>,
88) -> Result<(), YukiError> {
89 let entry = config.resolve_admin(admin)?;
90 let mut client = AccountingInfoClient::new();
91 client.authenticate(&config.api_key).await?;
92 let accounts = client.get_gl_account_scheme(&entry.admin_id).await?;
93
94 let headers = vec!["Code".into(), "Description".into(), "Type".into()];
95 let rows: Vec<Vec<String>> = accounts
96 .into_iter()
97 .map(|a| vec![a.code, a.description, a.account_type])
98 .collect();
99
100 let fmt = OutputFormat::from_flag(format, is_tty());
101 match fmt {
102 OutputFormat::Table => println!("{}", format_table(&headers, &rows)),
103 OutputFormat::Json => println!("{}", format_json(&headers, &rows)),
104 }
105 Ok(())
106}
107
108pub async fn start_balance(
109 config: &Config,
110 admin: Option<&str>,
111 year: Option<&str>,
112 format: Option<&str>,
113) -> Result<(), YukiError> {
114 let year_str;
115 let bookyear = match year {
116 Some(y) => y,
117 None => {
118 year_str = current_year().to_string();
119 &year_str
120 }
121 };
122 let entry = config.resolve_admin(admin)?;
123 let mut client = AccountingInfoClient::new();
124 client.authenticate(&config.api_key).await?;
125 let balances = client
126 .get_start_balance_by_gl_account(&entry.admin_id, bookyear)
127 .await?;
128
129 let headers = vec!["GL Account".into(), "Description".into(), "Balance".into()];
130 let rows: Vec<Vec<String>> = balances
131 .into_iter()
132 .map(|b| vec![b.gl_account_code, b.description, b.balance])
133 .collect();
134
135 let fmt = OutputFormat::from_flag(format, is_tty());
136 match fmt {
137 OutputFormat::Table => println!("{}", format_table(&headers, &rows)),
138 OutputFormat::Json => println!("{}", format_json(&headers, &rows)),
139 }
140 Ok(())
141}
142
143pub async fn revenue(
144 config: &Config,
145 admin: Option<&str>,
146 period: Option<&str>,
147 format: Option<&str>,
148) -> Result<(), YukiError> {
149 let (start, end) = resolve_period(period)?;
150 let (client, entry) = setup_domain(config, admin).await?;
151 let amount = client.net_revenue(&entry.admin_id, &start, &end).await?;
152
153 let headers = vec!["Period".into(), "Net Revenue".into()];
154 let rows = vec![vec![format!("{start} to {end}"), amount]];
155
156 let fmt = OutputFormat::from_flag(format, is_tty());
157 match fmt {
158 OutputFormat::Table => println!("{}", format_table(&headers, &rows)),
159 OutputFormat::Json => println!("{}", format_json(&headers, &rows)),
160 }
161 Ok(())
162}
163
164fn resolve_period(period: Option<&str>) -> Result<(String, String), YukiError> {
168 match period {
169 Some(p) => parse_period(p),
170 None => {
171 let year = current_year();
172 Ok((format!("{year}-01-01"), format!("{year}-12-31")))
173 }
174 }
175}
176
177fn current_year() -> u32 {
178 use std::time::{SystemTime, UNIX_EPOCH};
179 let secs = SystemTime::now()
180 .duration_since(UNIX_EPOCH)
181 .unwrap_or_default()
182 .as_secs();
183 1970 + (secs / 31_557_600) as u32
185}