invoice_cli/commands/
clients.rs1use crate::cli::ClientCmd;
2use crate::commands::split_multiline_arg;
3use crate::db::{self, Client};
4use crate::error::{AppError, Result};
5use crate::output::{print_success, Ctx};
6
7pub fn run(cmd: ClientCmd, ctx: Ctx) -> Result<()> {
8 let conn = db::open()?;
9 match cmd {
10 ClientCmd::Add {
11 slug,
12 name,
13 attn,
14 country,
15 tax_id,
16 address,
17 email,
18 notes,
19 default_issuer,
20 default_template,
21 } => {
22 if let Some(ref iss) = default_issuer {
23 db::issuer_by_slug(&conn, iss)
24 .map_err(|_| AppError::InvalidInput(format!("unknown issuer '{iss}'")))?;
25 }
26 if let Some(ref tmpl) = default_template {
27 if !crate::typst_assets::has_template(tmpl)? {
28 let available = crate::typst_assets::list_templates()?.join(", ");
29 return Err(AppError::InvalidInput(format!(
30 "unknown template '{tmpl}' — available: {available}"
31 )));
32 }
33 }
34 let client = Client {
35 id: 0,
36 slug: slug.clone(),
37 name,
38 attn,
39 country,
40 tax_id,
41 address: split_multiline_arg(&address),
42 email,
43 notes,
44 default_issuer_slug: default_issuer,
45 default_template,
46 };
47 let id = db::client_create(&conn, &client)?;
48 let mut out = client.clone();
49 out.id = id;
50 print_success(ctx, &out, |c| {
51 println!("added client '{}' (id {})", c.slug, c.id)
52 });
53 Ok(())
54 }
55 ClientCmd::Edit {
56 slug,
57 name,
58 attn,
59 country,
60 tax_id,
61 address,
62 email,
63 notes,
64 default_issuer,
65 default_template,
66 } => {
67 if let Some(ref iss) = default_issuer {
68 db::issuer_by_slug(&conn, iss)
69 .map_err(|_| AppError::InvalidInput(format!("unknown issuer '{iss}'")))?;
70 }
71 if let Some(ref tmpl) = default_template {
72 if !crate::typst_assets::has_template(tmpl)? {
73 let available = crate::typst_assets::list_templates()?.join(", ");
74 return Err(AppError::InvalidInput(format!(
75 "unknown template '{tmpl}' — available: {available}"
76 )));
77 }
78 }
79 let mut client = db::client_by_slug(&conn, &slug)?;
80 if let Some(v) = name {
81 client.name = v;
82 }
83 if let Some(v) = attn {
84 client.attn = Some(v);
85 }
86 if let Some(v) = country {
87 client.country = Some(v);
88 }
89 if let Some(v) = tax_id {
90 client.tax_id = Some(v);
91 }
92 if let Some(v) = address {
93 client.address = split_multiline_arg(&v);
94 }
95 if let Some(v) = email {
96 client.email = Some(v);
97 }
98 if let Some(v) = notes {
99 client.notes = Some(v);
100 }
101 if let Some(v) = default_issuer {
102 client.default_issuer_slug = Some(v);
103 }
104 if let Some(v) = default_template {
105 client.default_template = Some(v);
106 }
107 db::client_update(&conn, &client)?;
108 print_success(ctx, &client, |c| {
109 println!("updated client '{}' (id {})", c.slug, c.id)
110 });
111 Ok(())
112 }
113 ClientCmd::SetIssuer { slug, issuer_slug } => {
114 db::issuer_by_slug(&conn, &issuer_slug)
115 .map_err(|_| AppError::InvalidInput(format!("unknown issuer '{issuer_slug}'")))?;
116 let mut client = db::client_by_slug(&conn, &slug)?;
117 client.default_issuer_slug = Some(issuer_slug);
118 db::client_update(&conn, &client)?;
119 print_success(ctx, &client, |c| {
120 println!(
121 "set default issuer for client '{}' to '{}'",
122 c.slug,
123 c.default_issuer_slug.as_deref().unwrap_or("-")
124 )
125 });
126 Ok(())
127 }
128 ClientCmd::SetTemplate { slug, template } => {
129 if !crate::typst_assets::has_template(&template)? {
130 let available = crate::typst_assets::list_templates()?.join(", ");
131 return Err(AppError::InvalidInput(format!(
132 "unknown template '{template}' — available: {available}"
133 )));
134 }
135 let mut client = db::client_by_slug(&conn, &slug)?;
136 client.default_template = Some(template);
137 db::client_update(&conn, &client)?;
138 print_success(ctx, &client, |c| {
139 println!(
140 "set default template for client '{}' to '{}'",
141 c.slug,
142 c.default_template.as_deref().unwrap_or("-")
143 )
144 });
145 Ok(())
146 }
147 ClientCmd::List => {
148 let list = db::client_list(&conn)?;
149 print_success(ctx, &list, |list| {
150 if list.is_empty() {
151 println!("no clients. add one: invoice clients add <slug> --name ...");
152 }
153 for c in list {
154 let defaults = match (&c.default_issuer_slug, &c.default_template) {
155 (None, None) => String::new(),
156 (iss, tmpl) => {
157 let mut parts: Vec<String> = Vec::new();
158 if let Some(i) = iss {
159 parts.push(format!("issuer:{i}"));
160 }
161 if let Some(t) = tmpl {
162 parts.push(format!("tmpl:{t}"));
163 }
164 format!(" [{}]", parts.join(", "))
165 }
166 };
167 println!(
168 "{:<16} {:<32} {}{}",
169 c.slug,
170 c.name,
171 c.country.as_deref().unwrap_or("-"),
172 defaults
173 );
174 }
175 });
176 Ok(())
177 }
178 ClientCmd::Show { slug } => {
179 let c = db::client_by_slug(&conn, &slug)?;
180 print_success(ctx, &c, |c| println!("{:#?}", c));
181 Ok(())
182 }
183 ClientCmd::Delete { slug } => {
184 db::client_delete(&conn, &slug)?;
185 print_success(ctx, &slug, |s| println!("deleted client '{s}'"));
186 Ok(())
187 }
188 }
189}