invoice_cli/commands/
products.rs1use 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}