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}