@pisell/pisellos 2.3.100 → 2.3.102

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/model/strategy/adapter/dataVariant/evaluator.d.ts +18 -2
  2. package/dist/model/strategy/adapter/dataVariant/evaluator.js +287 -101
  3. package/dist/model/strategy/adapter/dataVariant/type.d.ts +25 -0
  4. package/dist/model/strategy/adapter/promotion/index.js +9 -0
  5. package/dist/modules/Discount/index.d.ts +2 -0
  6. package/dist/modules/Discount/index.js +38 -5
  7. package/dist/modules/Discount/types.d.ts +11 -0
  8. package/dist/modules/Order/index.d.ts +14 -0
  9. package/dist/modules/Order/index.js +617 -368
  10. package/dist/modules/Order/types.d.ts +11 -3
  11. package/dist/modules/Order/utils/virtualSettlement.d.ts +63 -0
  12. package/dist/modules/Order/utils/virtualSettlement.js +284 -0
  13. package/dist/modules/Order/utils.d.ts +1 -0
  14. package/dist/modules/Order/utils.js +63 -24
  15. package/dist/modules/Product/types.d.ts +23 -0
  16. package/dist/modules/Rules/index.d.ts +5 -0
  17. package/dist/modules/Rules/index.js +27 -12
  18. package/dist/modules/SalesSummary/index.d.ts +1 -0
  19. package/dist/modules/SalesSummary/index.js +4 -3
  20. package/dist/modules/SalesSummary/types.d.ts +1 -0
  21. package/dist/modules/SalesSummary/utils.d.ts +1 -0
  22. package/dist/modules/SalesSummary/utils.js +21 -1
  23. package/dist/solution/BaseSales/index.d.ts +10 -1
  24. package/dist/solution/BaseSales/index.js +1099 -784
  25. package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.d.ts +1 -0
  26. package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +72 -9
  27. package/dist/solution/BookingByStep/index.d.ts +2 -2
  28. package/dist/solution/BookingTicket/utils/addProductDecision.js +2 -1
  29. package/lib/model/strategy/adapter/dataVariant/evaluator.d.ts +18 -2
  30. package/lib/model/strategy/adapter/dataVariant/evaluator.js +137 -6
  31. package/lib/model/strategy/adapter/dataVariant/type.d.ts +25 -0
  32. package/lib/modules/Discount/index.d.ts +2 -0
  33. package/lib/modules/Discount/index.js +31 -2
  34. package/lib/modules/Discount/types.d.ts +11 -0
  35. package/lib/modules/Order/index.d.ts +14 -0
  36. package/lib/modules/Order/index.js +195 -15
  37. package/lib/modules/Order/types.d.ts +11 -3
  38. package/lib/modules/Order/utils/virtualSettlement.d.ts +63 -0
  39. package/lib/modules/Order/utils/virtualSettlement.js +262 -0
  40. package/lib/modules/Order/utils.d.ts +1 -0
  41. package/lib/modules/Order/utils.js +57 -20
  42. package/lib/modules/Product/types.d.ts +23 -0
  43. package/lib/modules/Rules/index.d.ts +5 -0
  44. package/lib/modules/Rules/index.js +20 -11
  45. package/lib/modules/SalesSummary/index.d.ts +1 -0
  46. package/lib/modules/SalesSummary/index.js +4 -2
  47. package/lib/modules/SalesSummary/types.d.ts +1 -0
  48. package/lib/modules/SalesSummary/utils.d.ts +1 -0
  49. package/lib/modules/SalesSummary/utils.js +17 -1
  50. package/lib/solution/BaseSales/index.d.ts +10 -1
  51. package/lib/solution/BaseSales/index.js +211 -9
  52. package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.d.ts +1 -0
  53. package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +38 -0
  54. package/lib/solution/BookingByStep/index.d.ts +2 -2
  55. package/lib/solution/BookingTicket/utils/addProductDecision.js +1 -0
  56. package/package.json +1 -1
@@ -18,6 +18,7 @@ var _manualProductDiscount = require("./utils/manualProductDiscount");
18
18
  var _bundleDiscountHydration = require("./utils/bundleDiscountHydration");
19
19
  var _orderCollectionIdentity = require("./utils/orderCollectionIdentity");
20
20
  var _discountProductLineIdentity = require("./utils/discountProductLineIdentity");
21
+ var _virtualSettlement = require("./utils/virtualSettlement");
21
22
  var _Discount = require("../Discount");
22
23
  var _Rules = require("../Rules");
23
24
  var _types2 = require("../Rules/types");
