Skip to main content

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}