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 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 }
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}