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