Skip to main content

invoice_cli/commands/
products.rs

1use rust_decimal::Decimal;
2use std::str::FromStr;
3
4use crate::cli::ProductCmd;
5use crate::db::{self, Product};
6use crate::error::{AppError, Result};
7use crate::money::MinorUnits;
8use crate::output::{print_success, Ctx};
9
10pub fn run(cmd: ProductCmd, ctx: Ctx) -> Result<()> {
11    let conn = db::open()?;
12    match cmd {
13        ProductCmd::Add {
14            slug,
15            description,
16            subtitle,
17            unit,
18            price,
19            currency,
20            tax_rate,
21        } => {
22            let price_dec = Decimal::from_str(&price)
23                .map_err(|e| AppError::InvalidInput(format!("bad price: {e}")))?;
24            let rate = Decimal::from_str(&tax_rate)
25                .map_err(|e| AppError::InvalidInput(format!("bad tax rate: {e}")))?;
26            let product = Product {
27                id: 0,
28                slug: slug.clone(),
29                description,
30                subtitle,
31                unit,
32                unit_price: MinorUnits::from_decimal(price_dec),
33                currency,
34                tax_rate: rate,
35            };
36            let id = db::product_create(&conn, &product)?;
37            let mut out = product.clone();
38            out.id = id;
39            print_success(ctx, &out, |p| println!("added product '{}' (id {})", p.slug, p.id));
40            Ok(())
41        }
42        ProductCmd::List => {
43            let list = db::product_list(&conn)?;
44            print_success(ctx, &list, |list| {
45                if list.is_empty() {
46                    println!("no products. add one: invoice products add <slug> --description ... --price 220.00 --currency SGD");
47                }
48                for p in list {
49                    println!(
50                        "{:<20}  {:<38}  {} / {}  {}%",
51                        p.slug,
52                        p.description,
53                        p.unit_price.format_with_symbol(""),
54                        p.unit,
55                        p.tax_rate
56                    );
57                }
58            });
59            Ok(())
60        }
61        ProductCmd::Show { slug } => {
62            let p = db::product_by_slug(&conn, &slug)?;
63            print_success(ctx, &p, |p| println!("{:#?}", p));
64            Ok(())
65        }
66        ProductCmd::Delete { slug } => {
67            db::product_delete(&conn, &slug)?;
68            print_success(ctx, &slug, |s| println!("deleted product '{s}'"));
69            Ok(())
70        }
71        ProductCmd::Edit {
72            slug,
73            description,
74            subtitle,
75            unit,
76            price,
77            currency,
78            tax_rate,
79        } => {
80            let mut product = db::product_by_slug(&conn, &slug)?;
81            if let Some(v) = description {
82                product.description = v;
83            }
84            if let Some(v) = subtitle {
85                product.subtitle = Some(v);
86            }
87            if let Some(v) = unit {
88                product.unit = v;
89            }
90            if let Some(v) = price {
91                let price_dec = Decimal::from_str(&v)
92                    .map_err(|e| AppError::InvalidInput(format!("bad price: {e}")))?;
93                product.unit_price = MinorUnits::from_decimal(price_dec);
94            }
95            if let Some(v) = currency {
96                product.currency = v;
97            }
98            if let Some(v) = tax_rate {
99                let rate = Decimal::from_str(&v)
100                    .map_err(|e| AppError::InvalidInput(format!("bad tax rate: {e}")))?;
101                product.tax_rate = rate;
102            }
103            db::product_update(&conn, &product)?;
104            print_success(ctx, &product, |p| {
105                println!("updated product '{}' (id {})", p.slug, p.id)
106            });
107            Ok(())
108        }
109    }
110}