1use chrono::{DateTime, Utc};
4use rust_decimal::Decimal;
5use serde::{Deserialize, Serialize};
6use stateset_primitives::{CurrencyCode, CustomerId, OrderId, OrderItemId, ProductId};
7use strum::{Display, EnumString};
8use uuid::Uuid;
9
10use crate::errors::Result;
11use crate::validation::{Validate, ValidationBuilder};
12
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15pub struct Order {
16 pub id: OrderId,
17 pub order_number: String,
18 pub customer_id: CustomerId,
19 pub status: OrderStatus,
20 pub order_date: DateTime<Utc>,
21 pub total_amount: Decimal,
22 pub currency: CurrencyCode,
23 pub payment_status: PaymentStatus,
24 pub fulfillment_status: FulfillmentStatus,
25 pub payment_method: Option<String>,
26 pub shipping_method: Option<String>,
27 pub tracking_number: Option<String>,
28 pub notes: Option<String>,
29 pub shipping_address: Option<Address>,
30 pub billing_address: Option<Address>,
31 pub items: Vec<OrderItem>,
32 pub version: i32,
34 pub created_at: DateTime<Utc>,
35 pub updated_at: DateTime<Utc>,
36}
37
38#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
40pub struct OrderItem {
41 pub id: OrderItemId,
42 pub order_id: OrderId,
43 pub product_id: ProductId,
44 pub variant_id: Option<Uuid>,
45 pub sku: String,
46 pub name: String,
47 pub quantity: i32,
48 pub unit_price: Decimal,
49 pub discount: Decimal,
50 pub tax_amount: Decimal,
51 pub total: Decimal,
52}
53
54#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
56pub struct Address {
57 pub line1: String,
58 pub line2: Option<String>,
59 pub city: String,
60 pub state: Option<String>,
61 pub postal_code: String,
62 pub country: String,
63}
64
65#[derive(
69 Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize, Display, EnumString,
70)]
71#[serde(rename_all = "snake_case")]
72#[strum(serialize_all = "snake_case", ascii_case_insensitive)]
73#[non_exhaustive]
74pub enum OrderStatus {
75 #[default]
76 Pending,
77 Confirmed,
78 Processing,
79 Shipped,
80 Delivered,
81 #[strum(serialize = "cancelled", serialize = "canceled")]
82 Cancelled,
83 Refunded,
84}
85
86impl OrderStatus {
87 #[must_use]
89 pub fn can_transition_to(self, next: Self) -> bool {
90 if self == next {
91 return true;
92 }
93
94 match self {
95 Self::Pending => matches!(next, Self::Confirmed | Self::Cancelled),
96 Self::Confirmed => matches!(next, Self::Processing | Self::Cancelled),
97 Self::Processing => matches!(next, Self::Shipped | Self::Cancelled),
98 Self::Shipped => matches!(next, Self::Delivered),
99 Self::Delivered => matches!(next, Self::Refunded),
100 Self::Cancelled | Self::Refunded => false,
101 }
102 }
103}
104
105#[derive(
109 Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize, Display, EnumString,
110)]
111#[serde(rename_all = "snake_case")]
112#[strum(serialize_all = "snake_case", ascii_case_insensitive)]
113#[non_exhaustive]
114pub enum PaymentStatus {
115 #[default]
116 Pending,
117 Authorized,
118 Paid,
119 #[strum(serialize = "partially_paid", serialize = "partiallypaid")]
120 PartiallyPaid,
121 Refunded,
122 #[strum(serialize = "partially_refunded", serialize = "partiallyrefunded")]
123 PartiallyRefunded,
124 Failed,
125}
126
127#[derive(
131 Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize, Display, EnumString,
132)]
133#[serde(rename_all = "snake_case")]
134#[strum(serialize_all = "snake_case", ascii_case_insensitive)]
135#[non_exhaustive]
136pub enum FulfillmentStatus {
137 #[default]
138 Unfulfilled,
139 #[strum(serialize = "partially_fulfilled", serialize = "partiallyfulfilled")]
140 PartiallyFulfilled,
141 Fulfilled,
142 Shipped,
143 Delivered,
144}
145
146#[derive(Debug, Clone, Serialize, Deserialize)]
148pub struct CreateOrder {
149 pub customer_id: CustomerId,
150 pub items: Vec<CreateOrderItem>,
151 pub currency: Option<CurrencyCode>,
152 pub shipping_address: Option<Address>,
153 pub billing_address: Option<Address>,
154 pub notes: Option<String>,
155 pub payment_method: Option<String>,
156 pub shipping_method: Option<String>,
157}
158
159impl Default for CreateOrder {
160 fn default() -> Self {
161 Self {
162 customer_id: CustomerId::nil(),
163 items: vec![],
164 currency: None,
165 shipping_address: None,
166 billing_address: None,
167 notes: None,
168 payment_method: None,
169 shipping_method: None,
170 }
171 }
172}
173
174#[derive(Debug, Clone, Serialize, Deserialize)]
176pub struct CreateOrderItem {
177 pub product_id: ProductId,
178 pub variant_id: Option<Uuid>,
179 pub sku: String,
180 pub name: String,
181 pub quantity: i32,
182 pub unit_price: Decimal,
183 pub discount: Option<Decimal>,
184 pub tax_amount: Option<Decimal>,
185}
186
187impl Default for CreateOrderItem {
188 fn default() -> Self {
189 Self {
190 product_id: ProductId::nil(),
191 variant_id: None,
192 sku: String::new(),
193 name: String::new(),
194 quantity: 0,
195 unit_price: Decimal::ZERO,
196 discount: None,
197 tax_amount: None,
198 }
199 }
200}
201
202#[derive(Debug, Clone, Default, Serialize, Deserialize)]
204pub struct UpdateOrder {
205 pub status: Option<OrderStatus>,
206 pub payment_status: Option<PaymentStatus>,
207 pub fulfillment_status: Option<FulfillmentStatus>,
208 pub tracking_number: Option<String>,
209 pub notes: Option<String>,
210 pub shipping_address: Option<Address>,
211 pub billing_address: Option<Address>,
212}
213
214#[derive(Debug, Clone, Default, Serialize, Deserialize)]
216pub struct OrderFilter {
217 pub customer_id: Option<CustomerId>,
218 pub status: Option<OrderStatus>,
219 pub payment_status: Option<PaymentStatus>,
220 pub fulfillment_status: Option<FulfillmentStatus>,
221 pub from_date: Option<DateTime<Utc>>,
222 pub to_date: Option<DateTime<Utc>>,
223 pub limit: Option<u32>,
224 pub offset: Option<u32>,
225 pub after_cursor: Option<(String, String)>,
228}
229
230impl Order {
231 #[must_use]
233 pub fn calculate_total(&self) -> Decimal {
234 self.items.iter().map(|item| item.total).sum()
235 }
236
237 #[must_use]
239 pub const fn can_cancel(&self) -> bool {
240 matches!(
241 self.status,
242 OrderStatus::Pending | OrderStatus::Confirmed | OrderStatus::Processing
243 )
244 }
245
246 #[must_use]
248 pub const fn can_refund(&self) -> bool {
249 matches!(self.payment_status, PaymentStatus::Paid | PaymentStatus::PartiallyPaid)
250 }
251}
252
253pub(crate) const MONEY_SCALE: u32 = 2;
260
261impl OrderItem {
262 #[must_use]
270 pub fn calculate_total(
271 quantity: i32,
272 unit_price: Decimal,
273 discount: Decimal,
274 tax: Decimal,
275 ) -> Decimal {
276 let subtotal = unit_price * Decimal::from(quantity);
277 (subtotal - discount + tax).round_dp(MONEY_SCALE)
278 }
279}
280
281impl Validate for CreateOrderItem {
282 fn validate(&self) -> Result<()> {
288 ValidationBuilder::new()
289 .required("sku", &self.sku)
290 .required("name", &self.name)
291 .positive_i32("quantity", self.quantity)
292 .non_negative("unit_price", self.unit_price)
293 .non_negative("discount", self.discount.unwrap_or(Decimal::ZERO))
294 .non_negative("tax_amount", self.tax_amount.unwrap_or(Decimal::ZERO))
295 .build()
296 }
297}
298
299impl Validate for CreateOrder {
300 fn validate(&self) -> Result<()> {
306 ValidationBuilder::new()
307 .uuid_not_nil("customer_id", self.customer_id.into_uuid())
308 .non_empty_list("items", &self.items)
309 .build()?;
310
311 for item in &self.items {
312 item.validate()?;
313 }
314
315 Ok(())
316 }
317}
318
319#[cfg(test)]
320mod tests {
321 use super::*;
322 use rust_decimal_macros::dec;
323
324 fn create_test_address() -> Address {
329 Address {
330 line1: "123 Main St".to_string(),
331 line2: Some("Apt 4".to_string()),
332 city: "San Francisco".to_string(),
333 state: Some("CA".to_string()),
334 postal_code: "94102".to_string(),
335 country: "US".to_string(),
336 }
337 }
338
339 fn create_test_order_item(quantity: i32, unit_price: Decimal) -> OrderItem {
340 let order_id = OrderId::new();
341 let discount = dec!(0.00);
342 let tax = (unit_price * Decimal::from(quantity) * dec!(0.08)).round_dp(2);
343 let total = OrderItem::calculate_total(quantity, unit_price, discount, tax);
344
345 OrderItem {
346 id: OrderItemId::new(),
347 order_id,
348 product_id: ProductId::new(),
349 variant_id: None,
350 sku: "TEST-SKU-001".to_string(),
351 name: "Test Product".to_string(),
352 quantity,
353 unit_price,
354 discount,
355 tax_amount: tax,
356 total,
357 }
358 }
359
360 fn create_test_order(status: OrderStatus, payment_status: PaymentStatus) -> Order {
361 let now = Utc::now();
362 let items =
363 vec![create_test_order_item(2, dec!(29.99)), create_test_order_item(1, dec!(49.99))];
364 let total: Decimal = items.iter().map(|i| i.total).sum();
365
366 Order {
367 id: OrderId::new(),
368 order_number: "ORD-2024-001".to_string(),
369 customer_id: CustomerId::new(),
370 status,
371 order_date: now,
372 total_amount: total,
373 currency: CurrencyCode::USD,
374 payment_status,
375 fulfillment_status: FulfillmentStatus::Unfulfilled,
376 payment_method: Some("credit_card".to_string()),
377 shipping_method: Some("standard".to_string()),
378 tracking_number: None,
379 notes: None,
380 shipping_address: Some(create_test_address()),
381 billing_address: None,
382 items,
383 version: 1,
384 created_at: now,
385 updated_at: now,
386 }
387 }
388
389 #[test]
394 fn test_order_calculate_total() {
395 let order = create_test_order(OrderStatus::Pending, PaymentStatus::Pending);
396 let calculated = order.calculate_total();
397 let expected: Decimal = order.items.iter().map(|i| i.total).sum();
398 assert_eq!(calculated, expected);
399 }
400
401 #[test]
402 fn test_order_calculate_total_empty_items() {
403 let mut order = create_test_order(OrderStatus::Pending, PaymentStatus::Pending);
404 order.items.clear();
405 assert_eq!(order.calculate_total(), dec!(0));
406 }
407
408 #[test]
409 fn test_order_can_cancel_pending() {
410 let order = create_test_order(OrderStatus::Pending, PaymentStatus::Pending);
411 assert!(order.can_cancel());
412 }
413
414 #[test]
415 fn test_order_can_cancel_confirmed() {
416 let order = create_test_order(OrderStatus::Confirmed, PaymentStatus::Authorized);
417 assert!(order.can_cancel());
418 }
419
420 #[test]
421 fn test_order_can_cancel_processing() {
422 let order = create_test_order(OrderStatus::Processing, PaymentStatus::Paid);
423 assert!(order.can_cancel());
424 }
425
426 #[test]
427 fn test_order_cannot_cancel_shipped() {
428 let order = create_test_order(OrderStatus::Shipped, PaymentStatus::Paid);
429 assert!(!order.can_cancel());
430 }
431
432 #[test]
433 fn test_order_cannot_cancel_delivered() {
434 let order = create_test_order(OrderStatus::Delivered, PaymentStatus::Paid);
435 assert!(!order.can_cancel());
436 }
437
438 #[test]
439 fn test_order_cannot_cancel_already_cancelled() {
440 let order = create_test_order(OrderStatus::Cancelled, PaymentStatus::Refunded);
441 assert!(!order.can_cancel());
442 }
443
444 #[test]
445 fn test_order_can_refund_when_paid() {
446 let order = create_test_order(OrderStatus::Delivered, PaymentStatus::Paid);
447 assert!(order.can_refund());
448 }
449
450 #[test]
451 fn test_order_can_refund_when_partially_paid() {
452 let order = create_test_order(OrderStatus::Delivered, PaymentStatus::PartiallyPaid);
453 assert!(order.can_refund());
454 }
455
456 #[test]
457 fn test_order_status_allows_valid_transitions() {
458 assert!(OrderStatus::Pending.can_transition_to(OrderStatus::Confirmed));
459 assert!(OrderStatus::Confirmed.can_transition_to(OrderStatus::Processing));
460 assert!(OrderStatus::Processing.can_transition_to(OrderStatus::Shipped));
461 assert!(OrderStatus::Shipped.can_transition_to(OrderStatus::Delivered));
462 assert!(OrderStatus::Delivered.can_transition_to(OrderStatus::Refunded));
463 assert!(OrderStatus::Pending.can_transition_to(OrderStatus::Cancelled));
464 }
465
466 #[test]
467 fn test_order_status_rejects_invalid_transitions() {
468 assert!(!OrderStatus::Pending.can_transition_to(OrderStatus::Delivered));
469 assert!(!OrderStatus::Shipped.can_transition_to(OrderStatus::Cancelled));
470 assert!(!OrderStatus::Refunded.can_transition_to(OrderStatus::Processing));
471 }
472
473 #[test]
474 fn test_order_status_allows_idempotent_transition() {
475 assert!(OrderStatus::Pending.can_transition_to(OrderStatus::Pending));
476 assert!(OrderStatus::Cancelled.can_transition_to(OrderStatus::Cancelled));
477 }
478
479 #[test]
480 fn test_status_from_str_accepts_legacy_variants() {
481 use std::str::FromStr;
482
483 assert_eq!(PaymentStatus::from_str("partiallypaid").unwrap(), PaymentStatus::PartiallyPaid);
484 assert_eq!(
485 PaymentStatus::from_str("partiallyrefunded").unwrap(),
486 PaymentStatus::PartiallyRefunded
487 );
488 assert_eq!(
489 FulfillmentStatus::from_str("partiallyfulfilled").unwrap(),
490 FulfillmentStatus::PartiallyFulfilled
491 );
492 assert_eq!(OrderStatus::from_str("canceled").unwrap(), OrderStatus::Cancelled);
493 }
494
495 #[test]
496 fn test_order_cannot_refund_when_pending() {
497 let order = create_test_order(OrderStatus::Pending, PaymentStatus::Pending);
498 assert!(!order.can_refund());
499 }
500
501 #[test]
502 fn test_order_cannot_refund_when_already_refunded() {
503 let order = create_test_order(OrderStatus::Refunded, PaymentStatus::Refunded);
504 assert!(!order.can_refund());
505 }
506
507 #[test]
512 fn test_order_item_calculate_total_basic() {
513 let total = OrderItem::calculate_total(2, dec!(29.99), dec!(0), dec!(4.80));
514 assert_eq!(total, dec!(64.78));
516 }
517
518 #[test]
519 fn test_order_item_calculate_total_with_discount() {
520 let total = OrderItem::calculate_total(2, dec!(29.99), dec!(10.00), dec!(4.00));
521 assert_eq!(total, dec!(53.98));
523 }
524
525 #[test]
526 fn test_order_item_calculate_total_zero_quantity() {
527 let total = OrderItem::calculate_total(0, dec!(29.99), dec!(0), dec!(0));
528 assert_eq!(total, dec!(0));
529 }
530
531 #[test]
532 fn test_order_item_calculate_total_high_quantity() {
533 let total = OrderItem::calculate_total(1000, dec!(9.99), dec!(0), dec!(799.20));
534 assert_eq!(total, dec!(10789.20));
536 }
537
538 #[test]
543 fn test_order_status_default() {
544 assert_eq!(OrderStatus::default(), OrderStatus::Pending);
545 }
546
547 #[test]
548 fn test_order_status_display() {
549 assert_eq!(format!("{}", OrderStatus::Pending), "pending");
550 assert_eq!(format!("{}", OrderStatus::Confirmed), "confirmed");
551 assert_eq!(format!("{}", OrderStatus::Processing), "processing");
552 assert_eq!(format!("{}", OrderStatus::Shipped), "shipped");
553 assert_eq!(format!("{}", OrderStatus::Delivered), "delivered");
554 assert_eq!(format!("{}", OrderStatus::Cancelled), "cancelled");
555 assert_eq!(format!("{}", OrderStatus::Refunded), "refunded");
556 }
557
558 #[test]
559 fn test_order_status_serialization() {
560 let status = OrderStatus::Processing;
561 let json = serde_json::to_string(&status).unwrap();
562 assert_eq!(json, "\"processing\"");
563
564 let deserialized: OrderStatus = serde_json::from_str(&json).unwrap();
565 assert_eq!(deserialized, status);
566 }
567
568 #[test]
573 fn test_payment_status_default() {
574 assert_eq!(PaymentStatus::default(), PaymentStatus::Pending);
575 }
576
577 #[test]
578 fn test_payment_status_display() {
579 assert_eq!(format!("{}", PaymentStatus::Pending), "pending");
580 assert_eq!(format!("{}", PaymentStatus::Authorized), "authorized");
581 assert_eq!(format!("{}", PaymentStatus::Paid), "paid");
582 assert_eq!(format!("{}", PaymentStatus::PartiallyPaid), "partially_paid");
583 assert_eq!(format!("{}", PaymentStatus::Refunded), "refunded");
584 assert_eq!(format!("{}", PaymentStatus::PartiallyRefunded), "partially_refunded");
585 assert_eq!(format!("{}", PaymentStatus::Failed), "failed");
586 }
587
588 #[test]
589 fn test_payment_status_serialization() {
590 let status = PaymentStatus::PartiallyPaid;
591 let json = serde_json::to_string(&status).unwrap();
592 assert_eq!(json, "\"partially_paid\"");
593
594 let deserialized: PaymentStatus = serde_json::from_str(&json).unwrap();
595 assert_eq!(deserialized, status);
596 }
597
598 #[test]
603 fn test_fulfillment_status_default() {
604 assert_eq!(FulfillmentStatus::default(), FulfillmentStatus::Unfulfilled);
605 }
606
607 #[test]
608 fn test_fulfillment_status_display() {
609 assert_eq!(format!("{}", FulfillmentStatus::Unfulfilled), "unfulfilled");
610 assert_eq!(format!("{}", FulfillmentStatus::PartiallyFulfilled), "partially_fulfilled");
611 assert_eq!(format!("{}", FulfillmentStatus::Fulfilled), "fulfilled");
612 assert_eq!(format!("{}", FulfillmentStatus::Shipped), "shipped");
613 assert_eq!(format!("{}", FulfillmentStatus::Delivered), "delivered");
614 }
615
616 #[test]
621 fn test_create_order_default() {
622 let create_order = CreateOrder::default();
623 assert!(create_order.customer_id.is_nil());
624 assert!(create_order.items.is_empty());
625 assert!(create_order.currency.is_none());
626 assert!(create_order.shipping_address.is_none());
627 }
628
629 #[test]
634 fn test_create_order_item_default() {
635 let item = CreateOrderItem::default();
636 assert!(item.product_id.is_nil());
637 assert_eq!(item.quantity, 0);
638 assert_eq!(item.unit_price, Decimal::ZERO);
639 assert!(item.sku.is_empty());
640 }
641
642 #[test]
647 fn test_address_serialization_roundtrip() {
648 let address = create_test_address();
649 let json = serde_json::to_string(&address).unwrap();
650 let deserialized: Address = serde_json::from_str(&json).unwrap();
651 assert_eq!(address, deserialized);
652 }
653
654 #[test]
655 fn test_address_without_optional_fields() {
656 let address = Address {
657 line1: "123 Main St".to_string(),
658 line2: None,
659 city: "NYC".to_string(),
660 state: None,
661 postal_code: "10001".to_string(),
662 country: "US".to_string(),
663 };
664
665 let json = serde_json::to_string(&address).unwrap();
666 let deserialized: Address = serde_json::from_str(&json).unwrap();
667 assert_eq!(address, deserialized);
668 }
669
670 #[test]
675 fn test_order_serialization_roundtrip() {
676 let order = create_test_order(OrderStatus::Confirmed, PaymentStatus::Paid);
677 let json = serde_json::to_string(&order).unwrap();
678 let deserialized: Order = serde_json::from_str(&json).unwrap();
679 assert_eq!(order, deserialized);
680 }
681
682 #[test]
683 fn test_order_item_serialization_roundtrip() {
684 let item = create_test_order_item(3, dec!(19.99));
685 let json = serde_json::to_string(&item).unwrap();
686 let deserialized: OrderItem = serde_json::from_str(&json).unwrap();
687 assert_eq!(item, deserialized);
688 }
689
690 #[test]
695 fn test_update_order_default() {
696 let update = UpdateOrder::default();
697 assert!(update.status.is_none());
698 assert!(update.payment_status.is_none());
699 assert!(update.fulfillment_status.is_none());
700 assert!(update.tracking_number.is_none());
701 }
702
703 #[test]
704 fn test_update_order_partial() {
705 let update = UpdateOrder {
706 status: Some(OrderStatus::Shipped),
707 tracking_number: Some("1Z999AA10123456784".to_string()),
708 ..Default::default()
709 };
710
711 assert_eq!(update.status, Some(OrderStatus::Shipped));
712 assert!(update.tracking_number.is_some());
713 assert!(update.payment_status.is_none());
714 }
715
716 #[test]
721 fn test_order_filter_default() {
722 let filter = OrderFilter::default();
723 assert!(filter.customer_id.is_none());
724 assert!(filter.status.is_none());
725 assert!(filter.limit.is_none());
726 assert!(filter.offset.is_none());
727 }
728
729 #[test]
730 fn test_order_filter_with_values() {
731 let customer_id = CustomerId::new();
732 let filter = OrderFilter {
733 customer_id: Some(customer_id),
734 status: Some(OrderStatus::Pending),
735 limit: Some(10),
736 offset: Some(0),
737 ..Default::default()
738 };
739
740 assert_eq!(filter.customer_id, Some(customer_id));
741 assert_eq!(filter.status, Some(OrderStatus::Pending));
742 assert_eq!(filter.limit, Some(10));
743 }
744
745 fn valid_create_order_item() -> CreateOrderItem {
746 CreateOrderItem {
747 product_id: ProductId::new(),
748 sku: "SKU-1".to_string(),
749 name: "Item".to_string(),
750 quantity: 1,
751 unit_price: dec!(10.00),
752 ..Default::default()
753 }
754 }
755
756 #[test]
757 fn test_create_order_item_validate() {
758 assert!(valid_create_order_item().validate().is_ok());
760
761 let mut item = valid_create_order_item();
763 item.unit_price = dec!(-0.01);
764 assert!(item.validate().is_err());
765
766 let mut item = valid_create_order_item();
768 item.quantity = 0;
769 assert!(item.validate().is_err());
770 item.quantity = -2;
771 assert!(item.validate().is_err());
772
773 let mut item = valid_create_order_item();
775 item.discount = Some(dec!(-1));
776 assert!(item.validate().is_err());
777 let mut item = valid_create_order_item();
778 item.tax_amount = Some(dec!(-1));
779 assert!(item.validate().is_err());
780
781 let mut item = valid_create_order_item();
783 item.unit_price = dec!(0);
784 assert!(item.validate().is_ok());
785 }
786
787 #[test]
788 fn test_create_order_validate() {
789 let order = CreateOrder {
791 customer_id: CustomerId::new(),
792 items: vec![valid_create_order_item()],
793 ..Default::default()
794 };
795 assert!(order.validate().is_ok());
796
797 let order = CreateOrder {
799 customer_id: CustomerId::nil(),
800 items: vec![valid_create_order_item()],
801 ..Default::default()
802 };
803 assert!(order.validate().is_err());
804
805 let order =
807 CreateOrder { customer_id: CustomerId::new(), items: vec![], ..Default::default() };
808 assert!(order.validate().is_err());
809
810 let mut bad_item = valid_create_order_item();
812 bad_item.quantity = -1;
813 let order = CreateOrder {
814 customer_id: CustomerId::new(),
815 items: vec![bad_item],
816 ..Default::default()
817 };
818 assert!(order.validate().is_err());
819 }
820}