Skip to main content

stateset_java/
lib.rs

1//! JNI bindings for StateSet Embedded Commerce
2//!
3//! This crate provides Java/JNI bindings for the StateSet commerce engine.
4
5use jni::JNIEnv;
6use jni::objects::{JClass, JObject, JString, JValue};
7use jni::sys::{jdouble, jint, jlong};
8use rust_decimal::Decimal;
9use stateset_core::{OrderStatus, ReturnReason};
10use stateset_embedded::{
11    AddCartItem, AnalyticsQuery, Commerce as RustCommerce, CreateCart, CreateCustomer,
12    CreateInventoryItem, CreateOrder, CreatePayment, CreateProduct, CreateProductVariant,
13    CreateReturn, CreateReturnItem, CustomerFilter, OrderFilter, PaymentMethodType, ProductFilter,
14    TimePeriod,
15};
16use std::sync::{Arc, Mutex};
17
18// =============================================================================
19// Handle Management
20// =============================================================================
21
22/// Wrapper around Commerce that's safe to share via JNI
23type CommerceHandle = Arc<Mutex<RustCommerce>>;
24
25fn create_handle(commerce: RustCommerce) -> jlong {
26    let handle: CommerceHandle = Arc::new(Mutex::new(commerce));
27    Arc::into_raw(handle) as jlong
28}
29
30fn get_handle(ptr: jlong) -> Option<CommerceHandle> {
31    if ptr == 0 {
32        return None;
33    }
34    let raw = ptr as *const Mutex<RustCommerce>;
35    unsafe {
36        Arc::increment_strong_count(raw);
37        Some(Arc::from_raw(raw))
38    }
39}
40
41fn use_handle<F, R>(ptr: jlong, f: F) -> Result<R, String>
42where
43    F: FnOnce(&RustCommerce) -> Result<R, String>,
44{
45    let handle = get_handle(ptr).ok_or_else(|| "Null handle".to_string())?;
46    let guard = handle.lock().map_err(|e| format!("Lock failed: {}", e))?;
47    f(&guard)
48}
49
50// =============================================================================
51// Helper Functions
52// =============================================================================
53
54fn get_string(env: &mut JNIEnv<'_>, s: &JString<'_>) -> String {
55    env.get_string(s).map(|s| s.into()).unwrap_or_default()
56}
57
58fn throw_exception(env: &mut JNIEnv<'_>, msg: &str) {
59    let _ = env.throw_new("com/stateset/embedded/StateSetException", msg);
60}
61
62fn jni_or_throw<'a, T>(
63    env: &mut JNIEnv<'a>,
64    result: jni::errors::Result<T>,
65    context: &str,
66) -> Option<T> {
67    match result {
68        Ok(value) => Some(value),
69        Err(err) => {
70            throw_exception(env, &format!("{}: {}", context, err));
71            None
72        }
73    }
74}
75
76fn to_f64_or_nan<T>(value: T) -> f64
77where
78    T: TryInto<f64>,
79    <T as TryInto<f64>>::Error: std::fmt::Display,
80{
81    match value.try_into() {
82        Ok(converted) => converted,
83        Err(err) => {
84            eprintln!("stateset-embedded: failed to convert to f64: {}", err);
85            f64::NAN
86        }
87    }
88}
89
90fn create_customer_object<'a>(
91    env: &mut JNIEnv<'a>,
92    customer: &stateset_core::Customer,
93) -> JObject<'a> {
94    let class_result = env.find_class("com/stateset/embedded/Customer");
95    let class = match jni_or_throw(env, class_result, "Customer class not found") {
96        Some(value) => value,
97        None => return JObject::null(),
98    };
99    let id_result = env.new_string(customer.id.to_string());
100    let id = match jni_or_throw(env, id_result, "Failed to create customer id") {
101        Some(value) => value,
102        None => return JObject::null(),
103    };
104    let email_result = env.new_string(&customer.email);
105    let email = match jni_or_throw(env, email_result, "Failed to create customer email") {
106        Some(value) => value,
107        None => return JObject::null(),
108    };
109    let first_name_result = env.new_string(&customer.first_name);
110    let first_name =
111        match jni_or_throw(env, first_name_result, "Failed to create customer first name") {
112            Some(value) => value,
113            None => return JObject::null(),
114        };
115    let last_name_result = env.new_string(&customer.last_name);
116    let last_name = match jni_or_throw(env, last_name_result, "Failed to create customer last name")
117    {
118        Some(value) => value,
119        None => return JObject::null(),
120    };
121    let phone_result = env.new_string(customer.phone.as_deref().unwrap_or(""));
122    let phone = match jni_or_throw(env, phone_result, "Failed to create customer phone") {
123        Some(value) => value,
124        None => return JObject::null(),
125    };
126    let created_at_result = env.new_string(customer.created_at.to_rfc3339());
127    let created_at =
128        match jni_or_throw(env, created_at_result, "Failed to create customer created_at") {
129            Some(value) => value,
130            None => return JObject::null(),
131        };
132
133    let obj_result = env.new_object(
134        class,
135        "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V",
136        &[
137            JValue::Object(&id),
138            JValue::Object(&email),
139            JValue::Object(&first_name),
140            JValue::Object(&last_name),
141            JValue::Object(&phone),
142            JValue::Object(&created_at),
143        ],
144    );
145    match jni_or_throw(env, obj_result, "Failed to create customer object") {
146        Some(obj) => obj,
147        None => JObject::null(),
148    }
149}
150
151fn create_product_object<'a>(
152    env: &mut JNIEnv<'a>,
153    product: &stateset_core::Product,
154) -> JObject<'a> {
155    let class_result = env.find_class("com/stateset/embedded/Product");
156    let class = match jni_or_throw(env, class_result, "Product class not found") {
157        Some(value) => value,
158        None => return JObject::null(),
159    };
160    let id_result = env.new_string(product.id.to_string());
161    let id = match jni_or_throw(env, id_result, "Failed to create product id") {
162        Some(value) => value,
163        None => return JObject::null(),
164    };
165    // Product doesn't have SKU directly - use slug as identifier
166    let sku_result = env.new_string(&product.slug);
167    let sku = match jni_or_throw(env, sku_result, "Failed to create product sku") {
168        Some(value) => value,
169        None => return JObject::null(),
170    };
171    let name_result = env.new_string(&product.name);
172    let name = match jni_or_throw(env, name_result, "Failed to create product name") {
173        Some(value) => value,
174        None => return JObject::null(),
175    };
176    // Product doesn't have base_price - that's on variants. Use 0.0 as placeholder.
177    let base_price: f64 = 0.0;
178
179    let obj_result = env.new_object(
180        class,
181        "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;D)V",
182        &[
183            JValue::Object(&id),
184            JValue::Object(&sku),
185            JValue::Object(&name),
186            JValue::Double(base_price),
187        ],
188    );
189    match jni_or_throw(env, obj_result, "Failed to create product object") {
190        Some(obj) => obj,
191        None => JObject::null(),
192    }
193}
194
195fn create_order_object<'a>(env: &mut JNIEnv<'a>, order: &stateset_core::Order) -> JObject<'a> {
196    let class_result = env.find_class("com/stateset/embedded/Order");
197    let class = match jni_or_throw(env, class_result, "Order class not found") {
198        Some(value) => value,
199        None => return JObject::null(),
200    };
201    let id_result = env.new_string(order.id.to_string());
202    let id = match jni_or_throw(env, id_result, "Failed to create order id") {
203        Some(value) => value,
204        None => return JObject::null(),
205    };
206    let order_number_result = env.new_string(&order.order_number);
207    let order_number = match jni_or_throw(env, order_number_result, "Failed to create order number")
208    {
209        Some(value) => value,
210        None => return JObject::null(),
211    };
212    let customer_id_result = env.new_string(order.customer_id.to_string());
213    let customer_id =
214        match jni_or_throw(env, customer_id_result, "Failed to create order customer id") {
215            Some(value) => value,
216            None => return JObject::null(),
217        };
218    let status_result = env.new_string(format!("{:?}", order.status));
219    let status = match jni_or_throw(env, status_result, "Failed to create order status") {
220        Some(value) => value,
221        None => return JObject::null(),
222    };
223    let total: f64 = to_f64_or_nan(order.total_amount);
224    let currency_result = env.new_string(&order.currency);
225    let currency = match jni_or_throw(env, currency_result, "Failed to create order currency") {
226        Some(value) => value,
227        None => return JObject::null(),
228    };
229    let created_at_result = env.new_string(order.created_at.to_rfc3339());
230    let created_at = match jni_or_throw(env, created_at_result, "Failed to create order created_at")
231    {
232        Some(value) => value,
233        None => return JObject::null(),
234    };
235
236    let obj_result = env.new_object(
237        class,
238        "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;DLjava/lang/String;Ljava/lang/String;)V",
239        &[
240            JValue::Object(&id),
241            JValue::Object(&order_number),
242            JValue::Object(&customer_id),
243            JValue::Object(&status),
244            JValue::Double(total),
245            JValue::Object(&currency),
246            JValue::Object(&created_at),
247        ],
248    );
249    match jni_or_throw(env, obj_result, "Failed to create order object") {
250        Some(obj) => obj,
251        None => JObject::null(),
252    }
253}
254
255fn create_inventory_item_object<'a>(
256    env: &mut JNIEnv<'a>,
257    item: &stateset_core::InventoryItem,
258) -> JObject<'a> {
259    let class_result = env.find_class("com/stateset/embedded/InventoryItem");
260    let class = match jni_or_throw(env, class_result, "InventoryItem class not found") {
261        Some(value) => value,
262        None => return JObject::null(),
263    };
264    let id_result = env.new_string(item.id.to_string());
265    let id = match jni_or_throw(env, id_result, "Failed to create inventory item id") {
266        Some(value) => value,
267        None => return JObject::null(),
268    };
269    let sku_result = env.new_string(&item.sku);
270    let sku = match jni_or_throw(env, sku_result, "Failed to create inventory item sku") {
271        Some(value) => value,
272        None => return JObject::null(),
273    };
274    let name_result = env.new_string(&item.name);
275    let name = match jni_or_throw(env, name_result, "Failed to create inventory item name") {
276        Some(value) => value,
277        None => return JObject::null(),
278    };
279    // InventoryItem doesn't have quantities directly - those are in InventoryBalance/StockLevel
280    // Use 0.0 as placeholder
281    let available: f64 = 0.0;
282    let reserved: f64 = 0.0;
283
284    let obj_result = env.new_object(
285        class,
286        "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;DD)V",
287        &[
288            JValue::Object(&id),
289            JValue::Object(&sku),
290            JValue::Object(&name),
291            JValue::Double(available),
292            JValue::Double(reserved),
293        ],
294    );
295    match jni_or_throw(env, obj_result, "Failed to create inventory item object") {
296        Some(obj) => obj,
297        None => JObject::null(),
298    }
299}
300
301fn create_cart_object<'a>(env: &mut JNIEnv<'a>, cart: &stateset_core::Cart) -> JObject<'a> {
302    let class_result = env.find_class("com/stateset/embedded/Cart");
303    let class = match jni_or_throw(env, class_result, "Cart class not found") {
304        Some(value) => value,
305        None => return JObject::null(),
306    };
307    let id_result = env.new_string(cart.id.to_string());
308    let id = match jni_or_throw(env, id_result, "Failed to create cart id") {
309        Some(value) => value,
310        None => return JObject::null(),
311    };
312    let customer_id_result =
313        env.new_string(cart.customer_id.map(|id| id.to_string()).unwrap_or_default());
314    let customer_id =
315        match jni_or_throw(env, customer_id_result, "Failed to create cart customer id") {
316            Some(value) => value,
317            None => return JObject::null(),
318        };
319    let status_result = env.new_string(format!("{:?}", cart.status));
320    let status = match jni_or_throw(env, status_result, "Failed to create cart status") {
321        Some(value) => value,
322        None => return JObject::null(),
323    };
324    let total: f64 = to_f64_or_nan(cart.grand_total);
325    let currency_result = env.new_string(&cart.currency);
326    let currency = match jni_or_throw(env, currency_result, "Failed to create cart currency") {
327        Some(value) => value,
328        None => return JObject::null(),
329    };
330
331    let obj_result = env.new_object(
332        class,
333        "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;DLjava/lang/String;)V",
334        &[
335            JValue::Object(&id),
336            JValue::Object(&customer_id),
337            JValue::Object(&status),
338            JValue::Double(total),
339            JValue::Object(&currency),
340        ],
341    );
342    match jni_or_throw(env, obj_result, "Failed to create cart object") {
343        Some(obj) => obj,
344        None => JObject::null(),
345    }
346}
347
348fn create_sales_summary_object<'a>(
349    env: &mut JNIEnv<'a>,
350    summary: &stateset_core::SalesSummary,
351) -> JObject<'a> {
352    let class_result = env.find_class("com/stateset/embedded/SalesSummary");
353    let class = match jni_or_throw(env, class_result, "SalesSummary class not found") {
354        Some(value) => value,
355        None => return JObject::null(),
356    };
357    let total_revenue: f64 = to_f64_or_nan(summary.total_revenue);
358    let total_orders = summary.order_count as jint;
359    let aov: f64 = to_f64_or_nan(summary.average_order_value);
360
361    let obj_result = env.new_object(
362        class,
363        "(DID)V",
364        &[JValue::Double(total_revenue), JValue::Int(total_orders), JValue::Double(aov)],
365    );
366    match jni_or_throw(env, obj_result, "Failed to create sales summary object") {
367        Some(obj) => obj,
368        None => JObject::null(),
369    }
370}
371
372fn create_return_object<'a>(env: &mut JNIEnv<'a>, ret: &stateset_core::Return) -> JObject<'a> {
373    let class_result = env.find_class("com/stateset/embedded/ReturnRequest");
374    let class = match jni_or_throw(env, class_result, "ReturnRequest class not found") {
375        Some(value) => value,
376        None => return JObject::null(),
377    };
378    let id_result = env.new_string(ret.id.to_string());
379    let id = match jni_or_throw(env, id_result, "Failed to create return id") {
380        Some(value) => value,
381        None => return JObject::null(),
382    };
383    let order_id_result = env.new_string(ret.order_id.to_string());
384    let order_id = match jni_or_throw(env, order_id_result, "Failed to create return order id") {
385        Some(value) => value,
386        None => return JObject::null(),
387    };
388    let reason_result = env.new_string(format!("{:?}", ret.reason));
389    let reason = match jni_or_throw(env, reason_result, "Failed to create return reason") {
390        Some(value) => value,
391        None => return JObject::null(),
392    };
393    let status_result = env.new_string(format!("{:?}", ret.status));
394    let status = match jni_or_throw(env, status_result, "Failed to create return status") {
395        Some(value) => value,
396        None => return JObject::null(),
397    };
398    let refund_amount: f64 = ret.refund_amount.map(to_f64_or_nan).unwrap_or(0.0);
399
400    let obj_result = env.new_object(
401        class,
402        "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;D)V",
403        &[
404            JValue::Object(&id),
405            JValue::Object(&order_id),
406            JValue::Object(&reason),
407            JValue::Object(&status),
408            JValue::Double(refund_amount),
409        ],
410    );
411    match jni_or_throw(env, obj_result, "Failed to create return object") {
412        Some(obj) => obj,
413        None => JObject::null(),
414    }
415}
416
417// =============================================================================
418// Commerce Class
419// =============================================================================
420
421#[unsafe(no_mangle)]
422pub extern "system" fn Java_com_stateset_embedded_Commerce_nativeCreate<'local>(
423    mut env: JNIEnv<'local>,
424    _class: JClass<'local>,
425    db_path: JString<'local>,
426) -> jlong {
427    let path = get_string(&mut env, &db_path);
428
429    match RustCommerce::new(&path) {
430        Ok(commerce) => create_handle(commerce),
431        Err(e) => {
432            throw_exception(&mut env, &format!("Failed to create commerce: {}", e));
433            0
434        }
435    }
436}
437
438#[unsafe(no_mangle)]
439pub extern "system" fn Java_com_stateset_embedded_Commerce_nativeDestroy<'local>(
440    _env: JNIEnv<'local>,
441    _class: JClass<'local>,
442    ptr: jlong,
443) {
444    if ptr != 0 {
445        // Reconstruct the Arc and let it drop
446        let _ = unsafe { Arc::from_raw(ptr as *const Mutex<RustCommerce>) };
447    }
448}
449
450// =============================================================================
451// Customers API
452// =============================================================================
453
454#[unsafe(no_mangle)]
455pub extern "system" fn Java_com_stateset_embedded_Customers_nativeCreate<'local>(
456    mut env: JNIEnv<'local>,
457    _class: JClass<'local>,
458    ptr: jlong,
459    email: JString<'local>,
460    first_name: JString<'local>,
461    last_name: JString<'local>,
462) -> JObject<'local> {
463    let email_str = get_string(&mut env, &email);
464    let first_name_str = get_string(&mut env, &first_name);
465    let last_name_str = get_string(&mut env, &last_name);
466
467    let result = use_handle(ptr, |commerce| {
468        commerce
469            .customers()
470            .create(CreateCustomer {
471                email: email_str,
472                first_name: first_name_str,
473                last_name: last_name_str,
474                ..Default::default()
475            })
476            .map_err(|e| e.to_string())
477    });
478
479    match result {
480        Ok(customer) => create_customer_object(&mut env, &customer),
481        Err(e) => {
482            throw_exception(&mut env, &e);
483            JObject::null()
484        }
485    }
486}
487
488#[unsafe(no_mangle)]
489pub extern "system" fn Java_com_stateset_embedded_Customers_nativeGet<'local>(
490    mut env: JNIEnv<'local>,
491    _class: JClass<'local>,
492    ptr: jlong,
493    id: JString<'local>,
494) -> JObject<'local> {
495    let id_str = get_string(&mut env, &id);
496    let uuid = match uuid::Uuid::parse_str(&id_str) {
497        Ok(u) => u,
498        Err(_) => {
499            throw_exception(&mut env, "Invalid UUID");
500            return JObject::null();
501        }
502    };
503
504    let result = use_handle(ptr, |commerce| {
505        commerce.customers().get(uuid.into()).map_err(|e| e.to_string())
506    });
507
508    match result {
509        Ok(Some(customer)) => create_customer_object(&mut env, &customer),
510        Ok(None) => JObject::null(),
511        Err(e) => {
512            throw_exception(&mut env, &e);
513            JObject::null()
514        }
515    }
516}
517
518#[unsafe(no_mangle)]
519pub extern "system" fn Java_com_stateset_embedded_Customers_nativeList<'local>(
520    mut env: JNIEnv<'local>,
521    _class: JClass<'local>,
522    ptr: jlong,
523) -> JObject<'local> {
524    let result = use_handle(ptr, |commerce| {
525        commerce.customers().list(CustomerFilter::default()).map_err(|e| e.to_string())
526    });
527
528    match result {
529        Ok(customers) => {
530            let find_class_result = env.find_class("java/util/ArrayList");
531            let list_class =
532                match jni_or_throw(&mut env, find_class_result, "ArrayList class not found") {
533                    Some(value) => value,
534                    None => return JObject::null(),
535                };
536            let new_object_result = env.new_object(list_class, "()V", &[]);
537            let list = match jni_or_throw(&mut env, new_object_result, "Failed to create ArrayList")
538            {
539                Some(value) => value,
540                None => return JObject::null(),
541            };
542
543            for customer in &customers {
544                let obj = create_customer_object(&mut env, customer);
545                let add_result =
546                    env.call_method(&list, "add", "(Ljava/lang/Object;)Z", &[JValue::Object(&obj)]);
547                if jni_or_throw(&mut env, add_result, "Failed to add customer to list").is_none() {
548                    return JObject::null();
549                }
550            }
551
552            list
553        }
554        Err(e) => {
555            throw_exception(&mut env, &e);
556            JObject::null()
557        }
558    }
559}
560
561// =============================================================================
562// Products API
563// =============================================================================
564
565#[unsafe(no_mangle)]
566pub extern "system" fn Java_com_stateset_embedded_Products_nativeCreate<'local>(
567    mut env: JNIEnv<'local>,
568    _class: JClass<'local>,
569    ptr: jlong,
570    sku: JString<'local>,
571    name: JString<'local>,
572    base_price: jdouble,
573) -> JObject<'local> {
574    let sku_str = get_string(&mut env, &sku);
575    let name_str = get_string(&mut env, &name);
576    let price = Decimal::try_from(base_price).unwrap_or_default();
577
578    let result = use_handle(ptr, |commerce| {
579        // Create product with a default variant that has SKU and price
580        commerce
581            .products()
582            .create(CreateProduct {
583                name: name_str,
584                variants: Some(vec![CreateProductVariant {
585                    sku: sku_str,
586                    price,
587                    is_default: Some(true),
588                    ..Default::default()
589                }]),
590                ..Default::default()
591            })
592            .map_err(|e| e.to_string())
593    });
594
595    match result {
596        Ok(product) => create_product_object(&mut env, &product),
597        Err(e) => {
598            throw_exception(&mut env, &e);
599            JObject::null()
600        }
601    }
602}
603
604#[unsafe(no_mangle)]
605pub extern "system" fn Java_com_stateset_embedded_Products_nativeGet<'local>(
606    mut env: JNIEnv<'local>,
607    _class: JClass<'local>,
608    ptr: jlong,
609    sku: JString<'local>,
610) -> JObject<'local> {
611    let sku_str = get_string(&mut env, &sku);
612
613    // Get variant by SKU, then get its parent product
614    let result = use_handle(ptr, |commerce| {
615        if let Some(variant) =
616            commerce.products().get_variant_by_sku(&sku_str).map_err(|e| e.to_string())?
617        {
618            commerce.products().get(variant.product_id).map_err(|e| e.to_string())
619        } else {
620            Ok(None)
621        }
622    });
623
624    match result {
625        Ok(Some(product)) => create_product_object(&mut env, &product),
626        Ok(None) => JObject::null(),
627        Err(e) => {
628            throw_exception(&mut env, &e);
629            JObject::null()
630        }
631    }
632}
633
634#[unsafe(no_mangle)]
635pub extern "system" fn Java_com_stateset_embedded_Products_nativeList<'local>(
636    mut env: JNIEnv<'local>,
637    _class: JClass<'local>,
638    ptr: jlong,
639) -> JObject<'local> {
640    let result = use_handle(ptr, |commerce| {
641        commerce.products().list(ProductFilter::default()).map_err(|e| e.to_string())
642    });
643
644    match result {
645        Ok(products) => {
646            let find_class_result = env.find_class("java/util/ArrayList");
647            let list_class =
648                match jni_or_throw(&mut env, find_class_result, "ArrayList class not found") {
649                    Some(value) => value,
650                    None => return JObject::null(),
651                };
652            let new_object_result = env.new_object(list_class, "()V", &[]);
653            let list = match jni_or_throw(&mut env, new_object_result, "Failed to create ArrayList")
654            {
655                Some(value) => value,
656                None => return JObject::null(),
657            };
658
659            for product in &products {
660                let obj = create_product_object(&mut env, product);
661                let add_result =
662                    env.call_method(&list, "add", "(Ljava/lang/Object;)Z", &[JValue::Object(&obj)]);
663                if jni_or_throw(&mut env, add_result, "Failed to add product to list").is_none() {
664                    return JObject::null();
665                }
666            }
667
668            list
669        }
670        Err(e) => {
671            throw_exception(&mut env, &e);
672            JObject::null()
673        }
674    }
675}
676
677// =============================================================================
678// Inventory API
679// =============================================================================
680
681#[unsafe(no_mangle)]
682pub extern "system" fn Java_com_stateset_embedded_Inventory_nativeCreateItem<'local>(
683    mut env: JNIEnv<'local>,
684    _class: JClass<'local>,
685    ptr: jlong,
686    sku: JString<'local>,
687    name: JString<'local>,
688    initial_quantity: jdouble,
689) -> JObject<'local> {
690    let sku_str = get_string(&mut env, &sku);
691    let name_str = get_string(&mut env, &name);
692    let qty = Decimal::try_from(initial_quantity).ok();
693
694    let result = use_handle(ptr, |commerce| {
695        commerce
696            .inventory()
697            .create_item(CreateInventoryItem {
698                sku: sku_str,
699                name: name_str,
700                initial_quantity: qty,
701                ..Default::default()
702            })
703            .map_err(|e| e.to_string())
704    });
705
706    match result {
707        Ok(item) => create_inventory_item_object(&mut env, &item),
708        Err(e) => {
709            throw_exception(&mut env, &e);
710            JObject::null()
711        }
712    }
713}
714
715#[unsafe(no_mangle)]
716pub extern "system" fn Java_com_stateset_embedded_Inventory_nativeAdjust<'local>(
717    mut env: JNIEnv<'local>,
718    _class: JClass<'local>,
719    ptr: jlong,
720    sku: JString<'local>,
721    quantity: jdouble,
722    reason: JString<'local>,
723) -> JObject<'local> {
724    let sku_str = get_string(&mut env, &sku);
725    let reason_str = get_string(&mut env, &reason);
726    let qty = Decimal::try_from(quantity).unwrap_or_default();
727
728    let result = use_handle(ptr, |commerce| {
729        commerce.inventory().adjust(&sku_str, qty, &reason_str).map_err(|e| e.to_string())?;
730        // Return the updated item
731        commerce
732            .inventory()
733            .get_item_by_sku(&sku_str)
734            .map_err(|e| e.to_string())?
735            .ok_or_else(|| "Item not found".to_string())
736    });
737
738    match result {
739        Ok(item) => create_inventory_item_object(&mut env, &item),
740        Err(e) => {
741            throw_exception(&mut env, &e);
742            JObject::null()
743        }
744    }
745}
746
747#[unsafe(no_mangle)]
748pub extern "system" fn Java_com_stateset_embedded_Inventory_nativeGet<'local>(
749    mut env: JNIEnv<'local>,
750    _class: JClass<'local>,
751    ptr: jlong,
752    sku: JString<'local>,
753) -> JObject<'local> {
754    let sku_str = get_string(&mut env, &sku);
755
756    let result = use_handle(ptr, |commerce| {
757        commerce.inventory().get_item_by_sku(&sku_str).map_err(|e| e.to_string())
758    });
759
760    match result {
761        Ok(Some(item)) => create_inventory_item_object(&mut env, &item),
762        Ok(None) => JObject::null(),
763        Err(e) => {
764            throw_exception(&mut env, &e);
765            JObject::null()
766        }
767    }
768}
769
770#[unsafe(no_mangle)]
771pub extern "system" fn Java_com_stateset_embedded_Inventory_nativeReserve<'local>(
772    mut env: JNIEnv<'local>,
773    _class: JClass<'local>,
774    ptr: jlong,
775    sku: JString<'local>,
776    quantity: jdouble,
777) {
778    let sku_str = get_string(&mut env, &sku);
779    let qty = Decimal::try_from(quantity).unwrap_or_default();
780
781    let result = use_handle(ptr, |commerce| {
782        commerce
783            .inventory()
784            .reserve(&sku_str, qty, "java", "reservation", None)
785            .map_err(|e| e.to_string())
786    });
787
788    if let Err(e) = result {
789        throw_exception(&mut env, &e);
790    }
791}
792
793#[unsafe(no_mangle)]
794pub extern "system" fn Java_com_stateset_embedded_Inventory_nativeRelease<'local>(
795    mut env: JNIEnv<'local>,
796    _class: JClass<'local>,
797    _ptr: jlong,
798    sku: JString<'local>,
799    _quantity: jdouble,
800) {
801    let _sku_str = get_string(&mut env, &sku);
802    // Note: Release requires a reservation ID, not SKU
803    // For now, this is a no-op
804    throw_exception(&mut env, "Release requires reservation ID, not SKU");
805}
806
807// =============================================================================
808// Orders API
809// =============================================================================
810
811#[unsafe(no_mangle)]
812pub extern "system" fn Java_com_stateset_embedded_Orders_nativeCreate<'local>(
813    mut env: JNIEnv<'local>,
814    _class: JClass<'local>,
815    ptr: jlong,
816    customer_id: JString<'local>,
817    currency: JString<'local>,
818) -> JObject<'local> {
819    let customer_id_str = get_string(&mut env, &customer_id);
820    let currency_str = get_string(&mut env, &currency);
821
822    let uuid = match uuid::Uuid::parse_str(&customer_id_str) {
823        Ok(u) => u,
824        Err(_) => {
825            throw_exception(&mut env, "Invalid customer UUID");
826            return JObject::null();
827        }
828    };
829
830    let result = use_handle(ptr, |commerce| {
831        commerce
832            .orders()
833            .create(CreateOrder {
834                customer_id: uuid.into(),
835                currency: Some(if currency_str.is_empty() {
836                    "USD".to_string()
837                } else {
838                    currency_str
839                }),
840                items: vec![],
841                ..Default::default()
842            })
843            .map_err(|e| e.to_string())
844    });
845
846    match result {
847        Ok(order) => create_order_object(&mut env, &order),
848        Err(e) => {
849            throw_exception(&mut env, &e);
850            JObject::null()
851        }
852    }
853}
854
855#[unsafe(no_mangle)]
856pub extern "system" fn Java_com_stateset_embedded_Orders_nativeGet<'local>(
857    mut env: JNIEnv<'local>,
858    _class: JClass<'local>,
859    ptr: jlong,
860    id: JString<'local>,
861) -> JObject<'local> {
862    let id_str = get_string(&mut env, &id);
863    let uuid = match uuid::Uuid::parse_str(&id_str) {
864        Ok(u) => u,
865        Err(_) => {
866            throw_exception(&mut env, "Invalid UUID");
867            return JObject::null();
868        }
869    };
870
871    let result =
872        use_handle(ptr, |commerce| commerce.orders().get(uuid.into()).map_err(|e| e.to_string()));
873
874    match result {
875        Ok(Some(order)) => create_order_object(&mut env, &order),
876        Ok(None) => JObject::null(),
877        Err(e) => {
878            throw_exception(&mut env, &e);
879            JObject::null()
880        }
881    }
882}
883
884#[unsafe(no_mangle)]
885pub extern "system" fn Java_com_stateset_embedded_Orders_nativeList<'local>(
886    mut env: JNIEnv<'local>,
887    _class: JClass<'local>,
888    ptr: jlong,
889) -> JObject<'local> {
890    let result = use_handle(ptr, |commerce| {
891        commerce.orders().list(OrderFilter::default()).map_err(|e| e.to_string())
892    });
893
894    match result {
895        Ok(orders) => {
896            let find_class_result = env.find_class("java/util/ArrayList");
897            let list_class =
898                match jni_or_throw(&mut env, find_class_result, "ArrayList class not found") {
899                    Some(value) => value,
900                    None => return JObject::null(),
901                };
902            let new_object_result = env.new_object(list_class, "()V", &[]);
903            let list = match jni_or_throw(&mut env, new_object_result, "Failed to create ArrayList")
904            {
905                Some(value) => value,
906                None => return JObject::null(),
907            };
908
909            for order in &orders {
910                let obj = create_order_object(&mut env, order);
911                let add_result =
912                    env.call_method(&list, "add", "(Ljava/lang/Object;)Z", &[JValue::Object(&obj)]);
913                if jni_or_throw(&mut env, add_result, "Failed to add order to list").is_none() {
914                    return JObject::null();
915                }
916            }
917
918            list
919        }
920        Err(e) => {
921            throw_exception(&mut env, &e);
922            JObject::null()
923        }
924    }
925}
926
927#[unsafe(no_mangle)]
928pub extern "system" fn Java_com_stateset_embedded_Orders_nativeUpdateStatus<'local>(
929    mut env: JNIEnv<'local>,
930    _class: JClass<'local>,
931    ptr: jlong,
932    id: JString<'local>,
933    status: JString<'local>,
934) {
935    let id_str = get_string(&mut env, &id);
936    let status_str = get_string(&mut env, &status);
937
938    let uuid = match uuid::Uuid::parse_str(&id_str) {
939        Ok(u) => u,
940        Err(_) => {
941            throw_exception(&mut env, "Invalid UUID");
942            return;
943        }
944    };
945
946    // Map status string to OrderStatus
947    let result = use_handle(ptr, |commerce| {
948        let status = match status_str.to_lowercase().as_str() {
949            "confirmed" => OrderStatus::Confirmed,
950            "processing" => OrderStatus::Processing,
951            "shipped" => OrderStatus::Shipped,
952            "delivered" => OrderStatus::Delivered,
953            "cancelled" => OrderStatus::Cancelled,
954            _ => OrderStatus::Pending,
955        };
956        commerce.orders().update_status(uuid.into(), status).map_err(|e| e.to_string())
957    });
958
959    if let Err(e) = result {
960        throw_exception(&mut env, &e);
961    }
962}
963
964// =============================================================================
965// Carts API
966// =============================================================================
967
968#[unsafe(no_mangle)]
969pub extern "system" fn Java_com_stateset_embedded_Carts_nativeCreate<'local>(
970    mut env: JNIEnv<'local>,
971    _class: JClass<'local>,
972    ptr: jlong,
973    customer_id: JString<'local>,
974    currency: JString<'local>,
975) -> JObject<'local> {
976    let customer_id_str = get_string(&mut env, &customer_id);
977    let currency_str = get_string(&mut env, &currency);
978
979    let customer_uuid = if customer_id_str.is_empty() {
980        None
981    } else {
982        uuid::Uuid::parse_str(&customer_id_str).ok()
983    };
984
985    let result = use_handle(ptr, |commerce| {
986        commerce
987            .carts()
988            .create(CreateCart {
989                customer_id: customer_uuid.map(Into::into),
990                customer_email: None,
991                customer_name: None,
992                currency: if currency_str.is_empty() { None } else { Some(currency_str) },
993                ..Default::default()
994            })
995            .map_err(|e| e.to_string())
996    });
997
998    match result {
999        Ok(cart) => create_cart_object(&mut env, &cart),
1000        Err(e) => {
1001            throw_exception(&mut env, &e);
1002            JObject::null()
1003        }
1004    }
1005}
1006
1007#[unsafe(no_mangle)]
1008pub extern "system" fn Java_com_stateset_embedded_Carts_nativeGet<'local>(
1009    mut env: JNIEnv<'local>,
1010    _class: JClass<'local>,
1011    ptr: jlong,
1012    id: JString<'local>,
1013) -> JObject<'local> {
1014    let id_str = get_string(&mut env, &id);
1015    let uuid = match uuid::Uuid::parse_str(&id_str) {
1016        Ok(u) => u,
1017        Err(_) => {
1018            throw_exception(&mut env, "Invalid UUID");
1019            return JObject::null();
1020        }
1021    };
1022
1023    let result =
1024        use_handle(ptr, |commerce| commerce.carts().get(uuid.into()).map_err(|e| e.to_string()));
1025
1026    match result {
1027        Ok(Some(cart)) => create_cart_object(&mut env, &cart),
1028        Ok(None) => JObject::null(),
1029        Err(e) => {
1030            throw_exception(&mut env, &e);
1031            JObject::null()
1032        }
1033    }
1034}
1035
1036#[unsafe(no_mangle)]
1037pub extern "system" fn Java_com_stateset_embedded_Carts_nativeAddItem<'local>(
1038    mut env: JNIEnv<'local>,
1039    _class: JClass<'local>,
1040    ptr: jlong,
1041    cart_id: JString<'local>,
1042    sku: JString<'local>,
1043    name: JString<'local>,
1044    quantity: jint,
1045    unit_price: jdouble,
1046) -> JObject<'local> {
1047    let cart_id_str = get_string(&mut env, &cart_id);
1048    let sku_str = get_string(&mut env, &sku);
1049    let name_str = get_string(&mut env, &name);
1050    let price = Decimal::try_from(unit_price).unwrap_or_default();
1051
1052    let uuid = match uuid::Uuid::parse_str(&cart_id_str) {
1053        Ok(u) => u,
1054        Err(_) => {
1055            throw_exception(&mut env, "Invalid cart UUID");
1056            return JObject::null();
1057        }
1058    };
1059
1060    let result = use_handle(ptr, |commerce| {
1061        commerce
1062            .carts()
1063            .add_item(
1064                uuid.into(),
1065                AddCartItem {
1066                    sku: sku_str,
1067                    name: name_str,
1068                    quantity,
1069                    unit_price: price,
1070                    ..Default::default()
1071                },
1072            )
1073            .map_err(|e| e.to_string())?;
1074        // Return the updated cart
1075        commerce
1076            .carts()
1077            .get(uuid.into())
1078            .map_err(|e| e.to_string())?
1079            .ok_or_else(|| "Cart not found".to_string())
1080    });
1081
1082    match result {
1083        Ok(cart) => create_cart_object(&mut env, &cart),
1084        Err(e) => {
1085            throw_exception(&mut env, &e);
1086            JObject::null()
1087        }
1088    }
1089}
1090
1091#[unsafe(no_mangle)]
1092pub extern "system" fn Java_com_stateset_embedded_Carts_nativeCheckout<'local>(
1093    mut env: JNIEnv<'local>,
1094    _class: JClass<'local>,
1095    ptr: jlong,
1096    cart_id: JString<'local>,
1097) -> JObject<'local> {
1098    let cart_id_str = get_string(&mut env, &cart_id);
1099
1100    let uuid = match uuid::Uuid::parse_str(&cart_id_str) {
1101        Ok(u) => u,
1102        Err(_) => {
1103            throw_exception(&mut env, "Invalid cart UUID");
1104            return JObject::null();
1105        }
1106    };
1107
1108    let result = use_handle(ptr, |commerce| {
1109        let checkout_result = commerce.carts().complete(uuid.into()).map_err(|e| e.to_string())?;
1110        // Get the order
1111        commerce
1112            .orders()
1113            .get(checkout_result.order_id)
1114            .map_err(|e| e.to_string())?
1115            .ok_or_else(|| "Order not found".to_string())
1116    });
1117
1118    match result {
1119        Ok(order) => create_order_object(&mut env, &order),
1120        Err(e) => {
1121            throw_exception(&mut env, &e);
1122            JObject::null()
1123        }
1124    }
1125}
1126
1127// =============================================================================
1128// Payments API
1129// =============================================================================
1130
1131#[unsafe(no_mangle)]
1132pub extern "system" fn Java_com_stateset_embedded_Payments_nativeRecordPayment<'local>(
1133    mut env: JNIEnv<'local>,
1134    _class: JClass<'local>,
1135    ptr: jlong,
1136    order_id: JString<'local>,
1137    amount: jdouble,
1138    method: JString<'local>,
1139    reference: JString<'local>,
1140) {
1141    let order_id_str = get_string(&mut env, &order_id);
1142    let method_str = get_string(&mut env, &method);
1143    let reference_str = get_string(&mut env, &reference);
1144    let amt = Decimal::try_from(amount).unwrap_or_default();
1145
1146    let uuid = match uuid::Uuid::parse_str(&order_id_str) {
1147        Ok(u) => u,
1148        Err(_) => {
1149            throw_exception(&mut env, "Invalid order UUID");
1150            return;
1151        }
1152    };
1153
1154    // Map method string to PaymentMethodType
1155    let payment_method = match method_str.to_lowercase().as_str() {
1156        "credit" | "credit_card" | "creditcard" | "card" => PaymentMethodType::CreditCard,
1157        "debit" | "debit_card" | "debitcard" => PaymentMethodType::DebitCard,
1158        "bank" | "bank_transfer" | "banktransfer" | "ach" => PaymentMethodType::BankTransfer,
1159        "paypal" => PaymentMethodType::PayPal,
1160        "apple" | "apple_pay" | "applepay" => PaymentMethodType::ApplePay,
1161        "google" | "google_pay" | "googlepay" => PaymentMethodType::GooglePay,
1162        "crypto" | "cryptocurrency" => PaymentMethodType::Crypto,
1163        _ => PaymentMethodType::CreditCard,
1164    };
1165
1166    let result = use_handle(ptr, |commerce| {
1167        commerce
1168            .payments()
1169            .create(CreatePayment {
1170                order_id: Some(uuid.into()),
1171                amount: amt,
1172                currency: Some("USD".to_string()),
1173                payment_method,
1174                external_id: if reference_str.is_empty() { None } else { Some(reference_str) },
1175                ..Default::default()
1176            })
1177            .map_err(|e| e.to_string())
1178    });
1179
1180    if let Err(e) = result {
1181        throw_exception(&mut env, &e);
1182    }
1183}
1184
1185// =============================================================================
1186// Returns API
1187// =============================================================================
1188
1189#[unsafe(no_mangle)]
1190pub extern "system" fn Java_com_stateset_embedded_Returns_nativeCreate<'local>(
1191    mut env: JNIEnv<'local>,
1192    _class: JClass<'local>,
1193    ptr: jlong,
1194    order_id: JString<'local>,
1195    reason: JString<'local>,
1196) -> JObject<'local> {
1197    let order_id_str = get_string(&mut env, &order_id);
1198    let reason_str = get_string(&mut env, &reason);
1199
1200    let uuid = match uuid::Uuid::parse_str(&order_id_str) {
1201        Ok(u) => u,
1202        Err(_) => {
1203            throw_exception(&mut env, "Invalid order UUID");
1204            return JObject::null();
1205        }
1206    };
1207
1208    // Map reason string to ReturnReason enum
1209    let reason_enum = match reason_str.to_lowercase().as_str() {
1210        "defective" => ReturnReason::Defective,
1211        "wrong_item" | "wrongitem" => ReturnReason::WrongItem,
1212        "not_as_described" | "notasdescribed" => ReturnReason::NotAsDescribed,
1213        "no_longer_needed" | "nolongerneeded" => ReturnReason::NoLongerNeeded,
1214        "better_price_found" | "betterpricefound" => ReturnReason::BetterPriceFound,
1215        _ => ReturnReason::Other,
1216    };
1217
1218    let result = use_handle(ptr, |commerce| {
1219        let order = commerce.orders().get(uuid.into()).map_err(|e| e.to_string())?;
1220        let order = order.ok_or_else(|| format!("Order not found: {}", uuid))?;
1221        let items: Vec<CreateReturnItem> = order
1222            .items
1223            .iter()
1224            .map(|item| CreateReturnItem {
1225                order_item_id: item.id,
1226                quantity: item.quantity,
1227                condition: None,
1228            })
1229            .collect();
1230        if items.is_empty() {
1231            return Err("Return must have at least one item".to_string());
1232        }
1233        commerce
1234            .returns()
1235            .create(CreateReturn {
1236                order_id: uuid.into(),
1237                reason: reason_enum,
1238                items,
1239                ..Default::default()
1240            })
1241            .map_err(|e| e.to_string())
1242    });
1243
1244    match result {
1245        Ok(ret) => create_return_object(&mut env, &ret),
1246        Err(e) => {
1247            throw_exception(&mut env, &e);
1248            JObject::null()
1249        }
1250    }
1251}
1252
1253#[unsafe(no_mangle)]
1254pub extern "system" fn Java_com_stateset_embedded_Returns_nativeProcess<'local>(
1255    mut env: JNIEnv<'local>,
1256    _class: JClass<'local>,
1257    ptr: jlong,
1258    return_id: JString<'local>,
1259) {
1260    let return_id_str = get_string(&mut env, &return_id);
1261
1262    let uuid = match uuid::Uuid::parse_str(&return_id_str) {
1263        Ok(u) => u,
1264        Err(_) => {
1265            throw_exception(&mut env, "Invalid return UUID");
1266            return;
1267        }
1268    };
1269
1270    let result = use_handle(ptr, |commerce| {
1271        commerce.returns().approve(uuid.into()).map_err(|e| e.to_string())
1272    });
1273
1274    if let Err(e) = result {
1275        throw_exception(&mut env, &e);
1276    }
1277}
1278
1279#[unsafe(no_mangle)]
1280pub extern "system" fn Java_com_stateset_embedded_Returns_nativeRefund<'local>(
1281    mut env: JNIEnv<'local>,
1282    _class: JClass<'local>,
1283    ptr: jlong,
1284    return_id: JString<'local>,
1285    amount: jdouble,
1286) {
1287    let return_id_str = get_string(&mut env, &return_id);
1288    let _amt = Decimal::try_from(amount).unwrap_or_default();
1289
1290    let uuid = match uuid::Uuid::parse_str(&return_id_str) {
1291        Ok(u) => u,
1292        Err(_) => {
1293            throw_exception(&mut env, "Invalid return UUID");
1294            return;
1295        }
1296    };
1297
1298    let result = use_handle(ptr, |commerce| {
1299        commerce.returns().complete(uuid.into()).map_err(|e| e.to_string())
1300    });
1301
1302    if let Err(e) = result {
1303        throw_exception(&mut env, &e);
1304    }
1305}
1306
1307// =============================================================================
1308// Analytics API
1309// =============================================================================
1310
1311#[unsafe(no_mangle)]
1312pub extern "system" fn Java_com_stateset_embedded_Analytics_nativeSalesSummary<'local>(
1313    mut env: JNIEnv<'local>,
1314    _class: JClass<'local>,
1315    ptr: jlong,
1316    days: jint,
1317) -> JObject<'local> {
1318    let period = if days <= 0 {
1319        TimePeriod::AllTime
1320    } else if days <= 7 {
1321        TimePeriod::Last7Days
1322    } else if days <= 30 {
1323        TimePeriod::Last30Days
1324    } else if days <= 90 {
1325        TimePeriod::ThisQuarter
1326    } else {
1327        TimePeriod::AllTime
1328    };
1329
1330    let result = use_handle(ptr, |commerce| {
1331        commerce
1332            .analytics()
1333            .sales_summary(AnalyticsQuery::new().period(period))
1334            .map_err(|e| e.to_string())
1335    });
1336
1337    match result {
1338        Ok(summary) => create_sales_summary_object(&mut env, &summary),
1339        Err(e) => {
1340            throw_exception(&mut env, &e);
1341            JObject::null()
1342        }
1343    }
1344}
1345
1346// =============================================================================
1347// Quality Control API
1348// =============================================================================
1349
1350fn to_json_string<'a>(env: &JNIEnv<'a>, data: &impl serde::Serialize) -> JObject<'a> {
1351    match serde_json::to_string(data) {
1352        Ok(json) => env.new_string(&json).map(|s| s.into()).unwrap_or(JObject::null()),
1353        Err(_) => JObject::null(),
1354    }
1355}
1356
1357#[unsafe(no_mangle)]
1358pub extern "system" fn Java_com_stateset_embedded_Quality_nativeCreateInspection<'local>(
1359    mut env: JNIEnv<'local>,
1360    _class: JClass<'local>,
1361    ptr: jlong,
1362    sku: JString<'local>,
1363    inspection_type: JString<'local>,
1364    quantity: jdouble,
1365) -> JObject<'local> {
1366    let sku_str = get_string(&mut env, &sku);
1367    let type_str = get_string(&mut env, &inspection_type);
1368    let qty = Decimal::try_from(quantity).unwrap_or_default();
1369
1370    let itype = match type_str.to_lowercase().as_str() {
1371        "incoming" => stateset_core::InspectionType::Incoming,
1372        "in_process" => stateset_core::InspectionType::InProcess,
1373        "final" => stateset_core::InspectionType::Final,
1374        "random" => stateset_core::InspectionType::Random,
1375        _ => stateset_core::InspectionType::Incoming,
1376    };
1377
1378    let result = use_handle(ptr, |commerce| {
1379        commerce
1380            .quality()
1381            .create_inspection(stateset_core::CreateInspection {
1382                inspection_type: itype,
1383                reference_type: sku_str.clone(),
1384                reference_id: uuid::Uuid::new_v4(),
1385                items: vec![stateset_core::CreateInspectionItem {
1386                    sku: sku_str,
1387                    quantity_to_inspect: qty,
1388                    ..Default::default()
1389                }],
1390                ..Default::default()
1391            })
1392            .map_err(|e| e.to_string())
1393    });
1394
1395    match result {
1396        Ok(inspection) => to_json_string(&env, &inspection),
1397        Err(e) => {
1398            throw_exception(&mut env, &e);
1399            JObject::null()
1400        }
1401    }
1402}
1403
1404#[unsafe(no_mangle)]
1405pub extern "system" fn Java_com_stateset_embedded_Quality_nativeListInspections<'local>(
1406    mut env: JNIEnv<'local>,
1407    _class: JClass<'local>,
1408    ptr: jlong,
1409) -> JObject<'local> {
1410    let result = use_handle(ptr, |commerce| {
1411        commerce.quality().list_inspections(Default::default()).map_err(|e| e.to_string())
1412    });
1413    match result {
1414        Ok(inspections) => to_json_string(&env, &inspections),
1415        Err(e) => {
1416            throw_exception(&mut env, &e);
1417            JObject::null()
1418        }
1419    }
1420}
1421
1422#[unsafe(no_mangle)]
1423pub extern "system" fn Java_com_stateset_embedded_Quality_nativeCreateNcr<'local>(
1424    mut env: JNIEnv<'local>,
1425    _class: JClass<'local>,
1426    ptr: jlong,
1427    sku: JString<'local>,
1428    description: JString<'local>,
1429    quantity: jdouble,
1430) -> JObject<'local> {
1431    let sku_str = get_string(&mut env, &sku);
1432    let desc_str = get_string(&mut env, &description);
1433    let qty = Decimal::try_from(quantity).unwrap_or_default();
1434
1435    let result = use_handle(ptr, |commerce| {
1436        commerce
1437            .quality()
1438            .create_ncr(stateset_core::CreateNcr {
1439                sku: sku_str,
1440                description: desc_str,
1441                quantity_affected: qty,
1442                ..Default::default()
1443            })
1444            .map_err(|e| e.to_string())
1445    });
1446
1447    match result {
1448        Ok(ncr) => to_json_string(&env, &ncr),
1449        Err(e) => {
1450            throw_exception(&mut env, &e);
1451            JObject::null()
1452        }
1453    }
1454}
1455
1456#[unsafe(no_mangle)]
1457pub extern "system" fn Java_com_stateset_embedded_Quality_nativeCreateHold<'local>(
1458    mut env: JNIEnv<'local>,
1459    _class: JClass<'local>,
1460    ptr: jlong,
1461    sku: JString<'local>,
1462    reason: JString<'local>,
1463    quantity: jdouble,
1464) -> JObject<'local> {
1465    let sku_str = get_string(&mut env, &sku);
1466    let reason_str = get_string(&mut env, &reason);
1467    let qty = Decimal::try_from(quantity).unwrap_or_default();
1468
1469    let result = use_handle(ptr, |commerce| {
1470        commerce
1471            .quality()
1472            .create_hold(stateset_core::CreateQualityHold {
1473                sku: sku_str,
1474                reason: reason_str,
1475                quantity: qty,
1476                ..Default::default()
1477            })
1478            .map_err(|e| e.to_string())
1479    });
1480
1481    match result {
1482        Ok(hold) => to_json_string(&env, &hold),
1483        Err(e) => {
1484            throw_exception(&mut env, &e);
1485            JObject::null()
1486        }
1487    }
1488}
1489
1490// =============================================================================
1491// Lots/Batch Tracking API
1492// =============================================================================
1493
1494#[unsafe(no_mangle)]
1495pub extern "system" fn Java_com_stateset_embedded_Lots_nativeCreate<'local>(
1496    mut env: JNIEnv<'local>,
1497    _class: JClass<'local>,
1498    ptr: jlong,
1499    sku: JString<'local>,
1500    lot_number: JString<'local>,
1501    quantity: jdouble,
1502) -> JObject<'local> {
1503    let sku_str = get_string(&mut env, &sku);
1504    let lot_str = get_string(&mut env, &lot_number);
1505    let qty = Decimal::try_from(quantity).unwrap_or_default();
1506
1507    let result = use_handle(ptr, |commerce| {
1508        commerce
1509            .lots()
1510            .create(stateset_core::CreateLot {
1511                sku: sku_str,
1512                lot_number: Some(lot_str),
1513                quantity: qty,
1514                ..Default::default()
1515            })
1516            .map_err(|e| e.to_string())
1517    });
1518
1519    match result {
1520        Ok(lot) => to_json_string(&env, &lot),
1521        Err(e) => {
1522            throw_exception(&mut env, &e);
1523            JObject::null()
1524        }
1525    }
1526}
1527
1528#[unsafe(no_mangle)]
1529pub extern "system" fn Java_com_stateset_embedded_Lots_nativeList<'local>(
1530    mut env: JNIEnv<'local>,
1531    _class: JClass<'local>,
1532    ptr: jlong,
1533) -> JObject<'local> {
1534    let result = use_handle(ptr, |commerce| {
1535        commerce.lots().list(Default::default()).map_err(|e| e.to_string())
1536    });
1537    match result {
1538        Ok(lots) => to_json_string(&env, &lots),
1539        Err(e) => {
1540            throw_exception(&mut env, &e);
1541            JObject::null()
1542        }
1543    }
1544}
1545
1546#[unsafe(no_mangle)]
1547pub extern "system" fn Java_com_stateset_embedded_Lots_nativeGetBySku<'local>(
1548    mut env: JNIEnv<'local>,
1549    _class: JClass<'local>,
1550    ptr: jlong,
1551    sku: JString<'local>,
1552) -> JObject<'local> {
1553    let sku_str = get_string(&mut env, &sku);
1554    let result = use_handle(ptr, |commerce| {
1555        commerce.lots().get_available_lots_for_sku(&sku_str).map_err(|e| e.to_string())
1556    });
1557    match result {
1558        Ok(lots) => to_json_string(&env, &lots),
1559        Err(e) => {
1560            throw_exception(&mut env, &e);
1561            JObject::null()
1562        }
1563    }
1564}
1565
1566#[unsafe(no_mangle)]
1567pub extern "system" fn Java_com_stateset_embedded_Lots_nativeGetExpiring<'local>(
1568    mut env: JNIEnv<'local>,
1569    _class: JClass<'local>,
1570    ptr: jlong,
1571    days: jint,
1572) -> JObject<'local> {
1573    let result = use_handle(ptr, |commerce| {
1574        commerce.lots().get_expiring_lots(days).map_err(|e| e.to_string())
1575    });
1576    match result {
1577        Ok(lots) => to_json_string(&env, &lots),
1578        Err(e) => {
1579            throw_exception(&mut env, &e);
1580            JObject::null()
1581        }
1582    }
1583}
1584
1585// =============================================================================
1586// Serial Numbers API
1587// =============================================================================
1588
1589#[unsafe(no_mangle)]
1590pub extern "system" fn Java_com_stateset_embedded_Serials_nativeCreate<'local>(
1591    mut env: JNIEnv<'local>,
1592    _class: JClass<'local>,
1593    ptr: jlong,
1594    sku: JString<'local>,
1595    serial_number: JString<'local>,
1596) -> JObject<'local> {
1597    let sku_str = get_string(&mut env, &sku);
1598    let serial_str = get_string(&mut env, &serial_number);
1599
1600    let result = use_handle(ptr, |commerce| {
1601        commerce
1602            .serials()
1603            .create(stateset_core::CreateSerialNumber {
1604                sku: sku_str,
1605                serial: Some(serial_str),
1606                ..Default::default()
1607            })
1608            .map_err(|e| e.to_string())
1609    });
1610
1611    match result {
1612        Ok(serial) => to_json_string(&env, &serial),
1613        Err(e) => {
1614            throw_exception(&mut env, &e);
1615            JObject::null()
1616        }
1617    }
1618}
1619
1620#[unsafe(no_mangle)]
1621pub extern "system" fn Java_com_stateset_embedded_Serials_nativeList<'local>(
1622    mut env: JNIEnv<'local>,
1623    _class: JClass<'local>,
1624    ptr: jlong,
1625) -> JObject<'local> {
1626    let result = use_handle(ptr, |commerce| {
1627        commerce.serials().list(Default::default()).map_err(|e| e.to_string())
1628    });
1629    match result {
1630        Ok(serials) => to_json_string(&env, &serials),
1631        Err(e) => {
1632            throw_exception(&mut env, &e);
1633            JObject::null()
1634        }
1635    }
1636}
1637
1638#[unsafe(no_mangle)]
1639pub extern "system" fn Java_com_stateset_embedded_Serials_nativeGetByNumber<'local>(
1640    mut env: JNIEnv<'local>,
1641    _class: JClass<'local>,
1642    ptr: jlong,
1643    serial_number: JString<'local>,
1644) -> JObject<'local> {
1645    let serial_str = get_string(&mut env, &serial_number);
1646    let result = use_handle(ptr, |commerce| {
1647        commerce.serials().get_by_serial(&serial_str).map_err(|e| e.to_string())
1648    });
1649    match result {
1650        Ok(Some(serial)) => to_json_string(&env, &serial),
1651        Ok(None) => JObject::null(),
1652        Err(e) => {
1653            throw_exception(&mut env, &e);
1654            JObject::null()
1655        }
1656    }
1657}
1658
1659// =============================================================================
1660// Warehouse API
1661// =============================================================================
1662
1663#[unsafe(no_mangle)]
1664pub extern "system" fn Java_com_stateset_embedded_Warehouse_nativeCreate<'local>(
1665    mut env: JNIEnv<'local>,
1666    _class: JClass<'local>,
1667    ptr: jlong,
1668    code: JString<'local>,
1669    name: JString<'local>,
1670) -> JObject<'local> {
1671    let code_str = get_string(&mut env, &code);
1672    let name_str = get_string(&mut env, &name);
1673
1674    let result = use_handle(ptr, |commerce| {
1675        commerce
1676            .warehouse()
1677            .create_warehouse(stateset_core::CreateWarehouse {
1678                code: code_str,
1679                name: name_str,
1680                ..Default::default()
1681            })
1682            .map_err(|e| e.to_string())
1683    });
1684
1685    match result {
1686        Ok(warehouse) => to_json_string(&env, &warehouse),
1687        Err(e) => {
1688            throw_exception(&mut env, &e);
1689            JObject::null()
1690        }
1691    }
1692}
1693
1694#[unsafe(no_mangle)]
1695pub extern "system" fn Java_com_stateset_embedded_Warehouse_nativeList<'local>(
1696    mut env: JNIEnv<'local>,
1697    _class: JClass<'local>,
1698    ptr: jlong,
1699) -> JObject<'local> {
1700    let result = use_handle(ptr, |commerce| {
1701        commerce.warehouse().list_warehouses(Default::default()).map_err(|e| e.to_string())
1702    });
1703    match result {
1704        Ok(warehouses) => to_json_string(&env, &warehouses),
1705        Err(e) => {
1706            throw_exception(&mut env, &e);
1707            JObject::null()
1708        }
1709    }
1710}
1711
1712#[unsafe(no_mangle)]
1713pub extern "system" fn Java_com_stateset_embedded_Warehouse_nativeCreateLocation<'local>(
1714    mut env: JNIEnv<'local>,
1715    _class: JClass<'local>,
1716    ptr: jlong,
1717    warehouse_id: jint,
1718    code: JString<'local>,
1719) -> JObject<'local> {
1720    let code_str = get_string(&mut env, &code);
1721
1722    let result = use_handle(ptr, |commerce| {
1723        commerce
1724            .warehouse()
1725            .create_location(stateset_core::CreateLocation {
1726                warehouse_id,
1727                code: Some(code_str),
1728                ..Default::default()
1729            })
1730            .map_err(|e| e.to_string())
1731    });
1732
1733    match result {
1734        Ok(location) => to_json_string(&env, &location),
1735        Err(e) => {
1736            throw_exception(&mut env, &e);
1737            JObject::null()
1738        }
1739    }
1740}
1741
1742// =============================================================================
1743// Receiving API
1744// =============================================================================
1745
1746#[unsafe(no_mangle)]
1747pub extern "system" fn Java_com_stateset_embedded_Receiving_nativeCreateReceipt<'local>(
1748    mut env: JNIEnv<'local>,
1749    _class: JClass<'local>,
1750    ptr: jlong,
1751) -> JObject<'local> {
1752    let result = use_handle(ptr, |commerce| {
1753        commerce
1754            .receiving()
1755            .create_receipt(stateset_core::CreateReceipt::default())
1756            .map_err(|e| e.to_string())
1757    });
1758
1759    match result {
1760        Ok(receipt) => to_json_string(&env, &receipt),
1761        Err(e) => {
1762            throw_exception(&mut env, &e);
1763            JObject::null()
1764        }
1765    }
1766}
1767
1768#[unsafe(no_mangle)]
1769pub extern "system" fn Java_com_stateset_embedded_Receiving_nativeListReceipts<'local>(
1770    mut env: JNIEnv<'local>,
1771    _class: JClass<'local>,
1772    ptr: jlong,
1773) -> JObject<'local> {
1774    let result = use_handle(ptr, |commerce| {
1775        commerce.receiving().list_receipts(Default::default()).map_err(|e| e.to_string())
1776    });
1777    match result {
1778        Ok(receipts) => to_json_string(&env, &receipts),
1779        Err(e) => {
1780            throw_exception(&mut env, &e);
1781            JObject::null()
1782        }
1783    }
1784}
1785
1786#[unsafe(no_mangle)]
1787pub extern "system" fn Java_com_stateset_embedded_Receiving_nativeAddLine<'local>(
1788    mut env: JNIEnv<'local>,
1789    _class: JClass<'local>,
1790    ptr: jlong,
1791    receipt_id: JString<'local>,
1792    sku: JString<'local>,
1793    quantity: jdouble,
1794) -> JObject<'local> {
1795    let receipt_id_str = get_string(&mut env, &receipt_id);
1796    let sku_str = get_string(&mut env, &sku);
1797    let qty = Decimal::try_from(quantity).unwrap_or_default();
1798
1799    let uuid = match uuid::Uuid::parse_str(&receipt_id_str) {
1800        Ok(u) => u,
1801        Err(_) => {
1802            throw_exception(&mut env, "Invalid UUID");
1803            return JObject::null();
1804        }
1805    };
1806
1807    // Note: To receive items, use receive_items with ReceiveItems input
1808    // For simplicity, this creates a ReceiveItemLine placeholder
1809    let result = use_handle(ptr, |commerce| {
1810        commerce
1811            .receiving()
1812            .receive_items(stateset_core::ReceiveItems {
1813                receipt_id: uuid,
1814                items: vec![stateset_core::ReceiveItemLine {
1815                    receipt_item_id: uuid::Uuid::new_v4(),
1816                    quantity_received: qty,
1817                    quantity_rejected: Some(Decimal::ZERO),
1818                    rejection_reason: None,
1819                    lot_number: None,
1820                    serial_numbers: None,
1821                    expiration_date: None,
1822                    notes: Some(sku_str),
1823                }],
1824                receiving_location_id: None,
1825                received_by: None,
1826            })
1827            .map_err(|e| e.to_string())
1828    });
1829
1830    match result {
1831        Ok(line) => to_json_string(&env, &line),
1832        Err(e) => {
1833            throw_exception(&mut env, &e);
1834            JObject::null()
1835        }
1836    }
1837}
1838
1839#[unsafe(no_mangle)]
1840pub extern "system" fn Java_com_stateset_embedded_Receiving_nativeComplete<'local>(
1841    mut env: JNIEnv<'local>,
1842    _class: JClass<'local>,
1843    ptr: jlong,
1844    receipt_id: JString<'local>,
1845) -> JObject<'local> {
1846    let receipt_id_str = get_string(&mut env, &receipt_id);
1847    let uuid = match uuid::Uuid::parse_str(&receipt_id_str) {
1848        Ok(u) => u,
1849        Err(_) => {
1850            throw_exception(&mut env, "Invalid UUID");
1851            return JObject::null();
1852        }
1853    };
1854
1855    let result = use_handle(ptr, |commerce| {
1856        commerce.receiving().complete_receiving(uuid).map_err(|e| e.to_string())
1857    });
1858
1859    match result {
1860        Ok(receipt) => to_json_string(&env, &receipt),
1861        Err(e) => {
1862            throw_exception(&mut env, &e);
1863            JObject::null()
1864        }
1865    }
1866}
1867
1868// =============================================================================
1869// Fulfillment API
1870// =============================================================================
1871
1872#[unsafe(no_mangle)]
1873pub extern "system" fn Java_com_stateset_embedded_Fulfillment_nativeCreateWave<'local>(
1874    mut env: JNIEnv<'local>,
1875    _class: JClass<'local>,
1876    ptr: jlong,
1877    warehouse_id: jint,
1878) -> JObject<'local> {
1879    let result = use_handle(ptr, |commerce| {
1880        commerce
1881            .fulfillment()
1882            .create_wave(stateset_core::CreateWave { warehouse_id, ..Default::default() })
1883            .map_err(|e| e.to_string())
1884    });
1885
1886    match result {
1887        Ok(wave) => to_json_string(&env, &wave),
1888        Err(e) => {
1889            throw_exception(&mut env, &e);
1890            JObject::null()
1891        }
1892    }
1893}
1894
1895#[unsafe(no_mangle)]
1896pub extern "system" fn Java_com_stateset_embedded_Fulfillment_nativeListWaves<'local>(
1897    mut env: JNIEnv<'local>,
1898    _class: JClass<'local>,
1899    ptr: jlong,
1900) -> JObject<'local> {
1901    let result = use_handle(ptr, |commerce| {
1902        commerce.fulfillment().list_waves(Default::default()).map_err(|e| e.to_string())
1903    });
1904    match result {
1905        Ok(waves) => to_json_string(&env, &waves),
1906        Err(e) => {
1907            throw_exception(&mut env, &e);
1908            JObject::null()
1909        }
1910    }
1911}
1912
1913#[unsafe(no_mangle)]
1914pub extern "system" fn Java_com_stateset_embedded_Fulfillment_nativeCreatePickTask<'local>(
1915    mut env: JNIEnv<'local>,
1916    _class: JClass<'local>,
1917    ptr: jlong,
1918    order_id: JString<'local>,
1919    warehouse_id: jint,
1920) -> JObject<'local> {
1921    let order_id_str = get_string(&mut env, &order_id);
1922
1923    let uuid = match uuid::Uuid::parse_str(&order_id_str) {
1924        Ok(u) => u,
1925        Err(_) => {
1926            throw_exception(&mut env, "Invalid UUID");
1927            return JObject::null();
1928        }
1929    };
1930
1931    // Create all pick tasks for the order at once
1932    let result = use_handle(ptr, |commerce| {
1933        commerce
1934            .fulfillment()
1935            .create_picks_for_order(uuid.into(), warehouse_id)
1936            .map_err(|e| e.to_string())
1937    });
1938
1939    match result {
1940        Ok(tasks) => to_json_string(&env, &tasks),
1941        Err(e) => {
1942            throw_exception(&mut env, &e);
1943            JObject::null()
1944        }
1945    }
1946}
1947
1948// =============================================================================
1949// Accounts Payable API
1950// =============================================================================
1951
1952#[unsafe(no_mangle)]
1953pub extern "system" fn Java_com_stateset_embedded_AccountsPayable_nativeCreateBill<'local>(
1954    mut env: JNIEnv<'local>,
1955    _class: JClass<'local>,
1956    ptr: jlong,
1957    supplier_id: JString<'local>,
1958    amount: jdouble,
1959) -> JObject<'local> {
1960    let supplier_id_str = get_string(&mut env, &supplier_id);
1961    let amt = Decimal::try_from(amount).unwrap_or_default();
1962
1963    let uuid = match uuid::Uuid::parse_str(&supplier_id_str) {
1964        Ok(u) => u,
1965        Err(_) => {
1966            throw_exception(&mut env, "Invalid UUID");
1967            return JObject::null();
1968        }
1969    };
1970
1971    let result = use_handle(ptr, |commerce| {
1972        commerce
1973            .accounts_payable()
1974            .create_bill(stateset_core::CreateBill {
1975                supplier_id: uuid,
1976                due_date: chrono::Utc::now() + chrono::Duration::days(30),
1977                items: vec![stateset_core::CreateBillItem {
1978                    description: "Bill amount".to_string(),
1979                    quantity: Decimal::from(1),
1980                    unit_price: amt,
1981                    ..Default::default()
1982                }],
1983                ..Default::default()
1984            })
1985            .map_err(|e| e.to_string())
1986    });
1987
1988    match result {
1989        Ok(bill) => to_json_string(&env, &bill),
1990        Err(e) => {
1991            throw_exception(&mut env, &e);
1992            JObject::null()
1993        }
1994    }
1995}
1996
1997#[unsafe(no_mangle)]
1998pub extern "system" fn Java_com_stateset_embedded_AccountsPayable_nativeListBills<'local>(
1999    mut env: JNIEnv<'local>,
2000    _class: JClass<'local>,
2001    ptr: jlong,
2002) -> JObject<'local> {
2003    let result = use_handle(ptr, |commerce| {
2004        commerce.accounts_payable().list_bills(Default::default()).map_err(|e| e.to_string())
2005    });
2006    match result {
2007        Ok(bills) => to_json_string(&env, &bills),
2008        Err(e) => {
2009            throw_exception(&mut env, &e);
2010            JObject::null()
2011        }
2012    }
2013}
2014
2015#[unsafe(no_mangle)]
2016pub extern "system" fn Java_com_stateset_embedded_AccountsPayable_nativeGetAgingSummary<'local>(
2017    mut env: JNIEnv<'local>,
2018    _class: JClass<'local>,
2019    ptr: jlong,
2020) -> JObject<'local> {
2021    let result = use_handle(ptr, |commerce| {
2022        commerce.accounts_payable().get_aging_summary().map_err(|e| e.to_string())
2023    });
2024    match result {
2025        Ok(summary) => to_json_string(&env, &summary),
2026        Err(e) => {
2027            throw_exception(&mut env, &e);
2028            JObject::null()
2029        }
2030    }
2031}
2032
2033// =============================================================================
2034// Accounts Receivable API
2035// =============================================================================
2036
2037#[unsafe(no_mangle)]
2038pub extern "system" fn Java_com_stateset_embedded_AccountsReceivable_nativeGetAgingSummary<
2039    'local,
2040>(
2041    mut env: JNIEnv<'local>,
2042    _class: JClass<'local>,
2043    ptr: jlong,
2044) -> JObject<'local> {
2045    let result = use_handle(ptr, |commerce| {
2046        commerce.accounts_receivable().get_aging_summary().map_err(|e| e.to_string())
2047    });
2048    match result {
2049        Ok(summary) => to_json_string(&env, &summary),
2050        Err(e) => {
2051            throw_exception(&mut env, &e);
2052            JObject::null()
2053        }
2054    }
2055}
2056
2057#[unsafe(no_mangle)]
2058pub extern "system" fn Java_com_stateset_embedded_AccountsReceivable_nativeGetDso<'local>(
2059    mut env: JNIEnv<'local>,
2060    _class: JClass<'local>,
2061    ptr: jlong,
2062    days: jint,
2063) -> jdouble {
2064    let result = use_handle(ptr, |commerce| {
2065        commerce.accounts_receivable().get_dso(days).map_err(|e| e.to_string())
2066    });
2067    match result {
2068        Ok(dso) => to_f64_or_nan(dso),
2069        Err(e) => {
2070            throw_exception(&mut env, &e);
2071            0.0
2072        }
2073    }
2074}
2075
2076#[unsafe(no_mangle)]
2077pub extern "system" fn Java_com_stateset_embedded_AccountsReceivable_nativeCreateCreditMemo<
2078    'local,
2079>(
2080    mut env: JNIEnv<'local>,
2081    _class: JClass<'local>,
2082    ptr: jlong,
2083    customer_id: JString<'local>,
2084    amount: jdouble,
2085    reason: JString<'local>,
2086) -> JObject<'local> {
2087    let customer_id_str = get_string(&mut env, &customer_id);
2088    let reason_str = get_string(&mut env, &reason);
2089    let amt = Decimal::try_from(amount).unwrap_or_default();
2090
2091    let uuid = match uuid::Uuid::parse_str(&customer_id_str) {
2092        Ok(u) => u,
2093        Err(_) => {
2094            throw_exception(&mut env, "Invalid UUID");
2095            return JObject::null();
2096        }
2097    };
2098
2099    // Convert reason string to CreditMemoReason enum
2100    let memo_reason = match reason_str.to_lowercase().as_str() {
2101        "returned_goods" | "return" => stateset_core::CreditMemoReason::ReturnedGoods,
2102        "pricing_error" | "pricing" => stateset_core::CreditMemoReason::PricingError,
2103        "overpayment" => stateset_core::CreditMemoReason::Overpayment,
2104        "damaged" => stateset_core::CreditMemoReason::Damaged,
2105        "service_credit" | "service" => stateset_core::CreditMemoReason::ServiceCredit,
2106        "goodwill" | "goodwill_adjustment" => stateset_core::CreditMemoReason::GoodwillAdjustment,
2107        _ => stateset_core::CreditMemoReason::Other,
2108    };
2109
2110    let result = use_handle(ptr, |commerce| {
2111        commerce
2112            .accounts_receivable()
2113            .create_credit_memo(stateset_core::CreateCreditMemo {
2114                customer_id: uuid,
2115                original_invoice_id: None,
2116                reason: memo_reason,
2117                amount: amt,
2118                notes: None,
2119            })
2120            .map_err(|e| e.to_string())
2121    });
2122
2123    match result {
2124        Ok(memo) => to_json_string(&env, &memo),
2125        Err(e) => {
2126            throw_exception(&mut env, &e);
2127            JObject::null()
2128        }
2129    }
2130}
2131
2132// =============================================================================
2133// Cost Accounting API
2134// =============================================================================
2135
2136#[unsafe(no_mangle)]
2137pub extern "system" fn Java_com_stateset_embedded_CostAccounting_nativeGetItemCost<'local>(
2138    mut env: JNIEnv<'local>,
2139    _class: JClass<'local>,
2140    ptr: jlong,
2141    sku: JString<'local>,
2142) -> JObject<'local> {
2143    let sku_str = get_string(&mut env, &sku);
2144    let result = use_handle(ptr, |commerce| {
2145        commerce.cost_accounting().get_item_cost(&sku_str).map_err(|e| e.to_string())
2146    });
2147    match result {
2148        Ok(Some(cost)) => to_json_string(&env, &cost),
2149        Ok(None) => JObject::null(),
2150        Err(e) => {
2151            throw_exception(&mut env, &e);
2152            JObject::null()
2153        }
2154    }
2155}
2156
2157#[unsafe(no_mangle)]
2158pub extern "system" fn Java_com_stateset_embedded_CostAccounting_nativeSetItemCost<'local>(
2159    mut env: JNIEnv<'local>,
2160    _class: JClass<'local>,
2161    ptr: jlong,
2162    sku: JString<'local>,
2163    standard_cost: jdouble,
2164) -> JObject<'local> {
2165    let sku_str = get_string(&mut env, &sku);
2166    let cost = Decimal::try_from(standard_cost).ok();
2167
2168    let result = use_handle(ptr, |commerce| {
2169        commerce
2170            .cost_accounting()
2171            .set_item_cost(stateset_core::SetItemCost {
2172                sku: sku_str,
2173                standard_cost: cost,
2174                ..Default::default()
2175            })
2176            .map_err(|e| e.to_string())
2177    });
2178
2179    match result {
2180        Ok(item_cost) => to_json_string(&env, &item_cost),
2181        Err(e) => {
2182            throw_exception(&mut env, &e);
2183            JObject::null()
2184        }
2185    }
2186}
2187
2188#[unsafe(no_mangle)]
2189pub extern "system" fn Java_com_stateset_embedded_CostAccounting_nativeGetTotalInventoryValue<
2190    'local,
2191>(
2192    mut env: JNIEnv<'local>,
2193    _class: JClass<'local>,
2194    ptr: jlong,
2195) -> jdouble {
2196    let result = use_handle(ptr, |commerce| {
2197        commerce.cost_accounting().get_total_inventory_value().map_err(|e| e.to_string())
2198    });
2199    match result {
2200        Ok(value) => to_f64_or_nan(value),
2201        Err(e) => {
2202            throw_exception(&mut env, &e);
2203            0.0
2204        }
2205    }
2206}
2207
2208// =============================================================================
2209// Credit Management API
2210// =============================================================================
2211
2212#[unsafe(no_mangle)]
2213pub extern "system" fn Java_com_stateset_embedded_Credit_nativeCreateAccount<'local>(
2214    mut env: JNIEnv<'local>,
2215    _class: JClass<'local>,
2216    ptr: jlong,
2217    customer_id: JString<'local>,
2218    credit_limit: jdouble,
2219) -> JObject<'local> {
2220    let customer_id_str = get_string(&mut env, &customer_id);
2221    let limit = Decimal::try_from(credit_limit).unwrap_or_default();
2222
2223    let uuid = match uuid::Uuid::parse_str(&customer_id_str) {
2224        Ok(u) => u,
2225        Err(_) => {
2226            throw_exception(&mut env, "Invalid UUID");
2227            return JObject::null();
2228        }
2229    };
2230
2231    let result = use_handle(ptr, |commerce| {
2232        commerce
2233            .credit()
2234            .create_credit_account(stateset_core::CreateCreditAccount {
2235                customer_id: uuid.into(),
2236                credit_limit: limit,
2237                ..Default::default()
2238            })
2239            .map_err(|e| e.to_string())
2240    });
2241
2242    match result {
2243        Ok(account) => to_json_string(&env, &account),
2244        Err(e) => {
2245            throw_exception(&mut env, &e);
2246            JObject::null()
2247        }
2248    }
2249}
2250
2251#[unsafe(no_mangle)]
2252pub extern "system" fn Java_com_stateset_embedded_Credit_nativeCheckCredit<'local>(
2253    mut env: JNIEnv<'local>,
2254    _class: JClass<'local>,
2255    ptr: jlong,
2256    customer_id: JString<'local>,
2257    order_amount: jdouble,
2258) -> JObject<'local> {
2259    let customer_id_str = get_string(&mut env, &customer_id);
2260    let amount = Decimal::try_from(order_amount).unwrap_or_default();
2261
2262    let uuid = match uuid::Uuid::parse_str(&customer_id_str) {
2263        Ok(u) => u,
2264        Err(_) => {
2265            throw_exception(&mut env, "Invalid UUID");
2266            return JObject::null();
2267        }
2268    };
2269
2270    let result = use_handle(ptr, |commerce| {
2271        commerce.credit().check_credit(uuid.into(), amount).map_err(|e| e.to_string())
2272    });
2273
2274    match result {
2275        Ok(check_result) => to_json_string(&env, &check_result),
2276        Err(e) => {
2277            throw_exception(&mut env, &e);
2278            JObject::null()
2279        }
2280    }
2281}
2282
2283#[unsafe(no_mangle)]
2284pub extern "system" fn Java_com_stateset_embedded_Credit_nativeGetOverLimitCustomers<'local>(
2285    mut env: JNIEnv<'local>,
2286    _class: JClass<'local>,
2287    ptr: jlong,
2288) -> JObject<'local> {
2289    let result = use_handle(ptr, |commerce| {
2290        commerce.credit().get_over_limit_customers().map_err(|e| e.to_string())
2291    });
2292    match result {
2293        Ok(customers) => to_json_string(&env, &customers),
2294        Err(e) => {
2295            throw_exception(&mut env, &e);
2296            JObject::null()
2297        }
2298    }
2299}
2300
2301// =============================================================================
2302// Backorder Management API
2303// =============================================================================
2304
2305#[unsafe(no_mangle)]
2306pub extern "system" fn Java_com_stateset_embedded_Backorders_nativeCreate<'local>(
2307    mut env: JNIEnv<'local>,
2308    _class: JClass<'local>,
2309    ptr: jlong,
2310    order_id: JString<'local>,
2311    customer_id: JString<'local>,
2312    sku: JString<'local>,
2313    quantity: jdouble,
2314) -> JObject<'local> {
2315    let order_id_str = get_string(&mut env, &order_id);
2316    let customer_id_str = get_string(&mut env, &customer_id);
2317    let sku_str = get_string(&mut env, &sku);
2318    let qty = Decimal::try_from(quantity).unwrap_or_default();
2319
2320    let order_uuid = match uuid::Uuid::parse_str(&order_id_str) {
2321        Ok(u) => u,
2322        Err(_) => {
2323            throw_exception(&mut env, "Invalid order UUID");
2324            return JObject::null();
2325        }
2326    };
2327    let customer_uuid = match uuid::Uuid::parse_str(&customer_id_str) {
2328        Ok(u) => u,
2329        Err(_) => {
2330            throw_exception(&mut env, "Invalid customer UUID");
2331            return JObject::null();
2332        }
2333    };
2334
2335    let result = use_handle(ptr, |commerce| {
2336        commerce
2337            .backorder()
2338            .create_backorder(stateset_core::CreateBackorder {
2339                order_id: order_uuid,
2340                order_line_id: None,
2341                customer_id: customer_uuid,
2342                sku: sku_str,
2343                quantity: qty,
2344                priority: None,
2345                expected_date: None,
2346                promised_date: None,
2347                source_location_id: None,
2348                notes: None,
2349            })
2350            .map_err(|e| e.to_string())
2351    });
2352
2353    match result {
2354        Ok(backorder) => to_json_string(&env, &backorder),
2355        Err(e) => {
2356            throw_exception(&mut env, &e);
2357            JObject::null()
2358        }
2359    }
2360}
2361
2362#[unsafe(no_mangle)]
2363pub extern "system" fn Java_com_stateset_embedded_Backorders_nativeList<'local>(
2364    mut env: JNIEnv<'local>,
2365    _class: JClass<'local>,
2366    ptr: jlong,
2367) -> JObject<'local> {
2368    let result = use_handle(ptr, |commerce| {
2369        commerce.backorder().list_backorders(Default::default()).map_err(|e| e.to_string())
2370    });
2371    match result {
2372        Ok(backorders) => to_json_string(&env, &backorders),
2373        Err(e) => {
2374            throw_exception(&mut env, &e);
2375            JObject::null()
2376        }
2377    }
2378}
2379
2380#[unsafe(no_mangle)]
2381pub extern "system" fn Java_com_stateset_embedded_Backorders_nativeGetSummary<'local>(
2382    mut env: JNIEnv<'local>,
2383    _class: JClass<'local>,
2384    ptr: jlong,
2385) -> JObject<'local> {
2386    let result =
2387        use_handle(ptr, |commerce| commerce.backorder().get_summary().map_err(|e| e.to_string()));
2388    match result {
2389        Ok(summary) => to_json_string(&env, &summary),
2390        Err(e) => {
2391            throw_exception(&mut env, &e);
2392            JObject::null()
2393        }
2394    }
2395}
2396
2397#[unsafe(no_mangle)]
2398pub extern "system" fn Java_com_stateset_embedded_Backorders_nativeGetOverdue<'local>(
2399    mut env: JNIEnv<'local>,
2400    _class: JClass<'local>,
2401    ptr: jlong,
2402) -> JObject<'local> {
2403    let result = use_handle(ptr, |commerce| {
2404        commerce.backorder().get_overdue_backorders().map_err(|e| e.to_string())
2405    });
2406    match result {
2407        Ok(backorders) => to_json_string(&env, &backorders),
2408        Err(e) => {
2409            throw_exception(&mut env, &e);
2410            JObject::null()
2411        }
2412    }
2413}
2414
2415// =============================================================================
2416// General Ledger API
2417// =============================================================================
2418
2419#[unsafe(no_mangle)]
2420pub extern "system" fn Java_com_stateset_embedded_GeneralLedger_nativeCreateAccount<'local>(
2421    mut env: JNIEnv<'local>,
2422    _class: JClass<'local>,
2423    ptr: jlong,
2424    account_number: JString<'local>,
2425    name: JString<'local>,
2426    account_type: JString<'local>,
2427) -> JObject<'local> {
2428    let number_str = get_string(&mut env, &account_number);
2429    let name_str = get_string(&mut env, &name);
2430    let type_str = get_string(&mut env, &account_type);
2431
2432    let acct_type = match type_str.to_lowercase().as_str() {
2433        "asset" => stateset_core::AccountType::Asset,
2434        "liability" => stateset_core::AccountType::Liability,
2435        "equity" => stateset_core::AccountType::Equity,
2436        "revenue" => stateset_core::AccountType::Revenue,
2437        "expense" => stateset_core::AccountType::Expense,
2438        _ => stateset_core::AccountType::Asset,
2439    };
2440
2441    let result = use_handle(ptr, |commerce| {
2442        commerce
2443            .general_ledger()
2444            .create_account(stateset_core::CreateGlAccount {
2445                account_number: number_str,
2446                name: name_str,
2447                description: None,
2448                account_type: acct_type,
2449                account_sub_type: None,
2450                parent_account_id: None,
2451                is_header: None,
2452                is_posting: None,
2453                currency: None,
2454            })
2455            .map_err(|e| e.to_string())
2456    });
2457
2458    match result {
2459        Ok(account) => to_json_string(&env, &account),
2460        Err(e) => {
2461            throw_exception(&mut env, &e);
2462            JObject::null()
2463        }
2464    }
2465}
2466
2467#[unsafe(no_mangle)]
2468pub extern "system" fn Java_com_stateset_embedded_GeneralLedger_nativeListAccounts<'local>(
2469    mut env: JNIEnv<'local>,
2470    _class: JClass<'local>,
2471    ptr: jlong,
2472) -> JObject<'local> {
2473    let result = use_handle(ptr, |commerce| {
2474        commerce.general_ledger().list_accounts(Default::default()).map_err(|e| e.to_string())
2475    });
2476    match result {
2477        Ok(accounts) => to_json_string(&env, &accounts),
2478        Err(e) => {
2479            throw_exception(&mut env, &e);
2480            JObject::null()
2481        }
2482    }
2483}
2484
2485#[unsafe(no_mangle)]
2486pub extern "system" fn Java_com_stateset_embedded_GeneralLedger_nativeGetTrialBalance<'local>(
2487    mut env: JNIEnv<'local>,
2488    _class: JClass<'local>,
2489    ptr: jlong,
2490) -> JObject<'local> {
2491    // Use today's date as the as_of_date for the trial balance
2492    let today = chrono::Utc::now().date_naive();
2493    let result = use_handle(ptr, |commerce| {
2494        commerce.general_ledger().get_trial_balance(today).map_err(|e| e.to_string())
2495    });
2496    match result {
2497        Ok(balance) => to_json_string(&env, &balance),
2498        Err(e) => {
2499            throw_exception(&mut env, &e);
2500            JObject::null()
2501        }
2502    }
2503}