Skip to main content

stateset_embedded/
shipments.rs

1//! Shipment operations for tracking order fulfillment and delivery
2//!
3//! # Example
4//!
5//! ```ignore
6//! use stateset_embedded::{Commerce, CreateShipment, CreateShipmentItem, OrderId};
7//!
8//! let commerce = Commerce::new("./store.db")?;
9//!
10//! // Create a shipment for an order
11//! let shipment = commerce.shipments().create(CreateShipment {
12//!     order_id: OrderId::new(),
13//!     recipient_name: "Alice Smith".into(),
14//!     shipping_address: "123 Main St, City, ST 12345".into(),
15//!     items: Some(vec![CreateShipmentItem {
16//!         sku: "SKU-001".into(),
17//!         name: "Widget".into(),
18//!         quantity: 2,
19//!         ..Default::default()
20//!     }]),
21//!     ..Default::default()
22//! })?;
23//!
24//! // Ship the order with tracking number
25//! let shipment = commerce.shipments().ship(shipment.id, Some("1Z999AA10123456784".into()))?;
26//!
27//! // Mark as delivered
28//! let shipment = commerce.shipments().mark_delivered(shipment.id)?;
29//! # Ok::<(), stateset_embedded::CommerceError>(())
30//! ```
31
32use crate::Database;
33use stateset_core::{
34    AddShipmentEvent, CreateShipment, CreateShipmentItem, OrderId, Result, Shipment, ShipmentEvent,
35    ShipmentFilter, ShipmentId, ShipmentItem,
36};
37use stateset_observability::Metrics;
38use std::sync::Arc;
39use uuid::Uuid;
40
41/// Shipment operations for order fulfillment and delivery tracking
42pub struct Shipments {
43    db: Arc<dyn Database>,
44    metrics: Metrics,
45}
46
47impl std::fmt::Debug for Shipments {
48    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49        f.debug_struct("Shipments").finish_non_exhaustive()
50    }
51}
52
53impl Shipments {
54    pub(crate) fn new(db: Arc<dyn Database>, metrics: Metrics) -> Self {
55        Self { db, metrics }
56    }
57
58    /// Create a new shipment for an order
59    ///
60    /// # Example
61    ///
62    /// ```rust,no_run
63    /// use stateset_embedded::{Commerce, CreateShipment, CreateShipmentItem, OrderId, ShippingCarrier};
64    ///
65    /// let commerce = Commerce::new("./store.db")?;
66    ///
67    /// let shipment = commerce.shipments().create(CreateShipment {
68    ///     order_id: OrderId::new(),
69    ///     carrier: Some(ShippingCarrier::Ups),
70    ///     recipient_name: "John Doe".into(),
71    ///     recipient_email: Some("john@example.com".into()),
72    ///     shipping_address: "456 Oak Ave, Town, ST 67890".into(),
73    ///     items: Some(vec![CreateShipmentItem {
74    ///         sku: "PROD-001".into(),
75    ///         name: "Product A".into(),
76    ///         quantity: 1,
77    ///         ..Default::default()
78    ///     }]),
79    ///     ..Default::default()
80    /// })?;
81    /// # Ok::<(), stateset_embedded::CommerceError>(())
82    /// ```
83    pub fn create(&self, input: CreateShipment) -> Result<Shipment> {
84        let shipment = self.db.shipments().create(input)?;
85        self.metrics.record_shipment_created(&shipment.id.to_string());
86        Ok(shipment)
87    }
88
89    /// Get a shipment by ID
90    pub fn get(&self, id: ShipmentId) -> Result<Option<Shipment>> {
91        self.db.shipments().get(id)
92    }
93
94    /// Get a shipment by shipment number
95    pub fn get_by_number(&self, shipment_number: &str) -> Result<Option<Shipment>> {
96        self.db.shipments().get_by_number(shipment_number)
97    }
98
99    /// Find a shipment by tracking number
100    pub fn get_by_tracking(&self, tracking_number: &str) -> Result<Option<Shipment>> {
101        self.db.shipments().get_by_tracking(tracking_number)
102    }
103
104    /// Update a shipment
105    pub fn update(&self, id: ShipmentId, input: stateset_core::UpdateShipment) -> Result<Shipment> {
106        self.db.shipments().update(id, input)
107    }
108
109    /// List shipments with optional filtering
110    pub fn list(&self, filter: ShipmentFilter) -> Result<Vec<Shipment>> {
111        self.db.shipments().list(filter)
112    }
113
114    /// Get all shipments for an order
115    ///
116    /// An order may have multiple shipments for partial fulfillment.
117    pub fn for_order(&self, order_id: OrderId) -> Result<Vec<Shipment>> {
118        self.db.shipments().for_order(order_id)
119    }
120
121    /// Mark shipment as processing (being prepared)
122    pub fn mark_processing(&self, id: ShipmentId) -> Result<Shipment> {
123        self.db.shipments().mark_processing(id)
124    }
125
126    /// Mark shipment as ready to ship
127    pub fn mark_ready(&self, id: ShipmentId) -> Result<Shipment> {
128        self.db.shipments().mark_ready(id)
129    }
130
131    /// Ship the order (hand off to carrier)
132    ///
133    /// # Example
134    ///
135    /// ```rust,no_run
136    /// use stateset_embedded::{Commerce, ShipmentId};
137    ///
138    /// let commerce = Commerce::new("./store.db")?;
139    ///
140    /// // Ship with a tracking number
141    /// let shipment = commerce.shipments().ship(
142    ///     ShipmentId::new(),
143    ///     Some("1Z999AA10123456784".into())
144    /// )?;
145    ///
146    /// println!("Tracking URL: {:?}", shipment.tracking_url);
147    /// # Ok::<(), stateset_embedded::CommerceError>(())
148    /// ```
149    pub fn ship(&self, id: ShipmentId, tracking_number: Option<String>) -> Result<Shipment> {
150        self.db.shipments().ship(id, tracking_number)
151    }
152
153    /// Mark shipment as in transit
154    pub fn mark_in_transit(&self, id: ShipmentId) -> Result<Shipment> {
155        self.db.shipments().mark_in_transit(id)
156    }
157
158    /// Mark shipment as out for delivery
159    pub fn mark_out_for_delivery(&self, id: ShipmentId) -> Result<Shipment> {
160        self.db.shipments().mark_out_for_delivery(id)
161    }
162
163    /// Mark shipment as delivered
164    ///
165    /// This records the delivery timestamp and marks the shipment complete.
166    pub fn mark_delivered(&self, id: ShipmentId) -> Result<Shipment> {
167        let shipment = self.db.shipments().mark_delivered(id)?;
168        self.metrics.record_shipment_delivered(&shipment.id.to_string());
169        Ok(shipment)
170    }
171
172    /// Mark shipment as failed delivery
173    pub fn mark_failed(&self, id: ShipmentId) -> Result<Shipment> {
174        self.db.shipments().mark_failed(id)
175    }
176
177    /// Put shipment on hold
178    pub fn hold(&self, id: ShipmentId) -> Result<Shipment> {
179        self.db.shipments().hold(id)
180    }
181
182    /// Cancel a shipment
183    pub fn cancel(&self, id: ShipmentId) -> Result<Shipment> {
184        self.db.shipments().cancel(id)
185    }
186
187    /// Add an item to a shipment
188    pub fn add_item(
189        &self,
190        shipment_id: ShipmentId,
191        item: CreateShipmentItem,
192    ) -> Result<ShipmentItem> {
193        self.db.shipments().add_item(shipment_id, item)
194    }
195
196    /// Remove an item from a shipment
197    pub fn remove_item(&self, item_id: Uuid) -> Result<()> {
198        self.db.shipments().remove_item(item_id)
199    }
200
201    /// Get items in a shipment
202    pub fn get_items(&self, shipment_id: ShipmentId) -> Result<Vec<ShipmentItem>> {
203        self.db.shipments().get_items(shipment_id)
204    }
205
206    /// Add a tracking event to the shipment history
207    ///
208    /// # Example
209    ///
210    /// ```rust,no_run
211    /// use stateset_embedded::{Commerce, AddShipmentEvent, ShipmentId};
212    ///
213    /// let commerce = Commerce::new("./store.db")?;
214    ///
215    /// commerce.shipments().add_event(ShipmentId::new(), AddShipmentEvent {
216    ///     event_type: "departed_facility".into(),
217    ///     location: Some("Chicago, IL".into()),
218    ///     description: Some("Package departed sorting facility".into()),
219    ///     event_time: None, // Uses current time
220    /// })?;
221    /// # Ok::<(), stateset_embedded::CommerceError>(())
222    /// ```
223    pub fn add_event(
224        &self,
225        shipment_id: ShipmentId,
226        event: AddShipmentEvent,
227    ) -> Result<ShipmentEvent> {
228        self.db.shipments().add_event(shipment_id, event)
229    }
230
231    /// Get tracking events for a shipment
232    pub fn get_events(&self, shipment_id: ShipmentId) -> Result<Vec<ShipmentEvent>> {
233        self.db.shipments().get_events(shipment_id)
234    }
235
236    /// Count shipments matching a filter
237    pub fn count(&self, filter: ShipmentFilter) -> Result<u64> {
238        self.db.shipments().count(filter)
239    }
240}