invoice_cli/commands/
clients.rs1use crate::cli::ClientCmd;
2use crate::db::{self, Client};
3use crate::error::{AppError, Result};
4use crate::output::{print_success, Ctx};
5
6pub fn run(cmd: ClientCmd, ctx: Ctx) -> Result<()> {
7 let conn = db::open()?;
8 match cmd {
9 ClientCmd::Add {
10 slug,
11 name,
12 attn,
13 country,
14 tax_id,
15 address,
16 email,
17 notes,
18 default_issuer,
19 default_template,
20 } => {
21 if let Some(ref iss) = default_issuer {
22 db::issuer_by_slug(&conn, iss).map_err(|_| {
23 AppError::InvalidInput(format!("unknown issuer '{iss}'"))
24 })?;
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: address.split('\n').map(|s| s.to_string()).collect(),
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| println!("added client '{}' (id {})", c.slug, c.id));
51 Ok(())
52 }
53 ClientCmd::Edit {
54 slug,
55 name,
56 attn,
57 country,
58 tax_id,
59 address,
60 email,
61 notes,
62 default_issuer,
63 default_template,
64 } => {
65 if let Some(ref iss) = default_issuer {
66 db::issuer_by_slug(&conn, iss).map_err(|_| {
67 AppError::InvalidInput(format!("unknown issuer '{iss}'"))
68 })?;
69 }
70 if let Some(ref tmpl) = default_template {
71 if !crate::typst_assets::has_template(tmpl)? {
72 let available = crate::typst_assets::list_templates()?.join(", ");
73 return Err(AppError::InvalidInput(format!(
74 "unknown template '{tmpl}' — available: {available}"
75 )));
76 }
77 }
78 let mut client = db::client_by_slug(&conn, &slug)?;
79 if let Some(v) = name {
80 client.name = v;
81 }
82 if let Some(v) = attn {
83 client.attn = Some(v);
84 }
85 if let Some(v) = country {
86 client.country = Some(v);
87 }
88 if let Some(v) = tax_id {
89 client.tax_id = Some(v);
90 }
91 if let Some(v) = address {
92 client.address = v.split('\n').map(|s| s.to_string()).collect();
93 }
94 if let Some(v) = email {
95 client.email = Some(v);
96 }
97 if let Some(v) = notes {
98 client.notes = Some(v);
99 }
100 if let Some(v) = default_issuer {
101 client.default_issuer_slug = Some(v);
102 }
103 if let Some(v) = default_template {
104 client.default_template = Some(v);
105 }
106 db::client_update(&conn, &client)?;
107 print_success(ctx, &client, |c| {
108 println!("updated client '{}' (id {})", c.slug, c.id)
109 });
110 Ok(())
111 }
112 ClientCmd::SetIssuer { slug, issuer_slug } => {
113 db::issuer_by_slug(&conn, &issuer_slug).map_err(|_| {
114 AppError::InvalidInput(format!("unknown issuer '{issuer_slug}'"))
115 })?;
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}