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