1use rust_decimal::Decimal;
6use stateset_core::{
7 AccountType, BackorderPriority, InspectionType, LocationType, OrderStatus, ReturnReason,
8 ShippingCarrier, WarehouseType,
9};
10use stateset_embedded::{
11 AddCartItem,
12 AnalyticsQuery,
13 Commerce as RustCommerce,
14 CreateBackorder,
15 CreateBill,
16 CreateBillItem,
17 CreateCart,
18 CreateCreditAccount,
19 CreateCustomer,
20 CreateGlAccount,
21 CreateInspection,
22 CreateInventoryItem,
23 CreateLocation,
24 CreateLot,
25 CreateOrder,
26 CreatePayment,
27 CreateProduct,
28 CreateProductVariant,
29 CreateReturn,
30 CreateReturnItem,
31 CreateSerialNumber,
32 CreateShipment,
33 CreateWarehouse,
35 CustomerFilter,
36 OrderFilter,
37 PaymentMethodType,
38 ProductFilter,
39 SetItemCost,
40 TimePeriod,
41};
42use std::ffi::{CStr, CString, c_char, c_double, c_int};
43use std::ptr;
44use std::sync::{Arc, Mutex};
45
46type CommerceHandle = Arc<Mutex<RustCommerce>>;
51
52fn create_handle(commerce: RustCommerce) -> *mut CommerceHandle {
53 let handle: CommerceHandle = Arc::new(Mutex::new(commerce));
54 Box::into_raw(Box::new(handle))
55}
56
57fn get_handle(ptr: *mut CommerceHandle) -> Option<CommerceHandle> {
58 if ptr.is_null() {
59 return None;
60 }
61 Some(unsafe { (*ptr).clone() })
62}
63
64fn use_handle<F, R>(ptr: *mut CommerceHandle, f: F) -> Result<R, String>
65where
66 F: FnOnce(&RustCommerce) -> Result<R, String>,
67{
68 let handle = get_handle(ptr).ok_or("Null handle")?;
69 let guard = handle.lock().map_err(|e| format!("Lock failed: {}", e))?;
70 f(&guard)
71}
72
73fn cstr_to_string(s: *const c_char) -> Option<String> {
78 if s.is_null() {
79 return None;
80 }
81 unsafe { CStr::from_ptr(s).to_str().ok().map(|s| s.to_string()) }
82}
83
84fn string_to_cstr(s: String) -> *mut c_char {
85 match CString::new(s) {
86 Ok(cstr) => cstr.into_raw(),
87 Err(_) => ptr::null_mut(),
88 }
89}
90
91fn to_json_cstr<T: serde::Serialize>(value: &T) -> *mut c_char {
92 match serde_json::to_string(value) {
93 Ok(json) => string_to_cstr(json),
94 Err(_) => ptr::null_mut(),
95 }
96}
97
98#[unsafe(no_mangle)]
106pub unsafe extern "C" fn stateset_string_free(s: *mut c_char) {
107 if !s.is_null() {
108 drop(unsafe { CString::from_raw(s) });
109 }
110}
111
112#[unsafe(no_mangle)]
119pub extern "C" fn stateset_commerce_new(db_path: *const c_char) -> *mut CommerceHandle {
120 let path = match cstr_to_string(db_path) {
121 Some(p) => p,
122 None => return ptr::null_mut(),
123 };
124
125 match RustCommerce::new(&path) {
126 Ok(commerce) => create_handle(commerce),
127 Err(_) => ptr::null_mut(),
128 }
129}
130
131#[unsafe(no_mangle)]
135pub unsafe extern "C" fn stateset_commerce_free(handle: *mut CommerceHandle) {
136 if !handle.is_null() {
137 drop(unsafe { Box::from_raw(handle) });
138 }
139}
140
141#[unsafe(no_mangle)]
144pub const extern "C" fn stateset_get_last_error() -> *mut c_char {
145 ptr::null_mut()
147}
148
149#[unsafe(no_mangle)]
156pub extern "C" fn stateset_customer_create(
157 handle: *mut CommerceHandle,
158 email: *const c_char,
159 first_name: *const c_char,
160 last_name: *const c_char,
161 phone: *const c_char,
162) -> *mut c_char {
163 let email_str = cstr_to_string(email).unwrap_or_default();
164 let first_name_str = cstr_to_string(first_name).unwrap_or_default();
165 let last_name_str = cstr_to_string(last_name).unwrap_or_default();
166 let phone_str = cstr_to_string(phone);
167
168 let result = use_handle(handle, |commerce| {
169 commerce
170 .customers()
171 .create(CreateCustomer {
172 email: email_str,
173 first_name: first_name_str,
174 last_name: last_name_str,
175 phone: phone_str,
176 ..Default::default()
177 })
178 .map_err(|e| e.to_string())
179 });
180
181 match result {
182 Ok(customer) => to_json_cstr(&customer),
183 Err(_) => ptr::null_mut(),
184 }
185}
186
187#[unsafe(no_mangle)]
190pub extern "C" fn stateset_customer_get(
191 handle: *mut CommerceHandle,
192 id: *const c_char,
193) -> *mut c_char {
194 let id_str = match cstr_to_string(id) {
195 Some(s) => s,
196 None => return ptr::null_mut(),
197 };
198
199 let uuid = match uuid::Uuid::parse_str(&id_str) {
200 Ok(u) => u,
201 Err(_) => return ptr::null_mut(),
202 };
203
204 let result = use_handle(handle, |commerce| {
205 commerce.customers().get(uuid.into()).map_err(|e| e.to_string())
206 });
207
208 match result {
209 Ok(Some(customer)) => to_json_cstr(&customer),
210 _ => ptr::null_mut(),
211 }
212}
213
214#[unsafe(no_mangle)]
217pub extern "C" fn stateset_customer_list(handle: *mut CommerceHandle) -> *mut c_char {
218 let result = use_handle(handle, |commerce| {
219 commerce.customers().list(CustomerFilter::default()).map_err(|e| e.to_string())
220 });
221
222 match result {
223 Ok(customers) => to_json_cstr(&customers),
224 Err(_) => ptr::null_mut(),
225 }
226}
227
228#[unsafe(no_mangle)]
231pub extern "C" fn stateset_customer_delete(
232 handle: *mut CommerceHandle,
233 id: *const c_char,
234) -> c_int {
235 let id_str = match cstr_to_string(id) {
236 Some(s) => s,
237 None => return 0,
238 };
239
240 let uuid = match uuid::Uuid::parse_str(&id_str) {
241 Ok(u) => u,
242 Err(_) => return 0,
243 };
244
245 let result = use_handle(handle, |commerce| {
246 commerce.customers().delete(uuid.into()).map_err(|e| e.to_string())
247 });
248
249 match result {
250 Ok(_) => 1,
251 Err(_) => 0,
252 }
253}
254
255#[unsafe(no_mangle)]
258pub extern "C" fn stateset_customer_count(handle: *mut CommerceHandle) -> c_int {
259 let result = use_handle(handle, |commerce| {
260 commerce.customers().list(CustomerFilter::default()).map_err(|e| e.to_string())
261 });
262
263 match result {
264 Ok(customers) => customers.len() as c_int,
265 Err(_) => -1,
266 }
267}
268
269#[unsafe(no_mangle)]
276pub extern "C" fn stateset_product_create(
277 handle: *mut CommerceHandle,
278 name: *const c_char,
279 sku: *const c_char,
280 price: c_double,
281 description: *const c_char,
282) -> *mut c_char {
283 let name_str = cstr_to_string(name).unwrap_or_default();
284 let sku_str = cstr_to_string(sku).unwrap_or_default();
285 let desc_str = cstr_to_string(description);
286 let price_decimal = Decimal::try_from(price).unwrap_or_default();
287
288 let result = use_handle(handle, |commerce| {
289 commerce
290 .products()
291 .create(CreateProduct {
292 name: name_str,
293 description: desc_str,
294 variants: Some(vec![CreateProductVariant {
295 sku: sku_str,
296 price: price_decimal,
297 is_default: Some(true),
298 ..Default::default()
299 }]),
300 ..Default::default()
301 })
302 .map_err(|e| e.to_string())
303 });
304
305 match result {
306 Ok(product) => to_json_cstr(&product),
307 Err(_) => ptr::null_mut(),
308 }
309}
310
311#[unsafe(no_mangle)]
314pub extern "C" fn stateset_product_get(
315 handle: *mut CommerceHandle,
316 id: *const c_char,
317) -> *mut c_char {
318 let id_str = match cstr_to_string(id) {
319 Some(s) => s,
320 None => return ptr::null_mut(),
321 };
322
323 let uuid = match uuid::Uuid::parse_str(&id_str) {
324 Ok(u) => u,
325 Err(_) => return ptr::null_mut(),
326 };
327
328 let result = use_handle(handle, |commerce| {
329 commerce.products().get(uuid.into()).map_err(|e| e.to_string())
330 });
331
332 match result {
333 Ok(Some(product)) => to_json_cstr(&product),
334 _ => ptr::null_mut(),
335 }
336}
337
338#[unsafe(no_mangle)]
341pub extern "C" fn stateset_product_list(handle: *mut CommerceHandle) -> *mut c_char {
342 let result = use_handle(handle, |commerce| {
343 commerce.products().list(ProductFilter::default()).map_err(|e| e.to_string())
344 });
345
346 match result {
347 Ok(products) => to_json_cstr(&products),
348 Err(_) => ptr::null_mut(),
349 }
350}
351
352#[unsafe(no_mangle)]
360pub extern "C" fn stateset_order_create(
361 handle: *mut CommerceHandle,
362 customer_id: *const c_char,
363 items_json: *const c_char,
364 currency: *const c_char,
365) -> *mut c_char {
366 let customer_id_str = match cstr_to_string(customer_id) {
367 Some(s) => s,
368 None => return ptr::null_mut(),
369 };
370 let items_str = cstr_to_string(items_json).unwrap_or_default();
371 let currency_str = cstr_to_string(currency).unwrap_or_else(|| "USD".to_string());
372
373 let customer_uuid = match uuid::Uuid::parse_str(&customer_id_str) {
374 Ok(u) => u,
375 Err(_) => return ptr::null_mut(),
376 };
377
378 let items: Vec<stateset_embedded::CreateOrderItem> = match serde_json::from_str(&items_str) {
379 Ok(i) => i,
380 Err(_) => return ptr::null_mut(),
381 };
382
383 let result = use_handle(handle, |commerce| {
384 commerce
385 .orders()
386 .create(CreateOrder {
387 customer_id: customer_uuid.into(),
388 items,
389 currency: Some(currency_str),
390 ..Default::default()
391 })
392 .map_err(|e| e.to_string())
393 });
394
395 match result {
396 Ok(order) => to_json_cstr(&order),
397 Err(_) => ptr::null_mut(),
398 }
399}
400
401#[unsafe(no_mangle)]
404pub extern "C" fn stateset_order_get(
405 handle: *mut CommerceHandle,
406 id: *const c_char,
407) -> *mut c_char {
408 let id_str = match cstr_to_string(id) {
409 Some(s) => s,
410 None => return ptr::null_mut(),
411 };
412
413 let uuid = match uuid::Uuid::parse_str(&id_str) {
414 Ok(u) => u,
415 Err(_) => return ptr::null_mut(),
416 };
417
418 let result = use_handle(handle, |commerce| {
419 commerce.orders().get(uuid.into()).map_err(|e| e.to_string())
420 });
421
422 match result {
423 Ok(Some(order)) => to_json_cstr(&order),
424 _ => ptr::null_mut(),
425 }
426}
427
428#[unsafe(no_mangle)]
431pub extern "C" fn stateset_order_list(handle: *mut CommerceHandle) -> *mut c_char {
432 let result = use_handle(handle, |commerce| {
433 commerce.orders().list(OrderFilter::default()).map_err(|e| e.to_string())
434 });
435
436 match result {
437 Ok(orders) => to_json_cstr(&orders),
438 Err(_) => ptr::null_mut(),
439 }
440}
441
442#[unsafe(no_mangle)]
446pub extern "C" fn stateset_order_update_status(
447 handle: *mut CommerceHandle,
448 id: *const c_char,
449 status: *const c_char,
450) -> *mut c_char {
451 let id_str = match cstr_to_string(id) {
452 Some(s) => s,
453 None => return ptr::null_mut(),
454 };
455 let status_str = cstr_to_string(status).unwrap_or_default();
456
457 let uuid = match uuid::Uuid::parse_str(&id_str) {
458 Ok(u) => u,
459 Err(_) => return ptr::null_mut(),
460 };
461
462 let order_status = match status_str.to_lowercase().as_str() {
463 "pending" => OrderStatus::Pending,
464 "confirmed" => OrderStatus::Confirmed,
465 "processing" => OrderStatus::Processing,
466 "shipped" => OrderStatus::Shipped,
467 "delivered" => OrderStatus::Delivered,
468 "cancelled" => OrderStatus::Cancelled,
469 "refunded" => OrderStatus::Refunded,
470 _ => return ptr::null_mut(),
471 };
472
473 let result = use_handle(handle, |commerce| {
474 commerce.orders().update_status(uuid.into(), order_status).map_err(|e| e.to_string())
475 });
476
477 match result {
478 Ok(order) => to_json_cstr(&order),
479 Err(_) => ptr::null_mut(),
480 }
481}
482
483#[unsafe(no_mangle)]
490pub extern "C" fn stateset_inventory_create_item(
491 handle: *mut CommerceHandle,
492 sku: *const c_char,
493 name: *const c_char,
494 initial_quantity: c_double,
495) -> *mut c_char {
496 let sku_str = cstr_to_string(sku).unwrap_or_default();
497 let name_str = cstr_to_string(name).unwrap_or_default();
498 let qty = Decimal::try_from(initial_quantity).ok();
499
500 let result = use_handle(handle, |commerce| {
501 commerce
502 .inventory()
503 .create_item(CreateInventoryItem {
504 sku: sku_str,
505 name: name_str,
506 initial_quantity: qty,
507 ..Default::default()
508 })
509 .map_err(|e| e.to_string())
510 });
511
512 match result {
513 Ok(item) => to_json_cstr(&item),
514 Err(_) => ptr::null_mut(),
515 }
516}
517
518#[unsafe(no_mangle)]
521pub extern "C" fn stateset_inventory_adjust(
522 handle: *mut CommerceHandle,
523 sku: *const c_char,
524 quantity_delta: c_double,
525 reason: *const c_char,
526) -> c_int {
527 let sku_str = cstr_to_string(sku).unwrap_or_default();
528 let reason_str = cstr_to_string(reason).unwrap_or_else(|| "adjustment".to_string());
529 let delta = Decimal::try_from(quantity_delta).unwrap_or_default();
530
531 let result = use_handle(handle, |commerce| {
532 commerce.inventory().adjust(&sku_str, delta, &reason_str).map_err(|e| e.to_string())
533 });
534
535 match result {
536 Ok(_) => 1,
537 Err(_) => 0,
538 }
539}
540
541#[unsafe(no_mangle)]
544pub extern "C" fn stateset_inventory_get_level(
545 handle: *mut CommerceHandle,
546 sku: *const c_char,
547) -> *mut c_char {
548 let sku_str = cstr_to_string(sku).unwrap_or_default();
549
550 let result = use_handle(handle, |commerce| {
551 commerce.inventory().get_stock(&sku_str).map_err(|e| e.to_string())
552 });
553
554 match result {
555 Ok(Some(level)) => to_json_cstr(&level),
556 _ => ptr::null_mut(),
557 }
558}
559
560#[unsafe(no_mangle)]
568pub extern "C" fn stateset_cart_create(
569 handle: *mut CommerceHandle,
570 customer_id: *const c_char,
571 currency: *const c_char,
572) -> *mut c_char {
573 let customer_id_str = cstr_to_string(customer_id);
574 let currency_str = cstr_to_string(currency);
575
576 let customer_uuid = customer_id_str
577 .and_then(|s| if s.is_empty() { None } else { uuid::Uuid::parse_str(&s).ok() });
578
579 let result = use_handle(handle, |commerce| {
580 commerce
581 .carts()
582 .create(CreateCart {
583 customer_id: customer_uuid.map(Into::into),
584 currency: currency_str,
585 ..Default::default()
586 })
587 .map_err(|e| e.to_string())
588 });
589
590 match result {
591 Ok(cart) => to_json_cstr(&cart),
592 Err(_) => ptr::null_mut(),
593 }
594}
595
596#[unsafe(no_mangle)]
599pub extern "C" fn stateset_cart_add_item(
600 handle: *mut CommerceHandle,
601 cart_id: *const c_char,
602 variant_id: *const c_char,
603 quantity: c_int,
604) -> *mut c_char {
605 let cart_id_str = match cstr_to_string(cart_id) {
606 Some(s) => s,
607 None => return ptr::null_mut(),
608 };
609 let variant_id_str = match cstr_to_string(variant_id) {
610 Some(s) => s,
611 None => return ptr::null_mut(),
612 };
613
614 let cart_uuid = match uuid::Uuid::parse_str(&cart_id_str) {
615 Ok(u) => u,
616 Err(_) => return ptr::null_mut(),
617 };
618
619 let variant_uuid = match uuid::Uuid::parse_str(&variant_id_str) {
620 Ok(u) => u,
621 Err(_) => return ptr::null_mut(),
622 };
623
624 let result = use_handle(handle, |commerce| {
625 commerce
626 .carts()
627 .add_item(
628 cart_uuid.into(),
629 AddCartItem { variant_id: Some(variant_uuid), quantity, ..Default::default() },
630 )
631 .map_err(|e| e.to_string())
632 });
633
634 match result {
635 Ok(cart) => to_json_cstr(&cart),
636 Err(_) => ptr::null_mut(),
637 }
638}
639
640#[unsafe(no_mangle)]
643pub extern "C" fn stateset_cart_get(
644 handle: *mut CommerceHandle,
645 cart_id: *const c_char,
646) -> *mut c_char {
647 let cart_id_str = match cstr_to_string(cart_id) {
648 Some(s) => s,
649 None => return ptr::null_mut(),
650 };
651
652 let cart_uuid = match uuid::Uuid::parse_str(&cart_id_str) {
653 Ok(u) => u,
654 Err(_) => return ptr::null_mut(),
655 };
656
657 let result = use_handle(handle, |commerce| {
658 commerce.carts().get(cart_uuid.into()).map_err(|e| e.to_string())
659 });
660
661 match result {
662 Ok(Some(cart)) => to_json_cstr(&cart),
663 _ => ptr::null_mut(),
664 }
665}
666
667#[unsafe(no_mangle)]
675pub extern "C" fn stateset_return_create(
676 handle: *mut CommerceHandle,
677 order_id: *const c_char,
678 reason: *const c_char,
679 notes: *const c_char,
680) -> *mut c_char {
681 let order_id_str = match cstr_to_string(order_id) {
682 Some(s) => s,
683 None => return ptr::null_mut(),
684 };
685 let reason_str = cstr_to_string(reason).unwrap_or_default();
686 let notes_str = cstr_to_string(notes);
687
688 let order_uuid = match uuid::Uuid::parse_str(&order_id_str) {
689 Ok(u) => u,
690 Err(_) => return ptr::null_mut(),
691 };
692
693 let return_reason = match reason_str.to_lowercase().as_str() {
694 "defective" => ReturnReason::Defective,
695 "wrong_item" | "wrongitem" => ReturnReason::WrongItem,
696 "not_as_described" | "notasdescribed" => ReturnReason::NotAsDescribed,
697 "changed_mind" | "changedmind" => ReturnReason::ChangedMind,
698 "damaged" => ReturnReason::Damaged,
699 _ => ReturnReason::Other,
700 };
701
702 let result = use_handle(handle, |commerce| {
703 let order = commerce.orders().get(order_uuid.into()).map_err(|e| e.to_string())?;
704 let order = order.ok_or_else(|| format!("Order not found: {}", order_uuid))?;
705 let items: Vec<CreateReturnItem> = order
706 .items
707 .iter()
708 .map(|item| CreateReturnItem {
709 order_item_id: item.id,
710 quantity: item.quantity,
711 condition: None,
712 })
713 .collect();
714 if items.is_empty() {
715 return Err("Return must have at least one item".to_string());
716 }
717 commerce
718 .returns()
719 .create(CreateReturn {
720 order_id: order_uuid.into(),
721 reason: return_reason,
722 notes: notes_str,
723 items,
724 ..Default::default()
725 })
726 .map_err(|e| e.to_string())
727 });
728
729 match result {
730 Ok(ret) => to_json_cstr(&ret),
731 Err(_) => ptr::null_mut(),
732 }
733}
734
735#[unsafe(no_mangle)]
738pub extern "C" fn stateset_return_list(handle: *mut CommerceHandle) -> *mut c_char {
739 let result = use_handle(handle, |commerce| {
740 commerce.returns().list(Default::default()).map_err(|e| e.to_string())
741 });
742
743 match result {
744 Ok(returns) => to_json_cstr(&returns),
745 Err(_) => ptr::null_mut(),
746 }
747}
748
749#[unsafe(no_mangle)]
757pub extern "C" fn stateset_payment_create(
758 handle: *mut CommerceHandle,
759 order_id: *const c_char,
760 amount: c_double,
761 currency: *const c_char,
762 method: *const c_char,
763) -> *mut c_char {
764 let order_id_str = match cstr_to_string(order_id) {
765 Some(s) => s,
766 None => return ptr::null_mut(),
767 };
768 let currency_str = cstr_to_string(currency).unwrap_or_else(|| "USD".to_string());
769 let method_str = cstr_to_string(method).unwrap_or_default();
770 let amount_decimal = Decimal::try_from(amount).unwrap_or_default();
771
772 let order_uuid = match uuid::Uuid::parse_str(&order_id_str) {
773 Ok(u) => u,
774 Err(_) => return ptr::null_mut(),
775 };
776
777 let payment_method = match method_str.to_lowercase().as_str() {
778 "credit_card" | "creditcard" => PaymentMethodType::CreditCard,
779 "debit_card" | "debitcard" => PaymentMethodType::DebitCard,
780 "bank_transfer" | "banktransfer" => PaymentMethodType::BankTransfer,
781 "paypal" => PaymentMethodType::PayPal,
782 "apple_pay" | "applepay" => PaymentMethodType::ApplePay,
783 "google_pay" | "googlepay" => PaymentMethodType::GooglePay,
784 "crypto" => PaymentMethodType::Crypto,
785 _ => PaymentMethodType::Other,
786 };
787
788 let result = use_handle(handle, |commerce| {
789 commerce
790 .payments()
791 .create(CreatePayment {
792 order_id: Some(order_uuid.into()),
793 amount: amount_decimal,
794 currency: Some(currency_str),
795 payment_method,
796 ..Default::default()
797 })
798 .map_err(|e| e.to_string())
799 });
800
801 match result {
802 Ok(payment) => to_json_cstr(&payment),
803 Err(_) => ptr::null_mut(),
804 }
805}
806
807#[unsafe(no_mangle)]
815pub extern "C" fn stateset_analytics_sales_summary(
816 handle: *mut CommerceHandle,
817 period: *const c_char,
818) -> *mut c_char {
819 let period_str = cstr_to_string(period).unwrap_or_else(|| "month".to_string());
820
821 let time_period = match period_str.to_lowercase().as_str() {
822 "today" => TimePeriod::Today,
823 "week" | "last_7_days" => TimePeriod::Last7Days,
824 "month" | "this_month" => TimePeriod::ThisMonth,
825 "quarter" | "this_quarter" => TimePeriod::ThisQuarter,
826 "year" | "this_year" => TimePeriod::ThisYear,
827 "all" | "all_time" => TimePeriod::AllTime,
828 _ => TimePeriod::ThisMonth,
829 };
830
831 let result = use_handle(handle, |commerce| {
832 commerce
833 .analytics()
834 .sales_summary(AnalyticsQuery { period: Some(time_period), ..Default::default() })
835 .map_err(|e| e.to_string())
836 });
837
838 match result {
839 Ok(summary) => to_json_cstr(&summary),
840 Err(_) => ptr::null_mut(),
841 }
842}
843
844#[unsafe(no_mangle)]
847pub extern "C" fn stateset_analytics_top_products(
848 handle: *mut CommerceHandle,
849 limit: c_int,
850) -> *mut c_char {
851 let result = use_handle(handle, |commerce| {
852 commerce
853 .analytics()
854 .top_products(AnalyticsQuery { limit: Some(limit as u32), ..Default::default() })
855 .map_err(|e| e.to_string())
856 });
857
858 match result {
859 Ok(products) => to_json_cstr(&products),
860 Err(_) => ptr::null_mut(),
861 }
862}
863
864#[unsafe(no_mangle)]
867pub extern "C" fn stateset_analytics_top_customers(
868 handle: *mut CommerceHandle,
869 limit: c_int,
870) -> *mut c_char {
871 let result = use_handle(handle, |commerce| {
872 commerce
873 .analytics()
874 .top_customers(AnalyticsQuery { limit: Some(limit as u32), ..Default::default() })
875 .map_err(|e| e.to_string())
876 });
877
878 match result {
879 Ok(customers) => to_json_cstr(&customers),
880 Err(_) => ptr::null_mut(),
881 }
882}
883
884#[unsafe(no_mangle)]
891pub extern "C" fn stateset_shipment_create(
892 handle: *mut CommerceHandle,
893 order_id: *const c_char,
894 recipient_name: *const c_char,
895 shipping_address: *const c_char,
896 carrier: *const c_char,
897) -> *mut c_char {
898 let order_id_str = match cstr_to_string(order_id) {
899 Some(s) => s,
900 None => return ptr::null_mut(),
901 };
902 let recipient_name_str = cstr_to_string(recipient_name).unwrap_or_default();
903 let shipping_address_str = cstr_to_string(shipping_address).unwrap_or_default();
904 let carrier_str = cstr_to_string(carrier);
905
906 let order_uuid = match uuid::Uuid::parse_str(&order_id_str) {
907 Ok(u) => u,
908 Err(_) => return ptr::null_mut(),
909 };
910
911 let shipping_carrier = carrier_str.map(|c| match c.to_lowercase().as_str() {
912 "ups" => ShippingCarrier::Ups,
913 "fedex" => ShippingCarrier::FedEx,
914 "usps" => ShippingCarrier::Usps,
915 "dhl" => ShippingCarrier::Dhl,
916 _ => ShippingCarrier::Other,
917 });
918
919 let result = use_handle(handle, |commerce| {
920 commerce
921 .shipments()
922 .create(CreateShipment {
923 order_id: order_uuid.into(),
924 recipient_name: recipient_name_str,
925 shipping_address: shipping_address_str,
926 carrier: shipping_carrier,
927 ..Default::default()
928 })
929 .map_err(|e| e.to_string())
930 });
931
932 match result {
933 Ok(shipment) => to_json_cstr(&shipment),
934 Err(_) => ptr::null_mut(),
935 }
936}
937
938#[unsafe(no_mangle)]
941pub extern "C" fn stateset_shipment_get(
942 handle: *mut CommerceHandle,
943 id: *const c_char,
944) -> *mut c_char {
945 let id_str = match cstr_to_string(id) {
946 Some(s) => s,
947 None => return ptr::null_mut(),
948 };
949
950 let uuid = match uuid::Uuid::parse_str(&id_str) {
951 Ok(u) => u,
952 Err(_) => return ptr::null_mut(),
953 };
954
955 let result = use_handle(handle, |commerce| {
956 commerce.shipments().get(uuid.into()).map_err(|e| e.to_string())
957 });
958
959 match result {
960 Ok(Some(shipment)) => to_json_cstr(&shipment),
961 _ => ptr::null_mut(),
962 }
963}
964
965#[unsafe(no_mangle)]
968pub extern "C" fn stateset_shipment_list(handle: *mut CommerceHandle) -> *mut c_char {
969 let result = use_handle(handle, |commerce| {
970 commerce.shipments().list(Default::default()).map_err(|e| e.to_string())
971 });
972
973 match result {
974 Ok(shipments) => to_json_cstr(&shipments),
975 Err(_) => ptr::null_mut(),
976 }
977}
978
979#[unsafe(no_mangle)]
982pub extern "C" fn stateset_shipment_ship(
983 handle: *mut CommerceHandle,
984 id: *const c_char,
985 tracking_number: *const c_char,
986) -> *mut c_char {
987 let id_str = match cstr_to_string(id) {
988 Some(s) => s,
989 None => return ptr::null_mut(),
990 };
991 let tracking = cstr_to_string(tracking_number);
992
993 let uuid = match uuid::Uuid::parse_str(&id_str) {
994 Ok(u) => u,
995 Err(_) => return ptr::null_mut(),
996 };
997
998 let result = use_handle(handle, |commerce| {
999 commerce.shipments().ship(uuid.into(), tracking).map_err(|e| e.to_string())
1000 });
1001
1002 match result {
1003 Ok(shipment) => to_json_cstr(&shipment),
1004 Err(_) => ptr::null_mut(),
1005 }
1006}
1007
1008#[unsafe(no_mangle)]
1011pub extern "C" fn stateset_shipment_deliver(
1012 handle: *mut CommerceHandle,
1013 id: *const c_char,
1014) -> *mut c_char {
1015 let id_str = match cstr_to_string(id) {
1016 Some(s) => s,
1017 None => return ptr::null_mut(),
1018 };
1019
1020 let uuid = match uuid::Uuid::parse_str(&id_str) {
1021 Ok(u) => u,
1022 Err(_) => return ptr::null_mut(),
1023 };
1024
1025 let result = use_handle(handle, |commerce| {
1026 commerce.shipments().mark_delivered(uuid.into()).map_err(|e| e.to_string())
1027 });
1028
1029 match result {
1030 Ok(shipment) => to_json_cstr(&shipment),
1031 Err(_) => ptr::null_mut(),
1032 }
1033}
1034
1035#[unsafe(no_mangle)]
1038pub extern "C" fn stateset_shipment_cancel(
1039 handle: *mut CommerceHandle,
1040 id: *const c_char,
1041) -> *mut c_char {
1042 let id_str = match cstr_to_string(id) {
1043 Some(s) => s,
1044 None => return ptr::null_mut(),
1045 };
1046
1047 let uuid = match uuid::Uuid::parse_str(&id_str) {
1048 Ok(u) => u,
1049 Err(_) => return ptr::null_mut(),
1050 };
1051
1052 let result = use_handle(handle, |commerce| {
1053 commerce.shipments().cancel(uuid.into()).map_err(|e| e.to_string())
1054 });
1055
1056 match result {
1057 Ok(shipment) => to_json_cstr(&shipment),
1058 Err(_) => ptr::null_mut(),
1059 }
1060}
1061
1062#[unsafe(no_mangle)]
1069pub extern "C" fn stateset_warehouse_create(
1070 handle: *mut CommerceHandle,
1071 code: *const c_char,
1072 name: *const c_char,
1073) -> *mut c_char {
1074 let code_str = match cstr_to_string(code) {
1075 Some(s) => s,
1076 None => return ptr::null_mut(),
1077 };
1078 let name_str = match cstr_to_string(name) {
1079 Some(s) => s,
1080 None => return ptr::null_mut(),
1081 };
1082
1083 let result = use_handle(handle, |commerce| {
1084 commerce
1085 .warehouse()
1086 .create_warehouse(CreateWarehouse {
1087 code: code_str,
1088 name: name_str,
1089 warehouse_type: WarehouseType::Distribution,
1090 ..Default::default()
1091 })
1092 .map_err(|e| e.to_string())
1093 });
1094
1095 match result {
1096 Ok(wh) => to_json_cstr(&wh),
1097 Err(_) => ptr::null_mut(),
1098 }
1099}
1100
1101#[unsafe(no_mangle)]
1104pub extern "C" fn stateset_warehouse_list(handle: *mut CommerceHandle) -> *mut c_char {
1105 let result = use_handle(handle, |commerce| {
1106 commerce.warehouse().list_warehouses(Default::default()).map_err(|e| e.to_string())
1107 });
1108
1109 match result {
1110 Ok(list) => to_json_cstr(&list),
1111 Err(_) => ptr::null_mut(),
1112 }
1113}
1114
1115#[unsafe(no_mangle)]
1118pub extern "C" fn stateset_warehouse_create_location(
1119 handle: *mut CommerceHandle,
1120 warehouse_id: *const c_char,
1121 zone: *const c_char,
1122 aisle: *const c_char,
1123 rack: *const c_char,
1124 bin: *const c_char,
1125) -> *mut c_char {
1126 let wh_id = match cstr_to_string(warehouse_id).and_then(|s| s.parse().ok()) {
1127 Some(id) => id,
1128 None => return ptr::null_mut(),
1129 };
1130
1131 let result = use_handle(handle, |commerce| {
1132 commerce
1133 .warehouse()
1134 .create_location(CreateLocation {
1135 warehouse_id: wh_id,
1136 location_type: LocationType::Pick,
1137 zone: cstr_to_string(zone),
1138 aisle: cstr_to_string(aisle),
1139 rack: cstr_to_string(rack),
1140 bin: cstr_to_string(bin),
1141 ..Default::default()
1142 })
1143 .map_err(|e| e.to_string())
1144 });
1145
1146 match result {
1147 Ok(loc) => to_json_cstr(&loc),
1148 Err(_) => ptr::null_mut(),
1149 }
1150}
1151
1152#[unsafe(no_mangle)]
1159pub extern "C" fn stateset_quality_create_inspection(
1160 handle: *mut CommerceHandle,
1161 reference_type: *const c_char,
1162 reference_id: *const c_char,
1163 sku: *const c_char,
1164) -> *mut c_char {
1165 let ref_type = cstr_to_string(reference_type).unwrap_or_else(|| "purchase_order".to_string());
1166 let ref_id = match cstr_to_string(reference_id).and_then(|s| s.parse().ok()) {
1167 Some(id) => id,
1168 None => return ptr::null_mut(),
1169 };
1170 let sku_str = match cstr_to_string(sku) {
1171 Some(s) => s,
1172 None => return ptr::null_mut(),
1173 };
1174
1175 let result = use_handle(handle, |commerce| {
1176 commerce
1177 .quality()
1178 .create_inspection(CreateInspection {
1179 inspection_type: InspectionType::Incoming,
1180 reference_type: ref_type,
1181 reference_id: ref_id,
1182 inspector_id: None,
1183 scheduled_at: None,
1184 notes: None,
1185 items: vec![stateset_embedded::CreateInspectionItem {
1186 sku: sku_str,
1187 lot_number: None,
1188 serial_number: None,
1189 quantity_to_inspect: rust_decimal::Decimal::ONE,
1190 }],
1191 })
1192 .map_err(|e| e.to_string())
1193 });
1194
1195 match result {
1196 Ok(insp) => to_json_cstr(&insp),
1197 Err(_) => ptr::null_mut(),
1198 }
1199}
1200
1201#[unsafe(no_mangle)]
1208pub extern "C" fn stateset_lots_create(
1209 handle: *mut CommerceHandle,
1210 lot_number: *const c_char,
1211 sku: *const c_char,
1212 quantity: c_double,
1213) -> *mut c_char {
1214 let sku_str = match cstr_to_string(sku) {
1215 Some(s) => s,
1216 None => return ptr::null_mut(),
1217 };
1218
1219 let result = use_handle(handle, |commerce| {
1220 commerce
1221 .lots()
1222 .create(CreateLot {
1223 lot_number: cstr_to_string(lot_number),
1224 sku: sku_str,
1225 quantity: Decimal::try_from(quantity).unwrap_or_default(),
1226 ..Default::default()
1227 })
1228 .map_err(|e| e.to_string())
1229 });
1230
1231 match result {
1232 Ok(lot) => to_json_cstr(&lot),
1233 Err(_) => ptr::null_mut(),
1234 }
1235}
1236
1237#[unsafe(no_mangle)]
1240pub extern "C" fn stateset_lots_list(handle: *mut CommerceHandle) -> *mut c_char {
1241 let result = use_handle(handle, |commerce| {
1242 commerce.lots().list(Default::default()).map_err(|e| e.to_string())
1243 });
1244
1245 match result {
1246 Ok(list) => to_json_cstr(&list),
1247 Err(_) => ptr::null_mut(),
1248 }
1249}
1250
1251#[unsafe(no_mangle)]
1258pub extern "C" fn stateset_serials_create(
1259 handle: *mut CommerceHandle,
1260 serial: *const c_char,
1261 sku: *const c_char,
1262) -> *mut c_char {
1263 let sku_str = match cstr_to_string(sku) {
1264 Some(s) => s,
1265 None => return ptr::null_mut(),
1266 };
1267
1268 let result = use_handle(handle, |commerce| {
1269 commerce
1270 .serials()
1271 .create(CreateSerialNumber {
1272 serial: cstr_to_string(serial),
1273 sku: sku_str,
1274 ..Default::default()
1275 })
1276 .map_err(|e| e.to_string())
1277 });
1278
1279 match result {
1280 Ok(sn) => to_json_cstr(&sn),
1281 Err(_) => ptr::null_mut(),
1282 }
1283}
1284
1285#[unsafe(no_mangle)]
1288pub extern "C" fn stateset_serials_list(handle: *mut CommerceHandle) -> *mut c_char {
1289 let result = use_handle(handle, |commerce| {
1290 commerce.serials().list(Default::default()).map_err(|e| e.to_string())
1291 });
1292
1293 match result {
1294 Ok(list) => to_json_cstr(&list),
1295 Err(_) => ptr::null_mut(),
1296 }
1297}
1298
1299#[unsafe(no_mangle)]
1306pub extern "C" fn stateset_ap_create_bill(
1307 handle: *mut CommerceHandle,
1308 supplier_id: *const c_char,
1309 description: *const c_char,
1310 amount: c_double,
1311) -> *mut c_char {
1312 let sup_id = match cstr_to_string(supplier_id).and_then(|s| s.parse().ok()) {
1313 Some(id) => id,
1314 None => return ptr::null_mut(),
1315 };
1316
1317 let result = use_handle(handle, |commerce| {
1318 commerce
1319 .accounts_payable()
1320 .create_bill(CreateBill {
1321 supplier_id: sup_id,
1322 items: vec![CreateBillItem {
1323 description: cstr_to_string(description).unwrap_or_default(),
1324 quantity: Decimal::ONE,
1325 unit_price: Decimal::try_from(amount).unwrap_or_default(),
1326 ..Default::default()
1327 }],
1328 ..Default::default()
1329 })
1330 .map_err(|e| e.to_string())
1331 });
1332
1333 match result {
1334 Ok(bill) => to_json_cstr(&bill),
1335 Err(_) => ptr::null_mut(),
1336 }
1337}
1338
1339#[unsafe(no_mangle)]
1342pub extern "C" fn stateset_ap_list_bills(handle: *mut CommerceHandle) -> *mut c_char {
1343 let result = use_handle(handle, |commerce| {
1344 commerce.accounts_payable().list_bills(Default::default()).map_err(|e| e.to_string())
1345 });
1346
1347 match result {
1348 Ok(list) => to_json_cstr(&list),
1349 Err(_) => ptr::null_mut(),
1350 }
1351}
1352
1353#[unsafe(no_mangle)]
1360pub extern "C" fn stateset_ar_aging_summary(handle: *mut CommerceHandle) -> *mut c_char {
1361 let result = use_handle(handle, |commerce| {
1362 commerce.accounts_receivable().get_aging_summary().map_err(|e| e.to_string())
1363 });
1364
1365 match result {
1366 Ok(summary) => to_json_cstr(&summary),
1367 Err(_) => ptr::null_mut(),
1368 }
1369}
1370
1371#[unsafe(no_mangle)]
1374pub extern "C" fn stateset_ar_customer_aging(
1375 handle: *mut CommerceHandle,
1376 customer_id: *const c_char,
1377) -> *mut c_char {
1378 let cust_id = match cstr_to_string(customer_id).and_then(|s| s.parse().ok()) {
1379 Some(id) => id,
1380 None => return ptr::null_mut(),
1381 };
1382
1383 let result = use_handle(handle, |commerce| {
1384 commerce.accounts_receivable().get_customer_aging(cust_id).map_err(|e| e.to_string())
1385 });
1386
1387 match result {
1388 Ok(aging) => to_json_cstr(&aging),
1389 Err(_) => ptr::null_mut(),
1390 }
1391}
1392
1393#[unsafe(no_mangle)]
1400pub extern "C" fn stateset_cost_set_item_cost(
1401 handle: *mut CommerceHandle,
1402 sku: *const c_char,
1403 standard_cost: c_double,
1404) -> *mut c_char {
1405 let sku_str = match cstr_to_string(sku) {
1406 Some(s) => s,
1407 None => return ptr::null_mut(),
1408 };
1409
1410 let result = use_handle(handle, |commerce| {
1411 commerce
1412 .cost_accounting()
1413 .set_item_cost(SetItemCost {
1414 sku: sku_str,
1415 standard_cost: Some(Decimal::try_from(standard_cost).unwrap_or_default()),
1416 ..Default::default()
1417 })
1418 .map_err(|e| e.to_string())
1419 });
1420
1421 match result {
1422 Ok(cost) => to_json_cstr(&cost),
1423 Err(_) => ptr::null_mut(),
1424 }
1425}
1426
1427#[unsafe(no_mangle)]
1430pub extern "C" fn stateset_cost_get_item_cost(
1431 handle: *mut CommerceHandle,
1432 sku: *const c_char,
1433) -> *mut c_char {
1434 let sku_str = match cstr_to_string(sku) {
1435 Some(s) => s,
1436 None => return ptr::null_mut(),
1437 };
1438
1439 let result = use_handle(handle, |commerce| {
1440 commerce.cost_accounting().get_item_cost(&sku_str).map_err(|e| e.to_string())
1441 });
1442
1443 match result {
1444 Ok(Some(cost)) => to_json_cstr(&cost),
1445 _ => ptr::null_mut(),
1446 }
1447}
1448
1449#[unsafe(no_mangle)]
1456pub extern "C" fn stateset_credit_create_account(
1457 handle: *mut CommerceHandle,
1458 customer_id: *const c_char,
1459 credit_limit: c_double,
1460) -> *mut c_char {
1461 let cust_id = match cstr_to_string(customer_id).and_then(|s| s.parse().ok()) {
1462 Some(id) => id,
1463 None => return ptr::null_mut(),
1464 };
1465
1466 let result = use_handle(handle, |commerce| {
1467 commerce
1468 .credit()
1469 .create_credit_account(CreateCreditAccount {
1470 customer_id: cust_id,
1471 credit_limit: Decimal::try_from(credit_limit).unwrap_or_default(),
1472 ..Default::default()
1473 })
1474 .map_err(|e| e.to_string())
1475 });
1476
1477 match result {
1478 Ok(acct) => to_json_cstr(&acct),
1479 Err(_) => ptr::null_mut(),
1480 }
1481}
1482
1483#[unsafe(no_mangle)]
1490pub extern "C" fn stateset_backorder_create(
1491 handle: *mut CommerceHandle,
1492 order_id: *const c_char,
1493 customer_id: *const c_char,
1494 sku: *const c_char,
1495 quantity: c_double,
1496) -> *mut c_char {
1497 let ord_id = match cstr_to_string(order_id).and_then(|s| s.parse().ok()) {
1498 Some(id) => id,
1499 None => return ptr::null_mut(),
1500 };
1501 let cust_id = match cstr_to_string(customer_id).and_then(|s| s.parse().ok()) {
1502 Some(id) => id,
1503 None => return ptr::null_mut(),
1504 };
1505 let sku_str = match cstr_to_string(sku) {
1506 Some(s) => s,
1507 None => return ptr::null_mut(),
1508 };
1509
1510 let result = use_handle(handle, |commerce| {
1511 commerce
1512 .backorder()
1513 .create_backorder(CreateBackorder {
1514 order_id: ord_id,
1515 customer_id: cust_id,
1516 sku: sku_str,
1517 quantity: Decimal::try_from(quantity).unwrap_or_default(),
1518 priority: Some(BackorderPriority::Normal),
1519 order_line_id: None,
1520 expected_date: None,
1521 promised_date: None,
1522 source_location_id: None,
1523 notes: None,
1524 })
1525 .map_err(|e| e.to_string())
1526 });
1527
1528 match result {
1529 Ok(bo) => to_json_cstr(&bo),
1530 Err(_) => ptr::null_mut(),
1531 }
1532}
1533
1534#[unsafe(no_mangle)]
1537pub extern "C" fn stateset_backorder_list(handle: *mut CommerceHandle) -> *mut c_char {
1538 let result = use_handle(handle, |commerce| {
1539 commerce.backorder().list_backorders(Default::default()).map_err(|e| e.to_string())
1540 });
1541
1542 match result {
1543 Ok(list) => to_json_cstr(&list),
1544 Err(_) => ptr::null_mut(),
1545 }
1546}
1547
1548#[unsafe(no_mangle)]
1555pub extern "C" fn stateset_gl_create_account(
1556 handle: *mut CommerceHandle,
1557 account_number: *const c_char,
1558 name: *const c_char,
1559 account_type: *const c_char,
1560) -> *mut c_char {
1561 let num = match cstr_to_string(account_number) {
1562 Some(s) => s,
1563 None => return ptr::null_mut(),
1564 };
1565 let name_str = match cstr_to_string(name) {
1566 Some(s) => s,
1567 None => return ptr::null_mut(),
1568 };
1569 let acct_type = match cstr_to_string(account_type).as_deref() {
1570 Some("asset") => AccountType::Asset,
1571 Some("liability") => AccountType::Liability,
1572 Some("equity") => AccountType::Equity,
1573 Some("revenue") => AccountType::Revenue,
1574 Some("expense") => AccountType::Expense,
1575 _ => AccountType::Asset,
1576 };
1577
1578 let result = use_handle(handle, |commerce| {
1579 commerce
1580 .general_ledger()
1581 .create_account(CreateGlAccount {
1582 account_number: num,
1583 name: name_str,
1584 account_type: acct_type,
1585 description: None,
1586 account_sub_type: None,
1587 parent_account_id: None,
1588 is_header: None,
1589 is_posting: Some(true),
1590 currency: None,
1591 })
1592 .map_err(|e| e.to_string())
1593 });
1594
1595 match result {
1596 Ok(acct) => to_json_cstr(&acct),
1597 Err(_) => ptr::null_mut(),
1598 }
1599}
1600
1601#[unsafe(no_mangle)]
1604pub extern "C" fn stateset_gl_list_accounts(handle: *mut CommerceHandle) -> *mut c_char {
1605 let result = use_handle(handle, |commerce| {
1606 commerce.general_ledger().list_accounts(Default::default()).map_err(|e| e.to_string())
1607 });
1608
1609 match result {
1610 Ok(list) => to_json_cstr(&list),
1611 Err(_) => ptr::null_mut(),
1612 }
1613}