stateset_embedded/work_orders.rs
1//! Work Order operations for manufacturing
2
3use rust_decimal::Decimal;
4use stateset_core::{
5 AddWorkOrderMaterial, CreateWorkOrder, CreateWorkOrderTask, ProductId, Result, UpdateWorkOrder,
6 UpdateWorkOrderTask, WorkOrder, WorkOrderFilter, WorkOrderMaterial, WorkOrderTask,
7};
8use stateset_db::Database;
9use std::sync::Arc;
10use uuid::Uuid;
11
12/// Work Order operations.
13///
14/// Access via `commerce.work_orders()`.
15///
16/// # Example
17///
18/// ```rust,no_run
19/// use stateset_embedded::{Commerce, CreateWorkOrder, CreateWorkOrderTask, ProductId};
20/// use rust_decimal_macros::dec;
21///
22/// let commerce = Commerce::new("./store.db")?;
23///
24/// // Create a work order
25/// let wo = commerce.work_orders().create(CreateWorkOrder {
26/// product_id: ProductId::new(),
27/// quantity_to_build: dec!(100),
28/// tasks: Some(vec![
29/// CreateWorkOrderTask {
30/// task_name: "Assembly".into(),
31/// sequence: Some(1),
32/// estimated_hours: Some(dec!(2)),
33/// ..Default::default()
34/// },
35/// ]),
36/// ..Default::default()
37/// })?;
38///
39/// // Start the work order
40/// let wo = commerce.work_orders().start(wo.id)?;
41///
42/// // Complete with quantity
43/// let wo = commerce.work_orders().complete(wo.id, dec!(100))?;
44/// # Ok::<(), stateset_embedded::CommerceError>(())
45/// ```
46pub struct WorkOrders {
47 db: Arc<dyn Database>,
48}
49
50impl std::fmt::Debug for WorkOrders {
51 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52 f.debug_struct("WorkOrders").finish_non_exhaustive()
53 }
54}
55
56impl WorkOrders {
57 pub(crate) fn new(db: Arc<dyn Database>) -> Self {
58 Self { db }
59 }
60
61 /// Create a new work order.
62 ///
63 /// # Example
64 ///
65 /// ```rust,no_run
66 /// use stateset_embedded::{Commerce, CreateWorkOrder, ProductId};
67 /// use rust_decimal_macros::dec;
68 ///
69 /// let commerce = Commerce::new(":memory:")?;
70 ///
71 /// let wo = commerce.work_orders().create(CreateWorkOrder {
72 /// product_id: ProductId::new(),
73 /// quantity_to_build: dec!(50),
74 /// notes: Some("Rush order".into()),
75 /// ..Default::default()
76 /// })?;
77 /// # Ok::<(), stateset_embedded::CommerceError>(())
78 /// ```
79 pub fn create(&self, input: CreateWorkOrder) -> Result<WorkOrder> {
80 self.db.work_orders().create(input)
81 }
82
83 /// Get a work order by ID.
84 pub fn get(&self, id: Uuid) -> Result<Option<WorkOrder>> {
85 self.db.work_orders().get(id)
86 }
87
88 /// Get a work order by its work order number.
89 pub fn get_by_number(&self, work_order_number: &str) -> Result<Option<WorkOrder>> {
90 self.db.work_orders().get_by_number(work_order_number)
91 }
92
93 /// Update a work order.
94 pub fn update(&self, id: Uuid, input: UpdateWorkOrder) -> Result<WorkOrder> {
95 self.db.work_orders().update(id, input)
96 }
97
98 /// List work orders with optional filter.
99 pub fn list(&self, filter: WorkOrderFilter) -> Result<Vec<WorkOrder>> {
100 self.db.work_orders().list(filter)
101 }
102
103 /// Delete a work order (cancels if not started).
104 pub fn delete(&self, id: Uuid) -> Result<()> {
105 self.db.work_orders().delete(id)
106 }
107
108 /// Start a work order.
109 ///
110 /// Transitions the work order from Planned to `InProgress` and records the actual start time.
111 ///
112 /// # Example
113 ///
114 /// ```rust,no_run
115 /// use stateset_embedded::Commerce;
116 /// use uuid::Uuid;
117 ///
118 /// let commerce = Commerce::new(":memory:")?;
119 /// let wo_id = Uuid::new_v4(); // Existing work order ID
120 ///
121 /// let wo = commerce.work_orders().start(wo_id)?;
122 /// assert_eq!(wo.status.to_string(), "in_progress");
123 /// # Ok::<(), stateset_embedded::CommerceError>(())
124 /// ```
125 pub fn start(&self, id: Uuid) -> Result<WorkOrder> {
126 self.db.work_orders().start(id)
127 }
128
129 /// Complete a work order with the quantity produced.
130 ///
131 /// If the quantity meets or exceeds the target, the order is marked as Completed.
132 /// Otherwise, it's marked as `PartiallyCompleted`.
133 ///
134 /// # Example
135 ///
136 /// ```rust,no_run
137 /// use stateset_embedded::Commerce;
138 /// use rust_decimal_macros::dec;
139 /// use uuid::Uuid;
140 ///
141 /// let commerce = Commerce::new(":memory:")?;
142 /// let wo_id = Uuid::new_v4(); // Existing work order ID
143 ///
144 /// // Complete with full quantity
145 /// let wo = commerce.work_orders().complete(wo_id, dec!(100))?;
146 /// # Ok::<(), stateset_embedded::CommerceError>(())
147 /// ```
148 pub fn complete(&self, id: Uuid, quantity_completed: Decimal) -> Result<WorkOrder> {
149 self.db.work_orders().complete(id, quantity_completed)
150 }
151
152 /// Put a work order on hold.
153 pub fn hold(&self, id: Uuid) -> Result<WorkOrder> {
154 self.db.work_orders().hold(id)
155 }
156
157 /// Resume a held work order.
158 pub fn resume(&self, id: Uuid) -> Result<WorkOrder> {
159 self.db.work_orders().resume(id)
160 }
161
162 /// Cancel a work order.
163 pub fn cancel(&self, id: Uuid) -> Result<WorkOrder> {
164 self.db.work_orders().cancel(id)
165 }
166
167 // Task operations
168
169 /// Add a task to a work order.
170 ///
171 /// # Example
172 ///
173 /// ```rust,no_run
174 /// use stateset_embedded::{Commerce, CreateWorkOrderTask};
175 /// use rust_decimal_macros::dec;
176 /// use uuid::Uuid;
177 ///
178 /// let commerce = Commerce::new(":memory:")?;
179 /// let wo_id = Uuid::new_v4(); // Existing work order ID
180 ///
181 /// let task = commerce.work_orders().add_task(wo_id, CreateWorkOrderTask {
182 /// task_name: "Quality Check".into(),
183 /// sequence: Some(10),
184 /// estimated_hours: Some(dec!(0.5)),
185 /// ..Default::default()
186 /// })?;
187 /// # Ok::<(), stateset_embedded::CommerceError>(())
188 /// ```
189 pub fn add_task(
190 &self,
191 work_order_id: Uuid,
192 task: CreateWorkOrderTask,
193 ) -> Result<WorkOrderTask> {
194 self.db.work_orders().add_task(work_order_id, task)
195 }
196
197 /// Update a task.
198 pub fn update_task(&self, task_id: Uuid, task: UpdateWorkOrderTask) -> Result<WorkOrderTask> {
199 self.db.work_orders().update_task(task_id, task)
200 }
201
202 /// Remove a task from a work order.
203 pub fn remove_task(&self, task_id: Uuid) -> Result<()> {
204 self.db.work_orders().remove_task(task_id)
205 }
206
207 /// Get all tasks for a work order.
208 pub fn get_tasks(&self, work_order_id: Uuid) -> Result<Vec<WorkOrderTask>> {
209 self.db.work_orders().get_tasks(work_order_id)
210 }
211
212 /// Start a task.
213 pub fn start_task(&self, task_id: Uuid) -> Result<WorkOrderTask> {
214 self.db.work_orders().start_task(task_id)
215 }
216
217 /// Complete a task with optional actual hours.
218 pub fn complete_task(
219 &self,
220 task_id: Uuid,
221 actual_hours: Option<Decimal>,
222 ) -> Result<WorkOrderTask> {
223 self.db.work_orders().complete_task(task_id, actual_hours)
224 }
225
226 // Material operations
227
228 /// Add material to a work order.
229 ///
230 /// # Example
231 ///
232 /// ```rust,no_run
233 /// use stateset_embedded::{Commerce, AddWorkOrderMaterial};
234 /// use rust_decimal_macros::dec;
235 /// use uuid::Uuid;
236 ///
237 /// let commerce = Commerce::new(":memory:")?;
238 /// let wo_id = Uuid::new_v4(); // Existing work order ID
239 ///
240 /// let material = commerce.work_orders().add_material(wo_id, AddWorkOrderMaterial {
241 /// component_sku: "SCREW-M3".into(),
242 /// component_name: "M3 Screw".into(),
243 /// quantity: dec!(200),
244 /// ..Default::default()
245 /// })?;
246 /// # Ok::<(), stateset_embedded::CommerceError>(())
247 /// ```
248 pub fn add_material(
249 &self,
250 work_order_id: Uuid,
251 material: AddWorkOrderMaterial,
252 ) -> Result<WorkOrderMaterial> {
253 self.db.work_orders().add_material(work_order_id, material)
254 }
255
256 /// Consume material during production.
257 ///
258 /// Records that a certain quantity of material has been used.
259 pub fn consume_material(
260 &self,
261 material_id: Uuid,
262 quantity: Decimal,
263 ) -> Result<WorkOrderMaterial> {
264 self.db.work_orders().consume_material(material_id, quantity)
265 }
266
267 /// Get all materials for a work order.
268 pub fn get_materials(&self, work_order_id: Uuid) -> Result<Vec<WorkOrderMaterial>> {
269 self.db.work_orders().get_materials(work_order_id)
270 }
271
272 /// Count work orders matching filter.
273 pub fn count(&self, filter: WorkOrderFilter) -> Result<u64> {
274 self.db.work_orders().count(filter)
275 }
276
277 /// Get work orders for a specific product.
278 pub fn for_product(&self, product_id: ProductId) -> Result<Vec<WorkOrder>> {
279 self.list(WorkOrderFilter { product_id: Some(product_id), ..Default::default() })
280 }
281
282 /// Get work orders using a specific BOM.
283 pub fn for_bom(&self, bom_id: Uuid) -> Result<Vec<WorkOrder>> {
284 self.list(WorkOrderFilter { bom_id: Some(bom_id), ..Default::default() })
285 }
286}