stateset_embedded 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/src/lib.rs ADDED
@@ -0,0 +1,4320 @@
1
+ //! Ruby bindings for StateSet Embedded Commerce
2
+ //!
3
+ //! Provides a local-first commerce library with SQLite storage for Ruby.
4
+ //!
5
+ //! ```ruby
6
+ //! require 'stateset_embedded'
7
+ //!
8
+ //! commerce = StateSet::Commerce.new("./store.db")
9
+ //! customer = commerce.customers.create(
10
+ //! email: "alice@example.com",
11
+ //! first_name: "Alice",
12
+ //! last_name: "Smith"
13
+ //! )
14
+ //! ```
15
+
16
+ use magnus::{
17
+ class, define_module, exception, function, method, prelude::*, DataTypeFunctions, Error, RHash,
18
+ Ruby, Symbol, TypedData, Value,
19
+ };
20
+ use rust_decimal::prelude::ToPrimitive;
21
+ use rust_decimal::Decimal;
22
+ use stateset_embedded::Commerce as RustCommerce;
23
+ use std::sync::{Arc, Mutex};
24
+
25
+ // ============================================================================
26
+ // Helper Macros
27
+ // ============================================================================
28
+
29
+ macro_rules! lock_commerce {
30
+ ($commerce:expr) => {
31
+ $commerce
32
+ .lock()
33
+ .map_err(|e| Error::new(exception::runtime_error(), format!("Lock error: {}", e)))?
34
+ };
35
+ }
36
+
37
+ macro_rules! parse_uuid {
38
+ ($id:expr, $name:expr) => {
39
+ $id.parse()
40
+ .map_err(|_| Error::new(exception::arg_error(), format!("Invalid {} UUID", $name)))?
41
+ };
42
+ }
43
+
44
+ // ============================================================================
45
+ // Commerce
46
+ // ============================================================================
47
+
48
+ /// Main Commerce instance for local commerce operations.
49
+ #[derive(Clone)]
50
+ #[magnus::wrap(class = "StateSet::Commerce", free_immediately, size)]
51
+ pub struct Commerce {
52
+ inner: Arc<Mutex<RustCommerce>>,
53
+ }
54
+
55
+ impl Commerce {
56
+ fn new(db_path: String) -> Result<Self, Error> {
57
+ let commerce = RustCommerce::new(&db_path).map_err(|e| {
58
+ Error::new(
59
+ exception::runtime_error(),
60
+ format!("Failed to initialize commerce: {}", e),
61
+ )
62
+ })?;
63
+
64
+ Ok(Self {
65
+ inner: Arc::new(Mutex::new(commerce)),
66
+ })
67
+ }
68
+
69
+ fn customers(&self) -> Customers {
70
+ Customers {
71
+ commerce: self.inner.clone(),
72
+ }
73
+ }
74
+
75
+ fn orders(&self) -> Orders {
76
+ Orders {
77
+ commerce: self.inner.clone(),
78
+ }
79
+ }
80
+
81
+ fn products(&self) -> Products {
82
+ Products {
83
+ commerce: self.inner.clone(),
84
+ }
85
+ }
86
+
87
+ fn inventory(&self) -> Inventory {
88
+ Inventory {
89
+ commerce: self.inner.clone(),
90
+ }
91
+ }
92
+
93
+ fn returns(&self) -> Returns {
94
+ Returns {
95
+ commerce: self.inner.clone(),
96
+ }
97
+ }
98
+
99
+ fn payments(&self) -> Payments {
100
+ Payments {
101
+ commerce: self.inner.clone(),
102
+ }
103
+ }
104
+
105
+ fn shipments(&self) -> Shipments {
106
+ Shipments {
107
+ commerce: self.inner.clone(),
108
+ }
109
+ }
110
+
111
+ fn warranties(&self) -> Warranties {
112
+ Warranties {
113
+ commerce: self.inner.clone(),
114
+ }
115
+ }
116
+
117
+ fn purchase_orders(&self) -> PurchaseOrders {
118
+ PurchaseOrders {
119
+ commerce: self.inner.clone(),
120
+ }
121
+ }
122
+
123
+ fn invoices(&self) -> Invoices {
124
+ Invoices {
125
+ commerce: self.inner.clone(),
126
+ }
127
+ }
128
+
129
+ fn bom(&self) -> BomApi {
130
+ BomApi {
131
+ commerce: self.inner.clone(),
132
+ }
133
+ }
134
+
135
+ fn work_orders(&self) -> WorkOrders {
136
+ WorkOrders {
137
+ commerce: self.inner.clone(),
138
+ }
139
+ }
140
+
141
+ fn carts(&self) -> Carts {
142
+ Carts {
143
+ commerce: self.inner.clone(),
144
+ }
145
+ }
146
+
147
+ fn analytics(&self) -> Analytics {
148
+ Analytics {
149
+ commerce: self.inner.clone(),
150
+ }
151
+ }
152
+
153
+ fn currency(&self) -> CurrencyOps {
154
+ CurrencyOps {
155
+ commerce: self.inner.clone(),
156
+ }
157
+ }
158
+
159
+ fn subscriptions(&self) -> Subscriptions {
160
+ Subscriptions {
161
+ commerce: self.inner.clone(),
162
+ }
163
+ }
164
+
165
+ fn promotions(&self) -> Promotions {
166
+ Promotions {
167
+ commerce: self.inner.clone(),
168
+ }
169
+ }
170
+
171
+ fn tax(&self) -> Tax {
172
+ Tax {
173
+ commerce: self.inner.clone(),
174
+ }
175
+ }
176
+ }
177
+
178
+ // ============================================================================
179
+ // Customer Types
180
+ // ============================================================================
181
+
182
+ #[derive(Clone)]
183
+ #[magnus::wrap(class = "StateSet::Customer", free_immediately, size)]
184
+ pub struct Customer {
185
+ id: String,
186
+ email: String,
187
+ first_name: String,
188
+ last_name: String,
189
+ phone: Option<String>,
190
+ status: String,
191
+ accepts_marketing: bool,
192
+ created_at: String,
193
+ updated_at: String,
194
+ }
195
+
196
+ impl Customer {
197
+ fn id(&self) -> String {
198
+ self.id.clone()
199
+ }
200
+
201
+ fn email(&self) -> String {
202
+ self.email.clone()
203
+ }
204
+
205
+ fn first_name(&self) -> String {
206
+ self.first_name.clone()
207
+ }
208
+
209
+ fn last_name(&self) -> String {
210
+ self.last_name.clone()
211
+ }
212
+
213
+ fn phone(&self) -> Option<String> {
214
+ self.phone.clone()
215
+ }
216
+
217
+ fn status(&self) -> String {
218
+ self.status.clone()
219
+ }
220
+
221
+ fn accepts_marketing(&self) -> bool {
222
+ self.accepts_marketing
223
+ }
224
+
225
+ fn created_at(&self) -> String {
226
+ self.created_at.clone()
227
+ }
228
+
229
+ fn updated_at(&self) -> String {
230
+ self.updated_at.clone()
231
+ }
232
+
233
+ fn full_name(&self) -> String {
234
+ format!("{} {}", self.first_name, self.last_name)
235
+ }
236
+
237
+ fn inspect(&self) -> String {
238
+ format!(
239
+ "#<StateSet::Customer id=\"{}\" email=\"{}\" name=\"{} {}\">",
240
+ self.id, self.email, self.first_name, self.last_name
241
+ )
242
+ }
243
+ }
244
+
245
+ impl From<stateset_core::Customer> for Customer {
246
+ fn from(c: stateset_core::Customer) -> Self {
247
+ Self {
248
+ id: c.id.to_string(),
249
+ email: c.email,
250
+ first_name: c.first_name,
251
+ last_name: c.last_name,
252
+ phone: c.phone,
253
+ status: format!("{}", c.status),
254
+ accepts_marketing: c.accepts_marketing,
255
+ created_at: c.created_at.to_rfc3339(),
256
+ updated_at: c.updated_at.to_rfc3339(),
257
+ }
258
+ }
259
+ }
260
+
261
+ // ============================================================================
262
+ // Customers API
263
+ // ============================================================================
264
+
265
+ #[derive(Clone)]
266
+ #[magnus::wrap(class = "StateSet::Customers", free_immediately, size)]
267
+ pub struct Customers {
268
+ commerce: Arc<Mutex<RustCommerce>>,
269
+ }
270
+
271
+ impl Customers {
272
+ fn create(
273
+ &self,
274
+ email: String,
275
+ first_name: String,
276
+ last_name: String,
277
+ phone: Option<String>,
278
+ accepts_marketing: Option<bool>,
279
+ ) -> Result<Customer, Error> {
280
+ let commerce = lock_commerce!(self.commerce);
281
+
282
+ let customer = commerce
283
+ .customers()
284
+ .create(stateset_core::CreateCustomer {
285
+ email,
286
+ first_name,
287
+ last_name,
288
+ phone,
289
+ accepts_marketing,
290
+ ..Default::default()
291
+ })
292
+ .map_err(|e| {
293
+ Error::new(
294
+ exception::runtime_error(),
295
+ format!("Failed to create customer: {}", e),
296
+ )
297
+ })?;
298
+
299
+ Ok(customer.into())
300
+ }
301
+
302
+ fn get(&self, id: String) -> Result<Option<Customer>, Error> {
303
+ let commerce = lock_commerce!(self.commerce);
304
+ let uuid = parse_uuid!(id, "customer");
305
+
306
+ let customer = commerce.customers().get(uuid).map_err(|e| {
307
+ Error::new(
308
+ exception::runtime_error(),
309
+ format!("Failed to get customer: {}", e),
310
+ )
311
+ })?;
312
+
313
+ Ok(customer.map(|c| c.into()))
314
+ }
315
+
316
+ fn get_by_email(&self, email: String) -> Result<Option<Customer>, Error> {
317
+ let commerce = lock_commerce!(self.commerce);
318
+
319
+ let customer = commerce.customers().get_by_email(&email).map_err(|e| {
320
+ Error::new(
321
+ exception::runtime_error(),
322
+ format!("Failed to get customer: {}", e),
323
+ )
324
+ })?;
325
+
326
+ Ok(customer.map(|c| c.into()))
327
+ }
328
+
329
+ fn list(&self) -> Result<Vec<Customer>, Error> {
330
+ let commerce = lock_commerce!(self.commerce);
331
+
332
+ let customers = commerce.customers().list(Default::default()).map_err(|e| {
333
+ Error::new(
334
+ exception::runtime_error(),
335
+ format!("Failed to list customers: {}", e),
336
+ )
337
+ })?;
338
+
339
+ Ok(customers.into_iter().map(|c| c.into()).collect())
340
+ }
341
+
342
+ fn count(&self) -> Result<i64, Error> {
343
+ let commerce = lock_commerce!(self.commerce);
344
+
345
+ let count = commerce
346
+ .customers()
347
+ .count(Default::default())
348
+ .map_err(|e| {
349
+ Error::new(
350
+ exception::runtime_error(),
351
+ format!("Failed to count customers: {}", e),
352
+ )
353
+ })?;
354
+
355
+ Ok(count)
356
+ }
357
+ }
358
+
359
+ // ============================================================================
360
+ // Order Types
361
+ // ============================================================================
362
+
363
+ #[derive(Clone)]
364
+ #[magnus::wrap(class = "StateSet::OrderItem", free_immediately, size)]
365
+ pub struct OrderItem {
366
+ id: String,
367
+ sku: String,
368
+ name: String,
369
+ quantity: i32,
370
+ unit_price: f64,
371
+ total: f64,
372
+ }
373
+
374
+ impl OrderItem {
375
+ fn id(&self) -> String {
376
+ self.id.clone()
377
+ }
378
+ fn sku(&self) -> String {
379
+ self.sku.clone()
380
+ }
381
+ fn name(&self) -> String {
382
+ self.name.clone()
383
+ }
384
+ fn quantity(&self) -> i32 {
385
+ self.quantity
386
+ }
387
+ fn unit_price(&self) -> f64 {
388
+ self.unit_price
389
+ }
390
+ fn total(&self) -> f64 {
391
+ self.total
392
+ }
393
+ fn inspect(&self) -> String {
394
+ format!(
395
+ "#<StateSet::OrderItem sku=\"{}\" qty={} price={}>",
396
+ self.sku, self.quantity, self.unit_price
397
+ )
398
+ }
399
+ }
400
+
401
+ #[derive(Clone)]
402
+ #[magnus::wrap(class = "StateSet::Order", free_immediately, size)]
403
+ pub struct Order {
404
+ id: String,
405
+ order_number: String,
406
+ customer_id: String,
407
+ status: String,
408
+ total_amount: f64,
409
+ currency: String,
410
+ payment_status: String,
411
+ fulfillment_status: String,
412
+ tracking_number: Option<String>,
413
+ items: Vec<OrderItem>,
414
+ version: i32,
415
+ created_at: String,
416
+ updated_at: String,
417
+ }
418
+
419
+ impl Order {
420
+ fn id(&self) -> String {
421
+ self.id.clone()
422
+ }
423
+ fn order_number(&self) -> String {
424
+ self.order_number.clone()
425
+ }
426
+ fn customer_id(&self) -> String {
427
+ self.customer_id.clone()
428
+ }
429
+ fn status(&self) -> String {
430
+ self.status.clone()
431
+ }
432
+ fn total_amount(&self) -> f64 {
433
+ self.total_amount
434
+ }
435
+ fn currency(&self) -> String {
436
+ self.currency.clone()
437
+ }
438
+ fn payment_status(&self) -> String {
439
+ self.payment_status.clone()
440
+ }
441
+ fn fulfillment_status(&self) -> String {
442
+ self.fulfillment_status.clone()
443
+ }
444
+ fn tracking_number(&self) -> Option<String> {
445
+ self.tracking_number.clone()
446
+ }
447
+ fn items(&self) -> Vec<OrderItem> {
448
+ self.items.clone()
449
+ }
450
+ fn version(&self) -> i32 {
451
+ self.version
452
+ }
453
+ fn created_at(&self) -> String {
454
+ self.created_at.clone()
455
+ }
456
+ fn updated_at(&self) -> String {
457
+ self.updated_at.clone()
458
+ }
459
+ fn item_count(&self) -> usize {
460
+ self.items.len()
461
+ }
462
+ fn inspect(&self) -> String {
463
+ format!(
464
+ "#<StateSet::Order number=\"{}\" status=\"{}\" total={} {}>",
465
+ self.order_number, self.status, self.total_amount, self.currency
466
+ )
467
+ }
468
+ }
469
+
470
+ impl From<stateset_core::Order> for Order {
471
+ fn from(o: stateset_core::Order) -> Self {
472
+ Self {
473
+ id: o.id.to_string(),
474
+ order_number: o.order_number,
475
+ customer_id: o.customer_id.to_string(),
476
+ status: format!("{}", o.status),
477
+ total_amount: o.total_amount.to_f64().unwrap_or(0.0),
478
+ currency: o.currency,
479
+ payment_status: format!("{}", o.payment_status),
480
+ fulfillment_status: format!("{}", o.fulfillment_status),
481
+ tracking_number: o.tracking_number,
482
+ items: o
483
+ .items
484
+ .into_iter()
485
+ .map(|i| OrderItem {
486
+ id: i.id.to_string(),
487
+ sku: i.sku,
488
+ name: i.name,
489
+ quantity: i.quantity,
490
+ unit_price: i.unit_price.to_f64().unwrap_or(0.0),
491
+ total: i.total.to_f64().unwrap_or(0.0),
492
+ })
493
+ .collect(),
494
+ version: o.version,
495
+ created_at: o.created_at.to_rfc3339(),
496
+ updated_at: o.updated_at.to_rfc3339(),
497
+ }
498
+ }
499
+ }
500
+
501
+ // ============================================================================
502
+ // Orders API
503
+ // ============================================================================
504
+
505
+ #[derive(Clone)]
506
+ #[magnus::wrap(class = "StateSet::Orders", free_immediately, size)]
507
+ pub struct Orders {
508
+ commerce: Arc<Mutex<RustCommerce>>,
509
+ }
510
+
511
+ impl Orders {
512
+ fn create(
513
+ &self,
514
+ customer_id: String,
515
+ items: Vec<RHash>,
516
+ currency: Option<String>,
517
+ notes: Option<String>,
518
+ ) -> Result<Order, Error> {
519
+ let commerce = lock_commerce!(self.commerce);
520
+ let cust_uuid = parse_uuid!(customer_id, "customer");
521
+
522
+ let order_items: Vec<stateset_core::CreateOrderItem> = items
523
+ .into_iter()
524
+ .map(|h| {
525
+ let sku: String = h.fetch(Symbol::new("sku")).unwrap_or_default();
526
+ let name: String = h.fetch(Symbol::new("name")).unwrap_or_default();
527
+ let quantity: i32 = h.fetch(Symbol::new("quantity")).unwrap_or(1);
528
+ let unit_price: f64 = h.fetch(Symbol::new("unit_price")).unwrap_or(0.0);
529
+
530
+ stateset_core::CreateOrderItem {
531
+ product_id: Default::default(),
532
+ variant_id: None,
533
+ sku,
534
+ name,
535
+ quantity,
536
+ unit_price: Decimal::from_f64_retain(unit_price).unwrap_or_default(),
537
+ ..Default::default()
538
+ }
539
+ })
540
+ .collect();
541
+
542
+ let order = commerce
543
+ .orders()
544
+ .create(stateset_core::CreateOrder {
545
+ customer_id: cust_uuid,
546
+ items: order_items,
547
+ currency,
548
+ notes,
549
+ ..Default::default()
550
+ })
551
+ .map_err(|e| {
552
+ Error::new(
553
+ exception::runtime_error(),
554
+ format!("Failed to create order: {}", e),
555
+ )
556
+ })?;
557
+
558
+ Ok(order.into())
559
+ }
560
+
561
+ fn get(&self, id: String) -> Result<Option<Order>, Error> {
562
+ let commerce = lock_commerce!(self.commerce);
563
+ let uuid = parse_uuid!(id, "order");
564
+
565
+ let order = commerce.orders().get(uuid).map_err(|e| {
566
+ Error::new(
567
+ exception::runtime_error(),
568
+ format!("Failed to get order: {}", e),
569
+ )
570
+ })?;
571
+
572
+ Ok(order.map(|o| o.into()))
573
+ }
574
+
575
+ fn list(&self) -> Result<Vec<Order>, Error> {
576
+ let commerce = lock_commerce!(self.commerce);
577
+
578
+ let orders = commerce.orders().list(Default::default()).map_err(|e| {
579
+ Error::new(
580
+ exception::runtime_error(),
581
+ format!("Failed to list orders: {}", e),
582
+ )
583
+ })?;
584
+
585
+ Ok(orders.into_iter().map(|o| o.into()).collect())
586
+ }
587
+
588
+ fn count(&self) -> Result<i64, Error> {
589
+ let commerce = lock_commerce!(self.commerce);
590
+
591
+ let count = commerce.orders().count(Default::default()).map_err(|e| {
592
+ Error::new(
593
+ exception::runtime_error(),
594
+ format!("Failed to count orders: {}", e),
595
+ )
596
+ })?;
597
+
598
+ Ok(count)
599
+ }
600
+
601
+ fn ship(
602
+ &self,
603
+ id: String,
604
+ tracking_number: Option<String>,
605
+ carrier: Option<String>,
606
+ ) -> Result<Order, Error> {
607
+ let commerce = lock_commerce!(self.commerce);
608
+ let uuid = parse_uuid!(id, "order");
609
+
610
+ let order = commerce
611
+ .orders()
612
+ .ship(uuid, tracking_number, carrier)
613
+ .map_err(|e| {
614
+ Error::new(
615
+ exception::runtime_error(),
616
+ format!("Failed to ship order: {}", e),
617
+ )
618
+ })?;
619
+
620
+ Ok(order.into())
621
+ }
622
+
623
+ fn cancel(&self, id: String, reason: Option<String>) -> Result<Order, Error> {
624
+ let commerce = lock_commerce!(self.commerce);
625
+ let uuid = parse_uuid!(id, "order");
626
+
627
+ let order = commerce.orders().cancel(uuid, reason).map_err(|e| {
628
+ Error::new(
629
+ exception::runtime_error(),
630
+ format!("Failed to cancel order: {}", e),
631
+ )
632
+ })?;
633
+
634
+ Ok(order.into())
635
+ }
636
+
637
+ fn confirm(&self, id: String) -> Result<Order, Error> {
638
+ let commerce = lock_commerce!(self.commerce);
639
+ let uuid = parse_uuid!(id, "order");
640
+
641
+ let order = commerce.orders().confirm(uuid).map_err(|e| {
642
+ Error::new(
643
+ exception::runtime_error(),
644
+ format!("Failed to confirm order: {}", e),
645
+ )
646
+ })?;
647
+
648
+ Ok(order.into())
649
+ }
650
+
651
+ fn deliver(&self, id: String) -> Result<Order, Error> {
652
+ let commerce = lock_commerce!(self.commerce);
653
+ let uuid = parse_uuid!(id, "order");
654
+
655
+ let order = commerce.orders().deliver(uuid).map_err(|e| {
656
+ Error::new(
657
+ exception::runtime_error(),
658
+ format!("Failed to deliver order: {}", e),
659
+ )
660
+ })?;
661
+
662
+ Ok(order.into())
663
+ }
664
+ }
665
+
666
+ // ============================================================================
667
+ // Product Types
668
+ // ============================================================================
669
+
670
+ #[derive(Clone)]
671
+ #[magnus::wrap(class = "StateSet::ProductVariant", free_immediately, size)]
672
+ pub struct ProductVariant {
673
+ id: String,
674
+ sku: String,
675
+ name: String,
676
+ price: f64,
677
+ compare_at_price: Option<f64>,
678
+ inventory_quantity: i32,
679
+ weight: Option<f64>,
680
+ barcode: Option<String>,
681
+ }
682
+
683
+ impl ProductVariant {
684
+ fn id(&self) -> String {
685
+ self.id.clone()
686
+ }
687
+ fn sku(&self) -> String {
688
+ self.sku.clone()
689
+ }
690
+ fn name(&self) -> String {
691
+ self.name.clone()
692
+ }
693
+ fn price(&self) -> f64 {
694
+ self.price
695
+ }
696
+ fn compare_at_price(&self) -> Option<f64> {
697
+ self.compare_at_price
698
+ }
699
+ fn inventory_quantity(&self) -> i32 {
700
+ self.inventory_quantity
701
+ }
702
+ fn weight(&self) -> Option<f64> {
703
+ self.weight
704
+ }
705
+ fn barcode(&self) -> Option<String> {
706
+ self.barcode.clone()
707
+ }
708
+ }
709
+
710
+ #[derive(Clone)]
711
+ #[magnus::wrap(class = "StateSet::Product", free_immediately, size)]
712
+ pub struct Product {
713
+ id: String,
714
+ name: String,
715
+ description: Option<String>,
716
+ vendor: Option<String>,
717
+ product_type: Option<String>,
718
+ status: String,
719
+ tags: Vec<String>,
720
+ variants: Vec<ProductVariant>,
721
+ created_at: String,
722
+ updated_at: String,
723
+ }
724
+
725
+ impl Product {
726
+ fn id(&self) -> String {
727
+ self.id.clone()
728
+ }
729
+ fn name(&self) -> String {
730
+ self.name.clone()
731
+ }
732
+ fn description(&self) -> Option<String> {
733
+ self.description.clone()
734
+ }
735
+ fn vendor(&self) -> Option<String> {
736
+ self.vendor.clone()
737
+ }
738
+ fn product_type(&self) -> Option<String> {
739
+ self.product_type.clone()
740
+ }
741
+ fn status(&self) -> String {
742
+ self.status.clone()
743
+ }
744
+ fn tags(&self) -> Vec<String> {
745
+ self.tags.clone()
746
+ }
747
+ fn variants(&self) -> Vec<ProductVariant> {
748
+ self.variants.clone()
749
+ }
750
+ fn created_at(&self) -> String {
751
+ self.created_at.clone()
752
+ }
753
+ fn updated_at(&self) -> String {
754
+ self.updated_at.clone()
755
+ }
756
+ fn inspect(&self) -> String {
757
+ format!(
758
+ "#<StateSet::Product id=\"{}\" name=\"{}\" status=\"{}\">",
759
+ self.id, self.name, self.status
760
+ )
761
+ }
762
+ }
763
+
764
+ impl From<stateset_core::Product> for Product {
765
+ fn from(p: stateset_core::Product) -> Self {
766
+ Self {
767
+ id: p.id.to_string(),
768
+ name: p.name,
769
+ description: p.description,
770
+ vendor: p.vendor,
771
+ product_type: p.product_type,
772
+ status: format!("{}", p.status),
773
+ tags: p.tags,
774
+ variants: p
775
+ .variants
776
+ .into_iter()
777
+ .map(|v| ProductVariant {
778
+ id: v.id.to_string(),
779
+ sku: v.sku,
780
+ name: v.name,
781
+ price: v.price.to_f64().unwrap_or(0.0),
782
+ compare_at_price: v.compare_at_price.and_then(|p| p.to_f64()),
783
+ inventory_quantity: v.inventory_quantity,
784
+ weight: v.weight.and_then(|w| w.to_f64()),
785
+ barcode: v.barcode,
786
+ })
787
+ .collect(),
788
+ created_at: p.created_at.to_rfc3339(),
789
+ updated_at: p.updated_at.to_rfc3339(),
790
+ }
791
+ }
792
+ }
793
+
794
+ // ============================================================================
795
+ // Products API
796
+ // ============================================================================
797
+
798
+ #[derive(Clone)]
799
+ #[magnus::wrap(class = "StateSet::Products", free_immediately, size)]
800
+ pub struct Products {
801
+ commerce: Arc<Mutex<RustCommerce>>,
802
+ }
803
+
804
+ impl Products {
805
+ fn create(
806
+ &self,
807
+ name: String,
808
+ description: Option<String>,
809
+ vendor: Option<String>,
810
+ product_type: Option<String>,
811
+ ) -> Result<Product, Error> {
812
+ let commerce = lock_commerce!(self.commerce);
813
+
814
+ let product = commerce
815
+ .products()
816
+ .create(stateset_core::CreateProduct {
817
+ name,
818
+ description,
819
+ vendor,
820
+ product_type,
821
+ ..Default::default()
822
+ })
823
+ .map_err(|e| {
824
+ Error::new(
825
+ exception::runtime_error(),
826
+ format!("Failed to create product: {}", e),
827
+ )
828
+ })?;
829
+
830
+ Ok(product.into())
831
+ }
832
+
833
+ fn get(&self, id: String) -> Result<Option<Product>, Error> {
834
+ let commerce = lock_commerce!(self.commerce);
835
+ let uuid = parse_uuid!(id, "product");
836
+
837
+ let product = commerce.products().get(uuid).map_err(|e| {
838
+ Error::new(
839
+ exception::runtime_error(),
840
+ format!("Failed to get product: {}", e),
841
+ )
842
+ })?;
843
+
844
+ Ok(product.map(|p| p.into()))
845
+ }
846
+
847
+ fn list(&self) -> Result<Vec<Product>, Error> {
848
+ let commerce = lock_commerce!(self.commerce);
849
+
850
+ let products = commerce.products().list(Default::default()).map_err(|e| {
851
+ Error::new(
852
+ exception::runtime_error(),
853
+ format!("Failed to list products: {}", e),
854
+ )
855
+ })?;
856
+
857
+ Ok(products.into_iter().map(|p| p.into()).collect())
858
+ }
859
+
860
+ fn count(&self) -> Result<i64, Error> {
861
+ let commerce = lock_commerce!(self.commerce);
862
+
863
+ let count = commerce.products().count(Default::default()).map_err(|e| {
864
+ Error::new(
865
+ exception::runtime_error(),
866
+ format!("Failed to count products: {}", e),
867
+ )
868
+ })?;
869
+
870
+ Ok(count)
871
+ }
872
+
873
+ fn get_by_sku(&self, sku: String) -> Result<Option<ProductVariant>, Error> {
874
+ let commerce = lock_commerce!(self.commerce);
875
+
876
+ let variant = commerce.products().get_variant_by_sku(&sku).map_err(|e| {
877
+ Error::new(
878
+ exception::runtime_error(),
879
+ format!("Failed to get variant: {}", e),
880
+ )
881
+ })?;
882
+
883
+ Ok(variant.map(|v| ProductVariant {
884
+ id: v.id.to_string(),
885
+ sku: v.sku,
886
+ name: v.name,
887
+ price: v.price.to_f64().unwrap_or(0.0),
888
+ compare_at_price: v.compare_at_price.and_then(|p| p.to_f64()),
889
+ inventory_quantity: v.inventory_quantity,
890
+ weight: v.weight.and_then(|w| w.to_f64()),
891
+ barcode: v.barcode,
892
+ }))
893
+ }
894
+ }
895
+
896
+ // ============================================================================
897
+ // Inventory Types & API
898
+ // ============================================================================
899
+
900
+ #[derive(Clone)]
901
+ #[magnus::wrap(class = "StateSet::InventoryItem", free_immediately, size)]
902
+ pub struct InventoryItem {
903
+ id: String,
904
+ sku: String,
905
+ quantity_on_hand: i32,
906
+ quantity_reserved: i32,
907
+ quantity_available: i32,
908
+ reorder_point: Option<i32>,
909
+ reorder_quantity: Option<i32>,
910
+ location_id: Option<String>,
911
+ }
912
+
913
+ impl InventoryItem {
914
+ fn id(&self) -> String {
915
+ self.id.clone()
916
+ }
917
+ fn sku(&self) -> String {
918
+ self.sku.clone()
919
+ }
920
+ fn quantity_on_hand(&self) -> i32 {
921
+ self.quantity_on_hand
922
+ }
923
+ fn quantity_reserved(&self) -> i32 {
924
+ self.quantity_reserved
925
+ }
926
+ fn quantity_available(&self) -> i32 {
927
+ self.quantity_available
928
+ }
929
+ fn reorder_point(&self) -> Option<i32> {
930
+ self.reorder_point
931
+ }
932
+ fn reorder_quantity(&self) -> Option<i32> {
933
+ self.reorder_quantity
934
+ }
935
+ fn location_id(&self) -> Option<String> {
936
+ self.location_id.clone()
937
+ }
938
+ fn inspect(&self) -> String {
939
+ format!(
940
+ "#<StateSet::InventoryItem sku=\"{}\" available={}>",
941
+ self.sku, self.quantity_available
942
+ )
943
+ }
944
+ }
945
+
946
+ impl From<stateset_core::InventoryItem> for InventoryItem {
947
+ fn from(i: stateset_core::InventoryItem) -> Self {
948
+ Self {
949
+ id: i.id.to_string(),
950
+ sku: i.sku,
951
+ quantity_on_hand: i.quantity_on_hand,
952
+ quantity_reserved: i.quantity_reserved,
953
+ quantity_available: i.quantity_available,
954
+ reorder_point: i.reorder_point,
955
+ reorder_quantity: i.reorder_quantity,
956
+ location_id: i.location_id.map(|id| id.to_string()),
957
+ }
958
+ }
959
+ }
960
+
961
+ #[derive(Clone)]
962
+ #[magnus::wrap(class = "StateSet::Inventory", free_immediately, size)]
963
+ pub struct Inventory {
964
+ commerce: Arc<Mutex<RustCommerce>>,
965
+ }
966
+
967
+ impl Inventory {
968
+ fn create(
969
+ &self,
970
+ sku: String,
971
+ quantity: i32,
972
+ reorder_point: Option<i32>,
973
+ reorder_quantity: Option<i32>,
974
+ ) -> Result<InventoryItem, Error> {
975
+ let commerce = lock_commerce!(self.commerce);
976
+
977
+ let item = commerce
978
+ .inventory()
979
+ .create(stateset_core::CreateInventoryItem {
980
+ sku,
981
+ quantity_on_hand: quantity,
982
+ reorder_point,
983
+ reorder_quantity,
984
+ ..Default::default()
985
+ })
986
+ .map_err(|e| {
987
+ Error::new(
988
+ exception::runtime_error(),
989
+ format!("Failed to create inventory: {}", e),
990
+ )
991
+ })?;
992
+
993
+ Ok(item.into())
994
+ }
995
+
996
+ fn get(&self, id: String) -> Result<Option<InventoryItem>, Error> {
997
+ let commerce = lock_commerce!(self.commerce);
998
+ let uuid = parse_uuid!(id, "inventory");
999
+
1000
+ let item = commerce.inventory().get(uuid).map_err(|e| {
1001
+ Error::new(
1002
+ exception::runtime_error(),
1003
+ format!("Failed to get inventory: {}", e),
1004
+ )
1005
+ })?;
1006
+
1007
+ Ok(item.map(|i| i.into()))
1008
+ }
1009
+
1010
+ fn get_by_sku(&self, sku: String) -> Result<Option<InventoryItem>, Error> {
1011
+ let commerce = lock_commerce!(self.commerce);
1012
+
1013
+ let item = commerce.inventory().get_by_sku(&sku).map_err(|e| {
1014
+ Error::new(
1015
+ exception::runtime_error(),
1016
+ format!("Failed to get inventory: {}", e),
1017
+ )
1018
+ })?;
1019
+
1020
+ Ok(item.map(|i| i.into()))
1021
+ }
1022
+
1023
+ fn list(&self) -> Result<Vec<InventoryItem>, Error> {
1024
+ let commerce = lock_commerce!(self.commerce);
1025
+
1026
+ let items = commerce.inventory().list(Default::default()).map_err(|e| {
1027
+ Error::new(
1028
+ exception::runtime_error(),
1029
+ format!("Failed to list inventory: {}", e),
1030
+ )
1031
+ })?;
1032
+
1033
+ Ok(items.into_iter().map(|i| i.into()).collect())
1034
+ }
1035
+
1036
+ fn adjust(
1037
+ &self,
1038
+ id: String,
1039
+ adjustment: i32,
1040
+ reason: Option<String>,
1041
+ ) -> Result<InventoryItem, Error> {
1042
+ let commerce = lock_commerce!(self.commerce);
1043
+ let uuid = parse_uuid!(id, "inventory");
1044
+
1045
+ let item = commerce
1046
+ .inventory()
1047
+ .adjust(uuid, adjustment, reason)
1048
+ .map_err(|e| {
1049
+ Error::new(
1050
+ exception::runtime_error(),
1051
+ format!("Failed to adjust inventory: {}", e),
1052
+ )
1053
+ })?;
1054
+
1055
+ Ok(item.into())
1056
+ }
1057
+
1058
+ fn reserve(
1059
+ &self,
1060
+ id: String,
1061
+ quantity: i32,
1062
+ order_id: Option<String>,
1063
+ ) -> Result<InventoryItem, Error> {
1064
+ let commerce = lock_commerce!(self.commerce);
1065
+ let uuid = parse_uuid!(id, "inventory");
1066
+ let order_uuid = order_id.map(|s| s.parse().ok()).flatten();
1067
+
1068
+ let item = commerce
1069
+ .inventory()
1070
+ .reserve(uuid, quantity, order_uuid)
1071
+ .map_err(|e| {
1072
+ Error::new(
1073
+ exception::runtime_error(),
1074
+ format!("Failed to reserve inventory: {}", e),
1075
+ )
1076
+ })?;
1077
+
1078
+ Ok(item.into())
1079
+ }
1080
+
1081
+ fn release(&self, id: String, quantity: i32) -> Result<InventoryItem, Error> {
1082
+ let commerce = lock_commerce!(self.commerce);
1083
+ let uuid = parse_uuid!(id, "inventory");
1084
+
1085
+ let item = commerce.inventory().release(uuid, quantity).map_err(|e| {
1086
+ Error::new(
1087
+ exception::runtime_error(),
1088
+ format!("Failed to release inventory: {}", e),
1089
+ )
1090
+ })?;
1091
+
1092
+ Ok(item.into())
1093
+ }
1094
+ }
1095
+
1096
+ // ============================================================================
1097
+ // Returns Types & API
1098
+ // ============================================================================
1099
+
1100
+ #[derive(Clone)]
1101
+ #[magnus::wrap(class = "StateSet::Return", free_immediately, size)]
1102
+ pub struct Return {
1103
+ id: String,
1104
+ order_id: String,
1105
+ customer_id: String,
1106
+ status: String,
1107
+ reason: String,
1108
+ refund_amount: f64,
1109
+ created_at: String,
1110
+ updated_at: String,
1111
+ }
1112
+
1113
+ impl Return {
1114
+ fn id(&self) -> String {
1115
+ self.id.clone()
1116
+ }
1117
+ fn order_id(&self) -> String {
1118
+ self.order_id.clone()
1119
+ }
1120
+ fn customer_id(&self) -> String {
1121
+ self.customer_id.clone()
1122
+ }
1123
+ fn status(&self) -> String {
1124
+ self.status.clone()
1125
+ }
1126
+ fn reason(&self) -> String {
1127
+ self.reason.clone()
1128
+ }
1129
+ fn refund_amount(&self) -> f64 {
1130
+ self.refund_amount
1131
+ }
1132
+ fn created_at(&self) -> String {
1133
+ self.created_at.clone()
1134
+ }
1135
+ fn updated_at(&self) -> String {
1136
+ self.updated_at.clone()
1137
+ }
1138
+ fn inspect(&self) -> String {
1139
+ format!(
1140
+ "#<StateSet::Return id=\"{}\" status=\"{}\" refund={}>",
1141
+ self.id, self.status, self.refund_amount
1142
+ )
1143
+ }
1144
+ }
1145
+
1146
+ impl From<stateset_core::Return> for Return {
1147
+ fn from(r: stateset_core::Return) -> Self {
1148
+ Self {
1149
+ id: r.id.to_string(),
1150
+ order_id: r.order_id.to_string(),
1151
+ customer_id: r.customer_id.to_string(),
1152
+ status: format!("{}", r.status),
1153
+ reason: r.reason,
1154
+ refund_amount: r.refund_amount.to_f64().unwrap_or(0.0),
1155
+ created_at: r.created_at.to_rfc3339(),
1156
+ updated_at: r.updated_at.to_rfc3339(),
1157
+ }
1158
+ }
1159
+ }
1160
+
1161
+ #[derive(Clone)]
1162
+ #[magnus::wrap(class = "StateSet::Returns", free_immediately, size)]
1163
+ pub struct Returns {
1164
+ commerce: Arc<Mutex<RustCommerce>>,
1165
+ }
1166
+
1167
+ impl Returns {
1168
+ fn create(&self, order_id: String, reason: String) -> Result<Return, Error> {
1169
+ let commerce = lock_commerce!(self.commerce);
1170
+ let uuid = parse_uuid!(order_id, "order");
1171
+
1172
+ let ret = commerce
1173
+ .returns()
1174
+ .create(stateset_core::CreateReturn {
1175
+ order_id: uuid,
1176
+ reason,
1177
+ ..Default::default()
1178
+ })
1179
+ .map_err(|e| {
1180
+ Error::new(
1181
+ exception::runtime_error(),
1182
+ format!("Failed to create return: {}", e),
1183
+ )
1184
+ })?;
1185
+
1186
+ Ok(ret.into())
1187
+ }
1188
+
1189
+ fn get(&self, id: String) -> Result<Option<Return>, Error> {
1190
+ let commerce = lock_commerce!(self.commerce);
1191
+ let uuid = parse_uuid!(id, "return");
1192
+
1193
+ let ret = commerce.returns().get(uuid).map_err(|e| {
1194
+ Error::new(
1195
+ exception::runtime_error(),
1196
+ format!("Failed to get return: {}", e),
1197
+ )
1198
+ })?;
1199
+
1200
+ Ok(ret.map(|r| r.into()))
1201
+ }
1202
+
1203
+ fn list(&self) -> Result<Vec<Return>, Error> {
1204
+ let commerce = lock_commerce!(self.commerce);
1205
+
1206
+ let returns = commerce.returns().list(Default::default()).map_err(|e| {
1207
+ Error::new(
1208
+ exception::runtime_error(),
1209
+ format!("Failed to list returns: {}", e),
1210
+ )
1211
+ })?;
1212
+
1213
+ Ok(returns.into_iter().map(|r| r.into()).collect())
1214
+ }
1215
+
1216
+ fn approve(&self, id: String, refund_amount: Option<f64>) -> Result<Return, Error> {
1217
+ let commerce = lock_commerce!(self.commerce);
1218
+ let uuid = parse_uuid!(id, "return");
1219
+ let amount = refund_amount.map(|a| Decimal::from_f64_retain(a).unwrap_or_default());
1220
+
1221
+ let ret = commerce.returns().approve(uuid, amount).map_err(|e| {
1222
+ Error::new(
1223
+ exception::runtime_error(),
1224
+ format!("Failed to approve return: {}", e),
1225
+ )
1226
+ })?;
1227
+
1228
+ Ok(ret.into())
1229
+ }
1230
+
1231
+ fn reject(&self, id: String, reason: Option<String>) -> Result<Return, Error> {
1232
+ let commerce = lock_commerce!(self.commerce);
1233
+ let uuid = parse_uuid!(id, "return");
1234
+
1235
+ let ret = commerce.returns().reject(uuid, reason).map_err(|e| {
1236
+ Error::new(
1237
+ exception::runtime_error(),
1238
+ format!("Failed to reject return: {}", e),
1239
+ )
1240
+ })?;
1241
+
1242
+ Ok(ret.into())
1243
+ }
1244
+ }
1245
+
1246
+ // ============================================================================
1247
+ // Payments API (Stub)
1248
+ // ============================================================================
1249
+
1250
+ #[derive(Clone)]
1251
+ #[magnus::wrap(class = "StateSet::Payments", free_immediately, size)]
1252
+ pub struct Payments {
1253
+ commerce: Arc<Mutex<RustCommerce>>,
1254
+ }
1255
+
1256
+ impl Payments {
1257
+ fn record(&self, order_id: String, amount: f64, method: Option<String>) -> Result<bool, Error> {
1258
+ let commerce = lock_commerce!(self.commerce);
1259
+ let uuid = parse_uuid!(order_id, "order");
1260
+ let decimal_amount = Decimal::from_f64_retain(amount).unwrap_or_default();
1261
+
1262
+ commerce
1263
+ .payments()
1264
+ .record(uuid, decimal_amount, method)
1265
+ .map_err(|e| {
1266
+ Error::new(
1267
+ exception::runtime_error(),
1268
+ format!("Failed to record payment: {}", e),
1269
+ )
1270
+ })?;
1271
+
1272
+ Ok(true)
1273
+ }
1274
+ }
1275
+
1276
+ // ============================================================================
1277
+ // Shipments Types & API
1278
+ // ============================================================================
1279
+
1280
+ #[derive(Clone)]
1281
+ #[magnus::wrap(class = "StateSet::Shipment", free_immediately, size)]
1282
+ pub struct Shipment {
1283
+ id: String,
1284
+ shipment_number: String,
1285
+ order_id: String,
1286
+ status: String,
1287
+ carrier: Option<String>,
1288
+ tracking_number: Option<String>,
1289
+ shipping_method: Option<String>,
1290
+ weight: Option<f64>,
1291
+ estimated_delivery: Option<String>,
1292
+ shipped_at: Option<String>,
1293
+ delivered_at: Option<String>,
1294
+ created_at: String,
1295
+ updated_at: String,
1296
+ }
1297
+
1298
+ impl Shipment {
1299
+ fn id(&self) -> String {
1300
+ self.id.clone()
1301
+ }
1302
+ fn shipment_number(&self) -> String {
1303
+ self.shipment_number.clone()
1304
+ }
1305
+ fn order_id(&self) -> String {
1306
+ self.order_id.clone()
1307
+ }
1308
+ fn status(&self) -> String {
1309
+ self.status.clone()
1310
+ }
1311
+ fn carrier(&self) -> Option<String> {
1312
+ self.carrier.clone()
1313
+ }
1314
+ fn tracking_number(&self) -> Option<String> {
1315
+ self.tracking_number.clone()
1316
+ }
1317
+ fn shipping_method(&self) -> Option<String> {
1318
+ self.shipping_method.clone()
1319
+ }
1320
+ fn weight(&self) -> Option<f64> {
1321
+ self.weight
1322
+ }
1323
+ fn estimated_delivery(&self) -> Option<String> {
1324
+ self.estimated_delivery.clone()
1325
+ }
1326
+ fn shipped_at(&self) -> Option<String> {
1327
+ self.shipped_at.clone()
1328
+ }
1329
+ fn delivered_at(&self) -> Option<String> {
1330
+ self.delivered_at.clone()
1331
+ }
1332
+ fn created_at(&self) -> String {
1333
+ self.created_at.clone()
1334
+ }
1335
+ fn updated_at(&self) -> String {
1336
+ self.updated_at.clone()
1337
+ }
1338
+ fn inspect(&self) -> String {
1339
+ format!(
1340
+ "#<StateSet::Shipment number=\"{}\" status=\"{}\">",
1341
+ self.shipment_number, self.status
1342
+ )
1343
+ }
1344
+ }
1345
+
1346
+ impl From<stateset_core::Shipment> for Shipment {
1347
+ fn from(s: stateset_core::Shipment) -> Self {
1348
+ Self {
1349
+ id: s.id.to_string(),
1350
+ shipment_number: s.shipment_number,
1351
+ order_id: s.order_id.to_string(),
1352
+ status: format!("{}", s.status),
1353
+ carrier: s.carrier,
1354
+ tracking_number: s.tracking_number,
1355
+ shipping_method: s.shipping_method,
1356
+ weight: s.weight.and_then(|w| w.to_f64()),
1357
+ estimated_delivery: s.estimated_delivery.map(|d| d.to_rfc3339()),
1358
+ shipped_at: s.shipped_at.map(|d| d.to_rfc3339()),
1359
+ delivered_at: s.delivered_at.map(|d| d.to_rfc3339()),
1360
+ created_at: s.created_at.to_rfc3339(),
1361
+ updated_at: s.updated_at.to_rfc3339(),
1362
+ }
1363
+ }
1364
+ }
1365
+
1366
+ #[derive(Clone)]
1367
+ #[magnus::wrap(class = "StateSet::Shipments", free_immediately, size)]
1368
+ pub struct Shipments {
1369
+ commerce: Arc<Mutex<RustCommerce>>,
1370
+ }
1371
+
1372
+ impl Shipments {
1373
+ fn create(
1374
+ &self,
1375
+ order_id: String,
1376
+ carrier: Option<String>,
1377
+ shipping_method: Option<String>,
1378
+ ) -> Result<Shipment, Error> {
1379
+ let commerce = lock_commerce!(self.commerce);
1380
+ let uuid = parse_uuid!(order_id, "order");
1381
+ let shipment = commerce
1382
+ .shipments()
1383
+ .create(stateset_core::CreateShipment {
1384
+ order_id: uuid,
1385
+ carrier,
1386
+ shipping_method,
1387
+ ..Default::default()
1388
+ })
1389
+ .map_err(|e| {
1390
+ Error::new(
1391
+ exception::runtime_error(),
1392
+ format!("Failed to create shipment: {}", e),
1393
+ )
1394
+ })?;
1395
+ Ok(shipment.into())
1396
+ }
1397
+
1398
+ fn get(&self, id: String) -> Result<Option<Shipment>, Error> {
1399
+ let commerce = lock_commerce!(self.commerce);
1400
+ let uuid = parse_uuid!(id, "shipment");
1401
+ let shipment = commerce.shipments().get(uuid).map_err(|e| {
1402
+ Error::new(
1403
+ exception::runtime_error(),
1404
+ format!("Failed to get shipment: {}", e),
1405
+ )
1406
+ })?;
1407
+ Ok(shipment.map(|s| s.into()))
1408
+ }
1409
+
1410
+ fn get_by_tracking(&self, tracking_number: String) -> Result<Option<Shipment>, Error> {
1411
+ let commerce = lock_commerce!(self.commerce);
1412
+ let shipment = commerce
1413
+ .shipments()
1414
+ .get_by_tracking(&tracking_number)
1415
+ .map_err(|e| {
1416
+ Error::new(
1417
+ exception::runtime_error(),
1418
+ format!("Failed to get shipment: {}", e),
1419
+ )
1420
+ })?;
1421
+ Ok(shipment.map(|s| s.into()))
1422
+ }
1423
+
1424
+ fn list(&self) -> Result<Vec<Shipment>, Error> {
1425
+ let commerce = lock_commerce!(self.commerce);
1426
+ let shipments = commerce.shipments().list(Default::default()).map_err(|e| {
1427
+ Error::new(
1428
+ exception::runtime_error(),
1429
+ format!("Failed to list shipments: {}", e),
1430
+ )
1431
+ })?;
1432
+ Ok(shipments.into_iter().map(|s| s.into()).collect())
1433
+ }
1434
+
1435
+ fn for_order(&self, order_id: String) -> Result<Vec<Shipment>, Error> {
1436
+ let commerce = lock_commerce!(self.commerce);
1437
+ let uuid = parse_uuid!(order_id, "order");
1438
+ let shipments = commerce.shipments().for_order(uuid).map_err(|e| {
1439
+ Error::new(
1440
+ exception::runtime_error(),
1441
+ format!("Failed to get shipments: {}", e),
1442
+ )
1443
+ })?;
1444
+ Ok(shipments.into_iter().map(|s| s.into()).collect())
1445
+ }
1446
+
1447
+ fn ship(&self, id: String, tracking_number: Option<String>) -> Result<Shipment, Error> {
1448
+ let commerce = lock_commerce!(self.commerce);
1449
+ let uuid = parse_uuid!(id, "shipment");
1450
+ let shipment = commerce
1451
+ .shipments()
1452
+ .ship(uuid, tracking_number)
1453
+ .map_err(|e| {
1454
+ Error::new(exception::runtime_error(), format!("Failed to ship: {}", e))
1455
+ })?;
1456
+ Ok(shipment.into())
1457
+ }
1458
+
1459
+ fn mark_delivered(&self, id: String) -> Result<Shipment, Error> {
1460
+ let commerce = lock_commerce!(self.commerce);
1461
+ let uuid = parse_uuid!(id, "shipment");
1462
+ let shipment = commerce.shipments().mark_delivered(uuid).map_err(|e| {
1463
+ Error::new(
1464
+ exception::runtime_error(),
1465
+ format!("Failed to mark delivered: {}", e),
1466
+ )
1467
+ })?;
1468
+ Ok(shipment.into())
1469
+ }
1470
+
1471
+ fn cancel(&self, id: String) -> Result<Shipment, Error> {
1472
+ let commerce = lock_commerce!(self.commerce);
1473
+ let uuid = parse_uuid!(id, "shipment");
1474
+ let shipment = commerce.shipments().cancel(uuid).map_err(|e| {
1475
+ Error::new(
1476
+ exception::runtime_error(),
1477
+ format!("Failed to cancel: {}", e),
1478
+ )
1479
+ })?;
1480
+ Ok(shipment.into())
1481
+ }
1482
+
1483
+ fn count(&self) -> Result<i64, Error> {
1484
+ let commerce = lock_commerce!(self.commerce);
1485
+ let count = commerce
1486
+ .shipments()
1487
+ .count(Default::default())
1488
+ .map_err(|e| {
1489
+ Error::new(
1490
+ exception::runtime_error(),
1491
+ format!("Failed to count: {}", e),
1492
+ )
1493
+ })?;
1494
+ Ok(count as i64)
1495
+ }
1496
+ }
1497
+
1498
+ // ============================================================================
1499
+ // Warranties Types & API
1500
+ // ============================================================================
1501
+
1502
+ #[derive(Clone)]
1503
+ #[magnus::wrap(class = "StateSet::Warranty", free_immediately, size)]
1504
+ pub struct Warranty {
1505
+ id: String,
1506
+ warranty_number: String,
1507
+ order_id: String,
1508
+ customer_id: String,
1509
+ product_id: Option<String>,
1510
+ serial_number: Option<String>,
1511
+ status: String,
1512
+ warranty_type: String,
1513
+ start_date: String,
1514
+ end_date: String,
1515
+ created_at: String,
1516
+ updated_at: String,
1517
+ }
1518
+
1519
+ impl Warranty {
1520
+ fn id(&self) -> String {
1521
+ self.id.clone()
1522
+ }
1523
+ fn warranty_number(&self) -> String {
1524
+ self.warranty_number.clone()
1525
+ }
1526
+ fn order_id(&self) -> String {
1527
+ self.order_id.clone()
1528
+ }
1529
+ fn customer_id(&self) -> String {
1530
+ self.customer_id.clone()
1531
+ }
1532
+ fn product_id(&self) -> Option<String> {
1533
+ self.product_id.clone()
1534
+ }
1535
+ fn serial_number(&self) -> Option<String> {
1536
+ self.serial_number.clone()
1537
+ }
1538
+ fn status(&self) -> String {
1539
+ self.status.clone()
1540
+ }
1541
+ fn warranty_type(&self) -> String {
1542
+ self.warranty_type.clone()
1543
+ }
1544
+ fn start_date(&self) -> String {
1545
+ self.start_date.clone()
1546
+ }
1547
+ fn end_date(&self) -> String {
1548
+ self.end_date.clone()
1549
+ }
1550
+ fn created_at(&self) -> String {
1551
+ self.created_at.clone()
1552
+ }
1553
+ fn updated_at(&self) -> String {
1554
+ self.updated_at.clone()
1555
+ }
1556
+ fn inspect(&self) -> String {
1557
+ format!(
1558
+ "#<StateSet::Warranty number=\"{}\" status=\"{}\">",
1559
+ self.warranty_number, self.status
1560
+ )
1561
+ }
1562
+ }
1563
+
1564
+ impl From<stateset_core::Warranty> for Warranty {
1565
+ fn from(w: stateset_core::Warranty) -> Self {
1566
+ Self {
1567
+ id: w.id.to_string(),
1568
+ warranty_number: w.warranty_number,
1569
+ order_id: w.order_id.to_string(),
1570
+ customer_id: w.customer_id.to_string(),
1571
+ product_id: w.product_id.map(|p| p.to_string()),
1572
+ serial_number: w.serial_number,
1573
+ status: format!("{}", w.status),
1574
+ warranty_type: format!("{}", w.warranty_type),
1575
+ start_date: w.start_date.to_string(),
1576
+ end_date: w.end_date.to_string(),
1577
+ created_at: w.created_at.to_rfc3339(),
1578
+ updated_at: w.updated_at.to_rfc3339(),
1579
+ }
1580
+ }
1581
+ }
1582
+
1583
+ #[derive(Clone)]
1584
+ #[magnus::wrap(class = "StateSet::WarrantyClaim", free_immediately, size)]
1585
+ pub struct WarrantyClaim {
1586
+ id: String,
1587
+ claim_number: String,
1588
+ warranty_id: String,
1589
+ status: String,
1590
+ description: String,
1591
+ resolution: Option<String>,
1592
+ created_at: String,
1593
+ updated_at: String,
1594
+ }
1595
+
1596
+ impl WarrantyClaim {
1597
+ fn id(&self) -> String {
1598
+ self.id.clone()
1599
+ }
1600
+ fn claim_number(&self) -> String {
1601
+ self.claim_number.clone()
1602
+ }
1603
+ fn warranty_id(&self) -> String {
1604
+ self.warranty_id.clone()
1605
+ }
1606
+ fn status(&self) -> String {
1607
+ self.status.clone()
1608
+ }
1609
+ fn description(&self) -> String {
1610
+ self.description.clone()
1611
+ }
1612
+ fn resolution(&self) -> Option<String> {
1613
+ self.resolution.clone()
1614
+ }
1615
+ fn created_at(&self) -> String {
1616
+ self.created_at.clone()
1617
+ }
1618
+ fn updated_at(&self) -> String {
1619
+ self.updated_at.clone()
1620
+ }
1621
+ }
1622
+
1623
+ impl From<stateset_core::WarrantyClaim> for WarrantyClaim {
1624
+ fn from(c: stateset_core::WarrantyClaim) -> Self {
1625
+ Self {
1626
+ id: c.id.to_string(),
1627
+ claim_number: c.claim_number,
1628
+ warranty_id: c.warranty_id.to_string(),
1629
+ status: format!("{}", c.status),
1630
+ description: c.description,
1631
+ resolution: c.resolution,
1632
+ created_at: c.created_at.to_rfc3339(),
1633
+ updated_at: c.updated_at.to_rfc3339(),
1634
+ }
1635
+ }
1636
+ }
1637
+
1638
+ #[derive(Clone)]
1639
+ #[magnus::wrap(class = "StateSet::Warranties", free_immediately, size)]
1640
+ pub struct Warranties {
1641
+ commerce: Arc<Mutex<RustCommerce>>,
1642
+ }
1643
+
1644
+ impl Warranties {
1645
+ fn create(
1646
+ &self,
1647
+ order_id: String,
1648
+ customer_id: String,
1649
+ warranty_type: String,
1650
+ duration_months: i32,
1651
+ ) -> Result<Warranty, Error> {
1652
+ let commerce = lock_commerce!(self.commerce);
1653
+ let order_uuid = parse_uuid!(order_id, "order");
1654
+ let customer_uuid = parse_uuid!(customer_id, "customer");
1655
+ let warranty = commerce
1656
+ .warranties()
1657
+ .create(stateset_core::CreateWarranty {
1658
+ order_id: order_uuid,
1659
+ customer_id: customer_uuid,
1660
+ warranty_type: warranty_type.parse().unwrap_or_default(),
1661
+ duration_months,
1662
+ ..Default::default()
1663
+ })
1664
+ .map_err(|e| {
1665
+ Error::new(
1666
+ exception::runtime_error(),
1667
+ format!("Failed to create warranty: {}", e),
1668
+ )
1669
+ })?;
1670
+ Ok(warranty.into())
1671
+ }
1672
+
1673
+ fn get(&self, id: String) -> Result<Option<Warranty>, Error> {
1674
+ let commerce = lock_commerce!(self.commerce);
1675
+ let uuid = parse_uuid!(id, "warranty");
1676
+ let warranty = commerce.warranties().get(uuid).map_err(|e| {
1677
+ Error::new(
1678
+ exception::runtime_error(),
1679
+ format!("Failed to get warranty: {}", e),
1680
+ )
1681
+ })?;
1682
+ Ok(warranty.map(|w| w.into()))
1683
+ }
1684
+
1685
+ fn list(&self) -> Result<Vec<Warranty>, Error> {
1686
+ let commerce = lock_commerce!(self.commerce);
1687
+ let warranties = commerce
1688
+ .warranties()
1689
+ .list(Default::default())
1690
+ .map_err(|e| {
1691
+ Error::new(
1692
+ exception::runtime_error(),
1693
+ format!("Failed to list warranties: {}", e),
1694
+ )
1695
+ })?;
1696
+ Ok(warranties.into_iter().map(|w| w.into()).collect())
1697
+ }
1698
+
1699
+ fn for_customer(&self, customer_id: String) -> Result<Vec<Warranty>, Error> {
1700
+ let commerce = lock_commerce!(self.commerce);
1701
+ let uuid = parse_uuid!(customer_id, "customer");
1702
+ let warranties = commerce.warranties().for_customer(uuid).map_err(|e| {
1703
+ Error::new(
1704
+ exception::runtime_error(),
1705
+ format!("Failed to get warranties: {}", e),
1706
+ )
1707
+ })?;
1708
+ Ok(warranties.into_iter().map(|w| w.into()).collect())
1709
+ }
1710
+
1711
+ fn is_valid(&self, id: String) -> Result<bool, Error> {
1712
+ let commerce = lock_commerce!(self.commerce);
1713
+ let uuid = parse_uuid!(id, "warranty");
1714
+ let valid = commerce.warranties().is_valid(uuid).map_err(|e| {
1715
+ Error::new(
1716
+ exception::runtime_error(),
1717
+ format!("Failed to check warranty: {}", e),
1718
+ )
1719
+ })?;
1720
+ Ok(valid)
1721
+ }
1722
+
1723
+ fn create_claim(
1724
+ &self,
1725
+ warranty_id: String,
1726
+ description: String,
1727
+ ) -> Result<WarrantyClaim, Error> {
1728
+ let commerce = lock_commerce!(self.commerce);
1729
+ let uuid = parse_uuid!(warranty_id, "warranty");
1730
+ let claim = commerce
1731
+ .warranties()
1732
+ .create_claim(stateset_core::CreateWarrantyClaim {
1733
+ warranty_id: uuid,
1734
+ description,
1735
+ ..Default::default()
1736
+ })
1737
+ .map_err(|e| {
1738
+ Error::new(
1739
+ exception::runtime_error(),
1740
+ format!("Failed to create claim: {}", e),
1741
+ )
1742
+ })?;
1743
+ Ok(claim.into())
1744
+ }
1745
+
1746
+ fn approve_claim(&self, id: String) -> Result<WarrantyClaim, Error> {
1747
+ let commerce = lock_commerce!(self.commerce);
1748
+ let uuid = parse_uuid!(id, "claim");
1749
+ let claim = commerce.warranties().approve_claim(uuid).map_err(|e| {
1750
+ Error::new(
1751
+ exception::runtime_error(),
1752
+ format!("Failed to approve claim: {}", e),
1753
+ )
1754
+ })?;
1755
+ Ok(claim.into())
1756
+ }
1757
+
1758
+ fn deny_claim(&self, id: String, reason: String) -> Result<WarrantyClaim, Error> {
1759
+ let commerce = lock_commerce!(self.commerce);
1760
+ let uuid = parse_uuid!(id, "claim");
1761
+ let claim = commerce
1762
+ .warranties()
1763
+ .deny_claim(uuid, &reason)
1764
+ .map_err(|e| {
1765
+ Error::new(
1766
+ exception::runtime_error(),
1767
+ format!("Failed to deny claim: {}", e),
1768
+ )
1769
+ })?;
1770
+ Ok(claim.into())
1771
+ }
1772
+
1773
+ fn count(&self) -> Result<i64, Error> {
1774
+ let commerce = lock_commerce!(self.commerce);
1775
+ let count = commerce
1776
+ .warranties()
1777
+ .count(Default::default())
1778
+ .map_err(|e| {
1779
+ Error::new(
1780
+ exception::runtime_error(),
1781
+ format!("Failed to count: {}", e),
1782
+ )
1783
+ })?;
1784
+ Ok(count as i64)
1785
+ }
1786
+ }
1787
+
1788
+ // ============================================================================
1789
+ // Purchase Orders Types & API
1790
+ // ============================================================================
1791
+
1792
+ #[derive(Clone)]
1793
+ #[magnus::wrap(class = "StateSet::Supplier", free_immediately, size)]
1794
+ pub struct Supplier {
1795
+ id: String,
1796
+ code: String,
1797
+ name: String,
1798
+ email: Option<String>,
1799
+ phone: Option<String>,
1800
+ status: String,
1801
+ created_at: String,
1802
+ updated_at: String,
1803
+ }
1804
+
1805
+ impl Supplier {
1806
+ fn id(&self) -> String {
1807
+ self.id.clone()
1808
+ }
1809
+ fn code(&self) -> String {
1810
+ self.code.clone()
1811
+ }
1812
+ fn name(&self) -> String {
1813
+ self.name.clone()
1814
+ }
1815
+ fn email(&self) -> Option<String> {
1816
+ self.email.clone()
1817
+ }
1818
+ fn phone(&self) -> Option<String> {
1819
+ self.phone.clone()
1820
+ }
1821
+ fn status(&self) -> String {
1822
+ self.status.clone()
1823
+ }
1824
+ fn created_at(&self) -> String {
1825
+ self.created_at.clone()
1826
+ }
1827
+ fn updated_at(&self) -> String {
1828
+ self.updated_at.clone()
1829
+ }
1830
+ }
1831
+
1832
+ impl From<stateset_core::Supplier> for Supplier {
1833
+ fn from(s: stateset_core::Supplier) -> Self {
1834
+ Self {
1835
+ id: s.id.to_string(),
1836
+ code: s.code,
1837
+ name: s.name,
1838
+ email: s.email,
1839
+ phone: s.phone,
1840
+ status: format!("{}", s.status),
1841
+ created_at: s.created_at.to_rfc3339(),
1842
+ updated_at: s.updated_at.to_rfc3339(),
1843
+ }
1844
+ }
1845
+ }
1846
+
1847
+ #[derive(Clone)]
1848
+ #[magnus::wrap(class = "StateSet::PurchaseOrder", free_immediately, size)]
1849
+ pub struct PurchaseOrder {
1850
+ id: String,
1851
+ po_number: String,
1852
+ supplier_id: String,
1853
+ status: String,
1854
+ total_amount: f64,
1855
+ currency: String,
1856
+ expected_delivery: Option<String>,
1857
+ created_at: String,
1858
+ updated_at: String,
1859
+ }
1860
+
1861
+ impl PurchaseOrder {
1862
+ fn id(&self) -> String {
1863
+ self.id.clone()
1864
+ }
1865
+ fn po_number(&self) -> String {
1866
+ self.po_number.clone()
1867
+ }
1868
+ fn supplier_id(&self) -> String {
1869
+ self.supplier_id.clone()
1870
+ }
1871
+ fn status(&self) -> String {
1872
+ self.status.clone()
1873
+ }
1874
+ fn total_amount(&self) -> f64 {
1875
+ self.total_amount
1876
+ }
1877
+ fn currency(&self) -> String {
1878
+ self.currency.clone()
1879
+ }
1880
+ fn expected_delivery(&self) -> Option<String> {
1881
+ self.expected_delivery.clone()
1882
+ }
1883
+ fn created_at(&self) -> String {
1884
+ self.created_at.clone()
1885
+ }
1886
+ fn updated_at(&self) -> String {
1887
+ self.updated_at.clone()
1888
+ }
1889
+ fn inspect(&self) -> String {
1890
+ format!(
1891
+ "#<StateSet::PurchaseOrder number=\"{}\" status=\"{}\">",
1892
+ self.po_number, self.status
1893
+ )
1894
+ }
1895
+ }
1896
+
1897
+ impl From<stateset_core::PurchaseOrder> for PurchaseOrder {
1898
+ fn from(p: stateset_core::PurchaseOrder) -> Self {
1899
+ Self {
1900
+ id: p.id.to_string(),
1901
+ po_number: p.po_number,
1902
+ supplier_id: p.supplier_id.to_string(),
1903
+ status: format!("{}", p.status),
1904
+ total_amount: p.total_amount.to_f64().unwrap_or(0.0),
1905
+ currency: p.currency,
1906
+ expected_delivery: p.expected_delivery.map(|d| d.to_string()),
1907
+ created_at: p.created_at.to_rfc3339(),
1908
+ updated_at: p.updated_at.to_rfc3339(),
1909
+ }
1910
+ }
1911
+ }
1912
+
1913
+ #[derive(Clone)]
1914
+ #[magnus::wrap(class = "StateSet::PurchaseOrders", free_immediately, size)]
1915
+ pub struct PurchaseOrders {
1916
+ commerce: Arc<Mutex<RustCommerce>>,
1917
+ }
1918
+
1919
+ impl PurchaseOrders {
1920
+ fn create_supplier(
1921
+ &self,
1922
+ code: String,
1923
+ name: String,
1924
+ email: Option<String>,
1925
+ ) -> Result<Supplier, Error> {
1926
+ let commerce = lock_commerce!(self.commerce);
1927
+ let supplier = commerce
1928
+ .purchase_orders()
1929
+ .create_supplier(stateset_core::CreateSupplier {
1930
+ code,
1931
+ name,
1932
+ email,
1933
+ ..Default::default()
1934
+ })
1935
+ .map_err(|e| {
1936
+ Error::new(
1937
+ exception::runtime_error(),
1938
+ format!("Failed to create supplier: {}", e),
1939
+ )
1940
+ })?;
1941
+ Ok(supplier.into())
1942
+ }
1943
+
1944
+ fn get_supplier(&self, id: String) -> Result<Option<Supplier>, Error> {
1945
+ let commerce = lock_commerce!(self.commerce);
1946
+ let uuid = parse_uuid!(id, "supplier");
1947
+ let supplier = commerce.purchase_orders().get_supplier(uuid).map_err(|e| {
1948
+ Error::new(
1949
+ exception::runtime_error(),
1950
+ format!("Failed to get supplier: {}", e),
1951
+ )
1952
+ })?;
1953
+ Ok(supplier.map(|s| s.into()))
1954
+ }
1955
+
1956
+ fn list_suppliers(&self) -> Result<Vec<Supplier>, Error> {
1957
+ let commerce = lock_commerce!(self.commerce);
1958
+ let suppliers = commerce
1959
+ .purchase_orders()
1960
+ .list_suppliers(Default::default())
1961
+ .map_err(|e| {
1962
+ Error::new(
1963
+ exception::runtime_error(),
1964
+ format!("Failed to list suppliers: {}", e),
1965
+ )
1966
+ })?;
1967
+ Ok(suppliers.into_iter().map(|s| s.into()).collect())
1968
+ }
1969
+
1970
+ fn create(
1971
+ &self,
1972
+ supplier_id: String,
1973
+ currency: Option<String>,
1974
+ ) -> Result<PurchaseOrder, Error> {
1975
+ let commerce = lock_commerce!(self.commerce);
1976
+ let uuid = parse_uuid!(supplier_id, "supplier");
1977
+ let po = commerce
1978
+ .purchase_orders()
1979
+ .create(stateset_core::CreatePurchaseOrder {
1980
+ supplier_id: uuid,
1981
+ currency,
1982
+ ..Default::default()
1983
+ })
1984
+ .map_err(|e| {
1985
+ Error::new(
1986
+ exception::runtime_error(),
1987
+ format!("Failed to create PO: {}", e),
1988
+ )
1989
+ })?;
1990
+ Ok(po.into())
1991
+ }
1992
+
1993
+ fn get(&self, id: String) -> Result<Option<PurchaseOrder>, Error> {
1994
+ let commerce = lock_commerce!(self.commerce);
1995
+ let uuid = parse_uuid!(id, "purchase_order");
1996
+ let po = commerce.purchase_orders().get(uuid).map_err(|e| {
1997
+ Error::new(
1998
+ exception::runtime_error(),
1999
+ format!("Failed to get PO: {}", e),
2000
+ )
2001
+ })?;
2002
+ Ok(po.map(|p| p.into()))
2003
+ }
2004
+
2005
+ fn list(&self) -> Result<Vec<PurchaseOrder>, Error> {
2006
+ let commerce = lock_commerce!(self.commerce);
2007
+ let pos = commerce
2008
+ .purchase_orders()
2009
+ .list(Default::default())
2010
+ .map_err(|e| {
2011
+ Error::new(
2012
+ exception::runtime_error(),
2013
+ format!("Failed to list POs: {}", e),
2014
+ )
2015
+ })?;
2016
+ Ok(pos.into_iter().map(|p| p.into()).collect())
2017
+ }
2018
+
2019
+ fn submit(&self, id: String) -> Result<PurchaseOrder, Error> {
2020
+ let commerce = lock_commerce!(self.commerce);
2021
+ let uuid = parse_uuid!(id, "purchase_order");
2022
+ let po = commerce.purchase_orders().submit(uuid).map_err(|e| {
2023
+ Error::new(
2024
+ exception::runtime_error(),
2025
+ format!("Failed to submit PO: {}", e),
2026
+ )
2027
+ })?;
2028
+ Ok(po.into())
2029
+ }
2030
+
2031
+ fn approve(&self, id: String, approved_by: String) -> Result<PurchaseOrder, Error> {
2032
+ let commerce = lock_commerce!(self.commerce);
2033
+ let uuid = parse_uuid!(id, "purchase_order");
2034
+ let po = commerce
2035
+ .purchase_orders()
2036
+ .approve(uuid, &approved_by)
2037
+ .map_err(|e| {
2038
+ Error::new(
2039
+ exception::runtime_error(),
2040
+ format!("Failed to approve PO: {}", e),
2041
+ )
2042
+ })?;
2043
+ Ok(po.into())
2044
+ }
2045
+
2046
+ fn cancel(&self, id: String) -> Result<PurchaseOrder, Error> {
2047
+ let commerce = lock_commerce!(self.commerce);
2048
+ let uuid = parse_uuid!(id, "purchase_order");
2049
+ let po = commerce.purchase_orders().cancel(uuid).map_err(|e| {
2050
+ Error::new(
2051
+ exception::runtime_error(),
2052
+ format!("Failed to cancel PO: {}", e),
2053
+ )
2054
+ })?;
2055
+ Ok(po.into())
2056
+ }
2057
+
2058
+ fn complete(&self, id: String) -> Result<PurchaseOrder, Error> {
2059
+ let commerce = lock_commerce!(self.commerce);
2060
+ let uuid = parse_uuid!(id, "purchase_order");
2061
+ let po = commerce.purchase_orders().complete(uuid).map_err(|e| {
2062
+ Error::new(
2063
+ exception::runtime_error(),
2064
+ format!("Failed to complete PO: {}", e),
2065
+ )
2066
+ })?;
2067
+ Ok(po.into())
2068
+ }
2069
+
2070
+ fn count(&self) -> Result<i64, Error> {
2071
+ let commerce = lock_commerce!(self.commerce);
2072
+ let count = commerce
2073
+ .purchase_orders()
2074
+ .count(Default::default())
2075
+ .map_err(|e| {
2076
+ Error::new(
2077
+ exception::runtime_error(),
2078
+ format!("Failed to count: {}", e),
2079
+ )
2080
+ })?;
2081
+ Ok(count as i64)
2082
+ }
2083
+ }
2084
+
2085
+ // ============================================================================
2086
+ // Invoices Types & API
2087
+ // ============================================================================
2088
+
2089
+ #[derive(Clone)]
2090
+ #[magnus::wrap(class = "StateSet::Invoice", free_immediately, size)]
2091
+ pub struct Invoice {
2092
+ id: String,
2093
+ invoice_number: String,
2094
+ customer_id: String,
2095
+ order_id: Option<String>,
2096
+ status: String,
2097
+ subtotal: f64,
2098
+ tax_amount: f64,
2099
+ total_amount: f64,
2100
+ amount_paid: f64,
2101
+ amount_due: f64,
2102
+ currency: String,
2103
+ due_date: Option<String>,
2104
+ created_at: String,
2105
+ updated_at: String,
2106
+ }
2107
+
2108
+ impl Invoice {
2109
+ fn id(&self) -> String {
2110
+ self.id.clone()
2111
+ }
2112
+ fn invoice_number(&self) -> String {
2113
+ self.invoice_number.clone()
2114
+ }
2115
+ fn customer_id(&self) -> String {
2116
+ self.customer_id.clone()
2117
+ }
2118
+ fn order_id(&self) -> Option<String> {
2119
+ self.order_id.clone()
2120
+ }
2121
+ fn status(&self) -> String {
2122
+ self.status.clone()
2123
+ }
2124
+ fn subtotal(&self) -> f64 {
2125
+ self.subtotal
2126
+ }
2127
+ fn tax_amount(&self) -> f64 {
2128
+ self.tax_amount
2129
+ }
2130
+ fn total_amount(&self) -> f64 {
2131
+ self.total_amount
2132
+ }
2133
+ fn amount_paid(&self) -> f64 {
2134
+ self.amount_paid
2135
+ }
2136
+ fn amount_due(&self) -> f64 {
2137
+ self.amount_due
2138
+ }
2139
+ fn currency(&self) -> String {
2140
+ self.currency.clone()
2141
+ }
2142
+ fn due_date(&self) -> Option<String> {
2143
+ self.due_date.clone()
2144
+ }
2145
+ fn created_at(&self) -> String {
2146
+ self.created_at.clone()
2147
+ }
2148
+ fn updated_at(&self) -> String {
2149
+ self.updated_at.clone()
2150
+ }
2151
+ fn inspect(&self) -> String {
2152
+ format!(
2153
+ "#<StateSet::Invoice number=\"{}\" due={}>",
2154
+ self.invoice_number, self.amount_due
2155
+ )
2156
+ }
2157
+ }
2158
+
2159
+ impl From<stateset_core::Invoice> for Invoice {
2160
+ fn from(i: stateset_core::Invoice) -> Self {
2161
+ Self {
2162
+ id: i.id.to_string(),
2163
+ invoice_number: i.invoice_number,
2164
+ customer_id: i.customer_id.to_string(),
2165
+ order_id: i.order_id.map(|o| o.to_string()),
2166
+ status: format!("{}", i.status),
2167
+ subtotal: i.subtotal.to_f64().unwrap_or(0.0),
2168
+ tax_amount: i.tax_amount.to_f64().unwrap_or(0.0),
2169
+ total_amount: i.total_amount.to_f64().unwrap_or(0.0),
2170
+ amount_paid: i.amount_paid.to_f64().unwrap_or(0.0),
2171
+ amount_due: i.amount_due.to_f64().unwrap_or(0.0),
2172
+ currency: i.currency,
2173
+ due_date: i.due_date.map(|d| d.to_string()),
2174
+ created_at: i.created_at.to_rfc3339(),
2175
+ updated_at: i.updated_at.to_rfc3339(),
2176
+ }
2177
+ }
2178
+ }
2179
+
2180
+ #[derive(Clone)]
2181
+ #[magnus::wrap(class = "StateSet::Invoices", free_immediately, size)]
2182
+ pub struct Invoices {
2183
+ commerce: Arc<Mutex<RustCommerce>>,
2184
+ }
2185
+
2186
+ impl Invoices {
2187
+ fn create(
2188
+ &self,
2189
+ customer_id: String,
2190
+ order_id: Option<String>,
2191
+ due_days: Option<i32>,
2192
+ ) -> Result<Invoice, Error> {
2193
+ let commerce = lock_commerce!(self.commerce);
2194
+ let cust_uuid = parse_uuid!(customer_id, "customer");
2195
+ let order_uuid = order_id.map(|s| s.parse().ok()).flatten();
2196
+ let invoice = commerce
2197
+ .invoices()
2198
+ .create(stateset_core::CreateInvoice {
2199
+ customer_id: cust_uuid,
2200
+ order_id: order_uuid,
2201
+ due_days,
2202
+ ..Default::default()
2203
+ })
2204
+ .map_err(|e| {
2205
+ Error::new(
2206
+ exception::runtime_error(),
2207
+ format!("Failed to create invoice: {}", e),
2208
+ )
2209
+ })?;
2210
+ Ok(invoice.into())
2211
+ }
2212
+
2213
+ fn get(&self, id: String) -> Result<Option<Invoice>, Error> {
2214
+ let commerce = lock_commerce!(self.commerce);
2215
+ let uuid = parse_uuid!(id, "invoice");
2216
+ let invoice = commerce.invoices().get(uuid).map_err(|e| {
2217
+ Error::new(
2218
+ exception::runtime_error(),
2219
+ format!("Failed to get invoice: {}", e),
2220
+ )
2221
+ })?;
2222
+ Ok(invoice.map(|i| i.into()))
2223
+ }
2224
+
2225
+ fn list(&self) -> Result<Vec<Invoice>, Error> {
2226
+ let commerce = lock_commerce!(self.commerce);
2227
+ let invoices = commerce.invoices().list(Default::default()).map_err(|e| {
2228
+ Error::new(
2229
+ exception::runtime_error(),
2230
+ format!("Failed to list invoices: {}", e),
2231
+ )
2232
+ })?;
2233
+ Ok(invoices.into_iter().map(|i| i.into()).collect())
2234
+ }
2235
+
2236
+ fn for_customer(&self, customer_id: String) -> Result<Vec<Invoice>, Error> {
2237
+ let commerce = lock_commerce!(self.commerce);
2238
+ let uuid = parse_uuid!(customer_id, "customer");
2239
+ let invoices = commerce.invoices().for_customer(uuid).map_err(|e| {
2240
+ Error::new(
2241
+ exception::runtime_error(),
2242
+ format!("Failed to get invoices: {}", e),
2243
+ )
2244
+ })?;
2245
+ Ok(invoices.into_iter().map(|i| i.into()).collect())
2246
+ }
2247
+
2248
+ fn send(&self, id: String) -> Result<Invoice, Error> {
2249
+ let commerce = lock_commerce!(self.commerce);
2250
+ let uuid = parse_uuid!(id, "invoice");
2251
+ let invoice = commerce.invoices().send(uuid).map_err(|e| {
2252
+ Error::new(
2253
+ exception::runtime_error(),
2254
+ format!("Failed to send invoice: {}", e),
2255
+ )
2256
+ })?;
2257
+ Ok(invoice.into())
2258
+ }
2259
+
2260
+ fn record_payment(
2261
+ &self,
2262
+ id: String,
2263
+ amount: f64,
2264
+ method: Option<String>,
2265
+ ) -> Result<Invoice, Error> {
2266
+ let commerce = lock_commerce!(self.commerce);
2267
+ let uuid = parse_uuid!(id, "invoice");
2268
+ let invoice = commerce
2269
+ .invoices()
2270
+ .record_payment(
2271
+ uuid,
2272
+ stateset_core::RecordInvoicePayment {
2273
+ amount: Decimal::from_f64_retain(amount).unwrap_or_default(),
2274
+ payment_method: method,
2275
+ ..Default::default()
2276
+ },
2277
+ )
2278
+ .map_err(|e| {
2279
+ Error::new(
2280
+ exception::runtime_error(),
2281
+ format!("Failed to record payment: {}", e),
2282
+ )
2283
+ })?;
2284
+ Ok(invoice.into())
2285
+ }
2286
+
2287
+ fn void(&self, id: String) -> Result<Invoice, Error> {
2288
+ let commerce = lock_commerce!(self.commerce);
2289
+ let uuid = parse_uuid!(id, "invoice");
2290
+ let invoice = commerce.invoices().void(uuid).map_err(|e| {
2291
+ Error::new(
2292
+ exception::runtime_error(),
2293
+ format!("Failed to void invoice: {}", e),
2294
+ )
2295
+ })?;
2296
+ Ok(invoice.into())
2297
+ }
2298
+
2299
+ fn get_overdue(&self) -> Result<Vec<Invoice>, Error> {
2300
+ let commerce = lock_commerce!(self.commerce);
2301
+ let invoices = commerce.invoices().get_overdue().map_err(|e| {
2302
+ Error::new(
2303
+ exception::runtime_error(),
2304
+ format!("Failed to get overdue: {}", e),
2305
+ )
2306
+ })?;
2307
+ Ok(invoices.into_iter().map(|i| i.into()).collect())
2308
+ }
2309
+
2310
+ fn customer_balance(&self, customer_id: String) -> Result<f64, Error> {
2311
+ let commerce = lock_commerce!(self.commerce);
2312
+ let uuid = parse_uuid!(customer_id, "customer");
2313
+ let balance = commerce.invoices().customer_balance(uuid).map_err(|e| {
2314
+ Error::new(
2315
+ exception::runtime_error(),
2316
+ format!("Failed to get balance: {}", e),
2317
+ )
2318
+ })?;
2319
+ Ok(balance.to_f64().unwrap_or(0.0))
2320
+ }
2321
+
2322
+ fn count(&self) -> Result<i64, Error> {
2323
+ let commerce = lock_commerce!(self.commerce);
2324
+ let count = commerce.invoices().count(Default::default()).map_err(|e| {
2325
+ Error::new(
2326
+ exception::runtime_error(),
2327
+ format!("Failed to count: {}", e),
2328
+ )
2329
+ })?;
2330
+ Ok(count as i64)
2331
+ }
2332
+ }
2333
+
2334
+ // ============================================================================
2335
+ // BOM Types & API
2336
+ // ============================================================================
2337
+
2338
+ #[derive(Clone)]
2339
+ #[magnus::wrap(class = "StateSet::BillOfMaterials", free_immediately, size)]
2340
+ pub struct BillOfMaterials {
2341
+ id: String,
2342
+ bom_number: String,
2343
+ name: String,
2344
+ product_id: Option<String>,
2345
+ status: String,
2346
+ version: i32,
2347
+ created_at: String,
2348
+ updated_at: String,
2349
+ }
2350
+
2351
+ impl BillOfMaterials {
2352
+ fn id(&self) -> String {
2353
+ self.id.clone()
2354
+ }
2355
+ fn bom_number(&self) -> String {
2356
+ self.bom_number.clone()
2357
+ }
2358
+ fn name(&self) -> String {
2359
+ self.name.clone()
2360
+ }
2361
+ fn product_id(&self) -> Option<String> {
2362
+ self.product_id.clone()
2363
+ }
2364
+ fn status(&self) -> String {
2365
+ self.status.clone()
2366
+ }
2367
+ fn version(&self) -> i32 {
2368
+ self.version
2369
+ }
2370
+ fn created_at(&self) -> String {
2371
+ self.created_at.clone()
2372
+ }
2373
+ fn updated_at(&self) -> String {
2374
+ self.updated_at.clone()
2375
+ }
2376
+ fn inspect(&self) -> String {
2377
+ format!(
2378
+ "#<StateSet::BillOfMaterials number=\"{}\" name=\"{}\">",
2379
+ self.bom_number, self.name
2380
+ )
2381
+ }
2382
+ }
2383
+
2384
+ impl From<stateset_core::BillOfMaterials> for BillOfMaterials {
2385
+ fn from(b: stateset_core::BillOfMaterials) -> Self {
2386
+ Self {
2387
+ id: b.id.to_string(),
2388
+ bom_number: b.bom_number,
2389
+ name: b.name,
2390
+ product_id: b.product_id.map(|p| p.to_string()),
2391
+ status: format!("{}", b.status),
2392
+ version: b.version,
2393
+ created_at: b.created_at.to_rfc3339(),
2394
+ updated_at: b.updated_at.to_rfc3339(),
2395
+ }
2396
+ }
2397
+ }
2398
+
2399
+ #[derive(Clone)]
2400
+ #[magnus::wrap(class = "StateSet::BomComponent", free_immediately, size)]
2401
+ pub struct BomComponent {
2402
+ id: String,
2403
+ bom_id: String,
2404
+ sku: String,
2405
+ name: String,
2406
+ quantity: f64,
2407
+ unit: String,
2408
+ }
2409
+
2410
+ impl BomComponent {
2411
+ fn id(&self) -> String {
2412
+ self.id.clone()
2413
+ }
2414
+ fn bom_id(&self) -> String {
2415
+ self.bom_id.clone()
2416
+ }
2417
+ fn sku(&self) -> String {
2418
+ self.sku.clone()
2419
+ }
2420
+ fn name(&self) -> String {
2421
+ self.name.clone()
2422
+ }
2423
+ fn quantity(&self) -> f64 {
2424
+ self.quantity
2425
+ }
2426
+ fn unit(&self) -> String {
2427
+ self.unit.clone()
2428
+ }
2429
+ }
2430
+
2431
+ impl From<stateset_core::BomComponent> for BomComponent {
2432
+ fn from(c: stateset_core::BomComponent) -> Self {
2433
+ Self {
2434
+ id: c.id.to_string(),
2435
+ bom_id: c.bom_id.to_string(),
2436
+ sku: c.sku,
2437
+ name: c.name,
2438
+ quantity: c.quantity.to_f64().unwrap_or(0.0),
2439
+ unit: c.unit,
2440
+ }
2441
+ }
2442
+ }
2443
+
2444
+ #[derive(Clone)]
2445
+ #[magnus::wrap(class = "StateSet::BomApi", free_immediately, size)]
2446
+ pub struct BomApi {
2447
+ commerce: Arc<Mutex<RustCommerce>>,
2448
+ }
2449
+
2450
+ impl BomApi {
2451
+ fn create(&self, name: String, product_id: Option<String>) -> Result<BillOfMaterials, Error> {
2452
+ let commerce = lock_commerce!(self.commerce);
2453
+ let prod_uuid = product_id.map(|s| s.parse().ok()).flatten();
2454
+ let bom = commerce
2455
+ .bom()
2456
+ .create(stateset_core::CreateBom {
2457
+ name,
2458
+ product_id: prod_uuid,
2459
+ ..Default::default()
2460
+ })
2461
+ .map_err(|e| {
2462
+ Error::new(
2463
+ exception::runtime_error(),
2464
+ format!("Failed to create BOM: {}", e),
2465
+ )
2466
+ })?;
2467
+ Ok(bom.into())
2468
+ }
2469
+
2470
+ fn get(&self, id: String) -> Result<Option<BillOfMaterials>, Error> {
2471
+ let commerce = lock_commerce!(self.commerce);
2472
+ let uuid = parse_uuid!(id, "bom");
2473
+ let bom = commerce.bom().get(uuid).map_err(|e| {
2474
+ Error::new(
2475
+ exception::runtime_error(),
2476
+ format!("Failed to get BOM: {}", e),
2477
+ )
2478
+ })?;
2479
+ Ok(bom.map(|b| b.into()))
2480
+ }
2481
+
2482
+ fn list(&self) -> Result<Vec<BillOfMaterials>, Error> {
2483
+ let commerce = lock_commerce!(self.commerce);
2484
+ let boms = commerce.bom().list(Default::default()).map_err(|e| {
2485
+ Error::new(
2486
+ exception::runtime_error(),
2487
+ format!("Failed to list BOMs: {}", e),
2488
+ )
2489
+ })?;
2490
+ Ok(boms.into_iter().map(|b| b.into()).collect())
2491
+ }
2492
+
2493
+ fn add_component(
2494
+ &self,
2495
+ bom_id: String,
2496
+ sku: String,
2497
+ name: String,
2498
+ quantity: f64,
2499
+ unit: String,
2500
+ ) -> Result<BomComponent, Error> {
2501
+ let commerce = lock_commerce!(self.commerce);
2502
+ let uuid = parse_uuid!(bom_id, "bom");
2503
+ let component = commerce
2504
+ .bom()
2505
+ .add_component(
2506
+ uuid,
2507
+ stateset_core::CreateBomComponent {
2508
+ sku,
2509
+ name,
2510
+ quantity: Decimal::from_f64_retain(quantity).unwrap_or_default(),
2511
+ unit,
2512
+ ..Default::default()
2513
+ },
2514
+ )
2515
+ .map_err(|e| {
2516
+ Error::new(
2517
+ exception::runtime_error(),
2518
+ format!("Failed to add component: {}", e),
2519
+ )
2520
+ })?;
2521
+ Ok(component.into())
2522
+ }
2523
+
2524
+ fn get_components(&self, bom_id: String) -> Result<Vec<BomComponent>, Error> {
2525
+ let commerce = lock_commerce!(self.commerce);
2526
+ let uuid = parse_uuid!(bom_id, "bom");
2527
+ let components = commerce.bom().get_components(uuid).map_err(|e| {
2528
+ Error::new(
2529
+ exception::runtime_error(),
2530
+ format!("Failed to get components: {}", e),
2531
+ )
2532
+ })?;
2533
+ Ok(components.into_iter().map(|c| c.into()).collect())
2534
+ }
2535
+
2536
+ fn remove_component(&self, component_id: String) -> Result<bool, Error> {
2537
+ let commerce = lock_commerce!(self.commerce);
2538
+ let uuid = parse_uuid!(component_id, "component");
2539
+ commerce.bom().remove_component(uuid).map_err(|e| {
2540
+ Error::new(
2541
+ exception::runtime_error(),
2542
+ format!("Failed to remove component: {}", e),
2543
+ )
2544
+ })?;
2545
+ Ok(true)
2546
+ }
2547
+
2548
+ fn activate(&self, id: String) -> Result<BillOfMaterials, Error> {
2549
+ let commerce = lock_commerce!(self.commerce);
2550
+ let uuid = parse_uuid!(id, "bom");
2551
+ let bom = commerce.bom().activate(uuid).map_err(|e| {
2552
+ Error::new(
2553
+ exception::runtime_error(),
2554
+ format!("Failed to activate BOM: {}", e),
2555
+ )
2556
+ })?;
2557
+ Ok(bom.into())
2558
+ }
2559
+
2560
+ fn delete(&self, id: String) -> Result<bool, Error> {
2561
+ let commerce = lock_commerce!(self.commerce);
2562
+ let uuid = parse_uuid!(id, "bom");
2563
+ commerce.bom().delete(uuid).map_err(|e| {
2564
+ Error::new(
2565
+ exception::runtime_error(),
2566
+ format!("Failed to delete BOM: {}", e),
2567
+ )
2568
+ })?;
2569
+ Ok(true)
2570
+ }
2571
+
2572
+ fn count(&self) -> Result<i64, Error> {
2573
+ let commerce = lock_commerce!(self.commerce);
2574
+ let count = commerce.bom().count(Default::default()).map_err(|e| {
2575
+ Error::new(
2576
+ exception::runtime_error(),
2577
+ format!("Failed to count: {}", e),
2578
+ )
2579
+ })?;
2580
+ Ok(count as i64)
2581
+ }
2582
+ }
2583
+
2584
+ // ============================================================================
2585
+ // Work Orders Types & API
2586
+ // ============================================================================
2587
+
2588
+ #[derive(Clone)]
2589
+ #[magnus::wrap(class = "StateSet::WorkOrder", free_immediately, size)]
2590
+ pub struct WorkOrder {
2591
+ id: String,
2592
+ work_order_number: String,
2593
+ bom_id: Option<String>,
2594
+ product_id: Option<String>,
2595
+ status: String,
2596
+ quantity_ordered: f64,
2597
+ quantity_completed: f64,
2598
+ priority: String,
2599
+ started_at: Option<String>,
2600
+ completed_at: Option<String>,
2601
+ created_at: String,
2602
+ updated_at: String,
2603
+ }
2604
+
2605
+ impl WorkOrder {
2606
+ fn id(&self) -> String {
2607
+ self.id.clone()
2608
+ }
2609
+ fn work_order_number(&self) -> String {
2610
+ self.work_order_number.clone()
2611
+ }
2612
+ fn bom_id(&self) -> Option<String> {
2613
+ self.bom_id.clone()
2614
+ }
2615
+ fn product_id(&self) -> Option<String> {
2616
+ self.product_id.clone()
2617
+ }
2618
+ fn status(&self) -> String {
2619
+ self.status.clone()
2620
+ }
2621
+ fn quantity_ordered(&self) -> f64 {
2622
+ self.quantity_ordered
2623
+ }
2624
+ fn quantity_completed(&self) -> f64 {
2625
+ self.quantity_completed
2626
+ }
2627
+ fn priority(&self) -> String {
2628
+ self.priority.clone()
2629
+ }
2630
+ fn started_at(&self) -> Option<String> {
2631
+ self.started_at.clone()
2632
+ }
2633
+ fn completed_at(&self) -> Option<String> {
2634
+ self.completed_at.clone()
2635
+ }
2636
+ fn created_at(&self) -> String {
2637
+ self.created_at.clone()
2638
+ }
2639
+ fn updated_at(&self) -> String {
2640
+ self.updated_at.clone()
2641
+ }
2642
+ fn inspect(&self) -> String {
2643
+ format!(
2644
+ "#<StateSet::WorkOrder number=\"{}\" status=\"{}\">",
2645
+ self.work_order_number, self.status
2646
+ )
2647
+ }
2648
+ }
2649
+
2650
+ impl From<stateset_core::WorkOrder> for WorkOrder {
2651
+ fn from(w: stateset_core::WorkOrder) -> Self {
2652
+ Self {
2653
+ id: w.id.to_string(),
2654
+ work_order_number: w.work_order_number,
2655
+ bom_id: w.bom_id.map(|b| b.to_string()),
2656
+ product_id: w.product_id.map(|p| p.to_string()),
2657
+ status: format!("{}", w.status),
2658
+ quantity_ordered: w.quantity_ordered.to_f64().unwrap_or(0.0),
2659
+ quantity_completed: w.quantity_completed.to_f64().unwrap_or(0.0),
2660
+ priority: format!("{}", w.priority),
2661
+ started_at: w.started_at.map(|d| d.to_rfc3339()),
2662
+ completed_at: w.completed_at.map(|d| d.to_rfc3339()),
2663
+ created_at: w.created_at.to_rfc3339(),
2664
+ updated_at: w.updated_at.to_rfc3339(),
2665
+ }
2666
+ }
2667
+ }
2668
+
2669
+ #[derive(Clone)]
2670
+ #[magnus::wrap(class = "StateSet::WorkOrders", free_immediately, size)]
2671
+ pub struct WorkOrders {
2672
+ commerce: Arc<Mutex<RustCommerce>>,
2673
+ }
2674
+
2675
+ impl WorkOrders {
2676
+ fn create(
2677
+ &self,
2678
+ bom_id: Option<String>,
2679
+ product_id: Option<String>,
2680
+ quantity: f64,
2681
+ ) -> Result<WorkOrder, Error> {
2682
+ let commerce = lock_commerce!(self.commerce);
2683
+ let bom_uuid = bom_id.map(|s| s.parse().ok()).flatten();
2684
+ let prod_uuid = product_id.map(|s| s.parse().ok()).flatten();
2685
+ let wo = commerce
2686
+ .work_orders()
2687
+ .create(stateset_core::CreateWorkOrder {
2688
+ bom_id: bom_uuid,
2689
+ product_id: prod_uuid,
2690
+ quantity_ordered: Decimal::from_f64_retain(quantity).unwrap_or_default(),
2691
+ ..Default::default()
2692
+ })
2693
+ .map_err(|e| {
2694
+ Error::new(
2695
+ exception::runtime_error(),
2696
+ format!("Failed to create work order: {}", e),
2697
+ )
2698
+ })?;
2699
+ Ok(wo.into())
2700
+ }
2701
+
2702
+ fn get(&self, id: String) -> Result<Option<WorkOrder>, Error> {
2703
+ let commerce = lock_commerce!(self.commerce);
2704
+ let uuid = parse_uuid!(id, "work_order");
2705
+ let wo = commerce.work_orders().get(uuid).map_err(|e| {
2706
+ Error::new(
2707
+ exception::runtime_error(),
2708
+ format!("Failed to get work order: {}", e),
2709
+ )
2710
+ })?;
2711
+ Ok(wo.map(|w| w.into()))
2712
+ }
2713
+
2714
+ fn list(&self) -> Result<Vec<WorkOrder>, Error> {
2715
+ let commerce = lock_commerce!(self.commerce);
2716
+ let wos = commerce
2717
+ .work_orders()
2718
+ .list(Default::default())
2719
+ .map_err(|e| {
2720
+ Error::new(
2721
+ exception::runtime_error(),
2722
+ format!("Failed to list work orders: {}", e),
2723
+ )
2724
+ })?;
2725
+ Ok(wos.into_iter().map(|w| w.into()).collect())
2726
+ }
2727
+
2728
+ fn start(&self, id: String) -> Result<WorkOrder, Error> {
2729
+ let commerce = lock_commerce!(self.commerce);
2730
+ let uuid = parse_uuid!(id, "work_order");
2731
+ let wo = commerce.work_orders().start(uuid).map_err(|e| {
2732
+ Error::new(
2733
+ exception::runtime_error(),
2734
+ format!("Failed to start: {}", e),
2735
+ )
2736
+ })?;
2737
+ Ok(wo.into())
2738
+ }
2739
+
2740
+ fn complete(&self, id: String, quantity_completed: f64) -> Result<WorkOrder, Error> {
2741
+ let commerce = lock_commerce!(self.commerce);
2742
+ let uuid = parse_uuid!(id, "work_order");
2743
+ let wo = commerce
2744
+ .work_orders()
2745
+ .complete(
2746
+ uuid,
2747
+ Decimal::from_f64_retain(quantity_completed).unwrap_or_default(),
2748
+ )
2749
+ .map_err(|e| {
2750
+ Error::new(
2751
+ exception::runtime_error(),
2752
+ format!("Failed to complete: {}", e),
2753
+ )
2754
+ })?;
2755
+ Ok(wo.into())
2756
+ }
2757
+
2758
+ fn hold(&self, id: String) -> Result<WorkOrder, Error> {
2759
+ let commerce = lock_commerce!(self.commerce);
2760
+ let uuid = parse_uuid!(id, "work_order");
2761
+ let wo = commerce.work_orders().hold(uuid).map_err(|e| {
2762
+ Error::new(exception::runtime_error(), format!("Failed to hold: {}", e))
2763
+ })?;
2764
+ Ok(wo.into())
2765
+ }
2766
+
2767
+ fn resume(&self, id: String) -> Result<WorkOrder, Error> {
2768
+ let commerce = lock_commerce!(self.commerce);
2769
+ let uuid = parse_uuid!(id, "work_order");
2770
+ let wo = commerce.work_orders().resume(uuid).map_err(|e| {
2771
+ Error::new(
2772
+ exception::runtime_error(),
2773
+ format!("Failed to resume: {}", e),
2774
+ )
2775
+ })?;
2776
+ Ok(wo.into())
2777
+ }
2778
+
2779
+ fn cancel(&self, id: String) -> Result<WorkOrder, Error> {
2780
+ let commerce = lock_commerce!(self.commerce);
2781
+ let uuid = parse_uuid!(id, "work_order");
2782
+ let wo = commerce.work_orders().cancel(uuid).map_err(|e| {
2783
+ Error::new(
2784
+ exception::runtime_error(),
2785
+ format!("Failed to cancel: {}", e),
2786
+ )
2787
+ })?;
2788
+ Ok(wo.into())
2789
+ }
2790
+
2791
+ fn count(&self) -> Result<i64, Error> {
2792
+ let commerce = lock_commerce!(self.commerce);
2793
+ let count = commerce
2794
+ .work_orders()
2795
+ .count(Default::default())
2796
+ .map_err(|e| {
2797
+ Error::new(
2798
+ exception::runtime_error(),
2799
+ format!("Failed to count: {}", e),
2800
+ )
2801
+ })?;
2802
+ Ok(count as i64)
2803
+ }
2804
+ }
2805
+
2806
+ // ============================================================================
2807
+ // Carts Types & API
2808
+ // ============================================================================
2809
+
2810
+ #[derive(Clone)]
2811
+ #[magnus::wrap(class = "StateSet::CartItem", free_immediately, size)]
2812
+ pub struct CartItem {
2813
+ id: String,
2814
+ sku: String,
2815
+ name: String,
2816
+ quantity: i32,
2817
+ unit_price: f64,
2818
+ total: f64,
2819
+ }
2820
+
2821
+ impl CartItem {
2822
+ fn id(&self) -> String {
2823
+ self.id.clone()
2824
+ }
2825
+ fn sku(&self) -> String {
2826
+ self.sku.clone()
2827
+ }
2828
+ fn name(&self) -> String {
2829
+ self.name.clone()
2830
+ }
2831
+ fn quantity(&self) -> i32 {
2832
+ self.quantity
2833
+ }
2834
+ fn unit_price(&self) -> f64 {
2835
+ self.unit_price
2836
+ }
2837
+ fn total(&self) -> f64 {
2838
+ self.total
2839
+ }
2840
+ }
2841
+
2842
+ #[derive(Clone)]
2843
+ #[magnus::wrap(class = "StateSet::Cart", free_immediately, size)]
2844
+ pub struct Cart {
2845
+ id: String,
2846
+ customer_id: Option<String>,
2847
+ status: String,
2848
+ items: Vec<CartItem>,
2849
+ subtotal: f64,
2850
+ total: f64,
2851
+ currency: String,
2852
+ created_at: String,
2853
+ updated_at: String,
2854
+ }
2855
+
2856
+ impl Cart {
2857
+ fn id(&self) -> String {
2858
+ self.id.clone()
2859
+ }
2860
+ fn customer_id(&self) -> Option<String> {
2861
+ self.customer_id.clone()
2862
+ }
2863
+ fn status(&self) -> String {
2864
+ self.status.clone()
2865
+ }
2866
+ fn items(&self) -> Vec<CartItem> {
2867
+ self.items.clone()
2868
+ }
2869
+ fn subtotal(&self) -> f64 {
2870
+ self.subtotal
2871
+ }
2872
+ fn total(&self) -> f64 {
2873
+ self.total
2874
+ }
2875
+ fn currency(&self) -> String {
2876
+ self.currency.clone()
2877
+ }
2878
+ fn created_at(&self) -> String {
2879
+ self.created_at.clone()
2880
+ }
2881
+ fn updated_at(&self) -> String {
2882
+ self.updated_at.clone()
2883
+ }
2884
+ fn inspect(&self) -> String {
2885
+ format!(
2886
+ "#<StateSet::Cart id=\"{}\" items={} total={} {}>",
2887
+ self.id,
2888
+ self.items.len(),
2889
+ self.total,
2890
+ self.currency
2891
+ )
2892
+ }
2893
+ }
2894
+
2895
+ impl From<stateset_core::Cart> for Cart {
2896
+ fn from(c: stateset_core::Cart) -> Self {
2897
+ Self {
2898
+ id: c.id.to_string(),
2899
+ customer_id: c.customer_id.map(|id| id.to_string()),
2900
+ status: format!("{}", c.status),
2901
+ items: c
2902
+ .items
2903
+ .into_iter()
2904
+ .map(|i| CartItem {
2905
+ id: i.id.to_string(),
2906
+ sku: i.sku,
2907
+ name: i.name,
2908
+ quantity: i.quantity,
2909
+ unit_price: i.unit_price.to_f64().unwrap_or(0.0),
2910
+ total: i.total.to_f64().unwrap_or(0.0),
2911
+ })
2912
+ .collect(),
2913
+ subtotal: c.subtotal.to_f64().unwrap_or(0.0),
2914
+ total: c.total.to_f64().unwrap_or(0.0),
2915
+ currency: c.currency,
2916
+ created_at: c.created_at.to_rfc3339(),
2917
+ updated_at: c.updated_at.to_rfc3339(),
2918
+ }
2919
+ }
2920
+ }
2921
+
2922
+ #[derive(Clone)]
2923
+ #[magnus::wrap(class = "StateSet::Carts", free_immediately, size)]
2924
+ pub struct Carts {
2925
+ commerce: Arc<Mutex<RustCommerce>>,
2926
+ }
2927
+
2928
+ impl Carts {
2929
+ fn create(&self, customer_id: Option<String>, currency: Option<String>) -> Result<Cart, Error> {
2930
+ let commerce = lock_commerce!(self.commerce);
2931
+ let cust_uuid = customer_id.map(|s| s.parse().ok()).flatten();
2932
+
2933
+ let cart = commerce
2934
+ .carts()
2935
+ .create(stateset_core::CreateCart {
2936
+ customer_id: cust_uuid,
2937
+ currency,
2938
+ ..Default::default()
2939
+ })
2940
+ .map_err(|e| {
2941
+ Error::new(
2942
+ exception::runtime_error(),
2943
+ format!("Failed to create cart: {}", e),
2944
+ )
2945
+ })?;
2946
+
2947
+ Ok(cart.into())
2948
+ }
2949
+
2950
+ fn get(&self, id: String) -> Result<Option<Cart>, Error> {
2951
+ let commerce = lock_commerce!(self.commerce);
2952
+ let uuid = parse_uuid!(id, "cart");
2953
+
2954
+ let cart = commerce.carts().get(uuid).map_err(|e| {
2955
+ Error::new(
2956
+ exception::runtime_error(),
2957
+ format!("Failed to get cart: {}", e),
2958
+ )
2959
+ })?;
2960
+
2961
+ Ok(cart.map(|c| c.into()))
2962
+ }
2963
+
2964
+ fn list(&self) -> Result<Vec<Cart>, Error> {
2965
+ let commerce = lock_commerce!(self.commerce);
2966
+
2967
+ let carts = commerce.carts().list(Default::default()).map_err(|e| {
2968
+ Error::new(
2969
+ exception::runtime_error(),
2970
+ format!("Failed to list carts: {}", e),
2971
+ )
2972
+ })?;
2973
+
2974
+ Ok(carts.into_iter().map(|c| c.into()).collect())
2975
+ }
2976
+
2977
+ fn add_item(
2978
+ &self,
2979
+ cart_id: String,
2980
+ sku: String,
2981
+ name: String,
2982
+ quantity: i32,
2983
+ unit_price: f64,
2984
+ ) -> Result<Cart, Error> {
2985
+ let commerce = lock_commerce!(self.commerce);
2986
+ let uuid = parse_uuid!(cart_id, "cart");
2987
+ let price = Decimal::from_f64_retain(unit_price).unwrap_or_default();
2988
+
2989
+ let cart = commerce
2990
+ .carts()
2991
+ .add_item(
2992
+ uuid,
2993
+ stateset_core::AddCartItem {
2994
+ sku,
2995
+ name,
2996
+ quantity,
2997
+ unit_price: price,
2998
+ ..Default::default()
2999
+ },
3000
+ )
3001
+ .map_err(|e| {
3002
+ Error::new(
3003
+ exception::runtime_error(),
3004
+ format!("Failed to add item: {}", e),
3005
+ )
3006
+ })?;
3007
+
3008
+ Ok(cart.into())
3009
+ }
3010
+
3011
+ fn checkout(&self, cart_id: String) -> Result<Order, Error> {
3012
+ let commerce = lock_commerce!(self.commerce);
3013
+ let uuid = parse_uuid!(cart_id, "cart");
3014
+
3015
+ let order = commerce.carts().checkout(uuid).map_err(|e| {
3016
+ Error::new(
3017
+ exception::runtime_error(),
3018
+ format!("Failed to checkout: {}", e),
3019
+ )
3020
+ })?;
3021
+
3022
+ Ok(order.into())
3023
+ }
3024
+ }
3025
+
3026
+ // ============================================================================
3027
+ // Analytics API
3028
+ // ============================================================================
3029
+
3030
+ #[derive(Clone)]
3031
+ #[magnus::wrap(class = "StateSet::SalesSummary", free_immediately, size)]
3032
+ pub struct SalesSummary {
3033
+ total_revenue: f64,
3034
+ total_orders: i64,
3035
+ average_order_value: f64,
3036
+ total_items_sold: i64,
3037
+ }
3038
+
3039
+ impl SalesSummary {
3040
+ fn total_revenue(&self) -> f64 {
3041
+ self.total_revenue
3042
+ }
3043
+ fn total_orders(&self) -> i64 {
3044
+ self.total_orders
3045
+ }
3046
+ fn average_order_value(&self) -> f64 {
3047
+ self.average_order_value
3048
+ }
3049
+ fn total_items_sold(&self) -> i64 {
3050
+ self.total_items_sold
3051
+ }
3052
+ }
3053
+
3054
+ #[derive(Clone)]
3055
+ #[magnus::wrap(class = "StateSet::Analytics", free_immediately, size)]
3056
+ pub struct Analytics {
3057
+ commerce: Arc<Mutex<RustCommerce>>,
3058
+ }
3059
+
3060
+ impl Analytics {
3061
+ fn sales_summary(&self, days: Option<i64>) -> Result<SalesSummary, Error> {
3062
+ let commerce = lock_commerce!(self.commerce);
3063
+
3064
+ let summary = commerce
3065
+ .analytics()
3066
+ .sales_summary(days.unwrap_or(30))
3067
+ .map_err(|e| {
3068
+ Error::new(
3069
+ exception::runtime_error(),
3070
+ format!("Failed to get sales summary: {}", e),
3071
+ )
3072
+ })?;
3073
+
3074
+ Ok(SalesSummary {
3075
+ total_revenue: summary.total_revenue.to_f64().unwrap_or(0.0),
3076
+ total_orders: summary.total_orders,
3077
+ average_order_value: summary.average_order_value.to_f64().unwrap_or(0.0),
3078
+ total_items_sold: summary.total_items_sold,
3079
+ })
3080
+ }
3081
+ }
3082
+
3083
+ // ============================================================================
3084
+ // Currency Types & API
3085
+ // ============================================================================
3086
+
3087
+ #[derive(Clone)]
3088
+ #[magnus::wrap(class = "StateSet::ExchangeRate", free_immediately, size)]
3089
+ pub struct ExchangeRate {
3090
+ id: String,
3091
+ from_currency: String,
3092
+ to_currency: String,
3093
+ rate: f64,
3094
+ effective_date: String,
3095
+ created_at: String,
3096
+ }
3097
+
3098
+ impl ExchangeRate {
3099
+ fn id(&self) -> String {
3100
+ self.id.clone()
3101
+ }
3102
+ fn from_currency(&self) -> String {
3103
+ self.from_currency.clone()
3104
+ }
3105
+ fn to_currency(&self) -> String {
3106
+ self.to_currency.clone()
3107
+ }
3108
+ fn rate(&self) -> f64 {
3109
+ self.rate
3110
+ }
3111
+ fn effective_date(&self) -> String {
3112
+ self.effective_date.clone()
3113
+ }
3114
+ fn created_at(&self) -> String {
3115
+ self.created_at.clone()
3116
+ }
3117
+ }
3118
+
3119
+ impl From<stateset_core::ExchangeRate> for ExchangeRate {
3120
+ fn from(r: stateset_core::ExchangeRate) -> Self {
3121
+ Self {
3122
+ id: r.id.to_string(),
3123
+ from_currency: format!("{}", r.from_currency),
3124
+ to_currency: format!("{}", r.to_currency),
3125
+ rate: r.rate.to_f64().unwrap_or(0.0),
3126
+ effective_date: r.effective_date.to_string(),
3127
+ created_at: r.created_at.to_rfc3339(),
3128
+ }
3129
+ }
3130
+ }
3131
+
3132
+ #[derive(Clone)]
3133
+ #[magnus::wrap(class = "StateSet::CurrencyOps", free_immediately, size)]
3134
+ pub struct CurrencyOps {
3135
+ commerce: Arc<Mutex<RustCommerce>>,
3136
+ }
3137
+
3138
+ impl CurrencyOps {
3139
+ fn get_rate(&self, from: String, to: String) -> Result<Option<ExchangeRate>, Error> {
3140
+ let commerce = lock_commerce!(self.commerce);
3141
+ let from_currency = from.parse().unwrap_or_default();
3142
+ let to_currency = to.parse().unwrap_or_default();
3143
+ let rate = commerce
3144
+ .currency()
3145
+ .get_rate(from_currency, to_currency)
3146
+ .map_err(|e| {
3147
+ Error::new(
3148
+ exception::runtime_error(),
3149
+ format!("Failed to get rate: {}", e),
3150
+ )
3151
+ })?;
3152
+ Ok(rate.map(|r| r.into()))
3153
+ }
3154
+
3155
+ fn list_rates(&self) -> Result<Vec<ExchangeRate>, Error> {
3156
+ let commerce = lock_commerce!(self.commerce);
3157
+ let rates = commerce
3158
+ .currency()
3159
+ .list_rates(Default::default())
3160
+ .map_err(|e| {
3161
+ Error::new(
3162
+ exception::runtime_error(),
3163
+ format!("Failed to list rates: {}", e),
3164
+ )
3165
+ })?;
3166
+ Ok(rates.into_iter().map(|r| r.into()).collect())
3167
+ }
3168
+
3169
+ fn set_rate(&self, from: String, to: String, rate: f64) -> Result<ExchangeRate, Error> {
3170
+ let commerce = lock_commerce!(self.commerce);
3171
+ let exchange_rate = commerce
3172
+ .currency()
3173
+ .set_rate(stateset_core::SetExchangeRate {
3174
+ from_currency: from.parse().unwrap_or_default(),
3175
+ to_currency: to.parse().unwrap_or_default(),
3176
+ rate: Decimal::from_f64_retain(rate).unwrap_or_default(),
3177
+ ..Default::default()
3178
+ })
3179
+ .map_err(|e| {
3180
+ Error::new(
3181
+ exception::runtime_error(),
3182
+ format!("Failed to set rate: {}", e),
3183
+ )
3184
+ })?;
3185
+ Ok(exchange_rate.into())
3186
+ }
3187
+
3188
+ fn convert(&self, amount: f64, from: String, to: String) -> Result<f64, Error> {
3189
+ let commerce = lock_commerce!(self.commerce);
3190
+ let result = commerce
3191
+ .currency()
3192
+ .convert_amount(
3193
+ Decimal::from_f64_retain(amount).unwrap_or_default(),
3194
+ from.parse().unwrap_or_default(),
3195
+ to.parse().unwrap_or_default(),
3196
+ )
3197
+ .map_err(|e| {
3198
+ Error::new(
3199
+ exception::runtime_error(),
3200
+ format!("Failed to convert: {}", e),
3201
+ )
3202
+ })?;
3203
+ Ok(result.to_f64().unwrap_or(0.0))
3204
+ }
3205
+
3206
+ fn base_currency(&self) -> Result<String, Error> {
3207
+ let commerce = lock_commerce!(self.commerce);
3208
+ let currency = commerce.currency().base_currency().map_err(|e| {
3209
+ Error::new(
3210
+ exception::runtime_error(),
3211
+ format!("Failed to get base currency: {}", e),
3212
+ )
3213
+ })?;
3214
+ Ok(format!("{}", currency))
3215
+ }
3216
+
3217
+ fn enabled_currencies(&self) -> Result<Vec<String>, Error> {
3218
+ let commerce = lock_commerce!(self.commerce);
3219
+ let currencies = commerce.currency().enabled_currencies().map_err(|e| {
3220
+ Error::new(
3221
+ exception::runtime_error(),
3222
+ format!("Failed to get currencies: {}", e),
3223
+ )
3224
+ })?;
3225
+ Ok(currencies.into_iter().map(|c| format!("{}", c)).collect())
3226
+ }
3227
+
3228
+ fn format(&self, amount: f64, currency: String) -> String {
3229
+ let commerce = self.commerce.lock().unwrap();
3230
+ commerce.currency().format(
3231
+ Decimal::from_f64_retain(amount).unwrap_or_default(),
3232
+ currency.parse().unwrap_or_default(),
3233
+ )
3234
+ }
3235
+ }
3236
+
3237
+ // ============================================================================
3238
+ // Subscriptions Types & API
3239
+ // ============================================================================
3240
+
3241
+ #[derive(Clone)]
3242
+ #[magnus::wrap(class = "StateSet::SubscriptionPlan", free_immediately, size)]
3243
+ pub struct SubscriptionPlan {
3244
+ id: String,
3245
+ code: String,
3246
+ name: String,
3247
+ description: Option<String>,
3248
+ price: f64,
3249
+ currency: String,
3250
+ billing_interval: String,
3251
+ trial_days: Option<i32>,
3252
+ status: String,
3253
+ created_at: String,
3254
+ updated_at: String,
3255
+ }
3256
+
3257
+ impl SubscriptionPlan {
3258
+ fn id(&self) -> String {
3259
+ self.id.clone()
3260
+ }
3261
+ fn code(&self) -> String {
3262
+ self.code.clone()
3263
+ }
3264
+ fn name(&self) -> String {
3265
+ self.name.clone()
3266
+ }
3267
+ fn description(&self) -> Option<String> {
3268
+ self.description.clone()
3269
+ }
3270
+ fn price(&self) -> f64 {
3271
+ self.price
3272
+ }
3273
+ fn currency(&self) -> String {
3274
+ self.currency.clone()
3275
+ }
3276
+ fn billing_interval(&self) -> String {
3277
+ self.billing_interval.clone()
3278
+ }
3279
+ fn trial_days(&self) -> Option<i32> {
3280
+ self.trial_days
3281
+ }
3282
+ fn status(&self) -> String {
3283
+ self.status.clone()
3284
+ }
3285
+ fn created_at(&self) -> String {
3286
+ self.created_at.clone()
3287
+ }
3288
+ fn updated_at(&self) -> String {
3289
+ self.updated_at.clone()
3290
+ }
3291
+ }
3292
+
3293
+ impl From<stateset_core::SubscriptionPlan> for SubscriptionPlan {
3294
+ fn from(p: stateset_core::SubscriptionPlan) -> Self {
3295
+ Self {
3296
+ id: p.id.to_string(),
3297
+ code: p.code,
3298
+ name: p.name,
3299
+ description: p.description,
3300
+ price: p.price.to_f64().unwrap_or(0.0),
3301
+ currency: p.currency,
3302
+ billing_interval: format!("{}", p.billing_interval),
3303
+ trial_days: p.trial_days,
3304
+ status: format!("{}", p.status),
3305
+ created_at: p.created_at.to_rfc3339(),
3306
+ updated_at: p.updated_at.to_rfc3339(),
3307
+ }
3308
+ }
3309
+ }
3310
+
3311
+ #[derive(Clone)]
3312
+ #[magnus::wrap(class = "StateSet::Subscription", free_immediately, size)]
3313
+ pub struct Subscription {
3314
+ id: String,
3315
+ subscription_number: String,
3316
+ customer_id: String,
3317
+ plan_id: String,
3318
+ status: String,
3319
+ current_period_start: String,
3320
+ current_period_end: String,
3321
+ trial_end: Option<String>,
3322
+ canceled_at: Option<String>,
3323
+ created_at: String,
3324
+ updated_at: String,
3325
+ }
3326
+
3327
+ impl Subscription {
3328
+ fn id(&self) -> String {
3329
+ self.id.clone()
3330
+ }
3331
+ fn subscription_number(&self) -> String {
3332
+ self.subscription_number.clone()
3333
+ }
3334
+ fn customer_id(&self) -> String {
3335
+ self.customer_id.clone()
3336
+ }
3337
+ fn plan_id(&self) -> String {
3338
+ self.plan_id.clone()
3339
+ }
3340
+ fn status(&self) -> String {
3341
+ self.status.clone()
3342
+ }
3343
+ fn current_period_start(&self) -> String {
3344
+ self.current_period_start.clone()
3345
+ }
3346
+ fn current_period_end(&self) -> String {
3347
+ self.current_period_end.clone()
3348
+ }
3349
+ fn trial_end(&self) -> Option<String> {
3350
+ self.trial_end.clone()
3351
+ }
3352
+ fn canceled_at(&self) -> Option<String> {
3353
+ self.canceled_at.clone()
3354
+ }
3355
+ fn created_at(&self) -> String {
3356
+ self.created_at.clone()
3357
+ }
3358
+ fn updated_at(&self) -> String {
3359
+ self.updated_at.clone()
3360
+ }
3361
+ fn inspect(&self) -> String {
3362
+ format!(
3363
+ "#<StateSet::Subscription number=\"{}\" status=\"{}\">",
3364
+ self.subscription_number, self.status
3365
+ )
3366
+ }
3367
+ }
3368
+
3369
+ impl From<stateset_core::Subscription> for Subscription {
3370
+ fn from(s: stateset_core::Subscription) -> Self {
3371
+ Self {
3372
+ id: s.id.to_string(),
3373
+ subscription_number: s.subscription_number,
3374
+ customer_id: s.customer_id.to_string(),
3375
+ plan_id: s.plan_id.to_string(),
3376
+ status: format!("{}", s.status),
3377
+ current_period_start: s.current_period_start.to_rfc3339(),
3378
+ current_period_end: s.current_period_end.to_rfc3339(),
3379
+ trial_end: s.trial_end.map(|d| d.to_rfc3339()),
3380
+ canceled_at: s.canceled_at.map(|d| d.to_rfc3339()),
3381
+ created_at: s.created_at.to_rfc3339(),
3382
+ updated_at: s.updated_at.to_rfc3339(),
3383
+ }
3384
+ }
3385
+ }
3386
+
3387
+ #[derive(Clone)]
3388
+ #[magnus::wrap(class = "StateSet::Subscriptions", free_immediately, size)]
3389
+ pub struct Subscriptions {
3390
+ commerce: Arc<Mutex<RustCommerce>>,
3391
+ }
3392
+
3393
+ impl Subscriptions {
3394
+ fn create_plan(
3395
+ &self,
3396
+ code: String,
3397
+ name: String,
3398
+ price: f64,
3399
+ currency: String,
3400
+ billing_interval: String,
3401
+ ) -> Result<SubscriptionPlan, Error> {
3402
+ let commerce = lock_commerce!(self.commerce);
3403
+ let plan = commerce
3404
+ .subscriptions()
3405
+ .create_plan(stateset_core::CreateSubscriptionPlan {
3406
+ code,
3407
+ name,
3408
+ price: Decimal::from_f64_retain(price).unwrap_or_default(),
3409
+ currency,
3410
+ billing_interval: billing_interval.parse().unwrap_or_default(),
3411
+ ..Default::default()
3412
+ })
3413
+ .map_err(|e| {
3414
+ Error::new(
3415
+ exception::runtime_error(),
3416
+ format!("Failed to create plan: {}", e),
3417
+ )
3418
+ })?;
3419
+ Ok(plan.into())
3420
+ }
3421
+
3422
+ fn get_plan(&self, id: String) -> Result<Option<SubscriptionPlan>, Error> {
3423
+ let commerce = lock_commerce!(self.commerce);
3424
+ let uuid = parse_uuid!(id, "plan");
3425
+ let plan = commerce.subscriptions().get_plan(uuid).map_err(|e| {
3426
+ Error::new(
3427
+ exception::runtime_error(),
3428
+ format!("Failed to get plan: {}", e),
3429
+ )
3430
+ })?;
3431
+ Ok(plan.map(|p| p.into()))
3432
+ }
3433
+
3434
+ fn list_plans(&self) -> Result<Vec<SubscriptionPlan>, Error> {
3435
+ let commerce = lock_commerce!(self.commerce);
3436
+ let plans = commerce
3437
+ .subscriptions()
3438
+ .list_plans(Default::default())
3439
+ .map_err(|e| {
3440
+ Error::new(
3441
+ exception::runtime_error(),
3442
+ format!("Failed to list plans: {}", e),
3443
+ )
3444
+ })?;
3445
+ Ok(plans.into_iter().map(|p| p.into()).collect())
3446
+ }
3447
+
3448
+ fn subscribe(&self, customer_id: String, plan_id: String) -> Result<Subscription, Error> {
3449
+ let commerce = lock_commerce!(self.commerce);
3450
+ let cust_uuid = parse_uuid!(customer_id, "customer");
3451
+ let plan_uuid = parse_uuid!(plan_id, "plan");
3452
+ let sub = commerce
3453
+ .subscriptions()
3454
+ .subscribe(stateset_core::CreateSubscription {
3455
+ customer_id: cust_uuid,
3456
+ plan_id: plan_uuid,
3457
+ ..Default::default()
3458
+ })
3459
+ .map_err(|e| {
3460
+ Error::new(
3461
+ exception::runtime_error(),
3462
+ format!("Failed to subscribe: {}", e),
3463
+ )
3464
+ })?;
3465
+ Ok(sub.into())
3466
+ }
3467
+
3468
+ fn get(&self, id: String) -> Result<Option<Subscription>, Error> {
3469
+ let commerce = lock_commerce!(self.commerce);
3470
+ let uuid = parse_uuid!(id, "subscription");
3471
+ let sub = commerce.subscriptions().get(uuid).map_err(|e| {
3472
+ Error::new(
3473
+ exception::runtime_error(),
3474
+ format!("Failed to get subscription: {}", e),
3475
+ )
3476
+ })?;
3477
+ Ok(sub.map(|s| s.into()))
3478
+ }
3479
+
3480
+ fn list(&self) -> Result<Vec<Subscription>, Error> {
3481
+ let commerce = lock_commerce!(self.commerce);
3482
+ let subs = commerce
3483
+ .subscriptions()
3484
+ .list(Default::default())
3485
+ .map_err(|e| {
3486
+ Error::new(
3487
+ exception::runtime_error(),
3488
+ format!("Failed to list subscriptions: {}", e),
3489
+ )
3490
+ })?;
3491
+ Ok(subs.into_iter().map(|s| s.into()).collect())
3492
+ }
3493
+
3494
+ fn pause(&self, id: String) -> Result<Subscription, Error> {
3495
+ let commerce = lock_commerce!(self.commerce);
3496
+ let uuid = parse_uuid!(id, "subscription");
3497
+ let sub = commerce
3498
+ .subscriptions()
3499
+ .pause(uuid, stateset_core::PauseSubscription::default())
3500
+ .map_err(|e| {
3501
+ Error::new(
3502
+ exception::runtime_error(),
3503
+ format!("Failed to pause: {}", e),
3504
+ )
3505
+ })?;
3506
+ Ok(sub.into())
3507
+ }
3508
+
3509
+ fn resume(&self, id: String) -> Result<Subscription, Error> {
3510
+ let commerce = lock_commerce!(self.commerce);
3511
+ let uuid = parse_uuid!(id, "subscription");
3512
+ let sub = commerce.subscriptions().resume(uuid).map_err(|e| {
3513
+ Error::new(
3514
+ exception::runtime_error(),
3515
+ format!("Failed to resume: {}", e),
3516
+ )
3517
+ })?;
3518
+ Ok(sub.into())
3519
+ }
3520
+
3521
+ fn cancel(&self, id: String, immediately: bool) -> Result<Subscription, Error> {
3522
+ let commerce = lock_commerce!(self.commerce);
3523
+ let uuid = parse_uuid!(id, "subscription");
3524
+ let sub = commerce
3525
+ .subscriptions()
3526
+ .cancel(
3527
+ uuid,
3528
+ stateset_core::CancelSubscription {
3529
+ cancel_immediately: immediately,
3530
+ ..Default::default()
3531
+ },
3532
+ )
3533
+ .map_err(|e| {
3534
+ Error::new(
3535
+ exception::runtime_error(),
3536
+ format!("Failed to cancel: {}", e),
3537
+ )
3538
+ })?;
3539
+ Ok(sub.into())
3540
+ }
3541
+
3542
+ fn for_customer(&self, customer_id: String) -> Result<Vec<Subscription>, Error> {
3543
+ let commerce = lock_commerce!(self.commerce);
3544
+ let uuid = parse_uuid!(customer_id, "customer");
3545
+ let subs = commerce
3546
+ .subscriptions()
3547
+ .get_customer_subscriptions(uuid)
3548
+ .map_err(|e| {
3549
+ Error::new(
3550
+ exception::runtime_error(),
3551
+ format!("Failed to get subscriptions: {}", e),
3552
+ )
3553
+ })?;
3554
+ Ok(subs.into_iter().map(|s| s.into()).collect())
3555
+ }
3556
+
3557
+ fn is_active(&self, id: String) -> Result<bool, Error> {
3558
+ let commerce = lock_commerce!(self.commerce);
3559
+ let uuid = parse_uuid!(id, "subscription");
3560
+ let active = commerce.subscriptions().is_active(uuid).map_err(|e| {
3561
+ Error::new(
3562
+ exception::runtime_error(),
3563
+ format!("Failed to check: {}", e),
3564
+ )
3565
+ })?;
3566
+ Ok(active)
3567
+ }
3568
+ }
3569
+
3570
+ // ============================================================================
3571
+ // Promotions Types & API
3572
+ // ============================================================================
3573
+
3574
+ #[derive(Clone)]
3575
+ #[magnus::wrap(class = "StateSet::Promotion", free_immediately, size)]
3576
+ pub struct Promotion {
3577
+ id: String,
3578
+ code: String,
3579
+ name: String,
3580
+ description: Option<String>,
3581
+ discount_type: String,
3582
+ discount_value: f64,
3583
+ min_purchase: Option<f64>,
3584
+ max_uses: Option<i32>,
3585
+ times_used: i32,
3586
+ status: String,
3587
+ starts_at: Option<String>,
3588
+ ends_at: Option<String>,
3589
+ created_at: String,
3590
+ updated_at: String,
3591
+ }
3592
+
3593
+ impl Promotion {
3594
+ fn id(&self) -> String {
3595
+ self.id.clone()
3596
+ }
3597
+ fn code(&self) -> String {
3598
+ self.code.clone()
3599
+ }
3600
+ fn name(&self) -> String {
3601
+ self.name.clone()
3602
+ }
3603
+ fn description(&self) -> Option<String> {
3604
+ self.description.clone()
3605
+ }
3606
+ fn discount_type(&self) -> String {
3607
+ self.discount_type.clone()
3608
+ }
3609
+ fn discount_value(&self) -> f64 {
3610
+ self.discount_value
3611
+ }
3612
+ fn min_purchase(&self) -> Option<f64> {
3613
+ self.min_purchase
3614
+ }
3615
+ fn max_uses(&self) -> Option<i32> {
3616
+ self.max_uses
3617
+ }
3618
+ fn times_used(&self) -> i32 {
3619
+ self.times_used
3620
+ }
3621
+ fn status(&self) -> String {
3622
+ self.status.clone()
3623
+ }
3624
+ fn starts_at(&self) -> Option<String> {
3625
+ self.starts_at.clone()
3626
+ }
3627
+ fn ends_at(&self) -> Option<String> {
3628
+ self.ends_at.clone()
3629
+ }
3630
+ fn created_at(&self) -> String {
3631
+ self.created_at.clone()
3632
+ }
3633
+ fn updated_at(&self) -> String {
3634
+ self.updated_at.clone()
3635
+ }
3636
+ fn inspect(&self) -> String {
3637
+ format!(
3638
+ "#<StateSet::Promotion code=\"{}\" status=\"{}\">",
3639
+ self.code, self.status
3640
+ )
3641
+ }
3642
+ }
3643
+
3644
+ impl From<stateset_core::Promotion> for Promotion {
3645
+ fn from(p: stateset_core::Promotion) -> Self {
3646
+ Self {
3647
+ id: p.id.to_string(),
3648
+ code: p.code,
3649
+ name: p.name,
3650
+ description: p.description,
3651
+ discount_type: format!("{}", p.discount_type),
3652
+ discount_value: p.discount_value.to_f64().unwrap_or(0.0),
3653
+ min_purchase: p.min_purchase.and_then(|m| m.to_f64()),
3654
+ max_uses: p.max_uses,
3655
+ times_used: p.times_used,
3656
+ status: format!("{}", p.status),
3657
+ starts_at: p.starts_at.map(|d| d.to_rfc3339()),
3658
+ ends_at: p.ends_at.map(|d| d.to_rfc3339()),
3659
+ created_at: p.created_at.to_rfc3339(),
3660
+ updated_at: p.updated_at.to_rfc3339(),
3661
+ }
3662
+ }
3663
+ }
3664
+
3665
+ #[derive(Clone)]
3666
+ #[magnus::wrap(class = "StateSet::Promotions", free_immediately, size)]
3667
+ pub struct Promotions {
3668
+ commerce: Arc<Mutex<RustCommerce>>,
3669
+ }
3670
+
3671
+ impl Promotions {
3672
+ fn create(
3673
+ &self,
3674
+ code: String,
3675
+ name: String,
3676
+ discount_type: String,
3677
+ discount_value: f64,
3678
+ ) -> Result<Promotion, Error> {
3679
+ let commerce = lock_commerce!(self.commerce);
3680
+ let promo = commerce
3681
+ .promotions()
3682
+ .create(stateset_core::CreatePromotion {
3683
+ code,
3684
+ name,
3685
+ discount_type: discount_type.parse().unwrap_or_default(),
3686
+ discount_value: Decimal::from_f64_retain(discount_value).unwrap_or_default(),
3687
+ ..Default::default()
3688
+ })
3689
+ .map_err(|e| {
3690
+ Error::new(
3691
+ exception::runtime_error(),
3692
+ format!("Failed to create promotion: {}", e),
3693
+ )
3694
+ })?;
3695
+ Ok(promo.into())
3696
+ }
3697
+
3698
+ fn get(&self, id: String) -> Result<Option<Promotion>, Error> {
3699
+ let commerce = lock_commerce!(self.commerce);
3700
+ let uuid = parse_uuid!(id, "promotion");
3701
+ let promo = commerce.promotions().get(uuid).map_err(|e| {
3702
+ Error::new(
3703
+ exception::runtime_error(),
3704
+ format!("Failed to get promotion: {}", e),
3705
+ )
3706
+ })?;
3707
+ Ok(promo.map(|p| p.into()))
3708
+ }
3709
+
3710
+ fn get_by_code(&self, code: String) -> Result<Option<Promotion>, Error> {
3711
+ let commerce = lock_commerce!(self.commerce);
3712
+ let promo = commerce.promotions().get_by_code(&code).map_err(|e| {
3713
+ Error::new(
3714
+ exception::runtime_error(),
3715
+ format!("Failed to get promotion: {}", e),
3716
+ )
3717
+ })?;
3718
+ Ok(promo.map(|p| p.into()))
3719
+ }
3720
+
3721
+ fn list(&self) -> Result<Vec<Promotion>, Error> {
3722
+ let commerce = lock_commerce!(self.commerce);
3723
+ let promos = commerce
3724
+ .promotions()
3725
+ .list(Default::default())
3726
+ .map_err(|e| {
3727
+ Error::new(
3728
+ exception::runtime_error(),
3729
+ format!("Failed to list promotions: {}", e),
3730
+ )
3731
+ })?;
3732
+ Ok(promos.into_iter().map(|p| p.into()).collect())
3733
+ }
3734
+
3735
+ fn activate(&self, id: String) -> Result<Promotion, Error> {
3736
+ let commerce = lock_commerce!(self.commerce);
3737
+ let uuid = parse_uuid!(id, "promotion");
3738
+ let promo = commerce.promotions().activate(uuid).map_err(|e| {
3739
+ Error::new(
3740
+ exception::runtime_error(),
3741
+ format!("Failed to activate: {}", e),
3742
+ )
3743
+ })?;
3744
+ Ok(promo.into())
3745
+ }
3746
+
3747
+ fn deactivate(&self, id: String) -> Result<Promotion, Error> {
3748
+ let commerce = lock_commerce!(self.commerce);
3749
+ let uuid = parse_uuid!(id, "promotion");
3750
+ let promo = commerce.promotions().deactivate(uuid).map_err(|e| {
3751
+ Error::new(
3752
+ exception::runtime_error(),
3753
+ format!("Failed to deactivate: {}", e),
3754
+ )
3755
+ })?;
3756
+ Ok(promo.into())
3757
+ }
3758
+
3759
+ fn get_active(&self) -> Result<Vec<Promotion>, Error> {
3760
+ let commerce = lock_commerce!(self.commerce);
3761
+ let promos = commerce.promotions().get_active().map_err(|e| {
3762
+ Error::new(
3763
+ exception::runtime_error(),
3764
+ format!("Failed to get active: {}", e),
3765
+ )
3766
+ })?;
3767
+ Ok(promos.into_iter().map(|p| p.into()).collect())
3768
+ }
3769
+
3770
+ fn is_valid(&self, id: String) -> Result<bool, Error> {
3771
+ let commerce = lock_commerce!(self.commerce);
3772
+ let uuid = parse_uuid!(id, "promotion");
3773
+ let valid = commerce.promotions().is_valid(uuid).map_err(|e| {
3774
+ Error::new(
3775
+ exception::runtime_error(),
3776
+ format!("Failed to check: {}", e),
3777
+ )
3778
+ })?;
3779
+ Ok(valid)
3780
+ }
3781
+
3782
+ fn delete(&self, id: String) -> Result<bool, Error> {
3783
+ let commerce = lock_commerce!(self.commerce);
3784
+ let uuid = parse_uuid!(id, "promotion");
3785
+ commerce.promotions().delete(uuid).map_err(|e| {
3786
+ Error::new(
3787
+ exception::runtime_error(),
3788
+ format!("Failed to delete: {}", e),
3789
+ )
3790
+ })?;
3791
+ Ok(true)
3792
+ }
3793
+ }
3794
+
3795
+ // ============================================================================
3796
+ // Tax Types & API
3797
+ // ============================================================================
3798
+
3799
+ #[derive(Clone)]
3800
+ #[magnus::wrap(class = "StateSet::TaxJurisdiction", free_immediately, size)]
3801
+ pub struct TaxJurisdiction {
3802
+ id: String,
3803
+ code: String,
3804
+ name: String,
3805
+ country: String,
3806
+ state: Option<String>,
3807
+ created_at: String,
3808
+ }
3809
+
3810
+ impl TaxJurisdiction {
3811
+ fn id(&self) -> String {
3812
+ self.id.clone()
3813
+ }
3814
+ fn code(&self) -> String {
3815
+ self.code.clone()
3816
+ }
3817
+ fn name(&self) -> String {
3818
+ self.name.clone()
3819
+ }
3820
+ fn country(&self) -> String {
3821
+ self.country.clone()
3822
+ }
3823
+ fn state(&self) -> Option<String> {
3824
+ self.state.clone()
3825
+ }
3826
+ fn created_at(&self) -> String {
3827
+ self.created_at.clone()
3828
+ }
3829
+ }
3830
+
3831
+ impl From<stateset_core::TaxJurisdiction> for TaxJurisdiction {
3832
+ fn from(j: stateset_core::TaxJurisdiction) -> Self {
3833
+ Self {
3834
+ id: j.id.to_string(),
3835
+ code: j.code,
3836
+ name: j.name,
3837
+ country: j.country,
3838
+ state: j.state,
3839
+ created_at: j.created_at.to_rfc3339(),
3840
+ }
3841
+ }
3842
+ }
3843
+
3844
+ #[derive(Clone)]
3845
+ #[magnus::wrap(class = "StateSet::TaxRate", free_immediately, size)]
3846
+ pub struct TaxRate {
3847
+ id: String,
3848
+ jurisdiction_id: String,
3849
+ name: String,
3850
+ rate: f64,
3851
+ category: String,
3852
+ created_at: String,
3853
+ }
3854
+
3855
+ impl TaxRate {
3856
+ fn id(&self) -> String {
3857
+ self.id.clone()
3858
+ }
3859
+ fn jurisdiction_id(&self) -> String {
3860
+ self.jurisdiction_id.clone()
3861
+ }
3862
+ fn name(&self) -> String {
3863
+ self.name.clone()
3864
+ }
3865
+ fn rate(&self) -> f64 {
3866
+ self.rate
3867
+ }
3868
+ fn category(&self) -> String {
3869
+ self.category.clone()
3870
+ }
3871
+ fn created_at(&self) -> String {
3872
+ self.created_at.clone()
3873
+ }
3874
+ }
3875
+
3876
+ impl From<stateset_core::TaxRate> for TaxRate {
3877
+ fn from(r: stateset_core::TaxRate) -> Self {
3878
+ Self {
3879
+ id: r.id.to_string(),
3880
+ jurisdiction_id: r.jurisdiction_id.to_string(),
3881
+ name: r.name,
3882
+ rate: r.rate.to_f64().unwrap_or(0.0),
3883
+ category: format!("{}", r.category),
3884
+ created_at: r.created_at.to_rfc3339(),
3885
+ }
3886
+ }
3887
+ }
3888
+
3889
+ #[derive(Clone)]
3890
+ #[magnus::wrap(class = "StateSet::Tax", free_immediately, size)]
3891
+ pub struct Tax {
3892
+ commerce: Arc<Mutex<RustCommerce>>,
3893
+ }
3894
+
3895
+ impl Tax {
3896
+ fn calculate(
3897
+ &self,
3898
+ unit_price: f64,
3899
+ quantity: f64,
3900
+ category: String,
3901
+ country: String,
3902
+ state: Option<String>,
3903
+ ) -> Result<f64, Error> {
3904
+ let commerce = lock_commerce!(self.commerce);
3905
+ let address = stateset_core::TaxAddress {
3906
+ country,
3907
+ state,
3908
+ city: None,
3909
+ postal_code: None,
3910
+ line1: None,
3911
+ };
3912
+ let result = commerce
3913
+ .tax()
3914
+ .calculate_for_item(
3915
+ Decimal::from_f64_retain(unit_price).unwrap_or_default(),
3916
+ Decimal::from_f64_retain(quantity).unwrap_or_default(),
3917
+ category.parse().unwrap_or_default(),
3918
+ &address,
3919
+ )
3920
+ .map_err(|e| {
3921
+ Error::new(
3922
+ exception::runtime_error(),
3923
+ format!("Failed to calculate: {}", e),
3924
+ )
3925
+ })?;
3926
+ Ok(result.to_f64().unwrap_or(0.0))
3927
+ }
3928
+
3929
+ fn get_effective_rate(
3930
+ &self,
3931
+ category: String,
3932
+ country: String,
3933
+ state: Option<String>,
3934
+ ) -> Result<f64, Error> {
3935
+ let commerce = lock_commerce!(self.commerce);
3936
+ let address = stateset_core::TaxAddress {
3937
+ country,
3938
+ state,
3939
+ city: None,
3940
+ postal_code: None,
3941
+ line1: None,
3942
+ };
3943
+ let rate = commerce
3944
+ .tax()
3945
+ .get_effective_rate(&address, category.parse().unwrap_or_default())
3946
+ .map_err(|e| {
3947
+ Error::new(
3948
+ exception::runtime_error(),
3949
+ format!("Failed to get rate: {}", e),
3950
+ )
3951
+ })?;
3952
+ Ok(rate.to_f64().unwrap_or(0.0))
3953
+ }
3954
+
3955
+ fn list_jurisdictions(&self) -> Result<Vec<TaxJurisdiction>, Error> {
3956
+ let commerce = lock_commerce!(self.commerce);
3957
+ let jurisdictions = commerce
3958
+ .tax()
3959
+ .list_jurisdictions(Default::default())
3960
+ .map_err(|e| {
3961
+ Error::new(exception::runtime_error(), format!("Failed to list: {}", e))
3962
+ })?;
3963
+ Ok(jurisdictions.into_iter().map(|j| j.into()).collect())
3964
+ }
3965
+
3966
+ fn create_jurisdiction(
3967
+ &self,
3968
+ code: String,
3969
+ name: String,
3970
+ country: String,
3971
+ state: Option<String>,
3972
+ ) -> Result<TaxJurisdiction, Error> {
3973
+ let commerce = lock_commerce!(self.commerce);
3974
+ let j = commerce
3975
+ .tax()
3976
+ .create_jurisdiction(stateset_core::CreateTaxJurisdiction {
3977
+ code,
3978
+ name,
3979
+ country,
3980
+ state,
3981
+ ..Default::default()
3982
+ })
3983
+ .map_err(|e| {
3984
+ Error::new(
3985
+ exception::runtime_error(),
3986
+ format!("Failed to create: {}", e),
3987
+ )
3988
+ })?;
3989
+ Ok(j.into())
3990
+ }
3991
+
3992
+ fn list_rates(&self) -> Result<Vec<TaxRate>, Error> {
3993
+ let commerce = lock_commerce!(self.commerce);
3994
+ let rates = commerce.tax().list_rates(Default::default()).map_err(|e| {
3995
+ Error::new(exception::runtime_error(), format!("Failed to list: {}", e))
3996
+ })?;
3997
+ Ok(rates.into_iter().map(|r| r.into()).collect())
3998
+ }
3999
+
4000
+ fn create_rate(
4001
+ &self,
4002
+ jurisdiction_id: String,
4003
+ name: String,
4004
+ rate: f64,
4005
+ category: String,
4006
+ ) -> Result<TaxRate, Error> {
4007
+ let commerce = lock_commerce!(self.commerce);
4008
+ let uuid = parse_uuid!(jurisdiction_id, "jurisdiction");
4009
+ let r = commerce
4010
+ .tax()
4011
+ .create_rate(stateset_core::CreateTaxRate {
4012
+ jurisdiction_id: uuid,
4013
+ name,
4014
+ rate: Decimal::from_f64_retain(rate).unwrap_or_default(),
4015
+ category: category.parse().unwrap_or_default(),
4016
+ ..Default::default()
4017
+ })
4018
+ .map_err(|e| {
4019
+ Error::new(
4020
+ exception::runtime_error(),
4021
+ format!("Failed to create: {}", e),
4022
+ )
4023
+ })?;
4024
+ Ok(r.into())
4025
+ }
4026
+
4027
+ fn is_enabled(&self) -> Result<bool, Error> {
4028
+ let commerce = lock_commerce!(self.commerce);
4029
+ let enabled = commerce.tax().is_enabled().map_err(|e| {
4030
+ Error::new(
4031
+ exception::runtime_error(),
4032
+ format!("Failed to check: {}", e),
4033
+ )
4034
+ })?;
4035
+ Ok(enabled)
4036
+ }
4037
+
4038
+ fn set_enabled(&self, enabled: bool) -> Result<bool, Error> {
4039
+ let commerce = lock_commerce!(self.commerce);
4040
+ commerce
4041
+ .tax()
4042
+ .set_enabled(enabled)
4043
+ .map_err(|e| Error::new(exception::runtime_error(), format!("Failed to set: {}", e)))?;
4044
+ Ok(enabled)
4045
+ }
4046
+ }
4047
+
4048
+ // ============================================================================
4049
+ // Module Initialization
4050
+ // ============================================================================
4051
+
4052
+ #[magnus::init]
4053
+ fn init(ruby: &Ruby) -> Result<(), Error> {
4054
+ let module = ruby.define_module("StateSet")?;
4055
+
4056
+ // Commerce
4057
+ let commerce_class = module.define_class("Commerce", ruby.class_object())?;
4058
+ commerce_class.define_singleton_method("new", function!(Commerce::new, 1))?;
4059
+ commerce_class.define_method("customers", method!(Commerce::customers, 0))?;
4060
+ commerce_class.define_method("orders", method!(Commerce::orders, 0))?;
4061
+ commerce_class.define_method("products", method!(Commerce::products, 0))?;
4062
+ commerce_class.define_method("inventory", method!(Commerce::inventory, 0))?;
4063
+ commerce_class.define_method("returns", method!(Commerce::returns, 0))?;
4064
+ commerce_class.define_method("payments", method!(Commerce::payments, 0))?;
4065
+ commerce_class.define_method("shipments", method!(Commerce::shipments, 0))?;
4066
+ commerce_class.define_method("warranties", method!(Commerce::warranties, 0))?;
4067
+ commerce_class.define_method("purchase_orders", method!(Commerce::purchase_orders, 0))?;
4068
+ commerce_class.define_method("invoices", method!(Commerce::invoices, 0))?;
4069
+ commerce_class.define_method("bom", method!(Commerce::bom, 0))?;
4070
+ commerce_class.define_method("work_orders", method!(Commerce::work_orders, 0))?;
4071
+ commerce_class.define_method("carts", method!(Commerce::carts, 0))?;
4072
+ commerce_class.define_method("analytics", method!(Commerce::analytics, 0))?;
4073
+ commerce_class.define_method("currency", method!(Commerce::currency, 0))?;
4074
+ commerce_class.define_method("subscriptions", method!(Commerce::subscriptions, 0))?;
4075
+ commerce_class.define_method("promotions", method!(Commerce::promotions, 0))?;
4076
+ commerce_class.define_method("tax", method!(Commerce::tax, 0))?;
4077
+
4078
+ // Customer
4079
+ let customer_class = module.define_class("Customer", ruby.class_object())?;
4080
+ customer_class.define_method("id", method!(Customer::id, 0))?;
4081
+ customer_class.define_method("email", method!(Customer::email, 0))?;
4082
+ customer_class.define_method("first_name", method!(Customer::first_name, 0))?;
4083
+ customer_class.define_method("last_name", method!(Customer::last_name, 0))?;
4084
+ customer_class.define_method("phone", method!(Customer::phone, 0))?;
4085
+ customer_class.define_method("status", method!(Customer::status, 0))?;
4086
+ customer_class.define_method("accepts_marketing", method!(Customer::accepts_marketing, 0))?;
4087
+ customer_class.define_method("created_at", method!(Customer::created_at, 0))?;
4088
+ customer_class.define_method("updated_at", method!(Customer::updated_at, 0))?;
4089
+ customer_class.define_method("full_name", method!(Customer::full_name, 0))?;
4090
+ customer_class.define_method("inspect", method!(Customer::inspect, 0))?;
4091
+ customer_class.define_method("to_s", method!(Customer::inspect, 0))?;
4092
+
4093
+ // Customers API
4094
+ let customers_class = module.define_class("Customers", ruby.class_object())?;
4095
+ customers_class.define_method("create", method!(Customers::create, 5))?;
4096
+ customers_class.define_method("get", method!(Customers::get, 1))?;
4097
+ customers_class.define_method("get_by_email", method!(Customers::get_by_email, 1))?;
4098
+ customers_class.define_method("list", method!(Customers::list, 0))?;
4099
+ customers_class.define_method("count", method!(Customers::count, 0))?;
4100
+
4101
+ // OrderItem
4102
+ let order_item_class = module.define_class("OrderItem", ruby.class_object())?;
4103
+ order_item_class.define_method("id", method!(OrderItem::id, 0))?;
4104
+ order_item_class.define_method("sku", method!(OrderItem::sku, 0))?;
4105
+ order_item_class.define_method("name", method!(OrderItem::name, 0))?;
4106
+ order_item_class.define_method("quantity", method!(OrderItem::quantity, 0))?;
4107
+ order_item_class.define_method("unit_price", method!(OrderItem::unit_price, 0))?;
4108
+ order_item_class.define_method("total", method!(OrderItem::total, 0))?;
4109
+ order_item_class.define_method("inspect", method!(OrderItem::inspect, 0))?;
4110
+
4111
+ // Order
4112
+ let order_class = module.define_class("Order", ruby.class_object())?;
4113
+ order_class.define_method("id", method!(Order::id, 0))?;
4114
+ order_class.define_method("order_number", method!(Order::order_number, 0))?;
4115
+ order_class.define_method("customer_id", method!(Order::customer_id, 0))?;
4116
+ order_class.define_method("status", method!(Order::status, 0))?;
4117
+ order_class.define_method("total_amount", method!(Order::total_amount, 0))?;
4118
+ order_class.define_method("currency", method!(Order::currency, 0))?;
4119
+ order_class.define_method("payment_status", method!(Order::payment_status, 0))?;
4120
+ order_class.define_method("fulfillment_status", method!(Order::fulfillment_status, 0))?;
4121
+ order_class.define_method("tracking_number", method!(Order::tracking_number, 0))?;
4122
+ order_class.define_method("items", method!(Order::items, 0))?;
4123
+ order_class.define_method("version", method!(Order::version, 0))?;
4124
+ order_class.define_method("created_at", method!(Order::created_at, 0))?;
4125
+ order_class.define_method("updated_at", method!(Order::updated_at, 0))?;
4126
+ order_class.define_method("item_count", method!(Order::item_count, 0))?;
4127
+ order_class.define_method("inspect", method!(Order::inspect, 0))?;
4128
+ order_class.define_method("to_s", method!(Order::inspect, 0))?;
4129
+
4130
+ // Orders API
4131
+ let orders_class = module.define_class("Orders", ruby.class_object())?;
4132
+ orders_class.define_method("create", method!(Orders::create, 4))?;
4133
+ orders_class.define_method("get", method!(Orders::get, 1))?;
4134
+ orders_class.define_method("list", method!(Orders::list, 0))?;
4135
+ orders_class.define_method("count", method!(Orders::count, 0))?;
4136
+ orders_class.define_method("ship", method!(Orders::ship, 3))?;
4137
+ orders_class.define_method("cancel", method!(Orders::cancel, 2))?;
4138
+ orders_class.define_method("confirm", method!(Orders::confirm, 1))?;
4139
+ orders_class.define_method("deliver", method!(Orders::deliver, 1))?;
4140
+
4141
+ // ProductVariant
4142
+ let variant_class = module.define_class("ProductVariant", ruby.class_object())?;
4143
+ variant_class.define_method("id", method!(ProductVariant::id, 0))?;
4144
+ variant_class.define_method("sku", method!(ProductVariant::sku, 0))?;
4145
+ variant_class.define_method("name", method!(ProductVariant::name, 0))?;
4146
+ variant_class.define_method("price", method!(ProductVariant::price, 0))?;
4147
+ variant_class.define_method(
4148
+ "compare_at_price",
4149
+ method!(ProductVariant::compare_at_price, 0),
4150
+ )?;
4151
+ variant_class.define_method(
4152
+ "inventory_quantity",
4153
+ method!(ProductVariant::inventory_quantity, 0),
4154
+ )?;
4155
+ variant_class.define_method("weight", method!(ProductVariant::weight, 0))?;
4156
+ variant_class.define_method("barcode", method!(ProductVariant::barcode, 0))?;
4157
+
4158
+ // Product
4159
+ let product_class = module.define_class("Product", ruby.class_object())?;
4160
+ product_class.define_method("id", method!(Product::id, 0))?;
4161
+ product_class.define_method("name", method!(Product::name, 0))?;
4162
+ product_class.define_method("description", method!(Product::description, 0))?;
4163
+ product_class.define_method("vendor", method!(Product::vendor, 0))?;
4164
+ product_class.define_method("product_type", method!(Product::product_type, 0))?;
4165
+ product_class.define_method("status", method!(Product::status, 0))?;
4166
+ product_class.define_method("tags", method!(Product::tags, 0))?;
4167
+ product_class.define_method("variants", method!(Product::variants, 0))?;
4168
+ product_class.define_method("created_at", method!(Product::created_at, 0))?;
4169
+ product_class.define_method("updated_at", method!(Product::updated_at, 0))?;
4170
+ product_class.define_method("inspect", method!(Product::inspect, 0))?;
4171
+ product_class.define_method("to_s", method!(Product::inspect, 0))?;
4172
+
4173
+ // Products API
4174
+ let products_class = module.define_class("Products", ruby.class_object())?;
4175
+ products_class.define_method("create", method!(Products::create, 4))?;
4176
+ products_class.define_method("get", method!(Products::get, 1))?;
4177
+ products_class.define_method("list", method!(Products::list, 0))?;
4178
+ products_class.define_method("count", method!(Products::count, 0))?;
4179
+ products_class.define_method("get_by_sku", method!(Products::get_by_sku, 1))?;
4180
+
4181
+ // InventoryItem
4182
+ let inv_item_class = module.define_class("InventoryItem", ruby.class_object())?;
4183
+ inv_item_class.define_method("id", method!(InventoryItem::id, 0))?;
4184
+ inv_item_class.define_method("sku", method!(InventoryItem::sku, 0))?;
4185
+ inv_item_class.define_method(
4186
+ "quantity_on_hand",
4187
+ method!(InventoryItem::quantity_on_hand, 0),
4188
+ )?;
4189
+ inv_item_class.define_method(
4190
+ "quantity_reserved",
4191
+ method!(InventoryItem::quantity_reserved, 0),
4192
+ )?;
4193
+ inv_item_class.define_method(
4194
+ "quantity_available",
4195
+ method!(InventoryItem::quantity_available, 0),
4196
+ )?;
4197
+ inv_item_class.define_method("reorder_point", method!(InventoryItem::reorder_point, 0))?;
4198
+ inv_item_class.define_method(
4199
+ "reorder_quantity",
4200
+ method!(InventoryItem::reorder_quantity, 0),
4201
+ )?;
4202
+ inv_item_class.define_method("location_id", method!(InventoryItem::location_id, 0))?;
4203
+ inv_item_class.define_method("inspect", method!(InventoryItem::inspect, 0))?;
4204
+ inv_item_class.define_method("to_s", method!(InventoryItem::inspect, 0))?;
4205
+
4206
+ // Inventory API
4207
+ let inventory_class = module.define_class("Inventory", ruby.class_object())?;
4208
+ inventory_class.define_method("create", method!(Inventory::create, 4))?;
4209
+ inventory_class.define_method("get", method!(Inventory::get, 1))?;
4210
+ inventory_class.define_method("get_by_sku", method!(Inventory::get_by_sku, 1))?;
4211
+ inventory_class.define_method("list", method!(Inventory::list, 0))?;
4212
+ inventory_class.define_method("adjust", method!(Inventory::adjust, 3))?;
4213
+ inventory_class.define_method("reserve", method!(Inventory::reserve, 3))?;
4214
+ inventory_class.define_method("release", method!(Inventory::release, 2))?;
4215
+
4216
+ // Return
4217
+ let return_class = module.define_class("Return", ruby.class_object())?;
4218
+ return_class.define_method("id", method!(Return::id, 0))?;
4219
+ return_class.define_method("order_id", method!(Return::order_id, 0))?;
4220
+ return_class.define_method("customer_id", method!(Return::customer_id, 0))?;
4221
+ return_class.define_method("status", method!(Return::status, 0))?;
4222
+ return_class.define_method("reason", method!(Return::reason, 0))?;
4223
+ return_class.define_method("refund_amount", method!(Return::refund_amount, 0))?;
4224
+ return_class.define_method("created_at", method!(Return::created_at, 0))?;
4225
+ return_class.define_method("updated_at", method!(Return::updated_at, 0))?;
4226
+ return_class.define_method("inspect", method!(Return::inspect, 0))?;
4227
+ return_class.define_method("to_s", method!(Return::inspect, 0))?;
4228
+
4229
+ // Returns API
4230
+ let returns_class = module.define_class("Returns", ruby.class_object())?;
4231
+ returns_class.define_method("create", method!(Returns::create, 2))?;
4232
+ returns_class.define_method("get", method!(Returns::get, 1))?;
4233
+ returns_class.define_method("list", method!(Returns::list, 0))?;
4234
+ returns_class.define_method("approve", method!(Returns::approve, 2))?;
4235
+ returns_class.define_method("reject", method!(Returns::reject, 2))?;
4236
+
4237
+ // Payments API
4238
+ let payments_class = module.define_class("Payments", ruby.class_object())?;
4239
+ payments_class.define_method("record", method!(Payments::record, 3))?;
4240
+
4241
+ // Shipments API (stub)
4242
+ let _shipments_class = module.define_class("Shipments", ruby.class_object())?;
4243
+
4244
+ // Warranties API (stub)
4245
+ let _warranties_class = module.define_class("Warranties", ruby.class_object())?;
4246
+
4247
+ // PurchaseOrders API (stub)
4248
+ let _po_class = module.define_class("PurchaseOrders", ruby.class_object())?;
4249
+
4250
+ // Invoices API (stub)
4251
+ let _invoices_class = module.define_class("Invoices", ruby.class_object())?;
4252
+
4253
+ // BomApi (stub)
4254
+ let _bom_class = module.define_class("BomApi", ruby.class_object())?;
4255
+
4256
+ // WorkOrders API (stub)
4257
+ let _wo_class = module.define_class("WorkOrders", ruby.class_object())?;
4258
+
4259
+ // CartItem
4260
+ let cart_item_class = module.define_class("CartItem", ruby.class_object())?;
4261
+ cart_item_class.define_method("id", method!(CartItem::id, 0))?;
4262
+ cart_item_class.define_method("sku", method!(CartItem::sku, 0))?;
4263
+ cart_item_class.define_method("name", method!(CartItem::name, 0))?;
4264
+ cart_item_class.define_method("quantity", method!(CartItem::quantity, 0))?;
4265
+ cart_item_class.define_method("unit_price", method!(CartItem::unit_price, 0))?;
4266
+ cart_item_class.define_method("total", method!(CartItem::total, 0))?;
4267
+
4268
+ // Cart
4269
+ let cart_class = module.define_class("Cart", ruby.class_object())?;
4270
+ cart_class.define_method("id", method!(Cart::id, 0))?;
4271
+ cart_class.define_method("customer_id", method!(Cart::customer_id, 0))?;
4272
+ cart_class.define_method("status", method!(Cart::status, 0))?;
4273
+ cart_class.define_method("items", method!(Cart::items, 0))?;
4274
+ cart_class.define_method("subtotal", method!(Cart::subtotal, 0))?;
4275
+ cart_class.define_method("total", method!(Cart::total, 0))?;
4276
+ cart_class.define_method("currency", method!(Cart::currency, 0))?;
4277
+ cart_class.define_method("created_at", method!(Cart::created_at, 0))?;
4278
+ cart_class.define_method("updated_at", method!(Cart::updated_at, 0))?;
4279
+ cart_class.define_method("inspect", method!(Cart::inspect, 0))?;
4280
+ cart_class.define_method("to_s", method!(Cart::inspect, 0))?;
4281
+
4282
+ // Carts API
4283
+ let carts_class = module.define_class("Carts", ruby.class_object())?;
4284
+ carts_class.define_method("create", method!(Carts::create, 2))?;
4285
+ carts_class.define_method("get", method!(Carts::get, 1))?;
4286
+ carts_class.define_method("list", method!(Carts::list, 0))?;
4287
+ carts_class.define_method("add_item", method!(Carts::add_item, 5))?;
4288
+ carts_class.define_method("checkout", method!(Carts::checkout, 1))?;
4289
+
4290
+ // SalesSummary
4291
+ let sales_class = module.define_class("SalesSummary", ruby.class_object())?;
4292
+ sales_class.define_method("total_revenue", method!(SalesSummary::total_revenue, 0))?;
4293
+ sales_class.define_method("total_orders", method!(SalesSummary::total_orders, 0))?;
4294
+ sales_class.define_method(
4295
+ "average_order_value",
4296
+ method!(SalesSummary::average_order_value, 0),
4297
+ )?;
4298
+ sales_class.define_method(
4299
+ "total_items_sold",
4300
+ method!(SalesSummary::total_items_sold, 0),
4301
+ )?;
4302
+
4303
+ // Analytics API
4304
+ let analytics_class = module.define_class("Analytics", ruby.class_object())?;
4305
+ analytics_class.define_method("sales_summary", method!(Analytics::sales_summary, 1))?;
4306
+
4307
+ // CurrencyOps API (stub)
4308
+ let _currency_class = module.define_class("CurrencyOps", ruby.class_object())?;
4309
+
4310
+ // Subscriptions API (stub)
4311
+ let _subs_class = module.define_class("Subscriptions", ruby.class_object())?;
4312
+
4313
+ // Promotions API (stub)
4314
+ let _promos_class = module.define_class("Promotions", ruby.class_object())?;
4315
+
4316
+ // Tax API (stub)
4317
+ let _tax_class = module.define_class("Tax", ruby.class_object())?;
4318
+
4319
+ Ok(())
4320
+ }