@@ -298,6 +299,7 @@ class OrderModule extends _BaseModule.BaseModule {
298
299
  this.store.tempOrder = null;
299
300
  this.store.summary = (0, _utils.createEmptySummary)();
300
301
  this.store.availableWalletIds = [];
302
+ this.store.virtualCurrencyList = [];
301
303
  this.store.lastOrderInfo = undefined;
302
304
  this.store.syncState = undefined;
303
305
  this.store.discountList = [];
@@ -310,6 +312,9 @@ class OrderModule extends _BaseModule.BaseModule {
310
312
  if (!Array.isArray(this.store.availableWalletIds)) {
311
313
  this.store.availableWalletIds = [];
312
314
  }
315
+ if (!Array.isArray(this.store.virtualCurrencyList)) {
316
+ this.store.virtualCurrencyList = [];
317
+ }
313
318
  this.request = this.core.getPlugin('request');
314
319
  const appPlugin = this.core.getPlugin('app');
315
320
  if (!appPlugin) {
@@ -364,6 +369,12 @@ class OrderModule extends _BaseModule.BaseModule {
364
369
  this.otherParams = otherParams;
365
370
  }
366
371
 
372
+ /** PC/H5 属于在线店铺;其它或缺失平台统一沿用 POS 行为。 */
373
+ isOnlineStorePlatform() {
374
+ const platform = String(this.otherParams?.platform || '').trim().toLowerCase();
375
+ return platform === 'pc' || platform === 'h5';
376
+ }
377
+
367
378
  /**
368
379
  * 记录信息日志
369
380
  */
@@ -451,6 +462,7 @@ class OrderModule extends _BaseModule.BaseModule {
451
462
  with_wallet_pass_holder: 1,
452
463
  with_available_wallet_flag: 1,
453
464
  with_available_wallet_pass_flag: 1,
465
+ with_virtual_currency: 1,
454
466
  request_timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
455
467
  ...(orderId ? {
456
468
  order_id: orderId
@@ -458,6 +470,7 @@ class OrderModule extends _BaseModule.BaseModule {
458
470
  });
459
471
  const discountList = prepareConfigResult?.discountList;
460
472
  this.store.availableWalletIds = prepareConfigResult?.availableWalletIds || [];
473
+ this.store.virtualCurrencyList = prepareConfigResult?.virtualCurrencyList || [];
461
474
  if (discountList) {
462
475
  const currentDiscountList = this.store.discount?.getDiscountList() || [];
463
476
  const currentEditDiscounts = currentDiscountList.filter(discount => discount?.isEditMode);
@@ -485,6 +498,9 @@ class OrderModule extends _BaseModule.BaseModule {
485
498
  getAvailableWalletIds() {
486
499
  return Array.isArray(this.store.availableWalletIds) ? [...this.store.availableWalletIds] : [];
487
500
  }
501
+ getVirtualCurrencyList() {
502
+ return (0, _lodashEs.cloneDeep)(this.store.virtualCurrencyList || []);
503
+ }
488
504
  async syncScannedDiscountsToOriginalDiscountList(discountList) {
489
505
  const scanDiscounts = (discountList || []).filter(discount => discount?.isScan);
490
506
  if (!scanDiscounts.length) return;
@@ -508,6 +524,16 @@ class OrderModule extends _BaseModule.BaseModule {
508
524
  });
509
525
  }
510
526
  async scanCode(code, customerId) {
527
+ if (this.isActiveVirtualSettlementOrder(this.store.tempOrder)) {
528
+ return {
529
+ type: 'clientCalc',
530
+ isAvailable: false,
531
+ isAccepted: false,
532
+ discountList: [],
533
+ scannedDiscountList: [],
534
+ unavailableReason: _types2.UnavailableReason.ProductNotApplicable
535
+ };
536
+ }
511
537
  const resultDiscountWithReason = (await this.store.discount?.batchSearch(code, {
512
538
  customerId
513
539
  })) || {
@@ -603,6 +629,14 @@ class OrderModule extends _BaseModule.BaseModule {
603
629
  // 也需要让 Rules.calcDiscount 把 DiscountModule 的 isSelected / isAvailable /
604
630
  // appliedProductDetails / savedAmount 复位。
605
631
  if (!tempOrder) return;
632
+ if (this.isActiveVirtualSettlementOrder(tempOrder)) {
633
+ tempOrder.products = (0, _virtualSettlement.normalizeVirtualSettlementProducts)(tempOrder.products);
634
+ tempOrder.discount_list = [];
635
+ tempOrder.shop_discount = '0.00';
636
+ void this.store.discount?.setDiscountList([]);
637
+ void this.store.discount?.setOriginalDiscountList([]);
638
+ return;
639
+ }
606
640
  this.syncLinkedBookingHoldersToProducts(tempOrder);
607
641
  delete tempOrder.discount_list;
608
642
  const discountList = this.store.discount?.getDiscountList() || [];
@@ -700,6 +734,10 @@ class OrderModule extends _BaseModule.BaseModule {
700
734
  if (!tempOrder) {
701
735
  return;
702
736
  }
737
+ if (this.isActiveVirtualSettlementOrder(tempOrder)) {
738
+ tempOrder.products = (0, _virtualSettlement.normalizeVirtualSettlementProducts)(tempOrder.products);
739
+ return;
740
+ }
703
741
  const strategyConfigs = this.promotionEvaluator.getStrategyConfigs?.();
704
742
  if (Array.isArray(strategyConfigs) && strategyConfigs.length === 0) {
705
743
  return;
@@ -1830,7 +1868,8 @@ class OrderModule extends _BaseModule.BaseModule {
1830
1868
  return product;
1831
1869
  }
1832
1870
  sanitizeTempOrderProducts(tempOrder) {
1833
- tempOrder.products = (tempOrder.products || []).map(product => this.sanitizeOrderProductForTempOrder(product));
1871
+ tempOrder.products = (tempOrder.products || []).map(product => this.sanitizeOrderProductForTempOrder(this.normalizeVirtualSettlementProductIfEnabled(product)));
1872
+ this.syncTempOrderSettlementState(tempOrder);
1834
1873
  (0, _discountProductLineIdentity.ensureUniqueProductLineUids)(tempOrder);
1835
1874
  }
1836
1875
  ensureExtend(tempOrder) {
@@ -2151,6 +2190,7 @@ class OrderModule extends _BaseModule.BaseModule {
2151
2190
  products: summaryProducts,
2152
2191
  isPriceIncludeTax: tempOrder.is_price_include_tax,
2153
2192
  shopDiscount: tempOrder.shop_discount,
2193
+ virtualSettlementMode: this.shouldUseVirtualSettlementSummary(tempOrder),
2154
2194
  ...(this.otherParams?.channel ? {
2155
2195
  channel: this.otherParams.channel
2156
2196
  } : {})
@@ -2619,6 +2659,7 @@ class OrderModule extends _BaseModule.BaseModule {
2619
2659
  const nextCustomerId = tempOrder.customer_id ?? null;
2620
2660
  if (String(previousCustomerId ?? '') !== String(nextCustomerId ?? '')) {
2621
2661
  this.store.availableWalletIds = [];
2662
+ this.store.virtualCurrencyList = [];
2622
2663
  (0, _utils.clearTempOrderHolderAssignments)(tempOrder);
2623
2664
  this.clearCustomerBoundDiscounts(tempOrder);
2624
2665
  this.applyDiscount();
@@ -2657,6 +2698,7 @@ class OrderModule extends _BaseModule.BaseModule {
2657
2698
  tempOrder.customer = null;
2658
2699
  if (tempOrder._extend) delete tempOrder._extend.customerSnapshot;
2659
2700
  this.store.availableWalletIds = [];
2701
+ this.store.virtualCurrencyList = [];
2660
2702
  this.clearCustomerBoundDiscounts(tempOrder);
2661
2703
  this.applyDiscount();
2662
2704
  this.persistTempOrder();
@@ -2976,6 +3018,78 @@ class OrderModule extends _BaseModule.BaseModule {
2976
3018
  const bookingUid = product?.booking_uid ?? product?.metadata?.booking_uid;
2977
3019
  return bookingUid !== undefined && bookingUid !== null && String(bookingUid) !== '';
2978
3020
  }
3021
+ isMultiCurrencySettlementEnabled() {
3022
+ return this.otherParams?.enableMultiCurrencySettlement === true;
3023
+ }
3024
+ isActiveVirtualSettlementOrder(tempOrder) {
3025
+ return this.isMultiCurrencySettlementEnabled() && (0, _virtualSettlement.isVirtualSettlementOrder)(tempOrder);
3026
+ }
3027
+ shouldUseVirtualSettlementSummary(tempOrder) {
3028
+ return this.isActiveVirtualSettlementOrder(tempOrder) || tempOrder?.metadata?.currency_settlement_config?.settlement_type === 'virtual_currency';
3029
+ }
3030
+ normalizeVirtualSettlementProductIfEnabled(product) {
3031
+ return this.isMultiCurrencySettlementEnabled() ? (0, _virtualSettlement.normalizeVirtualSettlementOrderProduct)(product) : product;
3032
+ }
3033
+ assertAndLockProductSettlement(tempOrder, product) {
3034
+ if (!this.isMultiCurrencySettlementEnabled()) return;
3035
+ const currentKey = (0, _virtualSettlement.getTempOrderSettlementKey)(tempOrder);
3036
+ const incomingKey = (0, _virtualSettlement.getSettlementKey)((0, _virtualSettlement.getSelectedSettlementSnapshot)(product)) || 'legal_currency';
3037
+ if (currentKey && currentKey !== incomingKey) {
3038
+ throw new _virtualSettlement.OrderSettlementConflictError(currentKey, incomingKey);
3039
+ }
3040
+ const snapshot = (0, _virtualSettlement.getSelectedSettlementSnapshot)(product);
3041
+ if (snapshot?.settlement_type === 'virtual_currency' && tempOrder.customer_id && Number(tempOrder.customer_id) !== 1) {
3042
+ const currencyProductId = Number(snapshot.currency_product_id);
3043
+ const asset = (this.store.virtualCurrencyList || []).find(item => Number(item?.product_id) === currencyProductId);
3044
+ if (!asset) {
3045
+ throw new _virtualSettlement.VirtualCurrencyAssetNotFoundError(currencyProductId);
3046
+ }
3047
+ const existedAmount = tempOrder.products.reduce((sum, item) => sum.plus(new _decimal.default(item.selling_price || 0).times(item.num || 1)), new _decimal.default(0));
3048
+ const requiredAmount = existedAmount.plus(new _decimal.default(snapshot.price || 0).times(product.num || 1));
3049
+ const balance = new _decimal.default(asset.balance || 0);
3050
+ if (balance.lt(requiredAmount)) {
3051
+ throw new _virtualSettlement.VirtualCurrencyBalanceError(balance.toFixed(2), requiredAmount.toFixed(2));
3052
+ }
3053
+ }
3054
+ (0, _virtualSettlement.applyTempOrderSettlementState)(tempOrder, snapshot || {
3055
+ settlement_type: 'legal_currency',
3056
+ price: product.selling_price ?? product.original_price ?? product.payment_price ?? 0,
3057
+ unit_label: tempOrder.currency_code,
3058
+ symbol: tempOrder.currency_symbol,
3059
+ amount_format: tempOrder.currency_format
3060
+ });
3061
+ }
3062
+ syncTempOrderSettlementState(tempOrder) {
3063
+ if (!this.isMultiCurrencySettlementEnabled()) return;
3064
+ tempOrder.metadata = tempOrder.metadata || {};
3065
+ if (!tempOrder.products.length) {
3066
+ (0, _virtualSettlement.releaseTempOrderSettlementState)(tempOrder);
3067
+ return;
3068
+ }
3069
+ const snapshot = (0, _virtualSettlement.getTempOrderSettlementSnapshot)(tempOrder);
3070
+ if (snapshot) {
3071
+ (0, _virtualSettlement.applyTempOrderSettlementState)(tempOrder, snapshot);
3072
+ return;
3073
+ }
3074
+ tempOrder.metadata.cart_settlement_key = 'legal_currency';
3075
+ }
3076
+ prepareVirtualSettlementTempOrder(tempOrder) {
3077
+ if (!this.isMultiCurrencySettlementEnabled()) return;
3078
+ this.syncTempOrderSettlementState(tempOrder);
3079
+ if (!this.isActiveVirtualSettlementOrder(tempOrder)) return;
3080
+ tempOrder.products = (0, _virtualSettlement.normalizeVirtualSettlementProducts)(tempOrder.products);
3081
+ tempOrder.discount_list = [];
3082
+ tempOrder.shop_discount = '0.00';
3083
+ tempOrder.surcharge_fee = '0.00';
3084
+ tempOrder.surcharges = [];
3085
+ const virtualOrder = tempOrder;
3086
+ virtualOrder.discount_amount = '0.00';
3087
+ virtualOrder.product_tax_fee = '0.00';
3088
+ virtualOrder.tax_fee = '0.00';
3089
+ virtualOrder.shipping_fee = '0.00';
3090
+ virtualOrder.shipping_tax_fee = '0.00';
3091
+ virtualOrder.tip_fee = '0.00';
3092
+ }
2979
3093
 
2980
3094
  // ─── TempOrder: 商品 CRUD ───
2981
3095
 
@@ -2987,13 +3101,15 @@ class OrderModule extends _BaseModule.BaseModule {
2987
3101
  */
2988
3102
  async finalizeProductOrderMutation(tempOrder, options) {
2989
3103
  this.syncTempOrderHolderFromBookings(tempOrder);
2990
- if (!options?.skipEditDiscountConfigRefresh) {
3104
+ this.prepareVirtualSettlementTempOrder(tempOrder);
3105
+ const isVirtualSettlement = this.isActiveVirtualSettlementOrder(tempOrder);
3106
+ if (!options?.skipEditDiscountConfigRefresh && !isVirtualSettlement) {
2991
3107
  await this.ensureEditDiscountConfigForProductChange(tempOrder);
2992
3108
  }
2993
- if (!options?.skipDiscountRecalculation) {
3109
+ if (!options?.skipDiscountRecalculation && !isVirtualSettlement) {
2994
3110
  await this.applyPromotion();
2995
3111
  }
2996
- if (!options?.skipDiscountRecalculation) {
3112
+ if (!options?.skipDiscountRecalculation && !isVirtualSettlement) {
2997
3113
  this.applyDiscount();
2998
3114
  }
2999
3115
  this.sanitizeTempOrderProducts(tempOrder);
@@ -3021,9 +3137,10 @@ class OrderModule extends _BaseModule.BaseModule {
3021
3137
  booking
3022
3138
  }) : null;
3023
3139
  const productToAdd = linked?.product || product;
3140
+ this.assertAndLockProductSettlement(tempOrder, productToAdd);
3024
3141
  const disableMerge = options?.disableMerge === true;
3025
3142
  const incomingFingerprint = disableMerge ? '' : (0, _utils3.buildProductLineFingerprint)((0, _utils.getProductSkuOptions)(productToAdd), productToAdd.product_bundle);
3026
- const normalizedIncoming = (0, _utils.mergeOrderProductDisplayFields)(productToAdd, (0, _utils3.normalizeOrderProduct)(productToAdd));
3143
+ const normalizedIncoming = this.normalizeVirtualSettlementProductIfEnabled((0, _utils.mergeOrderProductDisplayFields)(productToAdd, (0, _utils3.normalizeOrderProduct)(productToAdd)));
3027
3144
  if (disableMerge) {
3028
3145
  normalizedIncoming.metadata = {
3029
3146
  ...(normalizedIncoming.metadata || {}),
@@ -3084,13 +3201,13 @@ class OrderModule extends _BaseModule.BaseModule {
3084
3201
  } else {
3085
3202
  const targetProduct = matchedProduct;
3086
3203
  const targetUid = targetProduct.metadata?.unique_identification_number;
3087
- const normalizedProduct = (0, _utils.mergeOrderProductDisplayFields)(targetProduct, (0, _utils3.normalizeOrderProduct)({
3204
+ const normalizedProduct = this.normalizeVirtualSettlementProductIfEnabled((0, _utils.mergeOrderProductDisplayFields)(targetProduct, (0, _utils3.normalizeOrderProduct)({
3088
3205
  ...productToAdd,
3089
3206
  metadata: {
3090
3207
  ...(productToAdd.metadata || {}),
3091
3208
  unique_identification_number: targetUid
3092
3209
  }
3093
- }));
3210
+ })));
3094
3211
  normalizedProduct.discount_list = (0, _utils.normalizeOrderProductDiscountList)(normalizedProduct.discount_list);
3095
3212
  const nextNum = (0, _utils3.getSafeProductNum)(targetProduct.num + normalizedProduct.num);
3096
3213
  tempOrder.products[matchedIndex] = {
@@ -3498,7 +3615,7 @@ class OrderModule extends _BaseModule.BaseModule {
3498
3615
  }
3499
3616
  // normalizeOrderProduct 幂等:v2 metadata 存在 → 保留 main_product_* 折扣重算 composite;
3500
3617
  // 否则 → 把顶层 selling_price 视作 source,派生 main_product_* 并写 price_schema_version: 2。
3501
- let normalizedProduct = (0, _utils.mergeOrderProductDisplayFields)(nextProduct, (0, _utils3.normalizeOrderProduct)(nextProduct));
3618
+ let normalizedProduct = this.normalizeVirtualSettlementProductIfEnabled((0, _utils.mergeOrderProductDisplayFields)(nextProduct, (0, _utils3.normalizeOrderProduct)(nextProduct)));
3502
3619
  normalizedProduct.discount_list = (0, _utils.normalizeOrderProductDiscountList)(normalizedProduct.discount_list);
3503
3620
  const preservedBookingUid = nextProduct.booking_uid || nextProduct.metadata?.booking_uid || targetProduct.booking_uid || targetProduct.metadata?.booking_uid;
3504
3621
  if (!booking && preservedBookingUid) {
@@ -3615,18 +3732,38 @@ class OrderModule extends _BaseModule.BaseModule {
3615
3732
  throw new Error('[Order] 目标商品不存在,无法更新数量');
3616
3733
  }
3617
3734
  const targetProduct = tempOrder.products[productIndex];
3618
- const normalizedProduct = (0, _utils3.normalizeOrderProduct)({
3735
+ const normalizedProduct = this.normalizeVirtualSettlementProductIfEnabled((0, _utils3.normalizeOrderProduct)({
3619
3736
  ...targetProduct,
3620
3737
  num: (0, _utils3.getSafeProductNum)(num),
3621
3738
  metadata: {
3622
3739
  ...(targetProduct.metadata || {}),
3623
3740
  unique_identification_number: targetProduct.metadata?.unique_identification_number || unique_identification_number
3624
3741
  }
3625
- });
3742
+ }));
3626
3743
  normalizedProduct.discount_list = (0, _utils.normalizeOrderProductDiscountList)(normalizedProduct.discount_list);
3744
+ const settlementSnapshot = (0, _virtualSettlement.getSelectedSettlementSnapshot)(normalizedProduct);
3745
+ if (this.isMultiCurrencySettlementEnabled() && settlementSnapshot?.settlement_type === 'virtual_currency' && tempOrder.customer_id && Number(tempOrder.customer_id) !== 1) {
3746
+ const currencyProductId = Number(settlementSnapshot.currency_product_id);
3747
+ const asset = (this.store.virtualCurrencyList || []).find(item => Number(item?.product_id) === currencyProductId);
3748
+ if (!asset) {
3749
+ throw new _virtualSettlement.VirtualCurrencyAssetNotFoundError(currencyProductId);
3750
+ }
3751
+ const requiredAmount = tempOrder.products.reduce((sum, item, index) => {
3752
+ const current = index === productIndex ? normalizedProduct : item;
3753
+ const currentSnapshot = (0, _virtualSettlement.getSelectedSettlementSnapshot)(current);
3754
+ return sum.plus(new _decimal.default(currentSnapshot?.price || current.selling_price || 0).times(current.num || 1));
3755
+ }, new _decimal.default(0));
3756
+ const balance = new _decimal.default(asset.balance || 0);
3757
+ if (balance.lt(requiredAmount)) {
3758
+ throw new _virtualSettlement.VirtualCurrencyBalanceError(balance.toFixed(2), requiredAmount.toFixed(2));
3759
+ }
3760
+ }
3627
3761
  tempOrder.products[productIndex] = normalizedProduct;
3628
- await this.applyPromotion();
3629
- this.applyDiscount();
3762
+ this.prepareVirtualSettlementTempOrder(tempOrder);
3763
+ if (!this.isActiveVirtualSettlementOrder(tempOrder)) {
3764
+ await this.applyPromotion();
3765
+ this.applyDiscount();
3766
+ }
3630
3767
  this.sanitizeTempOrderProducts(tempOrder);
3631
3768
  await this.recalculateSummary({
3632
3769
  createIfMissing: true
@@ -3780,6 +3917,7 @@ class OrderModule extends _BaseModule.BaseModule {
3780
3917
  this.ensureSalesNotesLoadedForMutation(tempOrder);
3781
3918
  tempOrder.products = [];
3782
3919
  tempOrder.bookings = [];
3920
+ (0, _virtualSettlement.releaseTempOrderSettlementState)(tempOrder);
3783
3921
  this.removeSalesNotesForMissingProductLines(tempOrder);
3784
3922
  this.syncTempOrderHolderFromBookings(tempOrder);
3785
3923
  if (tempOrder._extend && typeof tempOrder._extend === 'object') {
@@ -3799,6 +3937,41 @@ class OrderModule extends _BaseModule.BaseModule {
3799
3937
  return tempOrder;
3800
3938
  }
3801
3939
 
3940
+ /**
3941
+ * 切换购物车结算单位时清理所有会影响新币种的订单状态,但保留当前客户。
3942
+ */
3943
+ async clearOrderForSettlementSwitch() {
3944
+ const tempOrder = this.ensureTempOrder();
3945
+ this.ensureSalesNotesLoadedForMutation(tempOrder);
3946
+ tempOrder.products = [];
3947
+ tempOrder.bookings = [];
3948
+ this.removeSalesNotesForMissingProductLines(tempOrder);
3949
+ tempOrder.discount_list = [];
3950
+ tempOrder.payments = [];
3951
+ tempOrder.refunds = [];
3952
+ tempOrder.shop_discount = '0.00';
3953
+ tempOrder.surcharges = [];
3954
+ (0, _virtualSettlement.releaseTempOrderSettlementState)(tempOrder);
3955
+ this.syncTempOrderHolderFromBookings(tempOrder);
3956
+ if (tempOrder._extend && typeof tempOrder._extend === 'object') {
3957
+ tempOrder._extend = {
3958
+ ...tempOrder._extend,
3959
+ productsByUid: {}
3960
+ };
3961
+ }
3962
+ await this.store.discount?.setDiscountList([]);
3963
+ await this.store.discount?.setOriginalDiscountList([]);
3964
+ this.hasActiveCustomerBoundDiscount = false;
3965
+ this.lastUnfulfilledPromotions = [];
3966
+ this.lastGiftActions = null;
3967
+ await this.recalculateSummary({
3968
+ createIfMissing: true
3969
+ });
3970
+ this.persistTempOrder();
3971
+ this.logInfo('clearOrderForSettlementSwitch success', {});
3972
+ return tempOrder;
3973
+ }
3974
+
3802
3975
  // ─── TempOrder: 提交 ───
3803
3976
 
3804
3977
  buildCurrentSubmitPayloadForLocalPrint(params) {
@@ -3888,9 +4061,14 @@ class OrderModule extends _BaseModule.BaseModule {
3888
4061
  }
3889
4062
  async runSubmitTempOrder(params) {
3890
4063
  const tempOrder = this.ensureTempOrder();
4064
+ this.prepareVirtualSettlementTempOrder(tempOrder);
4065
+ const isOnlineStorePlatform = this.isOnlineStorePlatform();
3891
4066
  const shouldConfirmPendingVoucherPayments = params?.confirmPendingVoucherPayments !== false;
3892
4067
  const hasPaymentOverride = params?.payments !== undefined;
3893
- const preparedPaymentOverride = hasPaymentOverride ? this.prepareVoucherPaymentsForSubmit(params?.payments || [], shouldConfirmPendingVoucherPayments) : undefined;
4068
+ let preparedPaymentOverride;
4069
+ if (hasPaymentOverride) {
4070
+ preparedPaymentOverride = isOnlineStorePlatform ? params?.payments || [] : this.prepareVoucherPaymentsForSubmit(params?.payments || [], shouldConfirmPendingVoucherPayments);
4071
+ }
3894
4072
  if (!hasPaymentOverride) {
3895
4073
  if (shouldConfirmPendingVoucherPayments) {
3896
4074
  this.confirmPendingVoucherPayments({
@@ -3910,6 +4088,7 @@ class OrderModule extends _BaseModule.BaseModule {
3910
4088
  })) || this.store.summary || (0, _utils.createEmptySummary)();
3911
4089
  const effectiveCacheId = params?.cacheId ?? this.cacheId;
3912
4090
  const hasPaymentStatusOverride = params?.paymentStatus !== undefined;
4091
+ const shouldOverridePaymentStatus = hasPaymentStatusOverride && !isOnlineStorePlatform && !shouldConfirmPendingVoucherPayments;
3913
4092
  const hasSmallTicketDataFlagOverride = params?.smallTicketDataFlag !== undefined;
3914
4093
  const enhancePayload = hasPaymentOverride || hasPaymentStatusOverride || hasSmallTicketDataFlagOverride || params?.enhancePayload ? (payload, ctx) => {
3915
4094
  const nextPayload = {
@@ -3917,7 +4096,7 @@ class OrderModule extends _BaseModule.BaseModule {
3917
4096
  ...(hasPaymentOverride ? {
3918
4097
  payments: preparedPaymentOverride || []
3919
4098
  } : {}),
3920
- ...(hasPaymentStatusOverride && !shouldConfirmPendingVoucherPayments ? {
4099
+ ...(shouldOverridePaymentStatus ? {
3921
4100
  payment_status: params?.paymentStatus
3922
4101
  } : {}),
3923
4102
  ...(hasSmallTicketDataFlagOverride ? {
@@ -3937,6 +4116,7 @@ class OrderModule extends _BaseModule.BaseModule {
3937
4116
  type: submitSnapshot.type ?? params?.type,
3938
4117
  summary: latestSummary,
3939
4118
  request_unique_idempotency_token: params?.request_unique_idempotency_token,
4119
+ includePaymentStatus: !isOnlineStorePlatform,
3940
4120
  enhance: enhancePayload
3941
4121
  });
3942
4122
  if (params?.syncMode) {
@@ -4868,7 +5048,7 @@ class OrderModule extends _BaseModule.BaseModule {
4868
5048
  };
4869
5049
  if (result.metadata) delete result.metadata.price_schema_version;
4870
5050
  const restored = (0, _bundleDiscountHydration.restoreHydratedBundleDiscounts)(result, _utils.normalizeOrderProductDiscountList);
4871
- return this.sanitizeOrderProductForTempOrder(restored);
5051
+ return this.sanitizeOrderProductForTempOrder(this.normalizeVirtualSettlementProductIfEnabled(restored));
4872
5052
  }
4873
5053
  getLastOrderInfo() {
4874
5054
  return this.store?.lastOrderInfo;
@@ -1,7 +1,7 @@
1
1
  import { CartItem } from '../Cart/types';
2
2
  import type { DiscountModule } from '../Discount';
3
3
  import type { RulesModule } from '../Rules';
4
- import type { Discount } from '../Discount/types';
4
+ import type { Discount, VirtualCurrencyAsset } from '../Discount/types';
5
5
  import type { UnavailableReason } from '../Rules/types';
6
6
  import type { SubmitPayloadEnhancer } from './utils';
7
7
  import type { PaymentItem } from '../Payment/types';
@@ -205,6 +205,7 @@ export interface OrderProduct extends OrderProductIdentity {
205
205
  gift_card?: number;
206
206
  selling_price: string;
207
207
  original_price: string;
208
+ payment_price?: string;
208
209
  tax_fee: string;
209
210
  is_charge_tax: number;
210
211
  discount_list: OrderProductDiscountItem[];
@@ -353,6 +354,7 @@ export interface OrderTempOrder {
353
354
  notes: SalesNote[];
354
355
  bookings: any[];
355
356
  payments: any[];
357
+ refunds?: any[];
356
358
  surcharges: any[];
357
359
  discount_list?: any[];
358
360
  relation_forms: any[];
@@ -372,8 +374,9 @@ export interface OrderSubmitProduct extends Omit<OrderProduct, 'unique_identific
372
374
  payment_price: string;
373
375
  product_sku: OrderProductSku;
374
376
  }
375
- export interface OrderSubmitPayload extends Omit<OrderTempOrder, 'platform' | 'products' | 'notes' | '_extend' | 'customer' | 'discount_list' | 'created_at'> {
377
+ export interface OrderSubmitPayload extends Omit<OrderTempOrder, 'platform' | 'products' | 'notes' | '_extend' | 'customer' | 'discount_list' | 'created_at' | 'payment_status'> {
376
378
  platform: string;
379
+ payment_status?: string;
377
380
  created_at?: string | undefined;
378
381
  request_unique_idempotency_token?: string;
379
382
  form_record_ids?: Array<{
@@ -392,6 +395,7 @@ export interface OrderState {
392
395
  syncState?: OrderSyncState;
393
396
  discountList: any[];
394
397
  availableWalletIds?: number[];
398
+ virtualCurrencyList?: VirtualCurrencyAsset[];
395
399
  discount: DiscountModule | null;
396
400
  rules: RulesModule | null;
397
401
  }
@@ -530,7 +534,7 @@ export interface SubmitSalesOrderParams {
530
534
  sales_channel: string;
531
535
  order_sales_channel: string;
532
536
  status: string;
533
- payment_status: string;
537
+ payment_status?: string;
534
538
  shipping_status: string;
535
539
  customer_id: number | null;
536
540
  customer_name: string;
@@ -794,6 +798,8 @@ export interface OrderModuleAPI {
794
798
  addNewOrder: () => Promise<OrderTempOrder>;
795
799
  restoreOrder: () => OrderTempOrder;
796
800
  getOrderProducts: () => OrderProduct[];
801
+ getVirtualCurrencyList: () => VirtualCurrencyAsset[];
802
+ clearOrderForSettlementSwitch: () => Promise<OrderTempOrder>;
797
803
  getOrderSummary: () => Promise<OrderSummary>;
798
804
  recalculateSummary: (options?: {
799
805
  createIfMissing?: boolean;
@@ -864,6 +870,8 @@ export interface OrderModuleAPI {
864
870
  isTempOrderPersistEnabled: () => boolean;
865
871
  /** 提交 tempOrder 变更并触发 `{moduleName}:changed` 事件 */
866
872
  notifyTempOrderChanged: () => void;
873
+ /** PC/H5 为在线店铺;其它或缺失平台统一按 POS 处理。 */
874
+ isOnlineStorePlatform: () => boolean;
867
875
  submitTempOrder: <T = any>(params?: {
868
876
  cacheId?: string;
869
877
  platform?: string;
@@ -0,0 +1,63 @@
1
+ import type { OrderProduct, OrderTempOrder } from '../types';
2
+ export interface SelectedSettlementSnapshot extends Record<string, any> {
3
+ settlement_type: 'legal_currency' | 'virtual_currency';
4
+ currency_product_id?: number;
5
+ price: string | number;
6
+ unit_label?: any;
7
+ symbol?: any;
8
+ amount_format?: string;
9
+ }
10
+ export interface LegalCurrencySnapshot {
11
+ currency_code: string;
12
+ currency_symbol: string;
13
+ currency_format: string;
14
+ }
15
+ export interface CurrencyDisplaySnapshot {
16
+ amount_format: string;
17
+ symbol: unknown;
18
+ unit_label: unknown;
19
+ }
20
+ export declare function buildCurrencyDisplaySnapshot(snapshot: Pick<SelectedSettlementSnapshot, 'amount_format' | 'symbol' | 'unit_label'>): CurrencyDisplaySnapshot;
21
+ export declare function resolveSettlementDisplayValue(value: unknown): string;
22
+ export declare class OrderSettlementConflictError extends Error {
23
+ readonly currentSettlementKey: string;
24
+ readonly incomingSettlementKey: string;
25
+ readonly code = "ORDER_SETTLEMENT_CONFLICT";
26
+ constructor(currentSettlementKey: string, incomingSettlementKey: string);
27
+ }
28
+ export declare class VirtualCurrencyAssetNotFoundError extends Error {
29
+ readonly currencyProductId: number;
30
+ readonly code = "VIRTUAL_CURRENCY_ASSET_NOT_FOUND";
31
+ constructor(currencyProductId: number);
32
+ }
33
+ export declare class VirtualCurrencyBalanceError extends Error {
34
+ readonly balance: string;
35
+ readonly requiredAmount: string;
36
+ readonly code = "VIRTUAL_CURRENCY_BALANCE_INSUFFICIENT";
37
+ constructor(balance: string, requiredAmount: string);
38
+ }
39
+ export declare function getSelectedSettlementSnapshot(product: Partial<OrderProduct> | null | undefined): SelectedSettlementSnapshot | null;
40
+ export declare function getTempOrderSettlementSnapshot(tempOrder: OrderTempOrder | null | undefined): SelectedSettlementSnapshot | null;
41
+ export declare function isVirtualSettlementProduct(product: Partial<OrderProduct> | null | undefined): boolean;
42
+ export declare function getSettlementKey(snapshot: SelectedSettlementSnapshot | null | undefined): string | null;
43
+ export declare function getTempOrderSettlementKey(tempOrder: OrderTempOrder | null | undefined): string | null;
44
+ export declare function isVirtualSettlementOrder(tempOrder: OrderTempOrder | null | undefined): boolean;
45
+ /**
46
+ * 详情展示识别同时支持已提交订单的 currency_settlement_config。
47
+ * cart_settlement_* 是交易过程内部状态,服务端详情不保证返回。
48
+ */
49
+ export declare function isVirtualSettlementDisplayOrder(tempOrder: OrderTempOrder | null | undefined): boolean;
50
+ /**
51
+ * 将购物车锁定的结算单位提升为订单级状态。
52
+ * 商品行快照同时负责价格归一化和生成订单级多语言展示快照;
53
+ * 交易中的即时展示仍统一读取 tempOrder.currency_*。
54
+ */
55
+ export declare function applyTempOrderSettlementState(tempOrder: OrderTempOrder, snapshot: SelectedSettlementSnapshot): void;
56
+ /** 清空购物车结算锁,并恢复进入虚拟币结算前的店铺法币。 */
57
+ export declare function releaseTempOrderSettlementState(tempOrder: OrderTempOrder): void;
58
+ /**
59
+ * 将已选择虚拟币的订单商品行固化为最终价语义。
60
+ * 函数幂等,可安全用于加车、合并、hydrate 和提交前兜底。
61
+ */
62
+ export declare function normalizeVirtualSettlementOrderProduct<T extends Partial<OrderProduct>>(product: T): T;
63
+ export declare function normalizeVirtualSettlementProducts<T extends Partial<OrderProduct>>(products: T[]): T[];