Skip to main content

invoice_cli/commands/
issuers.rs

1use crate::cli::IssuerCmd;
2use crate::commands::split_multiline_arg;
3use crate::db::{self, Issuer};
4use crate::error::{AppError, Result};
5use crate::output::{print_success, Ctx};
6use crate::tax::Jurisdiction;
7
8pub fn run(cmd: IssuerCmd, ctx: Ctx) -> Result<()> {
9    let conn = db::open()?;
10    match cmd {
11        IssuerCmd::Add {
12            slug,
13            name,
14            legal_name,
15            jurisdiction,
16            tax_registered,
17            tax_id,
18            company_no,
19            address,
20            email,
21            phone,
22            bank_line,
23            template,
24            logo,
25            output_dir,
26            notes,
27            number_format,
28        } => {
29            let jur = Jurisdiction::from_str(&jurisdiction).ok_or_else(|| {
30                AppError::InvalidInput(format!(
31                    "unknown jurisdiction '{jurisdiction}' — use one of: sg, uk, us, eu, custom"
32                ))
33            })?;
34            let profile = jur.profile();
35            let issuer = Issuer {
36                id: 0,
37                slug,
38                name,
39                legal_name,
40                jurisdiction: jur,
41                tax_registered,
42                tax_id,
43                company_no,
44                tagline: None,
45                address: split_multiline_arg(&address),
46                email,
47                phone,
48                bank_details: if bank_line.is_empty() {
49                    None
50                } else {
51                    Some(bank_line.join("\n"))
52                },
53                default_template: template,
54                currency: Some(profile.currency.to_string()),
55                symbol: Some(profile.symbol.to_string()),
56                number_format: number_format.unwrap_or_else(|| "{issuer}-{year}-{seq:04}".into()),
57                logo_path: logo,
58                default_output_dir: output_dir,
59                default_notes: notes,
60            };
61            let id = db::issuer_create(&conn, &issuer)?;
62            let mut out = issuer.clone();
63            out.id = id;
64            print_success(ctx, &out, |i| {
65                println!("added issuer '{}' (id {})", i.slug, i.id)
66            });
67            Ok(())
68        }
69        IssuerCmd::List => {
70            let list = db::issuer_list(&conn)?;
71            print_success(ctx, &list, |list| {
72                if list.is_empty() {
73                    println!("no issuers. add one: invoice issuer add <slug> --name ...");
74                }
75                for i in list {
76                    println!(
77                        "{:<16}  {:<24}  {} ({})",
78                        i.slug,
79                        i.name,
80                        i.jurisdiction.as_str(),
81                        if i.tax_registered {
82                            "tax-registered"
83                        } else {
84                            "-"
85                        }
86                    );
87                }
88            });
89            Ok(())
90        }
91        IssuerCmd::Show { slug } => {
92            let i = db::issuer_by_slug(&conn, &slug)?;
93            print_success(ctx, &i, |i| println!("{:#?}", i));
94            Ok(())
95        }
96        IssuerCmd::Delete { slug } => {
97            db::issuer_delete(&conn, &slug)?;
98            print_success(ctx, &slug, |s| println!("deleted issuer '{s}'"));
99            Ok(())
100        }
101        IssuerCmd::Edit {
102            slug,
103            name,
104            legal_name,
105            jurisdiction,
106            tax_registered,
107            tax_id,
108            company_no,
109            tagline,
110            address,
111            email,
112            phone,
113            bank_line,
114            bank_clear,
115            template,
116            output_dir,
117            notes,
118            currency,
119            symbol,
120            number_format,
121            logo,
122            logo_clear,
123        } => {
124            let mut issuer = db::issuer_by_slug(&conn, &slug)?;
125            if let Some(v) = name {
126                issuer.name = v;
127            }
128            if let Some(v) = legal_name {
129                issuer.legal_name = Some(v);
130            }
131            if let Some(v) = jurisdiction {
132                let jur = Jurisdiction::from_str(&v).ok_or_else(|| {
133                    AppError::InvalidInput(format!(
134                        "unknown jurisdiction '{v}' — use one of: sg, uk, us, eu, custom"
135                    ))
136                })?;
137                issuer.jurisdiction = jur;
138            }
139            if let Some(v) = tax_registered {
140                issuer.tax_registered = v;
141            }
142            if let Some(v) = tax_id {
143                issuer.tax_id = Some(v);
144            }
145            if let Some(v) = company_no {
146                issuer.company_no = Some(v);
147            }
148            if let Some(v) = tagline {
149                issuer.tagline = Some(v);
150            }
151            if let Some(v) = address {
152                issuer.address = split_multiline_arg(&v);
153            }
154            if let Some(v) = email {
155                issuer.email = Some(v);
156            }
157            if let Some(v) = phone {
158                issuer.phone = Some(v);
159            }
160            if bank_clear {
161                issuer.bank_details = None;
162            } else if !bank_line.is_empty() {
163                issuer.bank_details = Some(bank_line.join("\n"));
164            }
165            if let Some(v) = template {
166                issuer.default_template = v;
167            }
168            if let Some(v) = output_dir {
169                issuer.default_output_dir = Some(v);
170            }
171            if let Some(v) = notes {
172                issuer.default_notes = Some(v);
173            }
174            // Currency + symbol auto-linking: if user sets currency but
175            // doesn't supply a symbol, derive the conventional one via
176            // finance_core::money::currency_symbol. An explicit --symbol
177            // still wins when both are given.
178            let currency_changed = currency.is_some();
179            if let Some(v) = currency {
180                if !v.is_empty() {
181                    let derived = finance_core::money::currency_symbol(&v);
182                    if symbol.is_none() && !derived.is_empty() {
183                        issuer.symbol = Some(derived.to_string());
184                    }
185                    issuer.currency = Some(v);
186                }
187            }
188            if let Some(v) = symbol {
189                issuer.symbol = Some(v);
190            } else if !currency_changed {
191                // no-op: neither currency nor symbol changed
192            }
193            if let Some(v) = number_format {
194                issuer.number_format = v;
195            }
196            if logo_clear {
197                issuer.logo_path = None;
198            } else if let Some(v) = logo {
199                issuer.logo_path = Some(v);
200            }
201            db::issuer_update(&conn, &issuer)?;
202            print_success(ctx, &issuer, |i| {
203                println!("updated issuer '{}' (id {})", i.slug, i.id)
204            });
205            Ok(())
206        }
207        IssuerCmd::SetTemplate { slug, template } => {
208            if !crate::typst_assets::has_template(&template)? {
209                let available = crate::typst_assets::list_templates()?.join(", ");
210                return Err(AppError::InvalidInput(format!(
211                    "unknown template '{template}' — available: {available}"
212                )));
213            }
214            let mut issuer = db::issuer_by_slug(&conn, &slug)?;
215            issuer.default_template = template;
216            db::issuer_update(&conn, &issuer)?;
217            print_success(ctx, &issuer, |i| {
218                println!(
219                    "set template for issuer '{}' to '{}'",
220                    i.slug, i.default_template
221                )
222            });
223            Ok(())
224        }
225    }
226}