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| {
40                println!("added product '{}' (id {})", p.slug, p.id)
41            });
42            Ok(())
43        }
44        ProductCmd::List => {
45            let list = db::product_list(&conn)?;
46            print_success(ctx, &list, |list| {
47                if list.is_empty() {
48                    println!("no products. add one: invoice products add <slug> --description ... --price 220.00 --currency SGD");
49                }
50                for p in list {
51                    println!(
52                        "{:<20}  {:<38}  {} / {}  {}%",
53                        p.slug,
54                        p.description,
55                        p.unit_price.format_with_symbol(""),
56                        p.unit,
57                        p.tax_rate
58                    );
59                }
60            });
61            Ok(())
62        }
63        ProductCmd::Show { slug } => {
64            let p = db::product_by_slug(&conn, &slug)?;
65            print_success(ctx, &p, |p| println!("{:#?}", p));
66            Ok(())
67        }
68        ProductCmd::Delete { slug } => {
69            db::product_delete(&conn, &slug)?;
70            print_success(ctx, &slug, |s| println!("deleted product '{s}'"));
71            Ok(())
72        }
73        ProductCmd::Edit {
74            slug,
75            description,
76            subtitle,
77            unit,
78            price,
79            currency,
80            tax_rate,
81        } => {
82            let mut product = db::product_by_slug(&conn, &slug)?;
83            if let Some(v) = description {
84                product.description = v;
85            }
86            if let Some(v) = subtitle {
87                product.subtitle = Some(v);
88            }
89            if let Some(v) = unit {
90                product.unit = v;
91            }
92            if let Some(v) = price {
93                let price_dec = Decimal::from_str(&v)
94                    .map_err(|e| AppError::InvalidInput(format!("bad price: {e}")))?;
95                product.unit_price = MinorUnits::from_decimal(price_dec);
96            }
97            if let Some(v) = currency {
98                product.currency = v;
99            }
100            if let Some(v) = tax_rate {
101                let rate = Decimal::from_str(&v)
102                    .map_err(|e| AppError::InvalidInput(format!("bad tax rate: {e}")))?;
103                product.tax_rate = rate;
104            }
105            db::product_update(&conn, &product)?;
106            print_success(ctx, &product, |p| {
107                println!("updated product '{}' (id {})", p.slug, p.id)
108            });
109            Ok(())
110        }
111    }
112}