stateset_embedded/bom.rs
1//! Bill of Materials (BOM) operations
2
3use stateset_core::{
4 BillOfMaterials, BomComponent, BomFilter, CreateBom, CreateBomComponent, ProductId, Result,
5 UpdateBom,
6};
7use stateset_db::Database;
8use std::sync::Arc;
9use uuid::Uuid;
10
11/// Bill of Materials operations.
12///
13/// Access via `commerce.bom()`.
14///
15/// # Example
16///
17/// ```rust,no_run
18/// use stateset_embedded::{Commerce, CreateBom, CreateBomComponent, ProductId};
19/// use rust_decimal_macros::dec;
20///
21/// let commerce = Commerce::new("./store.db")?;
22///
23/// // Create a BOM
24/// let bom = commerce.bom().create(CreateBom {
25/// product_id: ProductId::new(),
26/// name: "Widget Assembly".into(),
27/// description: Some("Assembly instructions for widget".into()),
28/// components: Some(vec![
29/// CreateBomComponent {
30/// name: "Screw M3x10".into(),
31/// component_sku: Some("SCREW-M3-10".into()),
32/// quantity: dec!(4),
33/// ..Default::default()
34/// },
35/// ]),
36/// ..Default::default()
37/// })?;
38/// # Ok::<(), stateset_embedded::CommerceError>(())
39/// ```
40pub struct Bom {
41 db: Arc<dyn Database>,
42}
43
44impl std::fmt::Debug for Bom {
45 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46 f.debug_struct("Bom").finish_non_exhaustive()
47 }
48}
49
50impl Bom {
51 pub(crate) fn new(db: Arc<dyn Database>) -> Self {
52 Self { db }
53 }
54
55 /// Create a new Bill of Materials.
56 ///
57 /// # Example
58 ///
59 /// ```rust,no_run
60 /// use stateset_embedded::{Commerce, CreateBom, CreateBomComponent, ProductId};
61 /// use rust_decimal_macros::dec;
62 ///
63 /// let commerce = Commerce::new(":memory:")?;
64 ///
65 /// let bom = commerce.bom().create(CreateBom {
66 /// product_id: ProductId::new(),
67 /// name: "Widget Assembly".into(),
68 /// components: Some(vec![
69 /// CreateBomComponent {
70 /// name: "Part A".into(),
71 /// quantity: dec!(2),
72 /// ..Default::default()
73 /// },
74 /// ]),
75 /// ..Default::default()
76 /// })?;
77 /// # Ok::<(), stateset_embedded::CommerceError>(())
78 /// ```
79 pub fn create(&self, input: CreateBom) -> Result<BillOfMaterials> {
80 self.db.bom().create(input)
81 }
82
83 /// Get a BOM by ID.
84 pub fn get(&self, id: Uuid) -> Result<Option<BillOfMaterials>> {
85 self.db.bom().get(id)
86 }
87
88 /// Get a BOM by its BOM number.
89 pub fn get_by_number(&self, bom_number: &str) -> Result<Option<BillOfMaterials>> {
90 self.db.bom().get_by_number(bom_number)
91 }
92
93 /// Update a BOM.
94 pub fn update(&self, id: Uuid, input: UpdateBom) -> Result<BillOfMaterials> {
95 self.db.bom().update(id, input)
96 }
97
98 /// List BOMs with optional filter.
99 pub fn list(&self, filter: BomFilter) -> Result<Vec<BillOfMaterials>> {
100 self.db.bom().list(filter)
101 }
102
103 /// Delete a BOM (marks as obsolete).
104 pub fn delete(&self, id: Uuid) -> Result<()> {
105 self.db.bom().delete(id)
106 }
107
108 /// Add a component to a BOM.
109 ///
110 /// # Example
111 ///
112 /// ```rust,no_run
113 /// use stateset_embedded::{Commerce, CreateBomComponent};
114 /// use rust_decimal_macros::dec;
115 /// use uuid::Uuid;
116 ///
117 /// let commerce = Commerce::new(":memory:")?;
118 /// let bom_id = Uuid::new_v4(); // Existing BOM ID
119 ///
120 /// let component = commerce.bom().add_component(bom_id, CreateBomComponent {
121 /// name: "Resistor 10K".into(),
122 /// component_sku: Some("RES-10K".into()),
123 /// quantity: dec!(4),
124 /// position: Some("R1, R2, R3, R4".into()),
125 /// ..Default::default()
126 /// })?;
127 /// # Ok::<(), stateset_embedded::CommerceError>(())
128 /// ```
129 pub fn add_component(
130 &self,
131 bom_id: Uuid,
132 component: CreateBomComponent,
133 ) -> Result<BomComponent> {
134 self.db.bom().add_component(bom_id, component)
135 }
136
137 /// Update a component in a BOM.
138 pub fn update_component(
139 &self,
140 component_id: Uuid,
141 component: CreateBomComponent,
142 ) -> Result<BomComponent> {
143 self.db.bom().update_component(component_id, component)
144 }
145
146 /// Remove a component from a BOM.
147 pub fn remove_component(&self, component_id: Uuid) -> Result<()> {
148 self.db.bom().remove_component(component_id)
149 }
150
151 /// Get all components for a BOM.
152 pub fn get_components(&self, bom_id: Uuid) -> Result<Vec<BomComponent>> {
153 self.db.bom().get_components(bom_id)
154 }
155
156 /// Activate a BOM (make it ready for production use).
157 ///
158 /// Changes the BOM status from Draft to Active.
159 pub fn activate(&self, id: Uuid) -> Result<BillOfMaterials> {
160 self.db.bom().activate(id)
161 }
162
163 /// Count BOMs matching filter.
164 pub fn count(&self, filter: BomFilter) -> Result<u64> {
165 self.db.bom().count(filter)
166 }
167
168 /// Get BOMs for a specific product.
169 pub fn for_product(&self, product_id: ProductId) -> Result<Vec<BillOfMaterials>> {
170 self.list(BomFilter { product_id: Some(product_id), ..Default::default() })
171 }
172}