Skip to main content

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}