Skip to main content

stateset_embedded/
serials.rs

1//! Serial Number management operations
2//!
3//! Comprehensive serial number tracking supporting:
4//! - Individual unit tracking via unique serial numbers
5//! - Full lifecycle management (production to sale to return)
6//! - Serial reservations and ownership transfers
7//! - Complete audit trail of all serial events
8//!
9//! # Example
10//!
11//! ```ignore
12//! use stateset_embedded::{Commerce, CreateSerialNumber};
13//!
14//! let commerce = Commerce::new("./store.db")?;
15//!
16//! // Create a serial number for a high-value item
17//! let serial = commerce.serials().create(CreateSerialNumber {
18//!     serial: Some("SN-2025-ABC123".into()),
19//!     sku: "LAPTOP-PRO-15".into(),
20//!     ..Default::default()
21//! })?;
22//!
23//! println!("Created serial {}", serial.serial);
24//! # Ok::<(), stateset_embedded::CommerceError>(())
25//! ```
26
27use stateset_core::{
28    BatchResult, ChangeSerialStatus, CreateSerialNumber, CreateSerialNumbersBulk, MoveSerial,
29    ReserveSerialNumber, Result, SerialFilter, SerialHistory, SerialHistoryFilter,
30    SerialLookupResult, SerialNumber, SerialReservation, SerialValidation, TransferSerialOwnership,
31    UpdateSerialNumber,
32};
33use stateset_db::Database;
34use std::sync::Arc;
35use uuid::Uuid;
36
37/// Serial number management interface.
38pub struct Serials {
39    db: Arc<dyn Database>,
40}
41
42impl std::fmt::Debug for Serials {
43    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44        f.debug_struct("Serials").finish_non_exhaustive()
45    }
46}
47
48impl Serials {
49    pub(crate) fn new(db: Arc<dyn Database>) -> Self {
50        Self { db }
51    }
52
53    // ========================================================================
54    // Basic CRUD
55    // ========================================================================
56
57    /// Create a serial number.
58    ///
59    /// # Example
60    ///
61    /// ```rust,no_run
62    /// use stateset_embedded::{Commerce, CreateSerialNumber};
63    /// use chrono::Utc;
64    ///
65    /// let commerce = Commerce::new(":memory:")?;
66    ///
67    /// let serial = commerce.serials().create(CreateSerialNumber {
68    ///     serial: Some("SN-12345".into()),
69    ///     sku: "WIDGET-001".into(),
70    ///     lot_number: Some("LOT-2025-001".into()),
71    ///     manufactured_at: Some(Utc::now()),
72    ///     ..Default::default()
73    /// })?;
74    /// # Ok::<(), stateset_embedded::CommerceError>(())
75    /// ```
76    pub fn create(&self, input: CreateSerialNumber) -> Result<SerialNumber> {
77        self.db.serials().create(input)
78    }
79
80    /// Create multiple serial numbers in bulk.
81    ///
82    /// # Example
83    ///
84    /// ```rust,no_run
85    /// use stateset_embedded::{Commerce, CreateSerialNumbersBulk};
86    ///
87    /// let commerce = Commerce::new(":memory:")?;
88    ///
89    /// // Generate 100 serial numbers with prefix
90    /// let serials = commerce.serials().create_bulk(CreateSerialNumbersBulk {
91    ///     sku: "WIDGET-001".into(),
92    ///     quantity: 100,
93    ///     prefix: Some("WGT".into()),
94    ///     lot_number: Some("LOT-2025-001".into()),
95    ///     ..Default::default()
96    /// })?;
97    ///
98    /// println!("Created {} serial numbers", serials.len());
99    /// # Ok::<(), stateset_embedded::CommerceError>(())
100    /// ```
101    pub fn create_bulk(&self, input: CreateSerialNumbersBulk) -> Result<Vec<SerialNumber>> {
102        self.db.serials().create_bulk(input)
103    }
104
105    /// Get a serial by ID.
106    pub fn get(&self, id: Uuid) -> Result<Option<SerialNumber>> {
107        self.db.serials().get(id)
108    }
109
110    /// Get a serial by its serial number string.
111    ///
112    /// # Example
113    ///
114    /// ```rust,no_run
115    /// use stateset_embedded::Commerce;
116    ///
117    /// let commerce = Commerce::new(":memory:")?;
118    ///
119    /// if let Some(serial) = commerce.serials().get_by_serial("SN-12345")? {
120    ///     println!("Serial {} is currently {}", serial.serial, serial.status);
121    /// }
122    /// # Ok::<(), stateset_embedded::CommerceError>(())
123    /// ```
124    pub fn get_by_serial(&self, serial: &str) -> Result<Option<SerialNumber>> {
125        self.db.serials().get_by_serial(serial)
126    }
127
128    /// List serials with optional filtering.
129    pub fn list(&self, filter: SerialFilter) -> Result<Vec<SerialNumber>> {
130        self.db.serials().list(filter)
131    }
132
133    /// Update a serial number.
134    pub fn update(&self, id: Uuid, input: UpdateSerialNumber) -> Result<SerialNumber> {
135        self.db.serials().update(id, input)
136    }
137
138    /// Delete a serial (only if never used).
139    pub fn delete(&self, id: Uuid) -> Result<()> {
140        self.db.serials().delete(id)
141    }
142
143    // ========================================================================
144    // Status Management
145    // ========================================================================
146
147    /// Change serial status with full tracking.
148    ///
149    /// # Example
150    ///
151    /// ```rust,no_run
152    /// use stateset_embedded::{Commerce, ChangeSerialStatus, SerialStatus};
153    /// use uuid::Uuid;
154    ///
155    /// let commerce = Commerce::new(":memory:")?;
156    ///
157    /// commerce.serials().change_status(ChangeSerialStatus {
158    ///     serial_id: Uuid::new_v4(),
159    ///     new_status: SerialStatus::InService,
160    ///     reference_type: Some("repair_order".into()),
161    ///     reference_id: Some(Uuid::new_v4()),
162    ///     notes: Some("Sent for repair".into()),
163    ///     ..Default::default()
164    /// })?;
165    /// # Ok::<(), stateset_embedded::CommerceError>(())
166    /// ```
167    pub fn change_status(&self, input: ChangeSerialStatus) -> Result<SerialNumber> {
168        self.db.serials().change_status(input)
169    }
170
171    /// Mark a serial as sold.
172    pub fn mark_sold(
173        &self,
174        id: Uuid,
175        customer_id: Uuid,
176        order_id: Option<Uuid>,
177    ) -> Result<SerialNumber> {
178        self.db.serials().mark_sold(id, customer_id, order_id)
179    }
180
181    /// Mark a serial as shipped.
182    pub fn mark_shipped(&self, id: Uuid, shipment_id: Uuid) -> Result<SerialNumber> {
183        self.db.serials().mark_shipped(id, shipment_id)
184    }
185
186    /// Mark a serial as returned.
187    pub fn mark_returned(&self, id: Uuid, return_id: Uuid) -> Result<SerialNumber> {
188        self.db.serials().mark_returned(id, return_id)
189    }
190
191    /// Activate a serial (e.g., for warranty start).
192    pub fn activate(&self, id: Uuid) -> Result<SerialNumber> {
193        self.db.serials().activate(id)
194    }
195
196    /// Quarantine a serial.
197    pub fn quarantine(&self, id: Uuid, reason: &str) -> Result<SerialNumber> {
198        self.db.serials().quarantine(id, reason)
199    }
200
201    /// Release a serial from quarantine.
202    pub fn release_quarantine(&self, id: Uuid) -> Result<SerialNumber> {
203        self.db.serials().release_quarantine(id)
204    }
205
206    /// Scrap a serial.
207    pub fn scrap(&self, id: Uuid, reason: &str) -> Result<SerialNumber> {
208        self.db.serials().scrap(id, reason)
209    }
210
211    // ========================================================================
212    // Reservations
213    // ========================================================================
214
215    /// Reserve a serial for an order or other purpose.
216    ///
217    /// # Example
218    ///
219    /// ```rust,no_run
220    /// use stateset_embedded::{Commerce, ReserveSerialNumber};
221    /// use uuid::Uuid;
222    ///
223    /// let commerce = Commerce::new(":memory:")?;
224    ///
225    /// let reservation = commerce.serials().reserve(ReserveSerialNumber {
226    ///     serial_id: Uuid::new_v4(),
227    ///     reference_type: "order".into(),
228    ///     reference_id: Uuid::new_v4(),
229    ///     reserved_by: Some("sales_user".into()),
230    ///     expires_in_seconds: Some(3600), // 1 hour
231    ///     ..Default::default()
232    /// })?;
233    ///
234    /// println!("Reservation created, expires at {:?}", reservation.expires_at);
235    /// # Ok::<(), stateset_embedded::CommerceError>(())
236    /// ```
237    pub fn reserve(&self, input: ReserveSerialNumber) -> Result<SerialReservation> {
238        self.db.serials().reserve(input)
239    }
240
241    /// Release a reservation.
242    pub fn release_reservation(&self, reservation_id: Uuid) -> Result<()> {
243        self.db.serials().release_reservation(reservation_id)
244    }
245
246    /// Confirm a reservation (finalize the allocation).
247    pub fn confirm_reservation(&self, reservation_id: Uuid) -> Result<()> {
248        self.db.serials().confirm_reservation(reservation_id)
249    }
250
251    // ========================================================================
252    // Location & Ownership
253    // ========================================================================
254
255    /// Move a serial to a new location.
256    pub fn move_serial(&self, input: MoveSerial) -> Result<SerialNumber> {
257        self.db.serials().move_serial(input)
258    }
259
260    /// Transfer ownership of a serial.
261    ///
262    /// # Example
263    ///
264    /// ```rust,no_run
265    /// use stateset_embedded::{Commerce, TransferSerialOwnership};
266    /// use uuid::Uuid;
267    ///
268    /// let commerce = Commerce::new(":memory:")?;
269    ///
270    /// commerce.serials().transfer_ownership(TransferSerialOwnership {
271    ///     serial_id: Uuid::new_v4(),
272    ///     new_owner_id: Uuid::new_v4(),
273    ///     new_owner_type: "customer".into(),
274    ///     notes: Some("Warranty transfer requested".into()),
275    ///     ..Default::default()
276    /// })?;
277    /// # Ok::<(), stateset_embedded::CommerceError>(())
278    /// ```
279    pub fn transfer_ownership(&self, input: TransferSerialOwnership) -> Result<SerialNumber> {
280        self.db.serials().transfer_ownership(input)
281    }
282
283    // ========================================================================
284    // History & Lookup
285    // ========================================================================
286
287    /// Get serial history.
288    ///
289    /// # Example
290    ///
291    /// ```rust,no_run
292    /// use stateset_embedded::{Commerce, SerialHistoryFilter};
293    /// use uuid::Uuid;
294    ///
295    /// let commerce = Commerce::new(":memory:")?;
296    ///
297    /// let history = commerce.serials().get_history(
298    ///     Uuid::new_v4(),
299    ///     SerialHistoryFilter {
300    ///         limit: Some(50),
301    ///         ..Default::default()
302    ///     },
303    /// )?;
304    ///
305    /// for event in history {
306    ///     println!("{}: {} -> {}", event.event_type, event.from_status, event.to_status);
307    /// }
308    /// # Ok::<(), stateset_embedded::CommerceError>(())
309    /// ```
310    pub fn get_history(
311        &self,
312        serial_id: Uuid,
313        filter: SerialHistoryFilter,
314    ) -> Result<Vec<SerialHistory>> {
315        self.db.serials().get_history(serial_id, filter)
316    }
317
318    /// Full serial lookup with related data.
319    ///
320    /// Returns the serial along with lot info, warranty status, and recent history.
321    ///
322    /// # Example
323    ///
324    /// ```rust,no_run
325    /// use stateset_embedded::Commerce;
326    ///
327    /// let commerce = Commerce::new(":memory:")?;
328    ///
329    /// if let Some(result) = commerce.serials().lookup("SN-12345")? {
330    ///     println!("Serial: {}", result.serial.serial);
331    ///     println!("SKU: {}", result.serial.sku);
332    ///     println!("Status: {}", result.serial.status);
333    ///     if let Some(warranty) = result.warranty_status {
334    ///         println!("Warranty active: {}", warranty.is_active);
335    ///     }
336    /// }
337    /// # Ok::<(), stateset_embedded::CommerceError>(())
338    /// ```
339    pub fn lookup(&self, serial: &str) -> Result<Option<SerialLookupResult>> {
340        self.db.serials().lookup(serial)
341    }
342
343    /// Validate a serial number.
344    ///
345    /// Returns validation info without the full serial data.
346    pub fn validate(&self, serial: &str) -> Result<SerialValidation> {
347        self.db.serials().validate(serial)
348    }
349
350    // ========================================================================
351    // Queries
352    // ========================================================================
353
354    /// Get available serials for a SKU.
355    pub fn get_available(&self, sku: &str, limit: u32) -> Result<Vec<SerialNumber>> {
356        self.db.serials().get_available_for_sku(sku, limit)
357    }
358
359    /// Get serials for a lot.
360    pub fn get_for_lot(&self, lot_id: Uuid) -> Result<Vec<SerialNumber>> {
361        self.db.serials().get_for_lot(lot_id)
362    }
363
364    /// Get serials owned by a customer.
365    pub fn get_for_customer(&self, customer_id: Uuid) -> Result<Vec<SerialNumber>> {
366        self.db.serials().get_for_customer(customer_id)
367    }
368
369    /// Count serials matching filter.
370    pub fn count(&self, filter: SerialFilter) -> Result<u64> {
371        self.db.serials().count(filter)
372    }
373
374    // ========================================================================
375    // Batch Operations
376    // ========================================================================
377
378    /// Create multiple serials with partial success handling.
379    pub fn create_batch(
380        &self,
381        inputs: Vec<CreateSerialNumber>,
382    ) -> Result<BatchResult<SerialNumber>> {
383        self.db.serials().create_batch(inputs)
384    }
385
386    /// Get multiple serials by ID.
387    pub fn get_batch(&self, ids: Vec<Uuid>) -> Result<Vec<SerialNumber>> {
388        self.db.serials().get_batch(ids)
389    }
390
391    /// Get multiple serials by serial string.
392    pub fn get_batch_by_serial(&self, serials: Vec<String>) -> Result<Vec<SerialNumber>> {
393        self.db.serials().get_batch_by_serial(serials)
394    }
395
396    // ========================================================================
397    // Convenience Methods
398    // ========================================================================
399
400    /// Check if a serial is available for sale.
401    pub fn is_available(&self, serial: &str) -> Result<bool> {
402        if let Some(s) = self.get_by_serial(serial)? { Ok(s.is_available()) } else { Ok(false) }
403    }
404
405    /// Check if a serial can be shipped.
406    pub fn can_ship(&self, serial: &str) -> Result<bool> {
407        if let Some(s) = self.get_by_serial(serial)? { Ok(s.can_ship()) } else { Ok(false) }
408    }
409}