Skip to main content

stateset_embedded/
lib.rs

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