@pisell/pisellos 0.10.55 → 0.10.57

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 (114) hide show
  1. package/dist/model/strategy/adapter/promotion/index.js +0 -9
  2. package/dist/modules/OpenData/index.js +2 -1
  3. package/dist/modules/Order/index.d.ts +1 -0
  4. package/dist/modules/Order/index.js +118 -59
  5. package/dist/modules/Order/utils/weighedProduct.d.ts +12 -0
  6. package/dist/modules/Order/utils/weighedProduct.js +94 -0
  7. package/dist/modules/Order/utils.d.ts +1 -1
  8. package/dist/modules/Order/utils.js +9 -6
  9. package/dist/modules/Payment/index.d.ts +0 -5
  10. package/dist/modules/Payment/index.js +35 -85
  11. package/dist/modules/ProductList/index.js +18 -5
  12. package/dist/modules/ResourcePlanner/planner.js +10 -8
  13. package/dist/modules/Rules/index.js +16 -14
  14. package/dist/modules/Rules/types.d.ts +2 -0
  15. package/dist/plugins/request.d.ts +4 -0
  16. package/dist/server/index.js +122 -69
  17. package/dist/server/modules/floor-plan/index.js +34 -3
  18. package/dist/server/modules/payment/index.d.ts +25 -6
  19. package/dist/server/modules/payment/index.js +660 -133
  20. package/dist/server/test.json +1 -1
  21. package/dist/shared/receipt/customerInvoiceLanguages.d.ts +8 -0
  22. package/dist/shared/receipt/customerInvoiceLanguages.js +60 -0
  23. package/dist/shared/receipt/loadCustomerInvoiceLayout.d.ts +27 -0
  24. package/dist/shared/receipt/loadCustomerInvoiceLayout.js +184 -0
  25. package/dist/shared/receipt/small-ticket.d.ts +11 -0
  26. package/dist/shared/receipt/small-ticket.js +430 -255
  27. package/dist/shared/receipt/templateVisibility.d.ts +23 -0
  28. package/dist/shared/receipt/templateVisibility.js +64 -0
  29. package/dist/solution/BaseSales/index.d.ts +4 -2
  30. package/dist/solution/BaseSales/index.js +1328 -1214
  31. package/dist/solution/BaseSales/types.d.ts +5 -1
  32. package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +40 -25
  33. package/dist/solution/BaseSales/utils.js +3 -2
  34. package/dist/solution/BookingByStep/index.d.ts +1 -1
  35. package/dist/solution/BookingTicket/index.d.ts +4 -1
  36. package/dist/solution/BookingTicket/index.js +120 -80
  37. package/dist/solution/OrderInvoicePreview/index.d.ts +10 -0
  38. package/dist/solution/OrderInvoicePreview/index.js +87 -16
  39. package/dist/solution/RegisterAndLogin/config.js +54 -11
  40. package/dist/solution/RegisterAndLogin/index.d.ts +2 -0
  41. package/dist/solution/RegisterAndLogin/index.js +1 -1
  42. package/dist/solution/RegisterAndLogin/utils.d.ts +12 -4
  43. package/dist/solution/RegisterAndLogin/utils.js +36 -16
  44. package/dist/solution/Sales/index.d.ts +2 -0
  45. package/dist/solution/Sales/index.js +15 -1
  46. package/dist/solution/ScanOrder/utils.js +3 -2
  47. package/dist/solution/UnifiedBookingSales/index.js +12 -13
  48. package/dist/solution/VenueBooking/index.d.ts +15 -2
  49. package/dist/solution/VenueBooking/index.js +1062 -785
  50. package/dist/solution/VenueBooking/types.d.ts +2 -1
  51. package/dist/solution/VenueBooking/types.js +1 -0
  52. package/dist/solution/VenueBooking/utils/dateSummary.d.ts +1 -0
  53. package/dist/solution/VenueBooking/utils/dateSummary.js +7 -4
  54. package/dist/solution/VenueBooking/utils/smartPricing.d.ts +71 -0
  55. package/dist/solution/VenueBooking/utils/smartPricing.js +273 -0
  56. package/dist/solution/VenueBooking/utils/timeSlot.d.ts +1 -0
  57. package/dist/solution/VenueBooking/utils/timeSlot.js +6 -3
  58. package/lib/modules/OpenData/index.js +2 -1
  59. package/lib/modules/Order/index.d.ts +1 -0
  60. package/lib/modules/Order/index.js +71 -11
  61. package/lib/modules/Order/utils/weighedProduct.d.ts +12 -0
  62. package/lib/modules/Order/utils/weighedProduct.js +89 -0
  63. package/lib/modules/Order/utils.d.ts +1 -1
  64. package/lib/modules/Order/utils.js +5 -3
  65. package/lib/modules/Payment/index.d.ts +0 -5
  66. package/lib/modules/Payment/index.js +3 -28
  67. package/lib/modules/ProductList/index.js +14 -1
  68. package/lib/modules/ResourcePlanner/planner.js +10 -8
  69. package/lib/modules/Rules/index.js +16 -14
  70. package/lib/modules/Rules/types.d.ts +2 -0
  71. package/lib/plugins/request.d.ts +4 -0
  72. package/lib/server/index.js +72 -28
  73. package/lib/server/modules/floor-plan/index.js +29 -1
  74. package/lib/server/modules/payment/index.d.ts +25 -6
  75. package/lib/server/modules/payment/index.js +350 -78
  76. package/lib/server/test.json +1 -1
  77. package/lib/shared/receipt/customerInvoiceLanguages.d.ts +8 -0
  78. package/lib/shared/receipt/customerInvoiceLanguages.js +58 -0
  79. package/lib/shared/receipt/loadCustomerInvoiceLayout.d.ts +27 -0
  80. package/lib/shared/receipt/loadCustomerInvoiceLayout.js +124 -0
  81. package/lib/shared/receipt/small-ticket.d.ts +11 -0
  82. package/lib/shared/receipt/small-ticket.js +234 -30
  83. package/lib/shared/receipt/templateVisibility.d.ts +23 -0
  84. package/lib/shared/receipt/templateVisibility.js +63 -0
  85. package/lib/solution/BaseSales/index.d.ts +4 -2
  86. package/lib/solution/BaseSales/index.js +79 -18
  87. package/lib/solution/BaseSales/types.d.ts +5 -1
  88. package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +24 -9
  89. package/lib/solution/BaseSales/utils.js +2 -1
  90. package/lib/solution/BookingByStep/index.d.ts +1 -1
  91. package/lib/solution/BookingTicket/index.d.ts +4 -1
  92. package/lib/solution/BookingTicket/index.js +14 -2
  93. package/lib/solution/OrderInvoicePreview/index.d.ts +10 -0
  94. package/lib/solution/OrderInvoicePreview/index.js +62 -10
  95. package/lib/solution/RegisterAndLogin/config.js +57 -11
  96. package/lib/solution/RegisterAndLogin/index.d.ts +2 -0
  97. package/lib/solution/RegisterAndLogin/index.js +9 -1
  98. package/lib/solution/RegisterAndLogin/utils.d.ts +12 -4
  99. package/lib/solution/RegisterAndLogin/utils.js +24 -17
  100. package/lib/solution/Sales/index.d.ts +2 -0
  101. package/lib/solution/Sales/index.js +10 -1
  102. package/lib/solution/ScanOrder/utils.js +2 -1
  103. package/lib/solution/UnifiedBookingSales/index.js +5 -6
  104. package/lib/solution/VenueBooking/index.d.ts +15 -2
  105. package/lib/solution/VenueBooking/index.js +243 -96
  106. package/lib/solution/VenueBooking/types.d.ts +2 -1
  107. package/lib/solution/VenueBooking/types.js +1 -0
  108. package/lib/solution/VenueBooking/utils/dateSummary.d.ts +1 -0
  109. package/lib/solution/VenueBooking/utils/dateSummary.js +7 -4
  110. package/lib/solution/VenueBooking/utils/smartPricing.d.ts +71 -0
  111. package/lib/solution/VenueBooking/utils/smartPricing.js +224 -0
  112. package/lib/solution/VenueBooking/utils/timeSlot.d.ts +1 -0
  113. package/lib/solution/VenueBooking/utils/timeSlot.js +5 -2
  114. package/package.json +1 -1
