use crate::cli::setup_domain;
use crate::client::accounting::AccountingClient;
use crate::client::accounting_info::AccountingInfoClient;
use crate::client::soap_client::SoapClient;
use crate::config::Config;
use crate::error::YukiError;
use crate::output::{OutputFormat, format_json, format_table, is_tty};
use crate::period::parse_period;
pub async fn balance(
config: &Config,
admin: Option<&str>,
account: Option<&str>,
period: Option<&str>,
format: Option<&str>,
) -> Result<(), YukiError> {
let (start, _end) = resolve_period(period)?;
let gl_code = account.unwrap_or("");
let (client, entry) = setup_domain(config, admin).await?;
let xml = client
.gl_account_balance(&entry.admin_id, gl_code, &start)
.await?;
let balance = SoapClient::parse_single_result(&xml, "GLAccountBalanceResult").unwrap_or(xml);
let headers = vec!["Account".into(), "Date".into(), "Balance".into()];
let rows = vec![vec![gl_code.to_string(), start, balance]];
let fmt = OutputFormat::from_flag(format, is_tty());
match fmt {
OutputFormat::Table => println!("{}", format_table(&headers, &rows)),
OutputFormat::Json => println!("{}", format_json(&headers, &rows)),
}
Ok(())
}
pub async fn transactions(
config: &Config,
admin: Option<&str>,
account: Option<&str>,
period: Option<&str>,
format: Option<&str>,
) -> Result<(), YukiError> {
let (start, end) = resolve_period(period)?;
let gl_code = account.unwrap_or("");
let (client, entry) = setup_domain(config, admin).await?;
let xml = client
.gl_account_transactions(&entry.admin_id, gl_code, &start, &end)
.await?;
let transactions = AccountingClient::parse_gl_transactions(&xml)?;
let headers = vec![
"ID".into(),
"Date".into(),
"Amount".into(),
"Description".into(),
];
let rows: Vec<Vec<String>> = transactions
.iter()
.map(|t| {
vec![
t.id.clone(),
t.date.clone(),
t.amount.clone(),
t.description.clone(),
]
})
.collect();
let fmt = OutputFormat::from_flag(format, is_tty());
match fmt {
OutputFormat::Table => println!("{}", format_table(&headers, &rows)),
OutputFormat::Json => println!("{}", format_json(&headers, &rows)),
}
Ok(())
}
pub async fn scheme(
config: &Config,
admin: Option<&str>,
format: Option<&str>,
) -> Result<(), YukiError> {
let entry = config.resolve_admin(admin)?;
let mut client = AccountingInfoClient::new();
client.authenticate(&config.api_key).await?;
let accounts = client.get_gl_account_scheme(&entry.admin_id).await?;
let headers = vec!["Code".into(), "Description".into(), "Type".into()];
let rows: Vec<Vec<String>> = accounts
.into_iter()
.map(|a| vec![a.code, a.description, a.account_type])
.collect();
let fmt = OutputFormat::from_flag(format, is_tty());
match fmt {
OutputFormat::Table => println!("{}", format_table(&headers, &rows)),
OutputFormat::Json => println!("{}", format_json(&headers, &rows)),
}
Ok(())
}
pub async fn start_balance(
config: &Config,
admin: Option<&str>,
year: Option<&str>,
format: Option<&str>,
) -> Result<(), YukiError> {
let bookyear = year.unwrap_or(¤t_year().to_string()).to_string();
let entry = config.resolve_admin(admin)?;
let mut client = AccountingInfoClient::new();
client.authenticate(&config.api_key).await?;
let balances = client
.get_start_balance_by_gl_account(&entry.admin_id, &bookyear)
.await?;
let headers = vec!["GL Account".into(), "Description".into(), "Balance".into()];
let rows: Vec<Vec<String>> = balances
.into_iter()
.map(|b| vec![b.gl_account_code, b.description, b.balance])
.collect();
let fmt = OutputFormat::from_flag(format, is_tty());
match fmt {
OutputFormat::Table => println!("{}", format_table(&headers, &rows)),
OutputFormat::Json => println!("{}", format_json(&headers, &rows)),
}
Ok(())
}
pub async fn revenue(
config: &Config,
admin: Option<&str>,
period: Option<&str>,
format: Option<&str>,
) -> Result<(), YukiError> {
let (start, end) = resolve_period(period)?;
let (client, entry) = setup_domain(config, admin).await?;
let amount = client.net_revenue(&entry.admin_id, &start, &end).await?;
let headers = vec!["Period".into(), "Net Revenue".into()];
let rows = vec![vec![format!("{start} — {end}"), amount]];
let fmt = OutputFormat::from_flag(format, is_tty());
match fmt {
OutputFormat::Table => println!("{}", format_table(&headers, &rows)),
OutputFormat::Json => println!("{}", format_json(&headers, &rows)),
}
Ok(())
}
fn resolve_period(period: Option<&str>) -> Result<(String, String), YukiError> {
match period {
Some(p) => parse_period(p),
None => {
let year = current_year();
Ok((format!("{year}-01-01"), format!("{year}-12-31")))
}
}
}
fn current_year() -> u32 {
use std::time::{SystemTime, UNIX_EPOCH};
let secs = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
1970 + (secs / 31_557_600) as u32
}