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