Skip to main content

invoice_cli/commands/
clients.rs

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