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| {
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}