1use std::path::Path;
2
3use base64::Engine as _;
4use base64::engine::general_purpose::STANDARD as BASE64;
5
6use crate::client::archive::ArchiveClient;
7use crate::config::Config;
8use crate::error::YukiError;
9use crate::output::{OutputFormat, format_json, format_table, is_tty};
10
11pub struct UploadOptions<'a> {
13 pub folder: &'a str,
14 pub amount: Option<f64>,
15 pub category: Option<&'a str>,
16 pub payment_method: Option<&'a str>,
17 pub project: Option<&'a str>,
18 pub remarks: Option<&'a str>,
19 pub currency: &'a str,
20}
21
22fn folder_id(name: &str) -> Result<i32, YukiError> {
24 match name {
25 "inkoop" | "purchase" => Ok(1),
26 "verkoop" | "sales" => Ok(2),
27 "bank" => Ok(3),
28 "personeel" | "personnel" => Ok(4),
29 "belasting" | "tax" => Ok(5),
30 "uitzoeken" => Ok(7),
31 "overig-financieel" | "other" => Ok(8),
32 _ => Err(YukiError::Config(format!("unknown folder: {name}"))),
33 }
34}
35
36pub async fn run(
42 config: &Config,
43 admin: Option<&str>,
44 file: &str,
45 options: UploadOptions<'_>,
46 format: Option<&str>,
47 quiet: bool,
48) -> Result<(), YukiError> {
49 let fid = folder_id(options.folder)?;
50
51 let bytes = std::fs::read(file).map_err(|e| YukiError::Config(format!("{file}: {e}")))?;
52 let data_base64 = BASE64.encode(&bytes);
53 let filename = Path::new(file)
54 .file_name()
55 .and_then(|n| n.to_str())
56 .unwrap_or(file);
57
58 let entry = config.resolve_admin(admin)?;
59 let mut client = ArchiveClient::new();
60 client.authenticate(&config.api_key).await?;
61
62 let doc_id = match options.amount {
63 Some(amt) => {
64 client
65 .upload_document_with_data(
66 &entry.admin_id,
67 filename,
68 &data_base64,
69 fid,
70 options.currency,
71 amt,
72 options.category,
73 options.payment_method,
74 options.project,
75 options.remarks,
76 )
77 .await?
78 }
79 None => {
80 client
81 .upload_document(&entry.admin_id, filename, &data_base64, fid)
82 .await?
83 }
84 };
85
86 if quiet {
87 return Ok(());
88 }
89
90 let headers = vec!["Document ID".to_string()];
91 let rows = vec![vec![doc_id]];
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
99 Ok(())
100}
101
102pub async fn categories(config: &Config, format: Option<&str>) -> Result<(), YukiError> {
104 let mut client = ArchiveClient::new();
105 client.authenticate(&config.api_key).await?;
106 let cats = client.cost_categories().await?;
107
108 let headers = vec!["ID".to_string(), "Description".to_string()];
109 let rows: Vec<Vec<String>> = cats
110 .into_iter()
111 .map(|c| vec![c.id, c.description])
112 .collect();
113
114 let fmt = OutputFormat::from_flag(format, is_tty());
115 match fmt {
116 OutputFormat::Table => println!("{}", format_table(&headers, &rows)),
117 OutputFormat::Json => println!("{}", format_json(&headers, &rows)),
118 }
119
120 Ok(())
121}
122
123pub async fn payment_methods(config: &Config, format: Option<&str>) -> Result<(), YukiError> {
125 let mut client = ArchiveClient::new();
126 client.authenticate(&config.api_key).await?;
127 let methods = client.payment_methods().await?;
128
129 let headers = vec!["ID".to_string(), "Description".to_string()];
130 let rows: Vec<Vec<String>> = methods
131 .into_iter()
132 .map(|m| vec![m.id, m.description])
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
141 Ok(())
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 #[test]
149 fn folder_id_inkoop() {
150 assert_eq!(folder_id("inkoop").unwrap(), 1);
151 assert_eq!(folder_id("purchase").unwrap(), 1);
152 }
153
154 #[test]
155 fn folder_id_verkoop() {
156 assert_eq!(folder_id("verkoop").unwrap(), 2);
157 assert_eq!(folder_id("sales").unwrap(), 2);
158 }
159
160 #[test]
161 fn folder_id_uitzoeken_default() {
162 assert_eq!(folder_id("uitzoeken").unwrap(), 7);
163 }
164
165 #[test]
166 fn folder_id_all_known() {
167 assert_eq!(folder_id("bank").unwrap(), 3);
168 assert_eq!(folder_id("personeel").unwrap(), 4);
169 assert_eq!(folder_id("personnel").unwrap(), 4);
170 assert_eq!(folder_id("belasting").unwrap(), 5);
171 assert_eq!(folder_id("tax").unwrap(), 5);
172 assert_eq!(folder_id("overig-financieel").unwrap(), 8);
173 assert_eq!(folder_id("other").unwrap(), 8);
174 }
175
176 #[test]
177 fn folder_id_unknown_errors() {
178 assert!(folder_id("nonexistent").is_err());
179 }
180}