@pisell/pisellos 2.2.309 → 2.2.311

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 (122) hide show
  1. package/dist/model/strategy/adapter/promotion/index.js +0 -9
  2. package/dist/model/strategy/adapter/walletPass/evaluator.d.ts +2 -0
  3. package/dist/model/strategy/adapter/walletPass/evaluator.js +4 -2
  4. package/dist/modules/BookingContext/index.js +4 -4
  5. package/dist/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +58 -19
  6. package/dist/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +24 -12
  7. package/dist/modules/Customer/index.js +6 -3
  8. package/dist/modules/Customer/types.d.ts +2 -0
  9. package/dist/modules/Discount/availability.d.ts +7 -0
  10. package/dist/modules/Discount/availability.js +49 -0
  11. package/dist/modules/Discount/entitlementPass.d.ts +34 -0
  12. package/dist/modules/Discount/entitlementPass.js +147 -0
  13. package/dist/modules/Discount/index.d.ts +2 -2
  14. package/dist/modules/Discount/index.js +37 -151
  15. package/dist/modules/Discount/types.d.ts +36 -5
  16. package/dist/modules/OpenData/index.d.ts +1 -1
  17. package/dist/modules/OpenData/index.js +21 -12
  18. package/dist/modules/Order/index.d.ts +32 -4
  19. package/dist/modules/Order/index.js +733 -373
  20. package/dist/modules/Order/types.d.ts +17 -0
  21. package/dist/modules/Order/utils/orderCollectionIdentity.js +16 -12
  22. package/dist/modules/Order/utils.d.ts +19 -5
  23. package/dist/modules/Order/utils.js +306 -86
  24. package/dist/modules/Payment/index.d.ts +0 -5
  25. package/dist/modules/Payment/index.js +35 -85
  26. package/dist/modules/Payment/types.d.ts +21 -1
  27. package/dist/modules/Payment/types.js +2 -0
  28. package/dist/modules/Payment/walletpass.d.ts +1 -1
  29. package/dist/modules/Payment/walletpass.js +80 -47
  30. package/dist/modules/ProductList/index.js +18 -5
  31. package/dist/modules/Rules/index.d.ts +2 -2
  32. package/dist/modules/Rules/index.js +480 -186
  33. package/dist/modules/Rules/types.d.ts +4 -0
  34. package/dist/server/index.js +8 -5
  35. package/dist/server/modules/floor-plan/index.js +34 -3
  36. package/dist/server/modules/order/index.js +6 -0
  37. package/dist/server/modules/order/types.d.ts +12 -0
  38. package/dist/server/modules/payment/index.d.ts +25 -6
  39. package/dist/server/modules/payment/index.js +660 -133
  40. package/dist/server/utils/small-ticket.d.ts +8 -5
  41. package/dist/server/utils/small-ticket.js +1269 -199
  42. package/dist/solution/BaseSales/index.d.ts +11 -2
  43. package/dist/solution/BaseSales/index.js +1463 -1173
  44. package/dist/solution/BaseSales/types.d.ts +7 -3
  45. package/dist/solution/BaseSales/utils/cartPromotion.js +141 -126
  46. package/dist/solution/BookingTicket/index.d.ts +11 -2
  47. package/dist/solution/BookingTicket/index.js +663 -542
  48. package/dist/solution/BookingTicket/types.d.ts +5 -1
  49. package/dist/solution/BookingTicket/utils/scan/applyGlobalScan.d.ts +2 -0
  50. package/dist/solution/BookingTicket/utils/scan/applyGlobalScan.js +338 -147
  51. package/dist/solution/BookingTicket/utils/scan/cloudSearch.js +8 -4
  52. package/dist/solution/BookingTicket/utils/scan/formatGlobalScan.d.ts +6 -0
  53. package/dist/solution/BookingTicket/utils/scan/formatGlobalScan.js +19 -6
  54. package/dist/solution/ShopDiscount/index.d.ts +2 -0
  55. package/dist/solution/ShopDiscount/index.js +99 -42
  56. package/dist/solution/ShopDiscount/utils.d.ts +4 -2
  57. package/dist/solution/ShopDiscount/utils.js +43 -23
  58. package/dist/solution/UnifiedBookingSales/index.d.ts +2 -2
  59. package/dist/solution/UnifiedBookingSales/index.js +5 -5
  60. package/dist/utils/os-private-payload.d.ts +5 -0
  61. package/dist/utils/os-private-payload.js +17 -0
  62. package/lib/model/strategy/adapter/walletPass/evaluator.d.ts +2 -0
  63. package/lib/model/strategy/adapter/walletPass/evaluator.js +2 -0
  64. package/lib/modules/BookingContext/index.js +3 -3
  65. package/lib/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +35 -6
  66. package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +13 -6
  67. package/lib/modules/Customer/index.js +5 -1
  68. package/lib/modules/Customer/types.d.ts +2 -0
  69. package/lib/modules/Discount/availability.d.ts +7 -0
  70. package/lib/modules/Discount/availability.js +43 -0
  71. package/lib/modules/Discount/entitlementPass.d.ts +34 -0
  72. package/lib/modules/Discount/entitlementPass.js +141 -0
  73. package/lib/modules/Discount/index.d.ts +2 -2
  74. package/lib/modules/Discount/index.js +23 -129
  75. package/lib/modules/Discount/types.d.ts +36 -5
  76. package/lib/modules/OpenData/index.d.ts +1 -1
  77. package/lib/modules/OpenData/index.js +11 -2
  78. package/lib/modules/Order/index.d.ts +32 -4
  79. package/lib/modules/Order/index.js +341 -71
  80. package/lib/modules/Order/types.d.ts +17 -0
  81. package/lib/modules/Order/utils/orderCollectionIdentity.js +14 -11
  82. package/lib/modules/Order/utils.d.ts +19 -5
  83. package/lib/modules/Order/utils.js +215 -13
  84. package/lib/modules/Payment/index.d.ts +0 -5
  85. package/lib/modules/Payment/index.js +3 -28
  86. package/lib/modules/Payment/types.d.ts +21 -1
  87. package/lib/modules/Payment/types.js +1 -0
  88. package/lib/modules/Payment/walletpass.d.ts +1 -1
  89. package/lib/modules/Payment/walletpass.js +29 -9
  90. package/lib/modules/ProductList/index.js +14 -1
  91. package/lib/modules/Rules/index.d.ts +2 -2
  92. package/lib/modules/Rules/index.js +357 -110
  93. package/lib/modules/Rules/types.d.ts +4 -0
  94. package/lib/server/index.js +8 -5
  95. package/lib/server/modules/floor-plan/index.js +29 -1
  96. package/lib/server/modules/order/index.js +6 -0
  97. package/lib/server/modules/order/types.d.ts +12 -0
  98. package/lib/server/modules/payment/index.d.ts +25 -6
  99. package/lib/server/modules/payment/index.js +350 -78
  100. package/lib/server/utils/small-ticket.d.ts +8 -5
  101. package/lib/server/utils/small-ticket.js +1009 -121
  102. package/lib/solution/BaseSales/index.d.ts +11 -2
  103. package/lib/solution/BaseSales/index.js +164 -30
  104. package/lib/solution/BaseSales/types.d.ts +7 -3
  105. package/lib/solution/BaseSales/utils/cartPromotion.js +12 -4
  106. package/lib/solution/BookingTicket/index.d.ts +11 -2
  107. package/lib/solution/BookingTicket/index.js +103 -41
  108. package/lib/solution/BookingTicket/types.d.ts +5 -1
  109. package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.d.ts +2 -0
  110. package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.js +174 -42
  111. package/lib/solution/BookingTicket/utils/scan/cloudSearch.js +8 -4
  112. package/lib/solution/BookingTicket/utils/scan/formatGlobalScan.d.ts +6 -0
  113. package/lib/solution/BookingTicket/utils/scan/formatGlobalScan.js +11 -3
  114. package/lib/solution/ShopDiscount/index.d.ts +2 -0
  115. package/lib/solution/ShopDiscount/index.js +55 -9
  116. package/lib/solution/ShopDiscount/utils.d.ts +4 -2
  117. package/lib/solution/ShopDiscount/utils.js +42 -25
  118. package/lib/solution/UnifiedBookingSales/index.d.ts +2 -2
  119. package/lib/solution/UnifiedBookingSales/index.js +5 -5
  120. package/lib/utils/os-private-payload.d.ts +5 -0
  121. package/lib/utils/os-private-payload.js +22 -0
  122. package/package.json +1 -1
