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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
//! Work Order operations for manufacturing
use rust_decimal::Decimal;
use stateset_core::{
AddWorkOrderMaterial, CreateWorkOrder, CreateWorkOrderTask, ProductId, Result, UpdateWorkOrder,
UpdateWorkOrderTask, WorkOrder, WorkOrderFilter, WorkOrderMaterial, WorkOrderTask,
};
use stateset_db::Database;
use std::sync::Arc;
use uuid::Uuid;
/// Work Order operations.
///
/// Access via `commerce.work_orders()`.
///
/// # Example
///
/// ```rust,no_run
/// use stateset_embedded::{Commerce, CreateWorkOrder, CreateWorkOrderTask, ProductId};
/// use rust_decimal_macros::dec;
///
/// let commerce = Commerce::new("./store.db")?;
///
/// // Create a work order
/// let wo = commerce.work_orders().create(CreateWorkOrder {
/// product_id: ProductId::new(),
/// quantity_to_build: dec!(100),
/// tasks: Some(vec![
/// CreateWorkOrderTask {
/// task_name: "Assembly".into(),
/// sequence: Some(1),
/// estimated_hours: Some(dec!(2)),
/// ..Default::default()
/// },
/// ]),
/// ..Default::default()
/// })?;
///
/// // Start the work order
/// let wo = commerce.work_orders().start(wo.id)?;
///
/// // Complete with quantity
/// let wo = commerce.work_orders().complete(wo.id, dec!(100))?;
/// # Ok::<(), stateset_embedded::CommerceError>(())
/// ```
pub struct WorkOrders {
db: Arc<dyn Database>,
}
impl std::fmt::Debug for WorkOrders {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("WorkOrders").finish_non_exhaustive()
}
}
impl WorkOrders {
pub(crate) fn new(db: Arc<dyn Database>) -> Self {
Self { db }
}
/// Create a new work order.
///
/// # Example
///
/// ```rust,no_run
/// use stateset_embedded::{Commerce, CreateWorkOrder, ProductId};
/// use rust_decimal_macros::dec;
///
/// let commerce = Commerce::new(":memory:")?;
///
/// let wo = commerce.work_orders().create(CreateWorkOrder {
/// product_id: ProductId::new(),
/// quantity_to_build: dec!(50),
/// notes: Some("Rush order".into()),
/// ..Default::default()
/// })?;
/// # Ok::<(), stateset_embedded::CommerceError>(())
/// ```
pub fn create(&self, input: CreateWorkOrder) -> Result<WorkOrder> {
self.db.work_orders().create(input)
}
/// Get a work order by ID.
pub fn get(&self, id: Uuid) -> Result<Option<WorkOrder>> {
self.db.work_orders().get(id)
}
/// Get a work order by its work order number.
pub fn get_by_number(&self, work_order_number: &str) -> Result<Option<WorkOrder>> {
self.db.work_orders().get_by_number(work_order_number)
}
/// Update a work order.
pub fn update(&self, id: Uuid, input: UpdateWorkOrder) -> Result<WorkOrder> {
self.db.work_orders().update(id, input)
}
/// List work orders with optional filter.
pub fn list(&self, filter: WorkOrderFilter) -> Result<Vec<WorkOrder>> {
self.db.work_orders().list(filter)
}
/// Delete a work order (cancels if not started).
pub fn delete(&self, id: Uuid) -> Result<()> {
self.db.work_orders().delete(id)
}
/// Start a work order.
///
/// Transitions the work order from Planned to `InProgress` and records the actual start time.
///
/// # Example
///
/// ```rust,no_run
/// use stateset_embedded::Commerce;
/// use uuid::Uuid;
///
/// let commerce = Commerce::new(":memory:")?;
/// let wo_id = Uuid::new_v4(); // Existing work order ID
///
/// let wo = commerce.work_orders().start(wo_id)?;
/// assert_eq!(wo.status.to_string(), "in_progress");
/// # Ok::<(), stateset_embedded::CommerceError>(())
/// ```
pub fn start(&self, id: Uuid) -> Result<WorkOrder> {
self.db.work_orders().start(id)
}
/// Complete a work order with the quantity produced.
///
/// If the quantity meets or exceeds the target, the order is marked as Completed.
/// Otherwise, it's marked as `PartiallyCompleted`.
///
/// # Example
///
/// ```rust,no_run
/// use stateset_embedded::Commerce;
/// use rust_decimal_macros::dec;
/// use uuid::Uuid;
///
/// let commerce = Commerce::new(":memory:")?;
/// let wo_id = Uuid::new_v4(); // Existing work order ID
///
/// // Complete with full quantity
/// let wo = commerce.work_orders().complete(wo_id, dec!(100))?;
/// # Ok::<(), stateset_embedded::CommerceError>(())
/// ```
pub fn complete(&self, id: Uuid, quantity_completed: Decimal) -> Result<WorkOrder> {
self.db.work_orders().complete(id, quantity_completed)
}
/// Put a work order on hold.
pub fn hold(&self, id: Uuid) -> Result<WorkOrder> {
self.db.work_orders().hold(id)
}
/// Resume a held work order.
pub fn resume(&self, id: Uuid) -> Result<WorkOrder> {
self.db.work_orders().resume(id)
}
/// Cancel a work order.
pub fn cancel(&self, id: Uuid) -> Result<WorkOrder> {
self.db.work_orders().cancel(id)
}
// Task operations
/// Add a task to a work order.
///
/// # Example
///
/// ```rust,no_run
/// use stateset_embedded::{Commerce, CreateWorkOrderTask};
/// use rust_decimal_macros::dec;
/// use uuid::Uuid;
///
/// let commerce = Commerce::new(":memory:")?;
/// let wo_id = Uuid::new_v4(); // Existing work order ID
///
/// let task = commerce.work_orders().add_task(wo_id, CreateWorkOrderTask {
/// task_name: "Quality Check".into(),
/// sequence: Some(10),
/// estimated_hours: Some(dec!(0.5)),
/// ..Default::default()
/// })?;
/// # Ok::<(), stateset_embedded::CommerceError>(())
/// ```
pub fn add_task(
&self,
work_order_id: Uuid,
task: CreateWorkOrderTask,
) -> Result<WorkOrderTask> {
self.db.work_orders().add_task(work_order_id, task)
}
/// Update a task.
pub fn update_task(&self, task_id: Uuid, task: UpdateWorkOrderTask) -> Result<WorkOrderTask> {
self.db.work_orders().update_task(task_id, task)
}
/// Remove a task from a work order.
pub fn remove_task(&self, task_id: Uuid) -> Result<()> {
self.db.work_orders().remove_task(task_id)
}
/// Get all tasks for a work order.
pub fn get_tasks(&self, work_order_id: Uuid) -> Result<Vec<WorkOrderTask>> {
self.db.work_orders().get_tasks(work_order_id)
}
/// Start a task.
pub fn start_task(&self, task_id: Uuid) -> Result<WorkOrderTask> {
self.db.work_orders().start_task(task_id)
}
/// Complete a task with optional actual hours.
pub fn complete_task(
&self,
task_id: Uuid,
actual_hours: Option<Decimal>,
) -> Result<WorkOrderTask> {
self.db.work_orders().complete_task(task_id, actual_hours)
}
// Material operations
/// Add material to a work order.
///
/// # Example
///
/// ```rust,no_run
/// use stateset_embedded::{Commerce, AddWorkOrderMaterial};
/// use rust_decimal_macros::dec;
/// use uuid::Uuid;
///
/// let commerce = Commerce::new(":memory:")?;
/// let wo_id = Uuid::new_v4(); // Existing work order ID
///
/// let material = commerce.work_orders().add_material(wo_id, AddWorkOrderMaterial {
/// component_sku: "SCREW-M3".into(),
/// component_name: "M3 Screw".into(),
/// quantity: dec!(200),
/// ..Default::default()
/// })?;
/// # Ok::<(), stateset_embedded::CommerceError>(())
/// ```
pub fn add_material(
&self,
work_order_id: Uuid,
material: AddWorkOrderMaterial,
) -> Result<WorkOrderMaterial> {
self.db.work_orders().add_material(work_order_id, material)
}
/// Consume material during production.
///
/// Records that a certain quantity of material has been used.
pub fn consume_material(
&self,
material_id: Uuid,
quantity: Decimal,
) -> Result<WorkOrderMaterial> {
self.db.work_orders().consume_material(material_id, quantity)
}
/// Get all materials for a work order.
pub fn get_materials(&self, work_order_id: Uuid) -> Result<Vec<WorkOrderMaterial>> {
self.db.work_orders().get_materials(work_order_id)
}
/// Count work orders matching filter.
pub fn count(&self, filter: WorkOrderFilter) -> Result<u64> {
self.db.work_orders().count(filter)
}
/// Get work orders for a specific product.
pub fn for_product(&self, product_id: ProductId) -> Result<Vec<WorkOrder>> {
self.list(WorkOrderFilter { product_id: Some(product_id), ..Default::default() })
}
/// Get work orders using a specific BOM.
pub fn for_bom(&self, bom_id: Uuid) -> Result<Vec<WorkOrder>> {
self.list(WorkOrderFilter { bom_id: Some(bom_id), ..Default::default() })
}
}