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
22use crate::folders::folder_id;
23
24pub async fn run(
30 config: &Config,
31 admin: Option<&str>,
32 file: &str,
33 options: UploadOptions<'_>,
34 format: Option<&str>,
35 quiet: bool,
36) -> Result<(), YukiError> {
37 let fid = folder_id(options.folder)?;
38
39 let bytes = std::fs::read(file).map_err(|e| YukiError::Config(format!("{file}: {e}")))?;
40 let data_base64 = BASE64.encode(&bytes);
41 let filename = Path::new(file)
42 .file_name()
43 .and_then(|n| n.to_str())
44 .unwrap_or(file);
45
46 let entry = config.resolve_admin(admin)?;
47 let mut client = ArchiveClient::new();
48 client.authenticate(&config.api_key).await?;
49
50 let doc_id = match options.amount {
51 Some(amt) => {
52 client
53 .upload_document_with_data(
54 &entry.admin_id,
55 filename,
56 &data_base64,
57 fid,
58 options.currency,
59 amt,
60 options.category,
61 options.payment_method,
62 options.project,
63 options.remarks,
64 )
65 .await?
66 }
67 None => {
68 client
69 .upload_document(&entry.admin_id, filename, &data_base64, fid)
70 .await?
71 }
72 };
73
74 if quiet {
75 return Ok(());
76 }
77
78 let headers = vec!["Document ID".to_string()];
79 let rows = vec![vec![doc_id]];
80
81 let fmt = OutputFormat::from_flag(format, is_tty());
82 match fmt {
83 OutputFormat::Table => println!("{}", format_table(&headers, &rows)),
84 OutputFormat::Json => println!("{}", format_json(&headers, &rows)),
85 }
86
87 Ok(())
88}
89
90pub async fn categories(config: &Config, format: Option<&str>) -> Result<(), YukiError> {
92 let mut client = ArchiveClient::new();
93 client.authenticate(&config.api_key).await?;
94 let cats = client.cost_categories().await?;
95
96 let headers = vec!["ID".to_string(), "Description".to_string()];
97 let rows: Vec<Vec<String>> = cats
98 .into_iter()
99 .map(|c| vec![c.id, c.description])
100 .collect();
101
102 let fmt = OutputFormat::from_flag(format, is_tty());
103 match fmt {
104 OutputFormat::Table => println!("{}", format_table(&headers, &rows)),
105 OutputFormat::Json => println!("{}", format_json(&headers, &rows)),
106 }
107
108 Ok(())
109}
110
111pub async fn payment_methods(config: &Config, format: Option<&str>) -> Result<(), YukiError> {
113 let mut client = ArchiveClient::new();
114 client.authenticate(&config.api_key).await?;
115 let methods = client.payment_methods().await?;
116
117 let headers = vec!["ID".to_string(), "Description".to_string()];
118 let rows: Vec<Vec<String>> = methods
119 .into_iter()
120 .map(|m| vec![m.id, m.description])
121 .collect();
122
123 let fmt = OutputFormat::from_flag(format, is_tty());
124 match fmt {
125 OutputFormat::Table => println!("{}", format_table(&headers, &rows)),
126 OutputFormat::Json => println!("{}", format_json(&headers, &rows)),
127 }
128
129 Ok(())
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135
136 #[test]
137 fn folder_id_inkoop() {
138 assert_eq!(folder_id("inkoop").unwrap(), 1);
139 assert_eq!(folder_id("purchase").unwrap(), 1);
140 }
141
142 #[test]
143 fn folder_id_verkoop() {
144 assert_eq!(folder_id("verkoop").unwrap(), 2);
145 assert_eq!(folder_id("sales").unwrap(), 2);
146 }
147
148 #[test]
149 fn folder_id_uitzoeken_default() {
150 assert_eq!(folder_id("uitzoeken").unwrap(), 7);
151 }
152
153 #[test]
154 fn folder_id_all_known() {
155 assert_eq!(folder_id("bank").unwrap(), 3);
156 assert_eq!(folder_id("personeel").unwrap(), 4);
157 assert_eq!(folder_id("personnel").unwrap(), 4);
158 assert_eq!(folder_id("belasting").unwrap(), 5);
159 assert_eq!(folder_id("tax").unwrap(), 5);
160 assert_eq!(folder_id("overig-financieel").unwrap(), 8);
161 assert_eq!(folder_id("other").unwrap(), 8);
162 }
163
164 #[test]
165 fn folder_id_unknown_errors() {
166 assert!(folder_id("nonexistent").is_err());
167 }
168}