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}