Skip to main content

invoice_cli/commands/
clients.rs

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