1use crate::client::accounting_info::AccountingInfoClient;
2use crate::config::Config;
3use crate::error::YukiError;
4use crate::output::{OutputFormat, format_json, format_table, is_tty};
5use crate::period::parse_period;
6
7pub async fn list(
8 config: &Config,
9 admin: Option<&str>,
10 format: Option<&str>,
11) -> Result<(), YukiError> {
12 let entry = config.resolve_admin(admin)?;
13 let mut client = AccountingInfoClient::new();
14 client.authenticate(&config.api_key).await?;
15 let projects = client.get_projects(&entry.admin_id).await?;
16
17 let headers = vec!["ID".into(), "Code".into(), "Description".into()];
18 let rows: Vec<Vec<String>> = projects
19 .into_iter()
20 .map(|p| vec![p.id, p.code, p.description])
21 .collect();
22
23 let fmt = OutputFormat::from_flag(format, is_tty());
24 match fmt {
25 OutputFormat::Table => println!("{}", format_table(&headers, &rows)),
26 OutputFormat::Json => println!("{}", format_json(&headers, &rows)),
27 }
28 Ok(())
29}
30
31pub async fn balance(
32 config: &Config,
33 admin: Option<&str>,
34 project: &str,
35 account: Option<&str>,
36 period: Option<&str>,
37 format: Option<&str>,
38) -> Result<(), YukiError> {
39 let (start, end) = resolve_period(period)?;
40 let gl_code = account.unwrap_or("");
41 let entry = config.resolve_admin(admin)?;
42 let mut client = AccountingInfoClient::new();
43 client.authenticate(&config.api_key).await?;
44 let balances = client
45 .get_project_balance(&entry.admin_id, project, gl_code, &start, &end)
46 .await?;
47
48 let headers = vec!["Project".into(), "GL Account".into(), "Amount".into()];
49 let rows: Vec<Vec<String>> = balances
50 .into_iter()
51 .map(|b| vec![b.project_code, b.gl_account_code, b.amount])
52 .collect();
53
54 let fmt = OutputFormat::from_flag(format, is_tty());
55 match fmt {
56 OutputFormat::Table => println!("{}", format_table(&headers, &rows)),
57 OutputFormat::Json => println!("{}", format_json(&headers, &rows)),
58 }
59 Ok(())
60}
61
62fn resolve_period(period: Option<&str>) -> Result<(String, String), YukiError> {
63 match period {
64 Some(p) => parse_period(p),
65 None => {
66 let year = current_year();
67 Ok((format!("{year}-01-01"), format!("{year}-12-31")))
68 }
69 }
70}
71
72fn current_year() -> u32 {
73 use std::time::{SystemTime, UNIX_EPOCH};
74 let secs = SystemTime::now()
75 .duration_since(UNIX_EPOCH)
76 .unwrap_or_default()
77 .as_secs();
78 1970 + (secs / 31_557_600) as u32
79}