Skip to main content

yuki_cli/cli/
projects.rs

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}