Skip to main content

stateset_embedded/
quality.rs

1//! Quality Control operations
2//!
3//! Comprehensive quality management system supporting:
4//! - Inspections (receiving, in-process, final, random)
5//! - Non-conformance reports (NCRs)
6//! - Quality holds on inventory
7//! - Defect code management
8//!
9//! # Example
10//!
11//! ```rust,ignore
12//! use stateset_embedded::{Commerce, CreateInspection, InspectionType};
13//! use uuid::Uuid;
14//!
15//! let commerce = Commerce::new("./store.db")?;
16//!
17//! // Create an inspection for received goods
18//! let inspection = commerce.quality().create_inspection(CreateInspection {
19//!     inspection_type: InspectionType::Receiving,
20//!     reference_type: "purchase_order".into(),
21//!     reference_id: Uuid::new_v4(),
22//!     ..Default::default()
23//! })?;
24//!
25//! println!("Created inspection #{}", inspection.inspection_number);
26//! # Ok::<(), stateset_embedded::CommerceError>(())
27//! ```
28
29use stateset_core::{
30    CreateDefectCode, CreateInspection, CreateNonConformance, CreateQualityHold, DefectCode,
31    Inspection, InspectionFilter, InspectionItem, InspectionStatus, NcrStatus, NonConformance,
32    NonConformanceFilter, QualityHold, QualityHoldFilter, RecordInspectionResult,
33    ReleaseQualityHold, Result, UpdateInspection, UpdateNonConformance,
34};
35use stateset_db::Database;
36use std::sync::Arc;
37use uuid::Uuid;
38
39/// Quality control management interface.
40pub struct Quality {
41    db: Arc<dyn Database>,
42}
43
44impl std::fmt::Debug for Quality {
45    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46        f.debug_struct("Quality").finish_non_exhaustive()
47    }
48}
49
50impl Quality {
51    pub(crate) fn new(db: Arc<dyn Database>) -> Self {
52        Self { db }
53    }
54
55    // ========================================================================
56    // Inspections
57    // ========================================================================
58
59    /// Create a new inspection.
60    ///
61    /// # Example
62    ///
63    /// ```rust,ignore
64    /// use stateset_embedded::{Commerce, CreateInspection, InspectionType};
65    /// use uuid::Uuid;
66    ///
67    /// let commerce = Commerce::new(":memory:")?;
68    ///
69    /// let inspection = commerce.quality().create_inspection(CreateInspection {
70    ///     inspection_type: InspectionType::Receiving,
71    ///     reference_type: "purchase_order".into(),
72    ///     reference_id: Uuid::new_v4(),
73    ///     ..Default::default()
74    /// })?;
75    /// # Ok::<(), stateset_embedded::CommerceError>(())
76    /// ```
77    pub fn create_inspection(&self, input: CreateInspection) -> Result<Inspection> {
78        self.db.quality().create_inspection(input)
79    }
80
81    /// Get an inspection by ID.
82    pub fn get_inspection(&self, id: Uuid) -> Result<Option<Inspection>> {
83        self.db.quality().get_inspection(id)
84    }
85
86    /// Get an inspection by its number.
87    pub fn get_inspection_by_number(&self, number: &str) -> Result<Option<Inspection>> {
88        self.db.quality().get_inspection_by_number(number)
89    }
90
91    /// List inspections with optional filtering.
92    pub fn list_inspections(&self, filter: InspectionFilter) -> Result<Vec<Inspection>> {
93        self.db.quality().list_inspections(filter)
94    }
95
96    /// Update an inspection.
97    pub fn update_inspection(&self, id: Uuid, input: UpdateInspection) -> Result<Inspection> {
98        self.db.quality().update_inspection(id, input)
99    }
100
101    /// Start an inspection (set status to `InProgress`).
102    pub fn start_inspection(&self, id: Uuid) -> Result<Inspection> {
103        self.db.quality().start_inspection(id)
104    }
105
106    /// Record inspection results for an item.
107    pub fn record_inspection_result(
108        &self,
109        input: RecordInspectionResult,
110    ) -> Result<InspectionItem> {
111        self.db.quality().record_inspection_result(input)
112    }
113
114    /// Get inspection items for an inspection.
115    pub fn get_inspection_items(&self, inspection_id: Uuid) -> Result<Vec<InspectionItem>> {
116        self.db.quality().get_inspection_items(inspection_id)
117    }
118
119    /// Complete an inspection (mark as Passed or Failed based on results).
120    pub fn complete_inspection(&self, id: Uuid) -> Result<Inspection> {
121        self.db.quality().complete_inspection(id)
122    }
123
124    /// Delete an inspection (only if Pending).
125    pub fn delete_inspection(&self, id: Uuid) -> Result<()> {
126        self.db.quality().delete_inspection(id)
127    }
128
129    /// Count inspections matching filter.
130    pub fn count_inspections(&self, filter: InspectionFilter) -> Result<u64> {
131        self.db.quality().count_inspections(filter)
132    }
133
134    // ========================================================================
135    // Non-Conformance Reports
136    // ========================================================================
137
138    /// Create a non-conformance report (NCR).
139    ///
140    /// # Example
141    ///
142    /// ```rust,ignore
143    /// use stateset_embedded::{Commerce, CreateNonConformance, NonConformanceSource, Severity};
144    /// use rust_decimal_macros::dec;
145    ///
146    /// let commerce = Commerce::new(":memory:")?;
147    ///
148    /// let ncr = commerce.quality().create_ncr(CreateNonConformance {
149    ///     source: NonConformanceSource::Inspection,
150    ///     severity: Severity::Major,
151    ///     sku: "SKU-001".into(),
152    ///     quantity_affected: dec!(10),
153    ///     description: "Parts do not meet specification".into(),
154    ///     ..Default::default()
155    /// })?;
156    ///
157    /// println!("Created NCR #{}", ncr.ncr_number);
158    /// # Ok::<(), stateset_embedded::CommerceError>(())
159    /// ```
160    pub fn create_ncr(&self, input: CreateNonConformance) -> Result<NonConformance> {
161        self.db.quality().create_ncr(input)
162    }
163
164    /// Get a non-conformance report by ID.
165    pub fn get_ncr(&self, id: Uuid) -> Result<Option<NonConformance>> {
166        self.db.quality().get_ncr(id)
167    }
168
169    /// Get a non-conformance report by its number.
170    pub fn get_ncr_by_number(&self, number: &str) -> Result<Option<NonConformance>> {
171        self.db.quality().get_ncr_by_number(number)
172    }
173
174    /// List non-conformance reports with optional filtering.
175    pub fn list_ncrs(&self, filter: NonConformanceFilter) -> Result<Vec<NonConformance>> {
176        self.db.quality().list_ncrs(filter)
177    }
178
179    /// Update a non-conformance report.
180    ///
181    /// Use this to set root cause, corrective action, disposition, etc.
182    pub fn update_ncr(&self, id: Uuid, input: UpdateNonConformance) -> Result<NonConformance> {
183        self.db.quality().update_ncr(id, input)
184    }
185
186    /// Close an NCR.
187    pub fn close_ncr(&self, id: Uuid) -> Result<NonConformance> {
188        self.db.quality().close_ncr(id)
189    }
190
191    /// Cancel an NCR.
192    pub fn cancel_ncr(&self, id: Uuid) -> Result<NonConformance> {
193        self.db.quality().cancel_ncr(id)
194    }
195
196    /// Count NCRs matching filter.
197    pub fn count_ncrs(&self, filter: NonConformanceFilter) -> Result<u64> {
198        self.db.quality().count_ncrs(filter)
199    }
200
201    // ========================================================================
202    // Quality Holds
203    // ========================================================================
204
205    /// Create a quality hold on inventory.
206    ///
207    /// # Example
208    ///
209    /// ```rust,ignore
210    /// use stateset_embedded::{Commerce, CreateQualityHold, HoldType};
211    /// use rust_decimal_macros::dec;
212    ///
213    /// let commerce = Commerce::new(":memory:")?;
214    ///
215    /// let hold = commerce.quality().create_hold(CreateQualityHold {
216    ///     sku: "SKU-001".into(),
217    ///     lot_number: Some("LOT-2025-001".into()),
218    ///     quantity_held: dec!(50),
219    ///     reason: "Pending quality inspection".into(),
220    ///     hold_type: HoldType::QualityInspection,
221    ///     placed_by: Some("QA Team".into()),
222    ///     ..Default::default()
223    /// })?;
224    ///
225    /// println!("Hold placed on {} units", hold.quantity_held);
226    /// # Ok::<(), stateset_embedded::CommerceError>(())
227    /// ```
228    pub fn create_hold(&self, input: CreateQualityHold) -> Result<QualityHold> {
229        self.db.quality().create_hold(input)
230    }
231
232    /// Get a quality hold by ID.
233    pub fn get_hold(&self, id: Uuid) -> Result<Option<QualityHold>> {
234        self.db.quality().get_hold(id)
235    }
236
237    /// List quality holds with optional filtering.
238    pub fn list_holds(&self, filter: QualityHoldFilter) -> Result<Vec<QualityHold>> {
239        self.db.quality().list_holds(filter)
240    }
241
242    /// Release a quality hold.
243    ///
244    /// # Example
245    ///
246    /// ```rust,ignore
247    /// use stateset_embedded::{Commerce, ReleaseQualityHold};
248    /// use uuid::Uuid;
249    ///
250    /// let commerce = Commerce::new(":memory:")?;
251    ///
252    /// commerce.quality().release_hold(Uuid::new_v4(), ReleaseQualityHold {
253    ///     released_by: "QA Manager".into(),
254    ///     notes: Some("Inspection passed".into()),
255    /// })?;
256    /// # Ok::<(), stateset_embedded::CommerceError>(())
257    /// ```
258    pub fn release_hold(&self, id: Uuid, input: ReleaseQualityHold) -> Result<QualityHold> {
259        self.db.quality().release_hold(id, input)
260    }
261
262    /// Get active holds for a SKU.
263    pub fn get_active_holds_for_sku(&self, sku: &str) -> Result<Vec<QualityHold>> {
264        self.db.quality().get_active_holds_for_sku(sku)
265    }
266
267    /// Get active holds for a lot.
268    pub fn get_active_holds_for_lot(&self, lot_number: &str) -> Result<Vec<QualityHold>> {
269        self.db.quality().get_active_holds_for_lot(lot_number)
270    }
271
272    /// Count active holds.
273    pub fn count_active_holds(&self) -> Result<u64> {
274        self.db.quality().count_active_holds()
275    }
276
277    // ========================================================================
278    // Defect Codes
279    // ========================================================================
280
281    /// Create a defect code.
282    pub fn create_defect_code(&self, input: CreateDefectCode) -> Result<DefectCode> {
283        self.db.quality().create_defect_code(input)
284    }
285
286    /// Get a defect code by code.
287    pub fn get_defect_code(&self, code: &str) -> Result<Option<DefectCode>> {
288        self.db.quality().get_defect_code(code)
289    }
290
291    /// List all defect codes, optionally filtered by category.
292    pub fn list_defect_codes(&self, category: Option<&str>) -> Result<Vec<DefectCode>> {
293        self.db.quality().list_defect_codes(category)
294    }
295
296    /// Deactivate a defect code.
297    pub fn deactivate_defect_code(&self, id: Uuid) -> Result<()> {
298        self.db.quality().deactivate_defect_code(id)
299    }
300
301    // ========================================================================
302    // Convenience Methods
303    // ========================================================================
304
305    /// Get all pending inspections.
306    pub fn get_pending_inspections(&self) -> Result<Vec<Inspection>> {
307        self.list_inspections(InspectionFilter {
308            status: Some(InspectionStatus::Pending),
309            ..Default::default()
310        })
311    }
312
313    /// Get all open NCRs.
314    pub fn get_open_ncrs(&self) -> Result<Vec<NonConformance>> {
315        self.list_ncrs(NonConformanceFilter { status: Some(NcrStatus::Open), ..Default::default() })
316    }
317
318    /// Get all active holds.
319    pub fn get_active_holds(&self) -> Result<Vec<QualityHold>> {
320        self.list_holds(QualityHoldFilter { active_only: Some(true), ..Default::default() })
321    }
322}