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}