@@ -15,6 +15,7 @@ var _BaseModule = require("../BaseModule");
15
15
  var _types = require("./types");
16
16
  var _utils = require("./utils");
17
17
  var _utils2 = require("../Product/utils");
18
+ var _weighedProduct = require("./utils/weighedProduct");
18
19
  var _pickupTiming = require("./pickupTiming");
19
20
  var _dayjs = _interopRequireDefault(require("dayjs"));
20
21
  var _availability = require("../Discount/availability");
@@ -567,9 +568,9 @@ class OrderModule extends _BaseModule.BaseModule {
567
568
  with_wallet_pass_holder: 1,
568
569
  with_available_wallet_flag: 1,
569
570
  with_available_wallet_pass_flag: 1,
570
- with_virtual_currency: 1,
571
571
  with_entitlement_pass: 1,
572
572
  filter_order_wallet_pass_usage: 0,
573
+ with_virtual_currency: 1,
573
574
  request_timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
574
575
  ...(orderId ? {
575
576
  order_id: orderId
@@ -838,6 +839,7 @@ class OrderModule extends _BaseModule.BaseModule {
838
839
  orderTotalAmount: Number(this.store.summary?.total_amount || 0)
839
840
  }, {
840
841
  reapplyBookingDiscountProductUids: options?.reapplyBookingDiscountProductUids,
842
+ reapplyWeighedDiscountProductUids: options?.reapplyWeighedDiscountProductUids,
841
843
  preferredEntitlementPassIds: options?.preferredEntitlementPassIds
842
844
  });
843
845
  if (result?.productList) {
@@ -3938,7 +3940,7 @@ class OrderModule extends _BaseModule.BaseModule {
3938
3940
  const product = uid !== undefined ? tempOrder.products.find(item => String(item.metadata?.unique_identification_number || item.unique_identification_number || '') === String(uid)) : tempOrder.products[(0, _utils3.getProductIdentityIndex)(tempOrder.products, params)];
3939
3941
  return !product || (0, _utils3.getSafeProductNum)(params.updates.num) > product.num;
3940
3942
  }
3941
- applyOrderProductUpdateToTempOrder(tempOrder, params) {
3943
+ applyOrderProductUpdateToTempOrder(tempOrder, params, reapplyWeighedDiscountProductUids) {
3942
3944
  const {
3943
3945
  product_id,
3944
3946
  product_variant_id,
@@ -4011,7 +4013,8 @@ class OrderModule extends _BaseModule.BaseModule {
4011
4013
  const previousLineFingerprint = (0, _utils3.buildProductLineFingerprint)((0, _utils.getProductSkuOptions)(targetProduct), targetProduct.product_bundle);
4012
4014
  const nextLineFingerprint = (0, _utils3.buildProductLineFingerprint)((0, _utils.getProductSkuOptions)(nextProduct), nextProduct.product_bundle);
4013
4015
  const callerChangesVariant = Number(product_variant_id || 0) !== Number(targetProduct.product_variant_id || 0);
4014
- const callerChangesLineConfiguration = callerChangesVariant || previousLineFingerprint !== nextLineFingerprint;
4016
+ const callerChangesWeighing = (0, _weighedProduct.getWeighedProductPriceFingerprint)(targetProduct.product_sku) !== (0, _weighedProduct.getWeighedProductPriceFingerprint)(nextProduct.product_sku);
4017
+ const callerChangesLineConfiguration = callerChangesVariant || callerChangesWeighing || previousLineFingerprint !== nextLineFingerprint;
4015
4018
  const isPersistedOrderLine = targetProduct.order_detail_id !== undefined && targetProduct.order_detail_id !== null;
4016
4019
  const callerChangesDiscountList = Array.isArray(updates?.discount_list) && updates.discount_list.length > 0 && !isSameDiscountList(updates.discount_list, targetProduct.discount_list);
4017
4020
  const callerUpdatesManualPrice = updates?.metadata?.price_override !== undefined || updates?.metadata?.is_manual_discount !== undefined || updates?.metadata?.product_discount_reason !== undefined || callerChangesDiscountList;
@@ -4027,6 +4030,23 @@ class OrderModule extends _BaseModule.BaseModule {
4027
4030
  };
4028
4031
  return hasChangedPrice(updates.selling_price, targetProduct.selling_price) || hasChangedPrice(updates.original_price, targetProduct.original_price);
4029
4032
  })();
4033
+ const weighedSourcePrice = callerChangesWeighing ? (0, _weighedProduct.resolveWeighedSourcePrice)(nextProduct.product_sku) : undefined;
4034
+ const hasResolvedWeighedPrice = weighedSourcePrice !== undefined;
4035
+ if (hasResolvedWeighedPrice && !callerUpdatesSourcePrice) {
4036
+ const nextMainOriginal = new _decimal.default(weighedSourcePrice).plus((0, _utils.sumOptionUnitPrice)((0, _utils.getProductSkuOptions)(nextProduct)));
4037
+ const previousMainOriginal = new _decimal.default(targetProduct.metadata?.main_product_original_price ?? targetProduct.original_price ?? 0);
4038
+ const previousMainSelling = new _decimal.default(targetProduct.metadata?.main_product_selling_price ?? targetProduct.selling_price ?? 0);
4039
+ // A manual final price remains fixed; other legacy discounts keep their amount
4040
+ // until Rules reapplies the currently selected wallet discount below.
4041
+ const nextMainSelling = isManualProductDiscountProduct(nextProduct) ? previousMainSelling : nextMainOriginal.minus(previousMainOriginal.minus(previousMainSelling));
4042
+ nextProduct.metadata = {
4043
+ ...nextProduct.metadata,
4044
+ source_product_price: weighedSourcePrice,
4045
+ main_product_original_price: callerUpdatesMainOriginalPrice ? nextProduct.metadata.main_product_original_price : nextMainOriginal.toFixed(2),
4046
+ main_product_selling_price: callerUpdatesMainSellingPrice ? nextProduct.metadata.main_product_selling_price : _decimal.default.max(0, nextMainSelling).toFixed(2),
4047
+ price_schema_version: 2
4048
+ };
4049
+ }
4030
4050
  if (isPersistedOrderLine && !callerChangesLineConfiguration && !callerUpdatesManualPrice && !callerAllowsBookingReprice) {
4031
4051
  nextProduct.selling_price = targetProduct.selling_price;
4032
4052
  nextProduct.original_price = targetProduct.original_price;
@@ -4043,17 +4063,17 @@ class OrderModule extends _BaseModule.BaseModule {
4043
4063
  // 新配置只传了 source 时,不能把未传的 main_product_* 从旧行继承下来。
4044
4064
  // 以新 source + 新 option 重建券前价;券后价未显式提供时保留旧主商品折扣额。
4045
4065
  const previousMainOriginal = new _decimal.default(Number(targetProduct.metadata?.main_product_original_price) || 0);
4046
- const previousMainSelling = new _decimal.default(Number(targetProduct.metadata?.main_product_selling_price) || previousMainOriginal.toNumber());
4066
+ const previousMainSelling = new _decimal.default(Number(targetProduct.metadata?.main_product_selling_price ?? previousMainOriginal.toNumber()));
4047
4067
  const preservedDiscount = previousMainOriginal.minus(previousMainSelling);
4048
4068
  const nextMainOriginal = new _decimal.default(Number(updates.metadata.source_product_price) || 0).plus((0, _utils.sumOptionUnitPrice)((0, _utils.getProductSkuOptions)(nextProduct)));
4049
4069
  nextProduct.metadata = {
4050
4070
  ...(nextProduct.metadata || {}),
4051
4071
  source_product_price: String(updates.metadata.source_product_price),
4052
4072
  main_product_original_price: callerUpdatesMainOriginalPrice ? String(updates.metadata.main_product_original_price) : nextMainOriginal.toDecimalPlaces(2).toFixed(2),
4053
- main_product_selling_price: callerUpdatesMainSellingPrice ? String(updates.metadata.main_product_selling_price) : nextMainOriginal.minus(preservedDiscount).toDecimalPlaces(2).toFixed(2),
4073
+ main_product_selling_price: callerUpdatesMainSellingPrice ? String(updates.metadata.main_product_selling_price) : hasResolvedWeighedPrice && isManualProductDiscountProduct(nextProduct) ? previousMainSelling.toFixed(2) : nextMainOriginal.minus(preservedDiscount).toDecimalPlaces(2).toFixed(2),
4054
4074
  price_schema_version: 2
4055
4075
  };
4056
- } else if (callerChangesVariant && !callerUpdatesTopPrice && !callerUpdatesMainMeta) {
4076
+ } else if (callerChangesVariant && !hasResolvedWeighedPrice && !callerUpdatesTopPrice && !callerUpdatesMainMeta) {
4057
4077
  // variant 已变化但 caller 没有显式报价时,旧 v2 metadata 不能继续充当权威价格源。
4058
4078
  // 清理后由 normalize 优先读取 metadata.origin.variant 中新 variant 的目录价;
4059
4079
  // 顶层 original/selling 的旧差额仍会作为 legacy discount 迁移到新价格。
@@ -4065,7 +4085,7 @@ class OrderModule extends _BaseModule.BaseModule {
4065
4085
  delete nextProduct.metadata.main_product_selling_price;
4066
4086
  delete nextProduct.metadata.main_product_original_price;
4067
4087
  delete nextProduct.metadata.price_schema_version;
4068
- } else if (callerChangesLineConfiguration && !callerUpdatesTopPrice && !callerUpdatesMainMeta) {
4088
+ } else if (callerChangesLineConfiguration && !hasResolvedWeighedPrice && !callerUpdatesTopPrice && !callerUpdatesMainMeta) {
4069
4089
  // 仅 option / bundle 变化时,保留历史 source 和主商品折扣金额,
4070
4090
  // 但必须让 original 与 selling 一起随新的 option 金额移动。
4071
4091
  const previousOptionSum = (0, _utils.sumOptionUnitPrice)((0, _utils.getProductSkuOptions)(targetProduct));
@@ -4082,7 +4102,7 @@ class OrderModule extends _BaseModule.BaseModule {
4082
4102
  main_product_selling_price: nextMainOriginal.minus(preservedDiscount).toDecimalPlaces(2).toFixed(2),
4083
4103
  price_schema_version: 2
4084
4104
  };
4085
- } else if (callerUpdatesTopPrice && !callerUpdatesMainMeta) {
4105
+ } else if (callerUpdatesTopPrice && !callerUpdatesMainMeta && !hasResolvedWeighedPrice) {
4086
4106
  const existedMeta = nextProduct.metadata || {};
4087
4107
  nextProduct.metadata = {
4088
4108
  ...existedMeta
@@ -4092,6 +4112,37 @@ class OrderModule extends _BaseModule.BaseModule {
4092
4112
  delete nextProduct.metadata.main_product_original_price;
4093
4113
  delete nextProduct.metadata.price_schema_version;
4094
4114
  }
4115
+ if (callerChangesLineConfiguration && (0, _weighedProduct.resolveWeighedSourcePrice)(nextProduct.product_sku) !== undefined && nextProduct.metadata.main_product_original_price != null && nextProduct.metadata.main_product_selling_price != null && isManualProductDiscountProduct(nextProduct)) {
4116
+ const originalPrice = (0, _utils.composeLinePrice)({
4117
+ mainPrice: nextProduct.metadata.main_product_original_price,
4118
+ bundle: (0, _weighedProduct.withOriginalBundleOptionPrices)(nextProduct.product_bundle),
4119
+ useOriginalBundle: true
4120
+ });
4121
+ const sellingPrice = (0, _utils.composeLinePrice)({
4122
+ mainPrice: nextProduct.metadata.main_product_selling_price,
4123
+ bundle: nextProduct.product_bundle
4124
+ });
4125
+ const previousManualDiscount = (0, _manualProductDiscount.findManualProductDiscountItem)(nextProduct.discount_list);
4126
+ nextProduct.original_price = originalPrice;
4127
+ nextProduct.metadata = {
4128
+ ...nextProduct.metadata,
4129
+ origin: {
4130
+ ...nextProduct.metadata.origin,
4131
+ _extend: {
4132
+ ...nextProduct.metadata.origin?._extend,
4133
+ origin_total: Number(originalPrice)
4134
+ }
4135
+ }
4136
+ };
4137
+ nextProduct.discount_list = (0, _manualProductDiscount.mergeManualProductDiscountIntoList)(nextProduct.discount_list, (0, _manualProductDiscount.buildManualProductDiscountItem)({
4138
+ originTotal: Number(originalPrice),
4139
+ sellingTotal: Number(sellingPrice),
4140
+ quantity: nextProduct.num,
4141
+ message: previousManualDiscount?.discount.message,
4142
+ discountway: previousManualDiscount?.discount.discountway,
4143
+ discount_per: previousManualDiscount?.discount.discount_per
4144
+ }));
4145
+ }
4095
4146
  // normalizeOrderProduct 幂等:v2 metadata 存在 → 保留 main_product_* 折扣重算 composite;
4096
4147
  // 否则 → 把顶层 selling_price 视作 source,派生 main_product_* 并写 price_schema_version: 2。
4097
4148
  let normalizedProduct = this.normalizeVirtualSettlementProductIfEnabled((0, _utils.mergeOrderProductDisplayFields)(nextProduct, (0, _utils3.normalizeOrderProduct)(nextProduct)));
@@ -4129,6 +4180,10 @@ class OrderModule extends _BaseModule.BaseModule {
4129
4180
  };
4130
4181
  tempOrder.bookings = [...(tempOrder.bookings || []), linked.booking];
4131
4182
  }
4183
+ if (callerChangesWeighing && !isManualProductDiscountProduct(normalizedProduct)) {
4184
+ const uid = normalizedProduct.metadata?.unique_identification_number;
4185
+ if (uid) reapplyWeighedDiscountProductUids?.push(String(uid));
4186
+ }
4132
4187
  tempOrder.products[productIndex] = normalizedProduct;
4133
4188
  this.syncTempOrderHolderFromBookings(tempOrder);
4134
4189
  this.captureProductRuntime(tempOrder, updates, tempOrder.products[productIndex], tempOrder.products[productIndex].metadata?.unique_identification_number);
@@ -4139,9 +4194,11 @@ class OrderModule extends _BaseModule.BaseModule {
4139
4194
  if (this.isRestrictedCartProductUpdate(tempOrder, params)) {
4140
4195
  this.createCartIntentCommitGuard('update_product', tempOrder)();
4141
4196
  }
4142
- const updatedProductUid = this.applyOrderProductUpdateToTempOrder(tempOrder, params);
4197
+ const reapplyWeighedDiscountProductUids = [];
4198
+ const updatedProductUid = this.applyOrderProductUpdateToTempOrder(tempOrder, params, reapplyWeighedDiscountProductUids);
4143
4199
  const promotionPayload = await this.evaluatePromotion();
4144
4200
  this.applyDiscount({
4201
+ reapplyWeighedDiscountProductUids,
4145
4202
  reapplyBookingDiscountProductUids: params.allow_booking_discount_reapply && updatedProductUid ? [String(updatedProductUid)] : undefined
4146
4203
  });
4147
4204
  this.sanitizeTempOrderProducts(tempOrder);
@@ -4163,8 +4220,9 @@ class OrderModule extends _BaseModule.BaseModule {
4163
4220
  if (paramsList.some(params => this.isRestrictedCartProductUpdate(tempOrder, params))) {
4164
4221
  this.createCartIntentCommitGuard('update_product', tempOrder)();
4165
4222
  }
4223
+ const reapplyWeighedDiscountProductUids = [];
4166
4224
  const reapplyBookingDiscountProductUids = paramsList.reduce((acc, params) => {
4167
- const updatedProductUid = this.applyOrderProductUpdateToTempOrder(tempOrder, params);
4225
+ const updatedProductUid = this.applyOrderProductUpdateToTempOrder(tempOrder, params, reapplyWeighedDiscountProductUids);
4168
4226
  // 批量改预约时间时,延迟到最后一次折扣计算里统一恢复已选预约折扣卡。
4169
4227
  if (params.allow_booking_discount_reapply && updatedProductUid) {
4170
4228
  acc.push(String(updatedProductUid));
@@ -4172,6 +4230,7 @@ class OrderModule extends _BaseModule.BaseModule {
4172
4230
  return acc;
4173
4231
  }, []);
4174
4232
  await this.finalizeAfterProductsMutation(tempOrder, {
4233
+ reapplyWeighedDiscountProductUids,
4175
4234
  reapplyBookingDiscountProductUids: reapplyBookingDiscountProductUids.length ? reapplyBookingDiscountProductUids : undefined
4176
4235
  });
4177
4236
  this.logInfo('updateOrderProduct success', {
@@ -4307,7 +4366,8 @@ class OrderModule extends _BaseModule.BaseModule {
4307
4366
  this.syncTempOrderHolderFromBookings(tempOrder);
4308
4367
  const promotionPayload = await this.evaluatePromotion();
4309
4368
  this.applyDiscount({
4310
- reapplyBookingDiscountProductUids: options?.reapplyBookingDiscountProductUids
4369
+ reapplyBookingDiscountProductUids: options?.reapplyBookingDiscountProductUids,
4370
+ reapplyWeighedDiscountProductUids: options?.reapplyWeighedDiscountProductUids
4311
4371
  });
4312
4372
  this.sanitizeTempOrderProducts(tempOrder);
4313
4373
  await this.recalculateSummary({
@@ -0,0 +1,12 @@
1
+ /** Only pricing/measurement fields participate; display and option data do not. */
2
+ export declare function getWeighedProductPriceFingerprint(sku?: Record<string, any>): string;
3
+ /**
4
+ * A weighed line's source is the amount for its net weight, never its catalog unit price.
5
+ * Persisted SKU measurements and the full catalog snapshot take priority over runtime
6
+ * _extend.weighing, which can survive repricing with an obsolete total_amount.
7
+ */
8
+ export declare function resolveWeighedSourcePrice(sku?: Record<string, any>, weighing?: Record<string, any>): string | undefined;
9
+ /** Original bundle totals must use the nested options before manual distribution. */
10
+ export declare function withOriginalBundleOptionPrices(bundles?: Array<Record<string, any>>): {
11
+ option: any;
12
+ }[] | undefined;
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.getWeighedProductPriceFingerprint = getWeighedProductPriceFingerprint;
7
+ exports.resolveWeighedSourcePrice = resolveWeighedSourcePrice;
8
+ exports.withOriginalBundleOptionPrices = withOriginalBundleOptionPrices;
9
+ var _decimal = _interopRequireDefault(require("decimal.js"));
10
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
11
+ const UNIT_GRAMS = {
12
+ kg: '1000',
13
+ KILOGRAMS: '1000',
14
+ g: '1',
15
+ GRAMS: '1',
16
+ lb: '453.59237',
17
+ POUNDS: '453.59237',
18
+ oz: '28.349523125',
19
+ OUNCES: '28.349523125'
20
+ };
21
+ function nonNegative(value) {
22
+ if (value === null || value === undefined || value === '') return undefined;
23
+ try {
24
+ const result = new _decimal.default(value);
25
+ return result.isFinite() && result.gte(0) ? result : undefined;
26
+ } catch {
27
+ return undefined;
28
+ }
29
+ }
30
+ function numericKey(value) {
31
+ return nonNegative(value)?.toString();
32
+ }
33
+ function unitKey(value) {
34
+ return UNIT_GRAMS[String(value)] || (value == null ? undefined : String(value));
35
+ }
36
+ function snapshotKey(snapshot) {
37
+ return [numericKey(snapshot?.price), unitKey(snapshot?.unit), numericKey(snapshot?.unit_value ?? 1)];
38
+ }
39
+
40
+ /** Only pricing/measurement fields participate; display and option data do not. */
41
+ function getWeighedProductPriceFingerprint(sku) {
42
+ if (Number(sku?.open_sold_weight) !== 1) return 'unweighed';
43
+ return JSON.stringify([1, numericKey(sku?.net_weight), unitKey(sku?.unit), numericKey(sku?.unit_price), numericKey(sku?.unit_weight ?? 1), snapshotKey(sku?.unit_data_snapshot)]);
44
+ }
45
+
46
+ /**
47
+ * A weighed line's source is the amount for its net weight, never its catalog unit price.
48
+ * Persisted SKU measurements and the full catalog snapshot take priority over runtime
49
+ * _extend.weighing, which can survive repricing with an obsolete total_amount.
50
+ */
51
+ function resolveWeighedSourcePrice(sku, weighing) {
52
+ if (sku?.open_sold_weight != null && Number(sku.open_sold_weight) !== 1) return undefined;
53
+ if (Number(sku?.open_sold_weight) !== 1 && !weighing) return undefined;
54
+ const hasSkuMeasurement = sku?.net_weight !== undefined && sku?.net_weight !== null;
55
+ const measurement = hasSkuMeasurement ? sku : weighing;
56
+ const netWeight = nonNegative(measurement?.net_weight);
57
+ if (netWeight === undefined) return undefined;
58
+ const snapshot = hasSkuMeasurement ? sku?.unit_data_snapshot : weighing?.scalePriceProduct;
59
+ const snapshotPrice = nonNegative(snapshot?.price);
60
+ const snapshotUnits = nonNegative(snapshot?.unit_value ?? 1);
61
+ const sourceGrams = UNIT_GRAMS[String(snapshot?.unit)];
62
+ const targetGrams = UNIT_GRAMS[String(measurement?.unit)];
63
+ if (snapshotPrice && snapshotUnits?.gt(0) && sourceGrams && targetGrams) {
64
+ return snapshotPrice.mul(netWeight).mul(targetGrams).div(sourceGrams).div(snapshotUnits).toFixed(2);
65
+ }
66
+
67
+ // Legacy callers may only retain the native scale's amount. Do not let an old
68
+ // amount overwrite a changed SKU measurement, unit price, or pricing snapshot.
69
+ const runtimeAmount = nonNegative(weighing?.total_amount);
70
+ const runtimeMatchesSku = !hasSkuMeasurement || numericKey(weighing?.net_weight) === numericKey(sku?.net_weight) && unitKey(weighing?.unit) === unitKey(sku?.unit) && numericKey(weighing?.unit_price) === numericKey(sku?.unit_price) && numericKey(weighing?.unit_weight ?? 1) === numericKey(sku?.unit_weight ?? 1) && JSON.stringify(snapshotKey(weighing?.scalePriceProduct)) === JSON.stringify(snapshotKey(sku?.unit_data_snapshot));
71
+ if (runtimeAmount && runtimeMatchesSku) return runtimeAmount.toFixed(2);
72
+
73
+ // Older persisted orders have only a rounded per-scale-unit price.
74
+ const unitPrice = nonNegative(measurement?.unit_price);
75
+ const unitWeight = nonNegative(measurement?.unit_weight ?? 1);
76
+ if (!unitPrice || !unitWeight?.gt(0)) return undefined;
77
+ return netWeight.div(unitWeight).mul(unitPrice).toFixed(2);
78
+ }
79
+
80
+ /** Original bundle totals must use the nested options before manual distribution. */
81
+ function withOriginalBundleOptionPrices(bundles) {
82
+ return bundles?.map(bundle => ({
83
+ ...bundle,
84
+ option: bundle.option?.map(option => ({
85
+ ...option,
86
+ add_price: option.original_price ?? option.add_price ?? option.price
87
+ }))
88
+ }));
89
+ }
@@ -1,5 +1,5 @@
1
1
  import Decimal from 'decimal.js';
2
- import type { CartItem } from '../Cart';
2
+ import type { CartItem } from "../Cart";
3
3
  import type { OrderProductDiscountItem, OrderMetaList, OrderProduct, OrderSubmitPayload, OrderSubmitProduct, OrderSummary, OrderTempOrder, PointCardSnapshotItem, VirtualCurrencySnapshotItem } from './types';
4
4
  import type { RulesParamsHooks } from '../Rules/types';
5
5
  import type { MapOrderPaymentsOptions, OrderPaymentData, OrderPaymentSource } from './types';
@@ -131,12 +131,13 @@ Object.defineProperty(exports, "syncManualProductDiscountProductNum", {
131
131
  var _dayjs = _interopRequireDefault(require("dayjs"));
132
132
  var _customParseFormat = _interopRequireDefault(require("dayjs/plugin/customParseFormat"));
133
133
  var _decimal = _interopRequireDefault(require("decimal.js"));
134
+ var _weighedProduct = require("./utils/weighedProduct");
135
+ var _manualProductDiscount = require("./utils/manualProductDiscount");
134
136
  var _utils = require("../Holder/utils");
135
137
  var _utils2 = require("../../solution/ScanOrder/utils");
136
138
  var _orderCollectionIdentity = require("./utils/orderCollectionIdentity");
137
139
  var _salesNotes = require("./salesNotes");
138
140
  var _pickupTiming = require("./pickupTiming");
139
- var _manualProductDiscount = require("./utils/manualProductDiscount");
140
141
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
141
142
  _dayjs.default.extend(_customParseFormat.default);
142
143
  const POINT_CARD_SNAPSHOT_TAG = 'point_card';
@@ -957,7 +958,7 @@ function createDefaultOrderRulesHooks(getBookings) {
957
958
  });
958
959
  const composedOriginalPrice = composeLinePrice({
959
960
  mainPrice: nextMainOriginalPrice,
960
- bundle: nextBundle,
961
+ bundle: Number(product.product_sku?.open_sold_weight) === 1 && (0, _manualProductDiscount.hasManualProductDiscountFlag)(metadataAny) ? (0, _weighedProduct.withOriginalBundleOptionPrices)(nextBundle) : nextBundle,
961
962
  useOriginalBundle: true
962
963
  });
963
964
  const finalOriginalPrice = resolveManualOverrideLineOriginalPrice({
@@ -1058,6 +1059,7 @@ function ensureCartItemOriginHolder(cartItem, window) {
1058
1059
  form_record: [formRecordId]
1059
1060
  };
1060
1061
  }
1062
+ ;
1061
1063
  const getAllDiscountList = cartItem => {
1062
1064
  let discountList = cartItem?._origin?.product?.discount_list || [];
1063
1065
  (cartItem?._origin?.product?.product_bundle || []).forEach(item => {
@@ -1538,7 +1540,7 @@ function buildSubmitPayload(params) {
1538
1540
  shop_discount: tempOrder.shop_discount || '0.00',
1539
1541
  surcharge_fee: tempOrder.surcharge_fee || '0.00',
1540
1542
  note: tempOrder.note || '',
1541
- delivery_type: tempOrder.delivery_type || 'nil',
1543
+ delivery_type: tempOrder.delivery_type || 'shop_service',
1542
1544
  schedule_date: scheduleDate,
1543
1545
  // created_at: tempOrder.created_at || formatDateTime(now), 和后端协定这个字段先不提交到接口
1544
1546
  bookings: (tempOrder.bookings || []).filter(booking => (0, _pickupTiming.isFulfillmentTimingBooking)(booking) || Number(booking.parent_id) !== 0).map(booking => normalizeSubmitBooking(booking)),
@@ -171,11 +171,6 @@ export declare class PaymentModule extends BaseModule implements Module, Payment
171
171
  * 获取钱包支付方式
172
172
  */
173
173
  getWalletPaymentMethod(): Promise<PaymentMethod | null>;
174
- /**
175
- * 确保支付模块所需的数据库表已创建
176
- */
177
- /** @deprecated */
178
- private ensurePaymentTables;
179
174
  /**
180
175
  * 获取部分支付的订单
181
176
  */
@@ -160,10 +160,6 @@ class PaymentModule extends _BaseModule.BaseModule {
160
160
  this.app = this.appPlugin.getApp();
161
161
  this.dbManager = this.app.dbManager;
162
162
  this.logger = this.app.logger;
163
-
164
- // 确保支付模块所需的对象存储已创建
165
- // await this.ensurePaymentTables();
166
-
167
163
  this.registerNetworkHandlers();
168
164
 
169
165
  // // 预连接数据库
@@ -1110,7 +1106,7 @@ class PaymentModule extends _BaseModule.BaseModule {
1110
1106
  */
1111
1107
  async getCashPaymentMethod() {
1112
1108
  try {
1113
- const payMethods = await this.dbManager.getAll('pay_method');
1109
+ const payMethods = await this.getPayMethodListAsync();
1114
1110
  return payMethods.find(method => method.code === _types.PaymentMethodType.Cash) || null;
1115
1111
  } catch (error) {
1116
1112
  console.error('[PaymentModule] 获取现金支付方式失败', error);
@@ -1123,7 +1119,7 @@ class PaymentModule extends _BaseModule.BaseModule {
1123
1119
  */
1124
1120
  async getEftposPaymentMethod() {
1125
1121
  try {
1126
- const payMethods = await this.dbManager.getAll('pay_method');
1122
+ const payMethods = await this.getPayMethodListAsync();
1127
1123
  return payMethods.find(method => method.code === _types.PaymentMethodType.Eftpos) || null;
1128
1124
  } catch (error) {
1129
1125
  console.error('[PaymentModule] 获取Eftpos支付方式失败', error);
@@ -1136,7 +1132,7 @@ class PaymentModule extends _BaseModule.BaseModule {
1136
1132
  */
1137
1133
  async getWalletPaymentMethod() {
1138
1134
  try {
1139
- const payMethods = await this.dbManager.getAll('pay_method');
1135
+ const payMethods = await this.getPayMethodListAsync();
1140
1136
  return payMethods.find(method => method.code === _types.PaymentMethodType.Wallet) || null;
1141
1137
  } catch (error) {
1142
1138
  console.error('[PaymentModule] 获取钱包支付方式失败', error);
@@ -1144,27 +1140,6 @@ class PaymentModule extends _BaseModule.BaseModule {
1144
1140
  }
1145
1141
  }
1146
1142
 
1147
- /**
1148
- * 确保支付模块所需的数据库表已创建
1149
- */
1150
- /** @deprecated */
1151
- async ensurePaymentTables() {
1152
- try {
1153
- // 尝试访问 pay_method 表,如果不存在会抛出错误
1154
- await this.dbManager.getAll('pay_method');
1155
- } catch (error) {
1156
- console.warn('[PaymentModule] pay_method 表不存在,请在数据库配置中添加以下配置:');
1157
- console.warn('{ name: "pay_method", keyPath: "id" }');
1158
- }
1159
- try {
1160
- // 尝试访问 order 表,如果不存在会抛出错误
1161
- await this.dbManager.getAll('order');
1162
- } catch (error) {
1163
- console.warn('[PaymentModule] order 表不存在,请在数据库配置中添加以下配置:');
1164
- console.warn('{ name: "order", keyPath: "uuid" }');
1165
- }
1166
- }
1167
-
1168
1143
  /**
1169
1144
  * 获取部分支付的订单
1170
1145
  */
@@ -130,6 +130,19 @@ class ProductList extends _BaseModule.BaseModule {
130
130
  };
131
131
  const rawApplicationCode = this.otherParams?.channel;
132
132
  const mappedApplicationCode = typeof rawApplicationCode === 'string' ? channelMap[rawApplicationCode] || rawApplicationCode : rawApplicationCode;
133
+ const configuredBusinessCode = this.otherParams?.businessCode ?? this.otherParams?.business_code;
134
+ // application_code 是后端商品筛选字段;智能定价评估器则只读取
135
+ // strategy_context 中的 channel / business_code。默认补齐两者,
136
+ // 但允许调用方显式传入的上下文覆盖默认值。
137
+ const resolvedStrategyContext = {
138
+ ...(mappedApplicationCode ? {
139
+ channel: mappedApplicationCode
140
+ } : {}),
141
+ ...(configuredBusinessCode ? {
142
+ business_code: configuredBusinessCode
143
+ } : {}),
144
+ ...(strategy_context || {})
145
+ };
133
146
  const queryPayload = {
134
147
  ...((0, _platform.isCustomerUserPlatform)(this.otherParams?.platform) ? {} : {
135
148
  open_quotation: 1
@@ -162,7 +175,7 @@ class ProductList extends _BaseModule.BaseModule {
162
175
  with_schedule,
163
176
  schedule_datetime,
164
177
  extension_type,
165
- strategy_context
178
+ strategy_context: Object.keys(resolvedStrategyContext).length ? resolvedStrategyContext : undefined
166
179
  };
167
180
  const handledByOsServer = (0, _clientDataVariants.isProductQueryHandledByOsServer)(this.core, this.request);
168
181
  const originalCallback = typeof options?.callback === 'function' ? options.callback : undefined;
@@ -412,7 +412,7 @@ function normalizeOccupancy(occupancy, coverage, timezoneName, fixedOffsetMinute
412
412
  };
413
413
  }
414
414
  function normalizeRequirementGroup(product, requirementGroup) {
415
- const resourceType = 'single';
415
+ const resourceType = requirementGroup.resourceType === 'multiple' || requirementGroup.resourceType === 'capacity' ? requirementGroup.resourceType : 'single';
416
416
  const required = requirementGroup.required !== false;
417
417
  const min = required ? 1 : 0;
418
418
  return {
@@ -457,7 +457,7 @@ function normalizeProducts(products) {
457
457
  function normalizeResources(resources) {
458
458
  return resources.map(resource => ({
459
459
  ...resource,
460
- resourceType: 'single',
460
+ resourceType: resource.resourceType === 'multiple' || resource.resourceType === 'capacity' ? resource.resourceType : 'single',
461
461
  capacity: Math.max(1, Number(resource.capacity || 1) || 1),
462
462
  windows: (resource.windows || []).map(window => ({
463
463
  ...window
@@ -573,7 +573,8 @@ function buildCells(projection) {
573
573
  occupancy
574
574
  }) => occupancy) : [];
575
575
  const usedCapacity = segmentOccupancies.reduce((sum, occupancy) => sum + Math.max(1, Number(occupancy.pax || 1) || 1), 0);
576
- const remainingCapacity = Math.max(0, resource.capacity - usedCapacity);
576
+ // 预约模式来自商品资源组;v2 资源记录可能不包含 type。
577
+ const remainingCapacity = requirementGroup.resourceType === 'single' && segmentOccupancies.length > 0 ? 0 : Math.max(0, resource.capacity - usedCapacity);
577
578
  const requiredCapacity = resolveRequiredCapacity(requirementGroup, product);
578
579
  const reasonCodes = Array.from(new Set(segmentOccupancies.flatMap(occupancy => {
579
580
  const source = occupancy.source || 'remote';
@@ -607,7 +608,7 @@ function buildCells(projection) {
607
608
  requirementFormId: requirementGroup.formId,
608
609
  resourceId: resource.id,
609
610
  resourceName: resource.name,
610
- resourceType: resource.resourceType,
611
+ resourceType: requirementGroup.resourceType,
611
612
  startAt: formatDateTime(start, timezoneName, fixedOffsetMinutes),
612
613
  endAt: formatDateTime(end, timezoneName, fixedOffsetMinutes),
613
614
  date: formatDate(start, timezoneName),
@@ -615,7 +616,7 @@ function buildCells(projection) {
615
616
  endTime: formatTime(end, timezoneName),
616
617
  timezone: projection.meta.timezone,
617
618
  status,
618
- // 资源组当前统一单选;具体资源仍用 capacity 表达可承载的预约数量。
619
+ // capacity 表示人数;single 被占用时不再贡献可用容量。
619
620
  remainingCapacity,
620
621
  maxCapacity: resource.capacity,
621
622
  reasonCodes: Array.from(new Set(reasonCodes)),
@@ -1500,7 +1501,7 @@ function evaluateCandidate(projection, candidate, request) {
1500
1501
  resourceName: resource.name,
1501
1502
  resourceFormId: resource.formId,
1502
1503
  policyId: resource.raw?.policy_id,
1503
- resourceType: resource.resourceType,
1504
+ resourceType: requirementGroup.resourceType,
1504
1505
  status: isPast ? 'past' : 'unavailable',
1505
1506
  maxPartySize: 0,
1506
1507
  reasonCodes: isPast ? ['past'] : [...(isExcludedScheduleDate ? ['schedule_excluded_date'] : []), 'outside_resource_window']
@@ -1524,7 +1525,7 @@ function evaluateCandidate(projection, candidate, request) {
1524
1525
  resourceName: resource.name,
1525
1526
  resourceFormId: resource.formId,
1526
1527
  policyId: resource.raw?.policy_id,
1527
- resourceType: resource.resourceType,
1528
+ resourceType: requirementGroup.resourceType,
1528
1529
  status,
1529
1530
  remainingCapacity,
1530
1531
  maxCapacity,
@@ -2008,7 +2009,8 @@ function validateAvailabilitySelection(projection, params, runtimeOptions = {})
2008
2009
  const remainingCapacity = matchedCell?.remainingCapacity;
2009
2010
  if (option && (option.status === 'available' || option.status === 'limited') && remainingCapacity !== undefined) {
2010
2011
  const capacityPerBooking = normalizedPartySize ?? 1;
2011
- const availableQuantity = Math.max(0, Math.floor(remainingCapacity / capacityPerBooking));
2012
+ const capacityQuantity = Math.max(0, Math.floor(remainingCapacity / capacityPerBooking));
2013
+ const availableQuantity = matchedGroup.resourceType === 'single' ? Math.min(1, capacityQuantity) : capacityQuantity;
2012
2014
  if (normalizedBookingCount > availableQuantity) {
2013
2015
  conflicts.push({
2014
2016
  code: 'resource_capacity_exceeded',
@@ -225,7 +225,8 @@ class RulesModule extends _BaseModule.BaseModule {
225
225
  // 识别出来是不是在编辑的界面里又新增了商品
226
226
  // 这种情况下,如果有可用的优惠券,也会自动勾选上
227
227
  const isEditModeAddNewProduct = productList.find(n => n.booking_id) && productList.find(n => !n.booking_id);
228
- const reapplyBookingDiscountProductUids = new Set((options?.reapplyBookingDiscountProductUids || []).filter(uid => uid !== undefined && uid !== null && uid !== '').map(String));
228
+ const reapplyWeighedDiscountProductUids = new Set((options?.reapplyWeighedDiscountProductUids || []).map(String));
229
+ const reapplyBookingDiscountProductUids = new Set([...(options?.reapplyBookingDiscountProductUids || []), ...reapplyWeighedDiscountProductUids].filter(uid => uid !== undefined && uid !== null && uid !== '').map(String));
229
230
  const getOriginProductUid = originProduct => {
230
231
  const uid = originProduct?.metadata?.unique_identification_number ?? originProduct?.unique_identification_number;
231
232
  if (uid === undefined || uid === null || uid === '') return undefined;
@@ -235,8 +236,8 @@ class RulesModule extends _BaseModule.BaseModule {
235
236
  if (!selectedDiscount) return false;
236
237
  const uid = getOriginProductUid(originProduct);
237
238
  if (!uid || !reapplyBookingDiscountProductUids.has(uid)) return false;
238
- const discountType = selectedDiscount.tag || selectedDiscount.type || '';
239
- return selectedDiscount.isEditMode === true && selectedDiscount.isSelected === true && ['entitlement_pass', 'good_pass', 'discount_card', 'product_discount_card'].includes(discountType);
239
+ const discountType = selectedDiscount.tag || selectedDiscount.type;
240
+ return selectedDiscount.isEditMode === true && selectedDiscount.isSelected === true && ['entitlement_pass', 'good_pass', 'discount_card', 'product_discount_card'].includes(discountType ?? '');
240
241
  };
241
242
  const resolveReapplyBookingDiscountPercent = (originProduct, selectedDiscount) => {
242
243
  if (!shouldReapplyBookingDiscount(originProduct, selectedDiscount)) return undefined;
@@ -287,8 +288,8 @@ class RulesModule extends _BaseModule.BaseModule {
287
288
  };
288
289
  }
289
290
  const canUseEditModeDiscountForReapply = discount => {
290
- const discountType = discount.tag || discount.type || '';
291
- return reapplyBookingDiscountProductUids.size > 0 && discount.isEditMode === true && discount.isSelected === true && ['entitlement_pass', 'good_pass', 'discount_card', 'product_discount_card'].includes(discountType);
291
+ const discountType = discount.tag || discount.type;
292
+ return reapplyBookingDiscountProductUids.size > 0 && discount.isEditMode === true && discount.isSelected === true && ['entitlement_pass', 'good_pass', 'discount_card', 'product_discount_card'].includes(discountType ?? '');
292
293
  };
293
294
 
294
295
  // 过滤掉手动取消false的优惠券,但仅用于应用优惠逻辑。
@@ -509,9 +510,9 @@ class RulesModule extends _BaseModule.BaseModule {
509
510
  addModeDiscount.forEach(discount => {
510
511
  // 即使没有匹配的动态策略,服务端状态和次数限制也不能被覆盖为可用。
511
512
  if (discount.reasonCode) return;
512
- const discountType = discount.tag || discount.type || '';
513
+ const discountType = discount.tag || discount.type;
513
514
  // 对两类全额抵扣卡和折扣卡执行附加策略检查。
514
- if (['entitlement_pass', 'good_pass', 'discount_card', 'product_discount_card'].includes(discountType)) {
515
+ if (['entitlement_pass', 'good_pass', 'discount_card', 'product_discount_card'].includes(discountType ?? '')) {
515
516
  const entitlementQuota = (0, _entitlementPass.isEntitlementPass)(discount) ? (0, _entitlementPass.resolveEntitlementQuota)(discount) : null;
516
517
  if ((0, _entitlementPass.isEntitlementPass)(discount) && !entitlementQuota) {
517
518
  discount.config = {
@@ -1079,7 +1080,8 @@ class RulesModule extends _BaseModule.BaseModule {
1079
1080
  });
1080
1081
 
1081
1082
  // 已持久化且已有钱包券/折扣卡的商品行跳过,保留后端历史折扣结果。
1082
- if (isPersistedProduct && hasExistingWalletDiscount) {
1083
+ const isReweighedProduct = reapplyWeighedDiscountProductUids.has(getOriginProductUid(originProduct) || '');
1084
+ if (isPersistedProduct && hasExistingWalletDiscount && !isReweighedProduct) {
1083
1085
  if (flatItem.type === 'main') {
1084
1086
  processedProductsMap.set(product._id, [originProduct]);
1085
1087
  }
@@ -1093,7 +1095,7 @@ class RulesModule extends _BaseModule.BaseModule {
1093
1095
  const applicableDiscounts = sortedDiscountList.filter(discount => {
1094
1096
  if (this.isItemRewardProductDiscount(product)) return false;
1095
1097
  // 🔥 检查策略可用性(针对全额抵扣卡和折扣卡)
1096
- const discountType = discount.tag || discount.type || '';
1098
+ const discountType = discount.tag || discount.type;
1097
1099
  const currentOriginUid = getOriginProductUid(originProduct);
1098
1100
  const isReapplyEditModeDiscountForProduct = discount.isEditMode && reapplyBookingDiscountProductUids.size > 0 && currentOriginUid && reapplyBookingDiscountProductUids.has(String(currentOriginUid)) && discount.isSelected === true;
1099
1101
  if (discount.isEditMode && reapplyBookingDiscountProductUids.size > 0) {
@@ -1101,7 +1103,7 @@ class RulesModule extends _BaseModule.BaseModule {
1101
1103
  if (!uid || !reapplyBookingDiscountProductUids.has(uid)) return false;
1102
1104
  if (!discount.isSelected) return false;
1103
1105
  }
1104
- if (['entitlement_pass', 'good_pass', 'discount_card', 'product_discount_card'].includes(discountType) && !isReapplyEditModeDiscountForProduct) {
1106
+ if (['entitlement_pass', 'good_pass', 'discount_card', 'product_discount_card'].includes(discountType ?? '') && !isReapplyEditModeDiscountForProduct) {
1105
1107
  // 如果策略检查后没有config,说明不可用
1106
1108
  if (discount.config === undefined || !discount?.config?.isAvailable) {
1107
1109
  return false;
@@ -1172,8 +1174,8 @@ class RulesModule extends _BaseModule.BaseModule {
1172
1174
  // 识别用户本次显式选择的卡券,供次卡和折扣卡各自的优先级使用。
1173
1175
  // 目标仍必须在当前商品的 applicableDiscounts 中,因此不会绕过 Holder、时间或商品范围校验。
1174
1176
  const explicitlySelectedDiscount = options?.isSelected === true && options.discountId !== undefined ? applicableDiscounts.find(discount => {
1175
- const discountType = discount.tag || discount.type || '';
1176
- return String(discount.id) === String(options.discountId) && ['entitlement_pass', 'good_pass', 'discount_card', 'product_discount_card'].includes(discountType);
1177
+ const discountType = discount.tag || discount.type;
1178
+ return String(discount.id) === String(options.discountId) && ['entitlement_pass', 'good_pass', 'discount_card', 'product_discount_card'].includes(discountType ?? '');
1177
1179
  }) : undefined;
1178
1180
  // 折扣卡会复用于整条商品行;商品券必须继续按单张拆分,不能复用该分支。
1179
1181
  const explicitlySelectedDiscountCard = explicitlySelectedDiscount && !(0, _entitlementPass.isFullCoveragePass)(explicitlySelectedDiscount) ? explicitlySelectedDiscount : undefined;
@@ -1182,8 +1184,8 @@ class RulesModule extends _BaseModule.BaseModule {
1182
1184
  // applicableDiscounts 中,就应保留该行选择,避免被自动排序中力度更大的卡替换。
1183
1185
  const appliedWalletDiscountIds = new Set((product.discount_list || []).filter(item => ['entitlement_pass', 'discount_card', 'product_discount_card'].includes(item?.tag || item?.type)).map(item => item?.discount?.resource_id ?? item?.resource_id ?? item?.id).filter(id => id !== undefined && id !== null).map(String));
1184
1186
  const appliedDiscountCard = options?.scan ? undefined : applicableDiscounts.find(discount => {
1185
- const discountType = discount.tag || discount.type || '';
1186
- return discount.isSelected === true && ['discount_card', 'product_discount_card'].includes(discountType) && appliedWalletDiscountIds.has(String(discount.id));
1187
+ const discountType = discount.tag || discount.type;
1188
+ return discount.isSelected === true && ['discount_card', 'product_discount_card'].includes(discountType ?? '') && appliedWalletDiscountIds.has(String(discount.id));
1187
1189
  });
1188
1190
  const appliedEntitlementPass = options?.scan || options?.preserveEntitlementSelection === false ? undefined : applicableDiscounts.find(discount => (0, _entitlementPass.isEntitlementPass)(discount) && discount.isSelected === true && appliedWalletDiscountIds.has(String(discount.id)));
1189
1191
 
@@ -35,6 +35,8 @@ export interface RulesCalcDiscountOptions {
35
35
  preserveEntitlementSelection?: boolean;
36
36
  /** Change time 重报价目标行:允许已选编辑态折扣卡重新应用到这些 booking 商品。 */
37
37
  reapplyBookingDiscountProductUids?: string[];
38
+ /** Reweighed order lines only: reapply selected wallet discounts against their new amount. */
39
+ reapplyWeighedDiscountProductUids?: string[];
38
40
  }
39
41
  export interface RulesModuleAPI {
40
42
  setRulesList: (rulesList: Rules[]) => Promise<void>;