@@ -10,6 +10,8 @@ var _utils = require("./utils");
10
10
  var _utils2 = require("../Product/utils");
11
11
  var _weighedProduct = require("./utils/weighedProduct");
12
12
  var _dayjs = _interopRequireDefault(require("dayjs"));
13
+ var _availability = require("../Discount/availability");
14
+ var _osPrivatePayload = require("../../utils/os-private-payload");
13
15
  var _salesNotes = require("./salesNotes");
14
16
  var _paymentUtils = require("./payment-utils");
15
17
  var _paymentNumber = require("../../utils/payment-number");
@@ -22,6 +24,7 @@ var _discountProductLineIdentity = require("./utils/discountProductLineIdentity"
22
24
  var _virtualSettlement = require("./utils/virtualSettlement");
23
25
  var _Discount = require("../Discount");
24
26
  var _Rules = require("../Rules");
27
+ var _entitlementPass = require("../Discount/entitlementPass");
25
28
  var _types2 = require("../Rules/types");
26
29
  var _decimal = _interopRequireDefault(require("decimal.js"));
27
30
  var _lodashEs = require("lodash-es");
@@ -41,9 +44,27 @@ function isSameDiscountList(left, right) {
41
44
  function isManualProductDiscountProduct(product) {
42
45
  return (0, _manualProductDiscount.hasManualProductDiscountFlag)(product?.metadata) || (product?.discount_list || []).some(item => item?.type === 'product');
43
46
  }
47
+ function syncReducedEntitlementPassUsageCount(discountList, previousQuantity, nextQuantity) {
48
+ if (!Array.isArray(discountList)) return discountList;
49
+ const previousCount = (0, _utils3.getSafeProductNum)(previousQuantity);
50
+ const nextCount = (0, _utils3.getSafeProductNum)(nextQuantity);
51
+ if (nextCount >= previousCount) return discountList;
52
+ return discountList.map(discount => {
53
+ if (!(0, _entitlementPass.isEntitlementPass)(discount)) return discount;
54
+ const rawUsageCount = Number(discount?.metadata?.num ?? previousCount);
55
+ const usageCount = Number.isFinite(rawUsageCount) ? Math.max(Math.floor(rawUsageCount), 0) : previousCount;
56
+ return {
57
+ ...discount,
58
+ metadata: {
59
+ ...(discount?.metadata || {}),
60
+ num: Math.min(usageCount, nextCount)
61
+ }
62
+ };
63
+ });
64
+ }
44
65
  const ORDER_PAYMENT_STATUS_PAID = 'paid';
45
66
  const ORDER_PAYMENT_STATUS_PENDING = 'payment_pending';
46
- const CUSTOMER_PROMOTION_DISCOUNT_TAGS = new Set(['good_pass', 'product_discount_card', 'discount_card']);
67
+ const CUSTOMER_PROMOTION_DISCOUNT_TAGS = new Set(['entitlement_pass', 'good_pass', 'product_discount_card', 'discount_card']);
47
68
  function isCustomerPromotionDiscount(discount) {
48
69
  return CUSTOMER_PROMOTION_DISCOUNT_TAGS.has(String(discount?.tag || discount?.type || '').toLowerCase());
49
70
  }
@@ -73,12 +94,27 @@ function getVoucherPaymentLifecycleKey(payment) {
73
94
  if (!isVoucherPaymentRecord(payment)) return null;
74
95
  return `${String(payment.voucher_id)}::${String(payment.status || '')}`;
75
96
  }
76
- function hasPersistedOrderId(orderId) {
77
- if (orderId === undefined || orderId === null || orderId === '') return false;
78
- if (typeof orderId === 'string' && orderId.startsWith('LOCAL_')) return false;
79
- const normalizedOrderId = Number(orderId);
80
- if (Number.isFinite(normalizedOrderId)) return normalizedOrderId > 0;
81
- return String(orderId).trim() !== '';
97
+ function getDiscountIdentityKey(discount) {
98
+ const id = discount?.id;
99
+ if (id === undefined || id === null || id === '') return null;
100
+ return String(id);
101
+ }
102
+ function mergeCommittedEntitlementWithRuntime(committed, runtime) {
103
+ const committedAppliedProductDetails = committed.committedAppliedProductDetails?.length ? committed.committedAppliedProductDetails : committed.appliedProductDetails || [];
104
+ return {
105
+ ...committed,
106
+ ...runtime,
107
+ id: runtime.id ?? committed.id,
108
+ tag: runtime.tag || committed.tag,
109
+ type: runtime.type || committed.type,
110
+ isEditMode: false,
111
+ isDisabled: runtime.isDisabled ?? false,
112
+ isSelected: runtime.isSelected ?? committed.isSelected,
113
+ isUsed: committed.isUsed || runtime.isUsed,
114
+ committedAppliedProductDetails: [...committedAppliedProductDetails],
115
+ applicableProductDetails: [...(committed.applicableProductDetails || []), ...(runtime.applicableProductDetails || [])],
116
+ appliedProductDetails: [...committedAppliedProductDetails, ...(runtime.appliedProductDetails || [])]
117
+ };
82
118
  }
83
119
  class OrderModule extends _BaseModule.BaseModule {
84
120
  defaultName = 'order';
@@ -93,6 +129,10 @@ class OrderModule extends _BaseModule.BaseModule {
93
129
  openCache = false;
94
130
  sessionStoragePersistEnabled = false;
95
131
  sessionStorageRestoreRecord = null;
132
+ /** 只允许当前订单最新的 prepare/config 请求回写客户资产态。 */
133
+ customerWalletPrepareConfigEpoch = 0;
134
+ /** Payment 可能早于 prepare/config 返回;先按实例 ID 暂存卡号等待 Snapshot。 */
135
+ pointCardSnapshotCodeCandidates = new Map();
96
136
  salesSummaryModuleName;
97
137
  rulesHooksOverride;
98
138
  moduleHooksOverride;
@@ -131,6 +171,7 @@ class OrderModule extends _BaseModule.BaseModule {
131
171
  const key = Number.isFinite(id) ? id : String(resourceId);
132
172
  const title = item?.discount?.title ?? {};
133
173
  const amount = Number(item?.amount || 0);
174
+ const encoded = item?.metadata?.encoded || item?.encoded || item?.discount?.encoded || '';
134
175
  const discountCardType = item?.discount?.discount_card_type;
135
176
  const parValue = isDiscountCard ? discountCardType === 'fixed_amount' ? item?.discount?.fixed_amount ?? item?.amount : item?.discount?.percent : '0';
136
177
  const detail = {
@@ -141,11 +182,21 @@ class OrderModule extends _BaseModule.BaseModule {
141
182
  original_amount: String(item?.discount?.original_amount ?? item?.amount ?? '0'),
142
183
  num: Number(item?.metadata?.num ?? product?.num ?? product?.product_quantity ?? 1) || 1,
143
184
  metadata: item?.metadata,
144
- discount: item?.discount,
185
+ discount: {
186
+ ...(item?.discount || {}),
187
+ product_id: item?.discount?.product_id ?? product?.product_id
188
+ },
145
189
  _num: Number(item?.metadata?.num ?? product?.num ?? product?.product_quantity ?? 1) || 1
146
190
  };
147
191
  const existed = discountMap.get(key);
148
192
  if (existed) {
193
+ if (!existed.encoded && encoded) {
194
+ existed.encoded = encoded;
195
+ existed.metadata = {
196
+ ...existed.metadata,
197
+ encoded
198
+ };
199
+ }
149
200
  existed.amount = Number(existed.amount || 0) + amount;
150
201
  existed.used_par_value = String(Number(existed.used_par_value || 0) + amount);
151
202
  if (!isDiscountCard) {
@@ -165,8 +216,11 @@ class OrderModule extends _BaseModule.BaseModule {
165
216
  discountMap.set(key, {
166
217
  id: key,
167
218
  product_name: '',
168
- encoded: '',
169
- code: '',
219
+ encoded,
220
+ code: item?.code || item?.discount?.code || '',
221
+ metadata: {
222
+ encoded
223
+ },
170
224
  tag: type,
171
225
  product_id: item?.discount?.discount_product_id ?? product?.product_id ?? 0,
172
226
  relation_type: '',
@@ -457,13 +511,24 @@ class OrderModule extends _BaseModule.BaseModule {
457
511
  this.store.rules = rules;
458
512
  }
459
513
  createDefaultRulesHooks() {
460
- return (0, _utils.createDefaultOrderRulesHooks)();
514
+ return (0, _utils.createDefaultOrderRulesHooks)(() => this.store.tempOrder?.bookings || []);
515
+ }
516
+
517
+ /**
518
+ * Stable across notifyTempOrderChanged's immutable copy, but changes when a
519
+ * different local/server order becomes current.
520
+ */
521
+ getDiscountConfigOrderIdentityKey(tempOrder) {
522
+ const identity = this.getOrderIdentity();
523
+ return JSON.stringify([identity?.orderId ?? tempOrder?.order_id ?? '', tempOrder?.request_unique_idempotency_token ?? '', identity?.externalSaleNumber ?? tempOrder?.external_sale_number ?? '', identity?.localId ?? tempOrder?._extend?.localId ?? this.cacheId ?? '']);
461
524
  }
462
525
 
463
526
  // ─── Discount: 公共 API ───
464
527
 
465
528
  async loadDiscountConfig(params) {
466
529
  const orderId = params.orderId || this.store.tempOrder?.order_id || undefined;
530
+ const requestEpoch = ++this.customerWalletPrepareConfigEpoch;
531
+ const requestOrderIdentityKey = this.getDiscountConfigOrderIdentityKey(this.store.tempOrder);
467
532
  const prepareConfigResult = await this.store.discount?.loadPrepareConfig({
468
533
  customer_id: params.customerId,
469
534
  action: params.action || (orderId ? 'edit' : 'create'),
@@ -472,38 +537,90 @@ class OrderModule extends _BaseModule.BaseModule {
472
537
  with_wallet_pass_holder: 1,
473
538
  with_available_wallet_flag: 1,
474
539
  with_available_wallet_pass_flag: 1,
540
+ with_entitlement_pass: 1,
541
+ filter_order_wallet_pass_usage: 0,
475
542
  with_virtual_currency: 1,
476
543
  request_timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
477
544
  ...(orderId ? {
478
545
  order_id: orderId
479
546
  } : {})
480
547
  });
548
+ const isCurrentRequest = () => requestEpoch === this.customerWalletPrepareConfigEpoch && this.getDiscountConfigOrderIdentityKey(this.store.tempOrder) === requestOrderIdentityKey && String(this.store.tempOrder?.customer_id ?? '') === String(params.customerId);
549
+ if (!isCurrentRequest()) {
550
+ return this.getDiscountList();
551
+ }
481
552
  const discountList = prepareConfigResult?.discountList;
482
- this.store.availableWalletIds = prepareConfigResult?.availableWalletIds || [];
483
- this.store.virtualCurrencyList = prepareConfigResult?.virtualCurrencyList || [];
484
553
  if (prepareConfigResult) {
485
- this.writePointCardSnapshotFromPrepareConfig({
486
- orderId,
554
+ this.writeCustomerWalletSnapshotsFromPrepareConfig({
487
555
  customerId: params.customerId,
488
- availableWalletPassList: prepareConfigResult.availableWalletPassList || []
556
+ availableWalletIds: prepareConfigResult.availableWalletIds || [],
557
+ availableWalletPassList: prepareConfigResult.availableWalletPassList || [],
558
+ virtualCurrencyList: prepareConfigResult.virtualCurrencyList || []
489
559
  });
490
560
  }
491
561
  if (discountList) {
492
562
  const currentDiscountList = this.store.discount?.getDiscountList() || [];
493
- const currentEditDiscounts = currentDiscountList.filter(discount => discount?.isEditMode);
563
+ const currentCommittedDiscounts = currentDiscountList.filter(discount => discount?.isEditMode || discount?.committedAppliedProductDetails?.length);
494
564
  const currentScanDiscounts = currentDiscountList.filter(discount => discount?.isScan);
495
- const currentScanDiscountIds = new Set(currentScanDiscounts.map(discount => discount?.id));
565
+ const currentScanDiscountById = new Map(currentScanDiscounts.flatMap(discount => {
566
+ const id = getDiscountIdentityKey(discount);
567
+ return id === null ? [] : [[id, discount]];
568
+ }));
569
+ const committedDiscountById = new Map(currentCommittedDiscounts.flatMap(discount => {
570
+ const id = getDiscountIdentityKey(discount);
571
+ return id === null ? [] : [[id, discount]];
572
+ }));
573
+ const mergedCommittedDiscountIds = new Set();
574
+ const refreshedScanDiscountIds = new Set();
496
575
  const runtimeDiscountIds = new Set();
497
- const runtimeDiscounts = discountList.filter(discount => {
498
- if (currentScanDiscountIds.has(discount?.id)) return false;
499
- if (runtimeDiscountIds.has(discount?.id)) return false;
500
- runtimeDiscountIds.add(discount?.id);
501
- return true;
576
+ const runtimeDiscounts = discountList.flatMap(discount => {
577
+ const id = getDiscountIdentityKey(discount);
578
+ if (id === null) return [discount];
579
+ if (runtimeDiscountIds.has(id)) return [];
580
+ runtimeDiscountIds.add(id);
581
+ const currentScanDiscount = currentScanDiscountById.get(id);
582
+ if (currentScanDiscount && (!(0, _entitlementPass.isEntitlementPass)(currentScanDiscount) || !(0, _entitlementPass.isEntitlementPass)(discount))) {
583
+ return [];
584
+ }
585
+ const committedDiscount = committedDiscountById.get(id);
586
+ let nextDiscount = discount;
587
+ if (committedDiscount && (0, _entitlementPass.isEntitlementPass)(committedDiscount) && (0, _entitlementPass.isEntitlementPass)(discount)) {
588
+ mergedCommittedDiscountIds.add(id);
589
+ nextDiscount = mergeCommittedEntitlementWithRuntime(committedDiscount, discount);
590
+ }
591
+ if (currentScanDiscount) {
592
+ refreshedScanDiscountIds.add(id);
593
+ nextDiscount = {
594
+ ...nextDiscount,
595
+ isScan: true,
596
+ isSelected: currentScanDiscount.isSelected ?? nextDiscount.isSelected,
597
+ isManualSelect: currentScanDiscount.isManualSelect ?? nextDiscount.isManualSelect
598
+ };
599
+ }
600
+ return [nextDiscount];
601
+ });
602
+ const retainedCommittedDiscounts = currentCommittedDiscounts.filter(discount => {
603
+ const id = getDiscountIdentityKey(discount);
604
+ return id === null || !mergedCommittedDiscountIds.has(id);
605
+ }).map(discount => discount.committedAppliedProductDetails?.length ? {
606
+ ...discount,
607
+ isEditMode: true,
608
+ isDisabled: true,
609
+ isSelected: true,
610
+ isUsed: true,
611
+ appliedProductDetails: [...discount.committedAppliedProductDetails]
612
+ } : discount);
613
+ const retainedScanDiscounts = currentScanDiscounts.filter(discount => {
614
+ const id = getDiscountIdentityKey(discount);
615
+ return id === null || !refreshedScanDiscountIds.has(id);
502
616
  });
503
- const mergedDiscountList = [...currentEditDiscounts, ...currentScanDiscounts, ...runtimeDiscounts];
617
+ const mergedDiscountList = [...retainedCommittedDiscounts, ...retainedScanDiscounts, ...runtimeDiscounts];
618
+ if (!isCurrentRequest()) return this.getDiscountList();
504
619
  await this.store.discount?.setOriginalDiscountList(mergedDiscountList);
620
+ if (!isCurrentRequest()) return this.getDiscountList();
505
621
  await this.store.discount?.setDiscountList(mergedDiscountList);
506
622
  }
623
+ if (!isCurrentRequest()) return this.getDiscountList();
507
624
  if (params.apply !== false && this.store.tempOrder?.products?.length) {
508
625
  this.applyDiscount();
509
626
  }
@@ -518,6 +635,9 @@ class OrderModule extends _BaseModule.BaseModule {
518
635
  getVirtualCurrencyList() {
519
636
  return (0, _lodashEs.cloneDeep)(this.store.virtualCurrencyList || []);
520
637
  }
638
+ getCustomerWalletPrepareConfigEpoch() {
639
+ return this.customerWalletPrepareConfigEpoch;
640
+ }
521
641
  async syncScannedDiscountsToOriginalDiscountList(discountList) {
522
642
  const scanDiscounts = (discountList || []).filter(discount => discount?.isScan);
523
643
  if (!scanDiscounts.length) return;
@@ -615,19 +735,34 @@ class OrderModule extends _BaseModule.BaseModule {
615
735
  isAvailable: false,
616
736
  discountList: this.getDiscountList()
617
737
  };
618
- const shouldRetainForLater = !isAvailable && unavailableReason === _types2.UnavailableReason.ProductNotApplicable && withScanList.some(isCustomerPromotionDiscount);
738
+ const scannedIds = new Set(withScanList.map(discount => String(discount.id)));
739
+ const evaluatedScannedDiscounts = (evaluatedDiscountList || withScanList).filter(discount => scannedIds.has(String(discount.id)) && isCustomerPromotionDiscount(discount) && !(0, _availability.getDiscountLifecycleReason)(discount));
740
+ const shouldRetainForLater = !isAvailable && evaluatedScannedDiscounts.length > 0;
619
741
  const normalizedNewDiscountList = newDiscountList ? markScannedDiscounts(newDiscountList, withScanList) : undefined;
620
- const retainedDiscountList = evaluatedDiscountList ? markScannedDiscounts(evaluatedDiscountList, withScanList) : normalizedNewDiscountList;
621
742
  let resolvedDiscountList = normalizedNewDiscountList || this.getDiscountList();
622
743
  if (isAvailable && normalizedNewDiscountList) {
623
744
  await this.store.discount?.setDiscountList(normalizedNewDiscountList);
624
- this.applyDiscount();
745
+ // 实际写回商品时沿用扫码试算的优先级,避免旧商品行的选卡覆盖本次扫码。
746
+ this.applyDiscount({
747
+ preferredEntitlementPassIds: withScanList.filter(discount => (0, _entitlementPass.isEntitlementPass)(discount)).map(discount => discount.id)
748
+ });
625
749
  await this.syncScannedDiscountsToOriginalDiscountList(this.getDiscountList());
626
750
  resolvedDiscountList = this.getDiscountList();
627
- } else if (shouldRetainForLater && retainedDiscountList) {
628
- // 卡券有效但当前购物车没有适用商品:先保留在 Promotion 中,
629
- // 不执行 applyDiscount,避免修改商品价格或订单金额。
630
- await this.store.discount?.setDiscountList(retainedDiscountList);
751
+ } else if (shouldRetainForLater) {
752
+ // 只合并本次识别的卡券;不把试算产生的其他选券状态写回订单。
753
+ const retainedDiscounts = new Map(this.getDiscountList().map(discount => [String(discount.id), discount]));
754
+ evaluatedScannedDiscounts.forEach(discount => {
755
+ retainedDiscounts.set(String(discount.id), {
756
+ ...discount,
757
+ isScan: true,
758
+ isAvailable: false,
759
+ isSelected: false,
760
+ reasonCode: discount.reasonCode || 'product_not_applicable',
761
+ appliedProductDetails: [],
762
+ savedAmount: 0
763
+ });
764
+ });
765
+ await this.store.discount?.setDiscountList([...retainedDiscounts.values()]);
631
766
  await this.syncScannedDiscountsToOriginalDiscountList(this.getDiscountList());
632
767
  resolvedDiscountList = this.getDiscountList();
633
768
  }
@@ -636,7 +771,7 @@ class OrderModule extends _BaseModule.BaseModule {
636
771
  isAvailable: isAvailable || false,
637
772
  isAccepted: Boolean(isAvailable || shouldRetainForLater),
638
773
  discountList: resolvedDiscountList,
639
- scannedDiscountList: resultDiscountList,
774
+ scannedDiscountList: evaluatedScannedDiscounts,
640
775
  unavailableReason
641
776
  };
642
777
  }
@@ -669,7 +804,8 @@ class OrderModule extends _BaseModule.BaseModule {
669
804
  orderTotalAmount: Number(this.store.summary?.total_amount || 0)
670
805
  }, {
671
806
  reapplyBookingDiscountProductUids: options?.reapplyBookingDiscountProductUids,
672
- reapplyWeighedDiscountProductUids: options?.reapplyWeighedDiscountProductUids
807
+ reapplyWeighedDiscountProductUids: options?.reapplyWeighedDiscountProductUids,
808
+ preferredEntitlementPassIds: options?.preferredEntitlementPassIds
673
809
  });
674
810
  if (result?.productList) {
675
811
  const previousProductsByUid = (0, _utils.indexOrderProductsByUid)(tempOrder.products);
@@ -1007,32 +1143,107 @@ class OrderModule extends _BaseModule.BaseModule {
1007
1143
  now: (0, _utils.formatDateTime)(new Date())
1008
1144
  });
1009
1145
  }
1010
- clearTempOrderPointCardSnapshot(tempOrder) {
1146
+ clearTempOrderCustomerWalletSnapshots(tempOrder) {
1011
1147
  const orderMetaList = (0, _utils.normalizeOrderMetaList)(tempOrder.order_meta_list);
1012
1148
  if (!orderMetaList) return;
1013
1149
  const nextOrderMetaList = {
1014
1150
  ...orderMetaList
1015
1151
  };
1016
1152
  delete nextOrderMetaList.point_cards_snapshot;
1153
+ delete nextOrderMetaList.virtual_currency_snapshot;
1017
1154
  if (Object.keys(nextOrderMetaList).length === 0) {
1018
1155
  delete tempOrder.order_meta_list;
1019
1156
  return;
1020
1157
  }
1021
1158
  tempOrder.order_meta_list = nextOrderMetaList;
1022
1159
  }
1023
- writePointCardSnapshotFromPrepareConfig(params) {
1160
+ clearCustomerWalletState(tempOrder) {
1161
+ this.clearCustomerWalletRuntimeState();
1162
+ this.clearTempOrderCustomerWalletSnapshots(tempOrder);
1163
+ }
1164
+ clearCustomerWalletRuntimeState() {
1165
+ this.customerWalletPrepareConfigEpoch += 1;
1166
+ this.pointCardSnapshotCodeCandidates.clear();
1167
+ this.store.availableWalletIds = [];
1168
+ this.store.virtualCurrencyList = [];
1169
+ }
1170
+ rememberPointCardSnapshotCodeCandidates(value) {
1171
+ (0, _utils.collectPointCardSnapshotCodeCandidates)(value).forEach(({
1172
+ id,
1173
+ code
1174
+ }) => {
1175
+ if (!this.pointCardSnapshotCodeCandidates.has(id)) {
1176
+ this.pointCardSnapshotCodeCandidates.set(id, code);
1177
+ }
1178
+ });
1179
+ }
1180
+ getRememberedPointCardSnapshotCodeCandidates() {
1181
+ return [...this.pointCardSnapshotCodeCandidates].map(([id, code]) => ({
1182
+ id,
1183
+ code
1184
+ }));
1185
+ }
1186
+ getPointCardSnapshotOrderIdentityKey(identity) {
1187
+ if (identity?.externalSaleNumber) {
1188
+ return `external:${identity.externalSaleNumber}`;
1189
+ }
1190
+ if (identity?.orderId !== undefined && identity.orderId !== null) {
1191
+ return `order:${identity.orderId}`;
1192
+ }
1193
+ if (identity?.localId) return `local:${identity.localId}`;
1194
+ if (identity?.orderNumber) return `number:${identity.orderNumber}`;
1195
+ return '';
1196
+ }
1197
+ isPointCardSnapshotMergeContextCurrent(context) {
1198
+ if (!context) return true;
1199
+ if (context.customerId !== undefined && String(context.customerId ?? '') !== String(this.store.tempOrder?.customer_id ?? '')) {
1200
+ return false;
1201
+ }
1202
+ const expectedOrderKey = this.getPointCardSnapshotOrderIdentityKey(context.orderIdentity);
1203
+ return !expectedOrderKey || expectedOrderKey === this.getPointCardSnapshotOrderIdentityKey(this.getOrderIdentity());
1204
+ }
1205
+ writeCustomerWalletSnapshotsFromPrepareConfig(params) {
1024
1206
  const tempOrder = this.store.tempOrder;
1025
1207
  if (!tempOrder) return;
1026
1208
  if (String(tempOrder.customer_id ?? '') !== String(params.customerId)) return;
1027
- const effectiveOrderId = params.orderId ?? tempOrder.order_id;
1028
- if (hasPersistedOrderId(effectiveOrderId)) return;
1209
+ const currentOrderMetaList = (0, _utils.normalizeOrderMetaList)(tempOrder.order_meta_list);
1210
+ const virtualCurrencyList = Array.isArray(params.virtualCurrencyList) ? params.virtualCurrencyList : [];
1211
+ const snapshotsWithExistingCodes = (0, _utils.mergePointCardSnapshotCodes)(params.availableWalletPassList, currentOrderMetaList?.point_cards_snapshot);
1029
1212
  tempOrder.order_meta_list = {
1030
- ...((0, _utils.normalizeOrderMetaList)(tempOrder.order_meta_list) || {}),
1031
- point_cards_snapshot: (0, _utils.buildPointCardSnapshotFromWalletPassList)(params.availableWalletPassList)
1213
+ ...(currentOrderMetaList || {}),
1214
+ point_cards_snapshot: (0, _utils.mergePointCardSnapshotCodes)(snapshotsWithExistingCodes, this.getRememberedPointCardSnapshotCodeCandidates()),
1215
+ virtual_currency_snapshot: (0, _utils.buildVirtualCurrencySnapshotFromList)(virtualCurrencyList)
1032
1216
  };
1217
+ this.store.availableWalletIds = [...params.availableWalletIds];
1218
+ this.store.virtualCurrencyList = (0, _lodashEs.cloneDeep)(virtualCurrencyList);
1033
1219
  this.persistTempOrder();
1034
1220
  this.saveDraftInBackground();
1035
1221
  }
1222
+
1223
+ /**
1224
+ * 将 Payment WalletPass 返回的实例 code 回填到当前订单 Snapshot。
1225
+ * 这里只按资产 ID 补空值,不新增卡、不覆盖 prepare/config 冻结的余额。
1226
+ */
1227
+ mergePointCardSnapshotCodes(walletPassList, context) {
1228
+ const tempOrder = this.store.tempOrder;
1229
+ const currentOrderMetaList = (0, _utils.normalizeOrderMetaList)(tempOrder?.order_meta_list);
1230
+ const currentSnapshots = currentOrderMetaList?.point_cards_snapshot || [];
1231
+ if (!tempOrder || !this.isPointCardSnapshotMergeContextCurrent(context)) {
1232
+ return currentSnapshots;
1233
+ }
1234
+ this.rememberPointCardSnapshotCodeCandidates(walletPassList);
1235
+ if (!currentSnapshots.length) return currentSnapshots;
1236
+ const nextSnapshots = (0, _utils.mergePointCardSnapshotCodes)(currentSnapshots, this.getRememberedPointCardSnapshotCodeCandidates());
1237
+ const hasChanged = nextSnapshots.some((snapshot, index) => snapshot.code !== currentSnapshots[index]?.code);
1238
+ if (!hasChanged) return currentSnapshots;
1239
+ tempOrder.order_meta_list = {
1240
+ ...(currentOrderMetaList || {}),
1241
+ point_cards_snapshot: nextSnapshots
1242
+ };
1243
+ this.persistTempOrder();
1244
+ this.saveDraftInBackground();
1245
+ return nextSnapshots;
1246
+ }
1036
1247
  createExternalSaleNumber() {
1037
1248
  const timestamp = (0, _dayjs.default)().format('YYYYMMDDHHmmssSSS');
1038
1249
  const random6 = String(Math.floor(Math.random() * 1000000)).padStart(6, '0');
@@ -1550,7 +1761,8 @@ class OrderModule extends _BaseModule.BaseModule {
1550
1761
  const sku = product?.product_sku || {};
1551
1762
  const metadata = product?.metadata || {};
1552
1763
  const discountTypes = (product?.discount_list || []).map(discount => discount?.type ?? discount?.tag);
1553
- const extensionType = product?.extension_type || (discountTypes.includes('good_pass') ? 'good_pass' : 'normal');
1764
+ const fullCoveragePassType = discountTypes.find(type => ['entitlement_pass', 'good_pass'].includes(type));
1765
+ const extensionType = product?.extension_type || fullCoveragePassType || 'normal';
1554
1766
  return {
1555
1767
  product_id: product?.product_id ?? null,
1556
1768
  product_variant_id: product?.product_variant_id ?? null,
@@ -1603,7 +1815,8 @@ class OrderModule extends _BaseModule.BaseModule {
1603
1815
  const sku = product?.product_sku || {};
1604
1816
  const metadata = product?.metadata || {};
1605
1817
  const discountTypes = (product?.discount_list || []).map(discount => discount?.type ?? discount?.tag);
1606
- const extensionType = product?.extension_type || (discountTypes.includes('good_pass') ? 'good_pass' : 'normal');
1818
+ const fullCoveragePassType = discountTypes.find(type => ['entitlement_pass', 'good_pass'].includes(type));
1819
+ const extensionType = product?.extension_type || fullCoveragePassType || 'normal';
1607
1820
  return {
1608
1821
  product_id: product?.product_id ?? null,
1609
1822
  product_variant_id: product?.product_variant_id ?? null,
@@ -1812,6 +2025,7 @@ class OrderModule extends _BaseModule.BaseModule {
1812
2025
  const freshTempOrder = this.createDefaultTempOrderInstance();
1813
2026
  this.ensureExternalSaleNumber(freshTempOrder);
1814
2027
  this.store.tempOrder = freshTempOrder;
2028
+ this.clearCustomerWalletRuntimeState();
1815
2029
  this.store.summary = (0, _utils.createEmptySummary)();
1816
2030
  this.store.lastOrderInfo = undefined;
1817
2031
  this.store.syncState = undefined;
@@ -1832,6 +2046,7 @@ class OrderModule extends _BaseModule.BaseModule {
1832
2046
  ensureIdentity: false
1833
2047
  });
1834
2048
  this.store.tempOrder = nextTempOrder;
2049
+ this.clearCustomerWalletRuntimeState();
1835
2050
  if (options?.recalculateSummary !== false) {
1836
2051
  await this.recalculateSummary({
1837
2052
  createIfMissing: false
@@ -2616,7 +2831,7 @@ class OrderModule extends _BaseModule.BaseModule {
2616
2831
  }
2617
2832
  isCustomerBoundDiscount(discount) {
2618
2833
  const type = discount?.type ?? discount?.tag;
2619
- return type === 'good_pass' || type === 'discount_card' || type === 'product_discount_card';
2834
+ return type === 'entitlement_pass' || type === 'good_pass' || type === 'discount_card' || type === 'product_discount_card';
2620
2835
  }
2621
2836
  shouldKeepCustomerBoundDiscountAfterCustomerChange(discount) {
2622
2837
  return Boolean(discount?.isScan || discount?.isEditMode);
@@ -2701,9 +2916,8 @@ class OrderModule extends _BaseModule.BaseModule {
2701
2916
  email: tempOrder.email
2702
2917
  });
2703
2918
  const nextCustomerId = tempOrder.customer_id ?? null;
2704
- if (String(previousCustomerId ?? '') !== String(nextCustomerId ?? '') && !hasPersistedOrderId(tempOrder.order_id)) {
2705
- this.store.availableWalletIds = [];
2706
- this.clearTempOrderPointCardSnapshot(tempOrder);
2919
+ if (String(previousCustomerId ?? '') !== String(nextCustomerId ?? '')) {
2920
+ this.clearCustomerWalletState(tempOrder);
2707
2921
  }
2708
2922
  this.persistTempOrder();
2709
2923
  return {
@@ -2739,11 +2953,7 @@ class OrderModule extends _BaseModule.BaseModule {
2739
2953
  }
2740
2954
  const nextCustomerId = tempOrder.customer_id ?? null;
2741
2955
  if (String(previousCustomerId ?? '') !== String(nextCustomerId ?? '')) {
2742
- this.store.availableWalletIds = [];
2743
- this.store.virtualCurrencyList = [];
2744
- if (!hasPersistedOrderId(tempOrder.order_id)) {
2745
- this.clearTempOrderPointCardSnapshot(tempOrder);
2746
- }
2956
+ this.clearCustomerWalletState(tempOrder);
2747
2957
  (0, _utils.clearTempOrderHolderAssignments)(tempOrder);
2748
2958
  this.clearCustomerBoundDiscounts(tempOrder);
2749
2959
  this.applyDiscount();
@@ -2781,11 +2991,7 @@ class OrderModule extends _BaseModule.BaseModule {
2781
2991
  tempOrder.email = '';
2782
2992
  tempOrder.customer = null;
2783
2993
  if (tempOrder._extend) delete tempOrder._extend.customerSnapshot;
2784
- this.store.availableWalletIds = [];
2785
- this.store.virtualCurrencyList = [];
2786
- if (!hasPersistedOrderId(tempOrder.order_id)) {
2787
- this.clearTempOrderPointCardSnapshot(tempOrder);
2788
- }
2994
+ this.clearCustomerWalletState(tempOrder);
2789
2995
  this.clearCustomerBoundDiscounts(tempOrder);
2790
2996
  this.applyDiscount();
2791
2997
  this.persistTempOrder();
@@ -3248,17 +3454,35 @@ class OrderModule extends _BaseModule.BaseModule {
3248
3454
  }
3249
3455
  normalizedIncoming.discount_list = (0, _utils.normalizeOrderProductDiscountList)(normalizedIncoming.discount_list);
3250
3456
  const hasIncomingGoodPass = this.hasGoodPassDiscount(normalizedIncoming);
3457
+ const hasIncomingEntitlementPass = this.hasEntitlementPassDiscount(normalizedIncoming);
3458
+ const isIncomingEntitlementAppend = this.isRuntimeEntitlementAppend(productToAdd, tempOrder);
3459
+ const isIncomingPersistedEntitlementPass = hasIncomingEntitlementPass && this.isPersistedProductLine(normalizedIncoming);
3251
3460
  const hasIncomingProductNote = this.hasOrderProductLineNote(normalizedIncoming);
3252
3461
  const hasIncomingManualProductDiscount = isManualProductDiscountProduct(normalizedIncoming);
3253
3462
  const hasIncomingBookingUid = this.hasLinkedBookingUid(productToAdd) || this.hasLinkedBookingUid(normalizedIncoming);
3254
3463
  const isIncomingCustomItem = productToAdd.metadata?.origin?.isCustomItem === true || productToAdd._origin?.isCustomItem === true;
3255
- const shouldForceNewLine = disableMerge || !!booking || hasIncomingProductNote || hasIncomingManualProductDiscount || hasIncomingBookingUid || isIncomingCustomItem || isWeighingProduct;
3256
- const matchedIndex = hasIncomingGoodPass || shouldForceNewLine ? -1 : tempOrder.products.findIndex(item => {
3464
+ const shouldForceNewLine = disableMerge || !!booking || hasIncomingGoodPass || isIncomingPersistedEntitlementPass || hasIncomingBookingUid || isIncomingCustomItem || isWeighingProduct;
3465
+ const matchedIndex = shouldForceNewLine ? -1 : tempOrder.products.findIndex(item => {
3257
3466
  if (this.hasGoodPassDiscount(item)) return false;
3258
- if (this.hasOrderProductLineNote(item)) return false;
3259
- if (isManualProductDiscountProduct(item)) return false;
3260
- if (item.order_detail_id !== undefined && item.order_detail_id !== null) return false;
3261
- if (item.metadata?.is_edit_for_runtime) return false;
3467
+ const isRuntimeEntitlementPass = this.isRuntimeEntitlementAppend(item, tempOrder);
3468
+ if (isIncomingEntitlementAppend !== isRuntimeEntitlementPass) {
3469
+ return false;
3470
+ }
3471
+ if (isIncomingEntitlementAppend && this.isPersistedProductLine(item)) {
3472
+ return false;
3473
+ }
3474
+ const hasExistingProductNote = this.hasOrderProductLineNote(item);
3475
+ if ((hasIncomingProductNote || hasExistingProductNote) && (!isRuntimeEntitlementPass || String(normalizedIncoming.note || '').trim() !== String(item.note || '').trim())) {
3476
+ return false;
3477
+ }
3478
+ const hasExistingManualProductDiscount = isManualProductDiscountProduct(item);
3479
+ if (isRuntimeEntitlementPass && !this.hasSameEntitlementMergePriceConfig(normalizedIncoming, item)) {
3480
+ return false;
3481
+ }
3482
+ if ((hasIncomingManualProductDiscount || hasExistingManualProductDiscount) && !isRuntimeEntitlementPass) {
3483
+ return false;
3484
+ }
3485
+ if (this.isPersistedProductLine(item)) return false;
3262
3486
  if (item.product_id !== productToAdd.product_id) return false;
3263
3487
  if (item.product_variant_id !== productToAdd.product_variant_id) return false;
3264
3488
  const existedFingerprint = (0, _utils3.buildProductLineFingerprint)((0, _utils.getProductSkuOptions)(item), item.product_bundle);
@@ -3559,17 +3783,51 @@ class OrderModule extends _BaseModule.BaseModule {
3559
3783
  });
3560
3784
  return tempOrder.products;
3561
3785
  }
3562
- hasGoodPassDiscount(product) {
3563
- const hasMainGoodPass = (product.discount_list || []).some(item => item?.type === 'good_pass' || item?.tag === 'good_pass');
3564
- if (hasMainGoodPass) return true;
3565
- const bundleDiscountList = [];
3786
+ getFullCoveragePassDiscounts(product) {
3787
+ const discountList = [...(product.discount_list || [])];
3566
3788
  (product.product_bundle || []).forEach(item => {
3567
- bundleDiscountList.push(...(item?.discount_list || []));
3789
+ discountList.push(...(item?.discount_list || []));
3568
3790
  });
3569
3791
  (product?._extend?.other?.bundle || []).forEach(item => {
3570
- bundleDiscountList.push(...(item?.discount_list || []));
3792
+ discountList.push(...(item?.discount_list || []));
3571
3793
  });
3572
- return bundleDiscountList.some(item => item?.type === 'good_pass' || item?.tag === 'good_pass');
3794
+ return discountList.filter(item => (0, _entitlementPass.isFullCoveragePass)(item));
3795
+ }
3796
+ hasEntitlementPassDiscount(product) {
3797
+ return this.getFullCoveragePassDiscounts(product).some(item => (0, _entitlementPass.isEntitlementPass)(item));
3798
+ }
3799
+ hasGoodPassDiscount(product) {
3800
+ return this.getFullCoveragePassDiscounts(product).some(item => !(0, _entitlementPass.isEntitlementPass)(item));
3801
+ }
3802
+ isPersistedProductLine(product) {
3803
+ return product.order_detail_id !== undefined && product.order_detail_id !== null && product.order_detail_id !== '' || product.metadata?.is_edit_for_runtime === true;
3804
+ }
3805
+ isRuntimeEntitlementAppend(product, tempOrder) {
3806
+ if (this.isPersistedProductLine(product)) return false;
3807
+ const uid = product.metadata?.unique_identification_number ?? product.unique_identification_number;
3808
+ const storedOrigin = uid !== undefined && uid !== null && uid !== '' ? tempOrder._extend?.productsByUid?.[String(uid)]?.origin : undefined;
3809
+ return Boolean(product?._origin?.__entitlementPassRuntimeAppend === true || storedOrigin?.__entitlementPassRuntimeAppend === true);
3810
+ }
3811
+ hasSameEntitlementMergePriceConfig(incoming, existing) {
3812
+ const buildPriceConfig = product => {
3813
+ const metadata = product.metadata || {};
3814
+ const bundle = (product.product_bundle || []).map(item => ({
3815
+ bundle_id: item.bundle_id ?? item.id ?? null,
3816
+ bundle_product_id: item.bundle_product_id ?? item._bundle_product_id ?? item.product_id ?? null,
3817
+ bundle_variant_id: item.bundle_variant_id ?? item.product_variant_id ?? 0,
3818
+ price_type: item.price_type ?? item.custom_price_type ?? '',
3819
+ price_type_ext: item.price_type_ext ?? item.custom_price_type_ext ?? ''
3820
+ })).sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right)));
3821
+ return JSON.stringify({
3822
+ original_price: this.normalizeFingerprintMoney(product.original_price),
3823
+ source_product_price: this.normalizeFingerprintMoney(metadata.source_product_price),
3824
+ main_product_original_price: this.normalizeFingerprintMoney(metadata.main_product_original_price),
3825
+ price_override: this.normalizeFingerprintMoney(metadata.price_override),
3826
+ is_manual_discount: Boolean(metadata.is_manual_discount),
3827
+ bundle
3828
+ });
3829
+ };
3830
+ return buildPriceConfig(incoming) === buildPriceConfig(existing);
3573
3831
  }
3574
3832
  getBundleRuntimeIdentity(bundle) {
3575
3833
  if (!bundle || typeof bundle !== 'object') return '';
@@ -3807,7 +4065,7 @@ class OrderModule extends _BaseModule.BaseModule {
3807
4065
  // normalizeOrderProduct 幂等:v2 metadata 存在 → 保留 main_product_* 折扣重算 composite;
3808
4066
  // 否则 → 把顶层 selling_price 视作 source,派生 main_product_* 并写 price_schema_version: 2。
3809
4067
  let normalizedProduct = this.normalizeVirtualSettlementProductIfEnabled((0, _utils.mergeOrderProductDisplayFields)(nextProduct, (0, _utils3.normalizeOrderProduct)(nextProduct)));
3810
- normalizedProduct.discount_list = (0, _utils.normalizeOrderProductDiscountList)(normalizedProduct.discount_list);
4068
+ normalizedProduct.discount_list = (0, _utils.normalizeOrderProductDiscountList)(isPersistedOrderLine ? syncReducedEntitlementPassUsageCount(normalizedProduct.discount_list, targetProduct.num, normalizedProduct.num) : normalizedProduct.discount_list);
3811
4069
  const preservedBookingUid = nextProduct.booking_uid || nextProduct.metadata?.booking_uid || targetProduct.booking_uid || targetProduct.metadata?.booking_uid;
3812
4070
  if (!booking && preservedBookingUid) {
3813
4071
  normalizedProduct.booking_uid = preservedBookingUid;
@@ -3932,6 +4190,7 @@ class OrderModule extends _BaseModule.BaseModule {
3932
4190
  throw new Error('[Order] 目标商品不存在,无法更新数量');
3933
4191
  }
3934
4192
  const targetProduct = tempOrder.products[productIndex];
4193
+ const isPersistedOrderLine = targetProduct.order_detail_id !== undefined && targetProduct.order_detail_id !== null;
3935
4194
  const normalizedProduct = this.normalizeVirtualSettlementProductIfEnabled((0, _utils3.normalizeOrderProduct)({
3936
4195
  ...targetProduct,
3937
4196
  num: (0, _utils3.getSafeProductNum)(num),
@@ -3940,7 +4199,7 @@ class OrderModule extends _BaseModule.BaseModule {
3940
4199
  unique_identification_number: targetProduct.metadata?.unique_identification_number || unique_identification_number
3941
4200
  }
3942
4201
  }));
3943
- normalizedProduct.discount_list = (0, _utils.normalizeOrderProductDiscountList)(normalizedProduct.discount_list);
4202
+ normalizedProduct.discount_list = (0, _utils.normalizeOrderProductDiscountList)(isPersistedOrderLine ? syncReducedEntitlementPassUsageCount(normalizedProduct.discount_list, targetProduct.num, normalizedProduct.num) : normalizedProduct.discount_list);
3944
4203
  const settlementSnapshot = (0, _virtualSettlement.getSelectedSettlementSnapshot)(normalizedProduct);
3945
4204
  if (this.isMultiCurrencySettlementEnabled() && settlementSnapshot?.settlement_type === 'virtual_currency' && tempOrder.customer_id && Number(tempOrder.customer_id) !== 1) {
3946
4205
  const currencyProductId = Number(settlementSnapshot.currency_product_id);
@@ -4740,11 +4999,21 @@ class OrderModule extends _BaseModule.BaseModule {
4740
4999
  query
4741
5000
  } = params;
4742
5001
  const fetchUrl = url || '/order/sales/checkout';
4743
- return this.request.post(fetchUrl, query, {
5002
+ const requestQuery = this.isSalesCheckoutHandledByOsServer(fetchUrl) ? query : (0, _osPrivatePayload.stripOsPrivatePayloadFields)(query);
5003
+ return this.request.post(fetchUrl, requestQuery, {
4744
5004
  osServer: true,
4745
5005
  customToast: () => {}
4746
5006
  });
4747
5007
  }
5008
+ isSalesCheckoutHandledByOsServer(fetchUrl) {
5009
+ const server = this.core?.server;
5010
+ if (!server || typeof server.hasRoute !== 'function') return false;
5011
+ const requestBaseUrl = String(this.request?.baseUrl || '/shop').replace(/\/+$/, '');
5012
+ if (!requestBaseUrl.startsWith('/')) return false;
5013
+ const normalizedFetchUrl = fetchUrl.startsWith('/') ? fetchUrl : `/${fetchUrl}`;
5014
+ const routePath = normalizedFetchUrl === requestBaseUrl || normalizedFetchUrl.startsWith(`${requestBaseUrl}/`) ? normalizedFetchUrl : `${requestBaseUrl}${normalizedFetchUrl}`;
5015
+ return server.hasRoute('post', routePath) === true;
5016
+ }
4748
5017
 
4749
5018
  /**
4750
5019
  * 发送支付链接邮件。
@@ -4833,6 +5102,7 @@ class OrderModule extends _BaseModule.BaseModule {
4833
5102
  allowUnresolvedSalesNotes: isSessionStorageHydrate
4834
5103
  });
4835
5104
  this.store.tempOrder = nextTempOrder;
5105
+ this.clearCustomerWalletRuntimeState();
4836
5106
  if (isSessionStorageHydrate) {
4837
5107
  // 会话快照是未提交购物车,不应获得编辑订单的 saved/edit 标记与服务端基线。
4838
5108
  const restoredSessionDiscounts = Array.isArray(raw.discount_list) ? (0, _lodashEs.cloneDeep)(raw.discount_list) : [];