@pisell/pisellos 0.0.567 → 0.0.568

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 (228) hide show
  1. package/dist/core/index.js +6 -9
  2. package/dist/model/strategy/adapter/walletPass/evaluator.js +0 -2
  3. package/dist/model/strategy/adapter/walletPass/example.js +3 -26
  4. package/dist/model/strategy/adapter/walletPass/utils.js +0 -3
  5. package/dist/model/strategy/strategy-example.js +5 -19
  6. package/dist/modules/Cart/index.js +0 -3
  7. package/dist/modules/Cart/utils/cartProduct.js +0 -1
  8. package/dist/modules/Date/index.js +1 -5
  9. package/dist/modules/Discount/index.js +1 -4
  10. package/dist/modules/OpenData/index.js +0 -2
  11. package/dist/modules/Order/index.d.ts +1 -1
  12. package/dist/modules/Order/index.js +14 -41
  13. package/dist/modules/Payment/index.js +142 -270
  14. package/dist/modules/Payment/walletpass.js +4 -5
  15. package/dist/modules/Rules/index.js +0 -7
  16. package/dist/modules/Schedule/utils.js +0 -1
  17. package/dist/modules/Step/index.js +0 -1
  18. package/dist/plugins/request.js +1 -4
  19. package/dist/plugins/window.js +2 -6
  20. package/dist/server/index.js +88 -121
  21. package/dist/server/modules/menu/index.js +32 -41
  22. package/dist/server/modules/products/index.js +29 -49
  23. package/dist/server/modules/quotation/index.js +29 -38
  24. package/dist/server/modules/resource/index.js +3 -4
  25. package/dist/server/modules/schedule/index.js +32 -40
  26. package/dist/server/utils/product.js +0 -1
  27. package/dist/solution/BookingByStep/index.d.ts +1 -1
  28. package/dist/solution/BookingByStep/index.js +7 -59
  29. package/dist/solution/BookingByStep/utils/capacity.js +1 -11
  30. package/dist/solution/BookingByStep/utils/resources.js +1 -3
  31. package/dist/solution/BookingByStep/utils/timeslots.js +2 -43
  32. package/dist/solution/BookingTicket/index.d.ts +1 -1
  33. package/dist/solution/BookingTicket/index.js +5 -7
  34. package/dist/solution/BookingTicket/utils/scan/handleScan.js +25 -31
  35. package/dist/solution/BookingTicket/utils/scan/index.js +6 -10
  36. package/dist/solution/BuyTickets/index.js +9 -12
  37. package/dist/solution/Checkout/index.js +177 -246
  38. package/dist/solution/Checkout/utils/index.js +4 -16
  39. package/dist/solution/RegisterAndLogin/index.js +30 -76
  40. package/dist/solution/ScanOrder/index.js +50 -60
  41. package/dist/solution/ShopDiscount/index.js +2 -7
  42. package/dist/solution/VenueBooking/index.d.ts +5 -0
  43. package/dist/solution/VenueBooking/index.js +364 -327
  44. package/dist/utils/task.js +15 -18
  45. package/dist/utils/watch.js +0 -1
  46. package/lib/apis/picoding.js +2 -0
  47. package/lib/core/index.js +130 -131
  48. package/lib/effects/index.js +46 -57
  49. package/lib/index.js +92 -53
  50. package/lib/model/index.js +14 -21
  51. package/lib/model/strategy/adapter/dataVariant/adapter.js +38 -50
  52. package/lib/model/strategy/adapter/dataVariant/evaluator.js +162 -236
  53. package/lib/model/strategy/adapter/dataVariant/examples.js +229 -237
  54. package/lib/model/strategy/adapter/dataVariant/index.js +39 -34
  55. package/lib/model/strategy/adapter/dataVariant/type.js +57 -28
  56. package/lib/model/strategy/adapter/index.js +100 -64
  57. package/lib/model/strategy/adapter/itemRule/adapter.js +156 -144
  58. package/lib/model/strategy/adapter/itemRule/evaluator.js +36 -48
  59. package/lib/model/strategy/adapter/itemRule/examples.js +295 -225
  60. package/lib/model/strategy/adapter/itemRule/index.js +83 -57
  61. package/lib/model/strategy/adapter/itemRule/type.js +97 -36
  62. package/lib/model/strategy/adapter/promotion/adapter.js +84 -63
  63. package/lib/model/strategy/adapter/promotion/evaluator.js +381 -231
  64. package/lib/model/strategy/adapter/promotion/examples.js +139 -159
  65. package/lib/model/strategy/adapter/promotion/index.js +1 -49
  66. package/lib/model/strategy/adapter/promotion/type.js +199 -35
  67. package/lib/model/strategy/adapter/type.js +4 -16
  68. package/lib/model/strategy/adapter/walletPass/evaluator.js +180 -152
  69. package/lib/model/strategy/adapter/walletPass/example.js +217 -190
  70. package/lib/model/strategy/adapter/walletPass/index.js +75 -51
  71. package/lib/model/strategy/adapter/walletPass/locales.js +36 -58
  72. package/lib/model/strategy/adapter/walletPass/type.js +4 -16
  73. package/lib/model/strategy/adapter/walletPass/utils.js +652 -402
  74. package/lib/model/strategy/engine.js +270 -168
  75. package/lib/model/strategy/index.js +49 -34
  76. package/lib/model/strategy/strategy-example.js +243 -239
  77. package/lib/model/strategy/type.js +100 -40
  78. package/lib/modules/Account/index.js +39 -53
  79. package/lib/modules/Account/types.js +20 -33
  80. package/lib/modules/AccountList/index.js +116 -154
  81. package/lib/modules/AccountList/types.js +30 -31
  82. package/lib/modules/AccountList/utils.js +18 -30
  83. package/lib/modules/BaseModule.js +22 -45
  84. package/lib/modules/Cart/index.js +170 -124
  85. package/lib/modules/Cart/types.js +46 -51
  86. package/lib/modules/Cart/utils/cartAccount.js +40 -39
  87. package/lib/modules/Cart/utils/cartDate.js +56 -61
  88. package/lib/modules/Cart/utils/cartDiscount.js +28 -33
  89. package/lib/modules/Cart/utils/cartNote.js +27 -32
  90. package/lib/modules/Cart/utils/cartProduct.js +306 -241
  91. package/lib/modules/Cart/utils/cartRelationForms.js +29 -35
  92. package/lib/modules/Cart/utils/cartResource.js +72 -78
  93. package/lib/modules/Cart/utils/changePrice.js +26 -62
  94. package/lib/modules/Cart/utils/index.js +106 -48
  95. package/lib/modules/Customer/constants.js +13 -34
  96. package/lib/modules/Customer/index.js +216 -153
  97. package/lib/modules/Customer/types.js +38 -35
  98. package/lib/modules/Date/index.js +116 -115
  99. package/lib/modules/Date/types.js +16 -28
  100. package/lib/modules/Date/utils.js +174 -123
  101. package/lib/modules/Discount/index.js +117 -113
  102. package/lib/modules/Discount/types.js +9 -31
  103. package/lib/modules/Guests/index.js +30 -56
  104. package/lib/modules/Guests/types.js +23 -33
  105. package/lib/modules/Holder/index.js +209 -189
  106. package/lib/modules/Holder/types.js +4 -16
  107. package/lib/modules/Holder/utils.js +20 -49
  108. package/lib/modules/OpenData/index.js +63 -74
  109. package/lib/modules/OpenData/types.js +4 -16
  110. package/lib/modules/OpenData/utils.js +44 -78
  111. package/lib/modules/Order/index.d.ts +1 -1
  112. package/lib/modules/Order/index.js +332 -320
  113. package/lib/modules/Order/types.js +24 -33
  114. package/lib/modules/Order/utils.js +350 -284
  115. package/lib/modules/Payment/cash.js +20 -33
  116. package/lib/modules/Payment/cashRecommendationAlgorithm.js +145 -76
  117. package/lib/modules/Payment/eftpos.js +16 -30
  118. package/lib/modules/Payment/index.js +566 -432
  119. package/lib/modules/Payment/mx51.js +1 -0
  120. package/lib/modules/Payment/types.js +216 -107
  121. package/lib/modules/Payment/utils.js +50 -47
  122. package/lib/modules/Payment/walletpass.js +221 -203
  123. package/lib/modules/Product/index.js +51 -46
  124. package/lib/modules/Product/types.js +4 -16
  125. package/lib/modules/Product/utils.js +32 -33
  126. package/lib/modules/ProductList/index.js +111 -110
  127. package/lib/modules/ProductList/types.js +9 -31
  128. package/lib/modules/Quotation/index.js +60 -82
  129. package/lib/modules/Quotation/types.js +4 -16
  130. package/lib/modules/Resource/index.js +27 -59
  131. package/lib/modules/Resource/types.js +22 -32
  132. package/lib/modules/Resource/utils.js +30 -38
  133. package/lib/modules/Rules/index.js +1172 -872
  134. package/lib/modules/Rules/types.js +17 -40
  135. package/lib/modules/SalesSummary/index.js +59 -68
  136. package/lib/modules/SalesSummary/types.js +4 -16
  137. package/lib/modules/SalesSummary/utils.js +176 -193
  138. package/lib/modules/ScanOrderLogger/index.js +59 -79
  139. package/lib/modules/ScanOrderLogger/providers/feishu.js +54 -78
  140. package/lib/modules/ScanOrderLogger/providers/grafana.js +13 -36
  141. package/lib/modules/ScanOrderLogger/types.js +4 -16
  142. package/lib/modules/Schedule/getDateIsInSchedule.js +334 -167
  143. package/lib/modules/Schedule/index.js +97 -107
  144. package/lib/modules/Schedule/type.js +4 -16
  145. package/lib/modules/Schedule/types.js +4 -16
  146. package/lib/modules/Schedule/utils.js +433 -291
  147. package/lib/modules/Step/index.js +40 -52
  148. package/lib/modules/Step/tyeps.js +4 -16
  149. package/lib/modules/Summary/index.js +66 -70
  150. package/lib/modules/Summary/types.js +4 -16
  151. package/lib/modules/Summary/utils.js +782 -429
  152. package/lib/modules/index.js +212 -57
  153. package/lib/plugins/app.js +4 -16
  154. package/lib/plugins/index.js +36 -25
  155. package/lib/plugins/request.js +137 -126
  156. package/lib/plugins/shopStore.js +4 -16
  157. package/lib/plugins/user.js +4 -16
  158. package/lib/plugins/window.js +171 -179
  159. package/lib/server/index.js +882 -623
  160. package/lib/server/modules/index.js +85 -59
  161. package/lib/server/modules/menu/index.js +146 -122
  162. package/lib/server/modules/menu/types.js +18 -31
  163. package/lib/server/modules/order/index.js +195 -231
  164. package/lib/server/modules/order/types.js +107 -32
  165. package/lib/server/modules/order/utils/filterBookings.js +174 -140
  166. package/lib/server/modules/order/utils/filterOrders.js +89 -60
  167. package/lib/server/modules/products/index.js +430 -335
  168. package/lib/server/modules/products/types.js +41 -34
  169. package/lib/server/modules/quotation/index.js +76 -77
  170. package/lib/server/modules/quotation/types.js +21 -31
  171. package/lib/server/modules/resource/index.js +216 -187
  172. package/lib/server/modules/resource/types.js +42 -33
  173. package/lib/server/modules/schedule/index.js +134 -119
  174. package/lib/server/modules/schedule/types.js +14 -21
  175. package/lib/server/modules/schedule/utils.js +334 -163
  176. package/lib/server/types.js +4 -16
  177. package/lib/server/utils/index.js +25 -23
  178. package/lib/server/utils/product.js +196 -139
  179. package/lib/server/utils/schedule.js +34 -41
  180. package/lib/server/utils/time.js +40 -49
  181. package/lib/solution/BookingByStep/index.d.ts +1 -1
  182. package/lib/solution/BookingByStep/index.js +1530 -1343
  183. package/lib/solution/BookingByStep/types.js +40 -99
  184. package/lib/solution/BookingByStep/utils/capacity.js +192 -160
  185. package/lib/solution/BookingByStep/utils/products.js +42 -52
  186. package/lib/solution/BookingByStep/utils/resources.js +527 -330
  187. package/lib/solution/BookingByStep/utils/stock.js +65 -44
  188. package/lib/solution/BookingByStep/utils/timeslots.js +129 -131
  189. package/lib/solution/BookingTicket/index.d.ts +1 -1
  190. package/lib/solution/BookingTicket/index.js +147 -122
  191. package/lib/solution/BookingTicket/types.js +38 -64
  192. package/lib/solution/BookingTicket/utils/scan/cloudSearch.js +64 -80
  193. package/lib/solution/BookingTicket/utils/scan/handleScan.js +82 -80
  194. package/lib/solution/BookingTicket/utils/scan/index.js +90 -94
  195. package/lib/solution/BookingTicket/utils/scan/scanCache.js +90 -43
  196. package/lib/solution/BuyTickets/index.js +67 -70
  197. package/lib/solution/BuyTickets/types.js +22 -38
  198. package/lib/solution/Checkout/index.js +1451 -1158
  199. package/lib/solution/Checkout/types.js +91 -61
  200. package/lib/solution/Checkout/utils/index.js +227 -158
  201. package/lib/solution/PlaceOrder/index.js +55 -0
  202. package/lib/solution/RegisterAndLogin/config.js +493 -460
  203. package/lib/solution/RegisterAndLogin/index.js +633 -630
  204. package/lib/solution/RegisterAndLogin/types.js +189 -76
  205. package/lib/solution/RegisterAndLogin/utils.js +183 -125
  206. package/lib/solution/Sales/index.js +218 -200
  207. package/lib/solution/Sales/types.js +27 -33
  208. package/lib/solution/ScanOrder/index.js +803 -840
  209. package/lib/solution/ScanOrder/types.js +34 -34
  210. package/lib/solution/ScanOrder/utils.js +372 -331
  211. package/lib/solution/ShopDiscount/index.js +230 -236
  212. package/lib/solution/ShopDiscount/types.js +15 -37
  213. package/lib/solution/ShopDiscount/utils.js +309 -172
  214. package/lib/solution/VenueBooking/index.d.ts +5 -0
  215. package/lib/solution/VenueBooking/index.js +886 -929
  216. package/lib/solution/VenueBooking/types.js +20 -40
  217. package/lib/solution/VenueBooking/utils/dateSummary.js +22 -54
  218. package/lib/solution/VenueBooking/utils/resource.js +31 -54
  219. package/lib/solution/VenueBooking/utils/slotMerge.js +88 -106
  220. package/lib/solution/VenueBooking/utils/smartPricing.js +112 -100
  221. package/lib/solution/VenueBooking/utils/timeSlot.js +133 -132
  222. package/lib/solution/VenueBooking/utils.js +130 -67
  223. package/lib/solution/index.js +102 -37
  224. package/lib/store/createStore.js +32 -29
  225. package/lib/types/index.js +4 -16
  226. package/lib/utils/task.js +36 -36
  227. package/lib/utils/watch.js +81 -47
  228. package/package.json +2 -1
@@ -1,120 +1,114 @@
1
- var __create = Object.create;
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __getProtoOf = Object.getPrototypeOf;
6
- var __hasOwnProp = Object.prototype.hasOwnProperty;
7
- var __export = (target, all) => {
8
- for (var name in all)
9
- __defProp(target, name, { get: all[name], enumerable: true });
10
- };
11
- var __copyProps = (to, from, except, desc) => {
12
- if (from && typeof from === "object" || typeof from === "function") {
13
- for (let key of __getOwnPropNames(from))
14
- if (!__hasOwnProp.call(to, key) && key !== except)
15
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
- }
17
- return to;
18
- };
19
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
- // If the importer is in node compatibility mode or this is not an ESM
21
- // file that has been converted to a CommonJS file using a Babel-
22
- // compatible transform (i.e. "__esModule" has not been set), then set
23
- // "default" to the CommonJS "module.exports" for node compatibility.
24
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
- mod
26
- ));
27
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
1
+ "use strict";
28
2
 
29
- // src/solution/ScanOrder/utils.ts
30
- var utils_exports = {};
31
- __export(utils_exports, {
32
- aggregateItemRuleLimit: () => aggregateItemRuleLimit,
33
- attachItemRuleLimitsToTopLevelProducts: () => attachItemRuleLimitsToTopLevelProducts,
34
- buildItemRuleBusinessData: () => buildItemRuleBusinessData,
35
- buildProductKey: () => buildProductKey,
36
- buildProductLineFingerprint: () => buildProductLineFingerprint,
37
- buildQuantityLimitIndex: () => buildQuantityLimitIndex,
38
- collectLinkProductIdsFromReservationRules: () => collectLinkProductIdsFromReservationRules,
39
- computeResourceIsFull: () => computeResourceIsFull,
40
- createEmptySummary: () => createEmptySummary,
41
- extractStrategyModelIdsFromTableConfig: () => extractStrategyModelIdsFromTableConfig,
42
- findReservationRuleProductByResourceId: () => findReservationRuleProductByResourceId,
43
- getProductIdentityIndex: () => getProductIdentityIndex,
44
- getSafeProductNum: () => getSafeProductNum,
45
- getTopLevelProductId: () => getTopLevelProductId,
46
- getTopLevelVariantId: () => getTopLevelVariantId,
47
- hasCustomCapacityProduct: () => hasCustomCapacityProduct,
48
- isIdentityMatch: () => isIdentityMatch,
49
- isSkuOnlyDeleteIdentity: () => isSkuOnlyDeleteIdentity,
50
- normalizeEnabledItemRuleIds: () => normalizeEnabledItemRuleIds,
51
- normalizeItemRuleStrategies: () => normalizeItemRuleStrategies,
52
- normalizeOrderProduct: () => normalizeOrderProduct,
53
- pickFirstCustomCapacityDimensionId: () => pickFirstCustomCapacityDimensionId,
54
- pickFirstCustomCapacityPaxBounds: () => pickFirstCustomCapacityPaxBounds,
55
- pickFirstDurationMinutesFromProducts: () => pickFirstDurationMinutesFromProducts,
56
- resolveSkuMatchedQuantityLimits: () => resolveSkuMatchedQuantityLimits,
57
- toBoolean: () => toBoolean,
58
- toNonNegativeInt: () => toNonNegativeInt,
59
- toNonNegativeNumber: () => toNonNegativeNumber,
60
- toPositiveString: () => toPositiveString,
61
- toPriceString: () => toPriceString
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
62
5
  });
63
- module.exports = __toCommonJS(utils_exports);
64
- var import_dayjs = __toESM(require("dayjs"));
65
- var import_decimal = __toESM(require("decimal.js"));
66
- var import_utils = require("../../modules/Order/utils");
6
+ exports.aggregateItemRuleLimit = aggregateItemRuleLimit;
7
+ exports.attachItemRuleLimitsToTopLevelProducts = attachItemRuleLimitsToTopLevelProducts;
8
+ exports.buildItemRuleBusinessData = buildItemRuleBusinessData;
9
+ exports.buildProductKey = buildProductKey;
10
+ exports.buildProductLineFingerprint = buildProductLineFingerprint;
11
+ exports.buildQuantityLimitIndex = buildQuantityLimitIndex;
12
+ exports.collectLinkProductIdsFromReservationRules = collectLinkProductIdsFromReservationRules;
13
+ exports.computeResourceIsFull = computeResourceIsFull;
14
+ exports.createEmptySummary = createEmptySummary;
15
+ exports.extractStrategyModelIdsFromTableConfig = extractStrategyModelIdsFromTableConfig;
16
+ exports.findReservationRuleProductByResourceId = findReservationRuleProductByResourceId;
17
+ exports.getProductIdentityIndex = getProductIdentityIndex;
18
+ exports.getSafeProductNum = getSafeProductNum;
19
+ exports.getTopLevelProductId = getTopLevelProductId;
20
+ exports.getTopLevelVariantId = getTopLevelVariantId;
21
+ exports.hasCustomCapacityProduct = hasCustomCapacityProduct;
22
+ exports.isIdentityMatch = isIdentityMatch;
23
+ exports.isSkuOnlyDeleteIdentity = isSkuOnlyDeleteIdentity;
24
+ exports.normalizeEnabledItemRuleIds = normalizeEnabledItemRuleIds;
25
+ exports.normalizeItemRuleStrategies = normalizeItemRuleStrategies;
26
+ exports.normalizeOrderProduct = normalizeOrderProduct;
27
+ exports.pickFirstCustomCapacityDimensionId = pickFirstCustomCapacityDimensionId;
28
+ exports.pickFirstCustomCapacityPaxBounds = pickFirstCustomCapacityPaxBounds;
29
+ exports.pickFirstDurationMinutesFromProducts = pickFirstDurationMinutesFromProducts;
30
+ exports.resolveSkuMatchedQuantityLimits = resolveSkuMatchedQuantityLimits;
31
+ exports.toBoolean = toBoolean;
32
+ exports.toNonNegativeInt = toNonNegativeInt;
33
+ exports.toNonNegativeNumber = toNonNegativeNumber;
34
+ exports.toPositiveString = toPositiveString;
35
+ exports.toPriceString = toPriceString;
36
+ var _dayjs = _interopRequireDefault(require("dayjs"));
37
+ var _decimal = _interopRequireDefault(require("decimal.js"));
38
+ var _utils = require("../../modules/Order/utils");
39
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
40
+ /**
41
+ * 构建金额全为 0 的空 summary。
42
+ * 作为尚未计算金额时的兜底默认值,避免下游因 undefined 报错。
43
+ */
67
44
  function createEmptySummary() {
68
45
  return {
69
46
  product_quantity: 0,
70
- product_original_amount: "0.00",
71
- product_amount: "0.00",
72
- product_expect_amount: "0.00",
73
- product_tax_fee: "0.00",
74
- shipping_fee: "0.00",
75
- shipping_tax_fee: "0.00",
76
- tax_fee: "0.00",
77
- surcharge_fee: "0.00",
78
- discount_amount: "0.00",
79
- deposit_amount: "0.00",
80
- expect_amount: "0.00",
81
- total_amount: "0.00",
82
- amount_gap: "0.00",
83
- rounding_amount: "0.00",
84
- pay_service_charge_amount: "0.00"
47
+ product_original_amount: '0.00',
48
+ product_amount: '0.00',
49
+ product_expect_amount: '0.00',
50
+ product_tax_fee: '0.00',
51
+ shipping_fee: '0.00',
52
+ shipping_tax_fee: '0.00',
53
+ tax_fee: '0.00',
54
+ surcharge_fee: '0.00',
55
+ discount_amount: '0.00',
56
+ deposit_amount: '0.00',
57
+ expect_amount: '0.00',
58
+ total_amount: '0.00',
59
+ amount_gap: '0.00',
60
+ rounding_amount: '0.00',
61
+ pay_service_charge_amount: '0.00'
85
62
  };
86
63
  }
64
+
65
+ /**
66
+ * 商品数量安全取值。
67
+ * - NaN / undefined / < 1 均视作 1(最低起订量)
68
+ * - 有值时取整(不支持小数数量)
69
+ */
87
70
  function getSafeProductNum(num) {
88
- if (!num || Number.isNaN(num))
89
- return 1;
90
- if (num < 1)
91
- return 1;
71
+ if (!num || Number.isNaN(num)) return 1;
72
+ if (num < 1) return 1;
92
73
  return Math.floor(num);
93
74
  }
75
+
76
+ /**
77
+ * 宽松布尔值转换。
78
+ * 兼容后端/配置中心常见的 boolean / number / string 表达,统一归一成 true/false。
79
+ */
94
80
  function toBoolean(value) {
95
- if (typeof value === "boolean")
96
- return value;
97
- if (typeof value === "number")
98
- return value !== 0;
99
- if (typeof value === "string") {
81
+ if (typeof value === 'boolean') return value;
82
+ if (typeof value === 'number') return value !== 0;
83
+ if (typeof value === 'string') {
100
84
  const normalized = value.trim().toLowerCase();
101
- return ["1", "true", "yes", "y", "on"].includes(normalized);
85
+ return ['1', 'true', 'yes', 'y', 'on'].includes(normalized);
102
86
  }
103
87
  return false;
104
88
  }
89
+
90
+ /**
91
+ * 将值归一化为有效的正向字符串。
92
+ * null / undefined / 空串 / '0' / 'null' 统一视为无效值并返回 undefined。
93
+ */
105
94
  function toPositiveString(value) {
106
- if (value === null || value === void 0 || value === "")
107
- return void 0;
95
+ if (value === null || value === undefined || value === '') return undefined;
108
96
  const normalized = String(value).trim();
109
- if (!normalized || normalized === "0" || normalized.toLowerCase() === "null") {
110
- return void 0;
97
+ if (!normalized || normalized === '0' || normalized.toLowerCase() === 'null') {
98
+ return undefined;
111
99
  }
112
100
  return normalized;
113
101
  }
102
+
103
+ /**
104
+ * 归一化 item rule 策略模型 ID 列表。
105
+ * - 支持 string / number 混合输入
106
+ * - 仅保留 > 0 的合法数字
107
+ * - 自动去重
108
+ */
114
109
  function normalizeEnabledItemRuleIds(rawIds) {
115
- if (!Array.isArray(rawIds))
116
- return [];
117
- const uniqueIds = /* @__PURE__ */ new Set();
110
+ if (!Array.isArray(rawIds)) return [];
111
+ const uniqueIds = new Set();
118
112
  for (const item of rawIds) {
119
113
  const strategyId = Number(item);
120
114
  if (Number.isFinite(strategyId) && strategyId > 0) {
@@ -123,27 +117,27 @@ function normalizeEnabledItemRuleIds(rawIds) {
123
117
  }
124
118
  return Array.from(uniqueIds);
125
119
  }
120
+
121
+ /**
122
+ * 从 tableConfig 中提取合法策略模型 ID 列表。
123
+ */
126
124
  function extractStrategyModelIdsFromTableConfig(tableConfig) {
127
- const rawIds = Array.isArray(tableConfig == null ? void 0 : tableConfig.strategy_model_ids) ? tableConfig.strategy_model_ids : [];
128
- return rawIds.map((id) => Number(id)).filter((id) => Number.isFinite(id) && id > 0);
125
+ const rawIds = Array.isArray(tableConfig?.strategy_model_ids) ? tableConfig.strategy_model_ids : [];
126
+ return rawIds.map(id => Number(id)).filter(id => Number.isFinite(id) && id > 0);
129
127
  }
128
+
129
+ /**
130
+ * 仅保留 item_rule 类型策略。
131
+ */
130
132
  function normalizeItemRuleStrategies(configs) {
131
- if (!Array.isArray(configs))
132
- return [];
133
- return configs.filter(
134
- (config) => {
135
- var _a;
136
- return Boolean(
137
- config && typeof config === "object" && ((_a = config == null ? void 0 : config.metadata) == null ? void 0 : _a.type) === "item_rule"
138
- );
139
- }
140
- );
133
+ if (!Array.isArray(configs)) return [];
134
+ return configs.filter(config => Boolean(config && typeof config === 'object' && config?.metadata?.type === 'item_rule'));
141
135
  }
142
136
  function toNonNegativeNumber(value) {
143
- if (typeof value === "number") {
137
+ if (typeof value === 'number') {
144
138
  return Number.isFinite(value) && value >= 0 ? value : 0;
145
139
  }
146
- if (typeof value === "string") {
140
+ if (typeof value === 'string') {
147
141
  const parsed = Number(value);
148
142
  return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
149
143
  }
@@ -152,10 +146,9 @@ function toNonNegativeNumber(value) {
152
146
  function toNonNegativeInt(value) {
153
147
  return Math.floor(toNonNegativeNumber(value));
154
148
  }
155
- function toPriceString(value, fallback = "0.00") {
149
+ function toPriceString(value, fallback = '0.00') {
156
150
  const parsedValue = Number(value);
157
- if (Number.isNaN(parsedValue))
158
- return fallback;
151
+ if (Number.isNaN(parsedValue)) return fallback;
159
152
  return parsedValue.toFixed(2);
160
153
  }
161
154
  function buildProductKey(productId, productVariantId) {
@@ -164,31 +157,29 @@ function buildProductKey(productId, productVariantId) {
164
157
  function getTopLevelProductId(product) {
165
158
  const rawProductId = product.product_id ?? product.id;
166
159
  const productId = Number(rawProductId);
167
- if (!Number.isFinite(productId) || productId <= 0)
168
- return null;
160
+ if (!Number.isFinite(productId) || productId <= 0) return null;
169
161
  return productId;
170
162
  }
171
163
  function getTopLevelVariantId(product) {
172
164
  const rawVariantId = product.product_variant_id ?? product.variant_id;
173
- if (rawVariantId === void 0 || rawVariantId === null || rawVariantId === "") {
165
+ if (rawVariantId === undefined || rawVariantId === null || rawVariantId === '') {
174
166
  return null;
175
167
  }
176
168
  const variantId = Number(rawVariantId);
177
- if (!Number.isFinite(variantId) || variantId <= 0)
178
- return null;
169
+ if (!Number.isFinite(variantId) || variantId <= 0) return null;
179
170
  return variantId;
180
171
  }
181
172
  function buildQuantityLimitIndex(limits) {
182
- const exactLimitMap = /* @__PURE__ */ new Map();
183
- const productLimitMap = /* @__PURE__ */ new Map();
173
+ const exactLimitMap = new Map();
174
+ const productLimitMap = new Map();
184
175
  for (const limit of limits) {
185
176
  const targets = limit.targets || [];
186
- if (targets.length !== 1)
187
- continue;
177
+ // 多目标规则(OR 组)只做购物车聚合校验,不下发到单品维度,
178
+ // 避免 UI 把「组内至少 1 份」误当作每款商品各自的 min,锁死减号按钮。
179
+ if (targets.length !== 1) continue;
188
180
  const target = targets[0];
189
181
  const productId = Number(target.product_id);
190
- if (!Number.isFinite(productId) || productId <= 0)
191
- continue;
182
+ if (!Number.isFinite(productId) || productId <= 0) continue;
192
183
  const variantId = Number(target.product_variant_id);
193
184
  const hasVariant = Number.isFinite(variantId) && variantId > 0;
194
185
  if (hasVariant) {
@@ -206,44 +197,53 @@ function buildQuantityLimitIndex(limits) {
206
197
  };
207
198
  }
208
199
  function resolveSkuMatchedQuantityLimits(params) {
209
- const { productId, variantId, limitIndex } = params;
200
+ const {
201
+ productId,
202
+ variantId,
203
+ limitIndex
204
+ } = params;
210
205
  const exactMatched = variantId && variantId > 0 ? limitIndex.exactLimitMap.get(buildProductKey(productId, variantId)) || [] : [];
211
206
  const productMatched = limitIndex.productLimitMap.get(productId) || [];
212
207
  return [...exactMatched, ...productMatched];
213
208
  }
214
209
  function aggregateItemRuleLimit(matchedLimits) {
215
- if (!matchedLimits.length)
216
- return null;
210
+ if (!matchedLimits.length) return null;
217
211
  let min;
218
212
  let max;
219
213
  let remainingMax;
220
214
  let exact;
221
215
  let mustInclude = false;
216
+
217
+ // 聚合 validationMessage 按 min / max 两个方向分别输出:
218
+ // - maxValidationMessage:触发"当前 max / remainingMax / exact"的那条规则的文案
219
+ // - minValidationMessage:触发"当前 min / exact"的那条规则的文案
220
+ // 各自再回退到其他带文案的规则(最后回退到第一条非空 validationMessage)。
221
+ // 前端可根据"加号/减号被拦"的方向取对应文案。
222
222
  let maxSourceLimit;
223
223
  let remainingMaxSourceLimit;
224
224
  let exactSourceLimit;
225
225
  let minSourceLimit;
226
226
  let firstMessagedLimit;
227
227
  for (const limit of matchedLimits) {
228
- if (typeof limit.requiredMin === "number" && Number.isFinite(limit.requiredMin)) {
229
- if (min === void 0 || limit.requiredMin > min) {
228
+ if (typeof limit.requiredMin === 'number' && Number.isFinite(limit.requiredMin)) {
229
+ if (min === undefined || limit.requiredMin > min) {
230
230
  min = limit.requiredMin;
231
231
  minSourceLimit = limit;
232
232
  }
233
233
  }
234
- if (typeof limit.requiredMax === "number" && Number.isFinite(limit.requiredMax)) {
235
- if (max === void 0 || limit.requiredMax < max) {
234
+ if (typeof limit.requiredMax === 'number' && Number.isFinite(limit.requiredMax)) {
235
+ if (max === undefined || limit.requiredMax < max) {
236
236
  max = limit.requiredMax;
237
237
  maxSourceLimit = limit;
238
238
  }
239
239
  }
240
- if (typeof limit.remainingMax === "number" && Number.isFinite(limit.remainingMax)) {
241
- if (remainingMax === void 0 || limit.remainingMax < remainingMax) {
240
+ if (typeof limit.remainingMax === 'number' && Number.isFinite(limit.remainingMax)) {
241
+ if (remainingMax === undefined || limit.remainingMax < remainingMax) {
242
242
  remainingMax = limit.remainingMax;
243
243
  remainingMaxSourceLimit = limit;
244
244
  }
245
245
  }
246
- if (typeof limit.requiredExact === "number" && Number.isFinite(limit.requiredExact)) {
246
+ if (typeof limit.requiredExact === 'number' && Number.isFinite(limit.requiredExact)) {
247
247
  exact = limit.requiredExact;
248
248
  exactSourceLimit = limit;
249
249
  }
@@ -252,16 +252,19 @@ function aggregateItemRuleLimit(matchedLimits) {
252
252
  firstMessagedLimit = limit;
253
253
  }
254
254
  }
255
- if (typeof exact === "number" && Number.isFinite(exact)) {
255
+ if (typeof exact === 'number' && Number.isFinite(exact)) {
256
256
  min = exact;
257
257
  max = exact;
258
258
  }
259
259
  const maxMessageSource = maxSourceLimit || remainingMaxSourceLimit || exactSourceLimit || firstMessagedLimit;
260
260
  const minMessageSource = minSourceLimit || exactSourceLimit || firstMessagedLimit;
261
- const maxValidationMessage = maxMessageSource == null ? void 0 : maxMessageSource.validationMessage;
262
- const rawMaxValidationMessage = maxMessageSource == null ? void 0 : maxMessageSource.rawValidationMessage;
263
- const minValidationMessage = minMessageSource == null ? void 0 : minMessageSource.validationMessage;
264
- const rawMinValidationMessage = minMessageSource == null ? void 0 : minMessageSource.rawValidationMessage;
261
+ const maxValidationMessage = maxMessageSource?.validationMessage;
262
+ const rawMaxValidationMessage = maxMessageSource?.rawValidationMessage;
263
+ const minValidationMessage = minMessageSource?.validationMessage;
264
+ const rawMinValidationMessage = minMessageSource?.rawValidationMessage;
265
+
266
+ // validationMessage / rawValidationMessage 保留为"最严格限制"的聚合文案,
267
+ // 便于旧消费者(只关心 max 侧)不破坏。优先 max → min。
265
268
  const validationMessage = maxValidationMessage ?? minValidationMessage;
266
269
  const rawValidationMessage = rawMaxValidationMessage ?? rawMinValidationMessage;
267
270
  return {
@@ -279,28 +282,34 @@ function aggregateItemRuleLimit(matchedLimits) {
279
282
  };
280
283
  }
281
284
  function buildItemRuleBusinessData(params) {
282
- const { tempOrder, runtimeConfig, itemRuleConfigs } = params;
285
+ const {
286
+ tempOrder,
287
+ runtimeConfig,
288
+ itemRuleConfigs
289
+ } = params;
283
290
  const rawPax = runtimeConfig.pax || {};
284
291
  const adultPax = toNonNegativeInt(rawPax.adult);
285
292
  const childPax = toNonNegativeInt(rawPax.child);
286
293
  const totalPax = toNonNegativeInt(rawPax.total) || adultPax + childPax || 1;
287
294
  const cartItems = tempOrder.products.reduce((result, product) => {
288
- if (typeof product.product_id !== "number")
289
- return result;
295
+ if (typeof product.product_id !== 'number') return result;
290
296
  result.push({
291
297
  product_id: product.product_id,
292
298
  quantity: toNonNegativeInt(product.num),
293
- ...typeof product.product_variant_id === "number" ? { product_variant_id: product.product_variant_id } : {}
299
+ ...(typeof product.product_variant_id === 'number' ? {
300
+ product_variant_id: product.product_variant_id
301
+ } : {})
294
302
  });
295
303
  return result;
296
304
  }, []);
297
305
  const historicalItems = Array.isArray(runtimeConfig.historicalItems) ? runtimeConfig.historicalItems.reduce((result, item) => {
298
- if (typeof (item == null ? void 0 : item.product_id) !== "number")
299
- return result;
306
+ if (typeof item?.product_id !== 'number') return result;
300
307
  result.push({
301
308
  product_id: item.product_id,
302
309
  quantity: toNonNegativeInt(item.quantity),
303
- ...typeof item.product_variant_id === "number" ? { product_variant_id: item.product_variant_id } : {}
310
+ ...(typeof item.product_variant_id === 'number' ? {
311
+ product_variant_id: item.product_variant_id
312
+ } : {})
304
313
  });
305
314
  return result;
306
315
  }, []) : [];
@@ -312,23 +321,20 @@ function buildItemRuleBusinessData(params) {
312
321
  },
313
322
  cartItems,
314
323
  historicalItems,
315
- serviceType: runtimeConfig.serviceType || "dine-in",
316
- submissionIndex: typeof runtimeConfig.submissionIndex === "number" ? toNonNegativeInt(runtimeConfig.submissionIndex) : tempOrder.order_id ? 1 : 0,
324
+ serviceType: runtimeConfig.serviceType || 'dine-in',
325
+ submissionIndex: typeof runtimeConfig.submissionIndex === 'number' ? toNonNegativeInt(runtimeConfig.submissionIndex) : tempOrder.order_id ? 1 : 0,
317
326
  custom: runtimeConfig.custom || {},
318
327
  strategyConfigs: itemRuleConfigs
319
328
  };
320
329
  }
321
330
  function attachItemRuleLimitsToTopLevelProducts(productList, limits) {
322
- if (!Array.isArray(productList))
323
- return productList;
331
+ if (!Array.isArray(productList)) return productList;
324
332
  const limitIndex = buildQuantityLimitIndex(limits || []);
325
333
  for (const item of productList) {
326
- if (!item || typeof item !== "object")
327
- continue;
334
+ if (!item || typeof item !== 'object') continue;
328
335
  const productItem = item;
329
336
  const productId = getTopLevelProductId(productItem);
330
- if (productId === null)
331
- continue;
337
+ if (productId === null) continue;
332
338
  const variantId = getTopLevelVariantId(productItem);
333
339
  const matchedLimits = limits.length ? resolveSkuMatchedQuantityLimits({
334
340
  productId,
@@ -338,137 +344,201 @@ function attachItemRuleLimitsToTopLevelProducts(productList, limits) {
338
344
  const aggregatedLimit = aggregateItemRuleLimit(matchedLimits);
339
345
  if (aggregatedLimit) {
340
346
  productItem.item_rule_limit = aggregatedLimit;
341
- } else if ("item_rule_limit" in productItem) {
347
+ } else if ('item_rule_limit' in productItem) {
342
348
  delete productItem.item_rule_limit;
343
349
  }
344
350
  }
345
351
  return productList;
346
352
  }
353
+
354
+ /**
355
+ * 将选项行 / 套餐行归一成稳定 JSON,用于「同 SKU 是否合并」判断。
356
+ * 数组元素先按主键排序再序列化,避免顺序差异导致误判为不同行。
357
+ */
347
358
  function buildProductLineFingerprint(productOptionItem, productBundle) {
348
359
  const optArr = Array.isArray(productOptionItem) ? productOptionItem : [];
349
- const normalizedOpts = optArr.map((item) => ({
350
- product_option_item_id: Number(item == null ? void 0 : item.product_option_item_id) || 0,
351
- option_group_id: Number(item == null ? void 0 : item.option_group_id) || 0,
352
- num: typeof (item == null ? void 0 : item.num) === "number" && !Number.isNaN(item.num) ? Math.max(1, Math.floor(item.num)) : 1,
353
- price: String((item == null ? void 0 : item.price) ?? "")
360
+ const normalizedOpts = optArr.map(item => ({
361
+ product_option_item_id: Number(item?.product_option_item_id) || 0,
362
+ option_group_id: Number(item?.option_group_id) || 0,
363
+ num: typeof item?.num === 'number' && !Number.isNaN(item.num) ? Math.max(1, Math.floor(item.num)) : 1,
364
+ price: String(item?.price ?? '')
354
365
  })).sort((p, q) => {
355
366
  if (p.product_option_item_id !== q.product_option_item_id) {
356
367
  return p.product_option_item_id - q.product_option_item_id;
357
368
  }
358
- if (p.option_group_id !== q.option_group_id)
359
- return p.option_group_id - q.option_group_id;
360
- if (p.num !== q.num)
361
- return p.num - q.num;
369
+ if (p.option_group_id !== q.option_group_id) return p.option_group_id - q.option_group_id;
370
+ if (p.num !== q.num) return p.num - q.num;
362
371
  return p.price.localeCompare(q.price);
363
372
  });
364
373
  const bundleArr = Array.isArray(productBundle) ? productBundle : [];
365
- const normalizedBundles = bundleArr.map((item) => ({
366
- bundle_id: Number((item == null ? void 0 : item.bundle_id) ?? (item == null ? void 0 : item.id) ?? 0) || 0,
367
- product_id: Number(item == null ? void 0 : item.product_id) || 0,
368
- num: typeof (item == null ? void 0 : item.num) === "number" && !Number.isNaN(item.num) ? Math.max(1, Math.floor(item.num)) : 1
374
+ const normalizedBundles = bundleArr.map(item => ({
375
+ bundle_id: Number(item?.bundle_id ?? item?.id ?? 0) || 0,
376
+ product_id: Number(item?.product_id) || 0,
377
+ num: typeof item?.num === 'number' && !Number.isNaN(item.num) ? Math.max(1, Math.floor(item.num)) : 1
369
378
  })).sort((p, q) => {
370
- if (p.bundle_id !== q.bundle_id)
371
- return p.bundle_id - q.bundle_id;
372
- if (p.product_id !== q.product_id)
373
- return p.product_id - q.product_id;
379
+ if (p.bundle_id !== q.bundle_id) return p.bundle_id - q.bundle_id;
380
+ if (p.product_id !== q.product_id) return p.product_id - q.product_id;
374
381
  return p.num - q.num;
375
382
  });
376
383
  return JSON.stringify([normalizedOpts, normalizedBundles]);
377
384
  }
385
+
386
+ /**
387
+ * removeProductFromOrder 仅传 SKU 时的通配 identity(对象上未声明选项键)。
388
+ * 与显式 `product_option_item: []` 区分:后者只匹配「无选项」行。
389
+ */
378
390
  function isSkuOnlyDeleteIdentity(x) {
379
- if (x.identity_key)
380
- return false;
381
- return !("product_option_item" in x) && !("product_bundle" in x);
391
+ if (x.identity_key) return false;
392
+ return !('product_option_item' in x) && !('product_bundle' in x);
382
393
  }
383
394
  function fingerprintForIdentityWithOptionKeys(x) {
384
395
  const row = x;
385
- const opts = "product_option_item" in row ? Array.isArray(row.product_option_item) ? row.product_option_item : [] : [];
386
- const buds = "product_bundle" in row ? Array.isArray(row.product_bundle) ? row.product_bundle : [] : [];
396
+ const opts = 'product_option_item' in row ? Array.isArray(row.product_option_item) ? row.product_option_item : [] : [];
397
+ const buds = 'product_bundle' in row ? Array.isArray(row.product_bundle) ? row.product_bundle : [] : [];
387
398
  return buildProductLineFingerprint(opts, buds);
388
399
  }
400
+
401
+ /**
402
+ * 判断两个商品 identity 是否匹配(不透明 identity 契约)。
403
+ *
404
+ * 调用约定:始终 `isIdentityMatch(line, callerIdentity)`。
405
+ * - 若 SKU(product_id + product_variant_id)不一致 → 不匹配。
406
+ * - **双方都带** `identity_key` → 严格字符串相等比较(不再猜测合成 key / metadata 桥接)。
407
+ * 这是 opaque identity 契约的标准路径:UI 删改时透传 SDK 回灌的 identity_key。
408
+ * - 否则(即至少一侧未声明 identity_key)→ 进入「SKU 通配 / 显式空选项 / 指纹」回退路径,
409
+ * 忽略线侧 identity_key,按内容语义匹配。这给「行已 opaque、调用方却想按 SKU 通配 / 选项指纹删除」留兼容入口。
410
+ */
389
411
  function isIdentityMatch(a, b) {
390
- if (a.product_id !== b.product_id || a.product_variant_id !== b.product_variant_id)
391
- return false;
392
- const aHasKey = typeof a.identity_key === "string" && a.identity_key.length > 0;
393
- const bHasKey = typeof b.identity_key === "string" && b.identity_key.length > 0;
394
- if (aHasKey && bHasKey)
395
- return a.identity_key === b.identity_key;
396
- if (isSkuOnlyDeleteIdentity(a) || isSkuOnlyDeleteIdentity(b))
397
- return true;
412
+ if (a.product_id !== b.product_id || a.product_variant_id !== b.product_variant_id) return false;
413
+ const aHasKey = typeof a.identity_key === 'string' && a.identity_key.length > 0;
414
+ const bHasKey = typeof b.identity_key === 'string' && b.identity_key.length > 0;
415
+ if (aHasKey && bHasKey) return a.identity_key === b.identity_key;
416
+ if (isSkuOnlyDeleteIdentity(a) || isSkuOnlyDeleteIdentity(b)) return true;
398
417
  return fingerprintForIdentityWithOptionKeys(a) === fingerprintForIdentityWithOptionKeys(b);
399
418
  }
419
+
420
+ /**
421
+ * 根据 identity 在商品列表中定位下标。
422
+ * 用于判断商品是否已存在(已存在则累加数量,否则新增)。
423
+ */
400
424
  function getProductIdentityIndex(products, identity) {
401
- return products.findIndex((item) => isIdentityMatch(item, identity));
425
+ return products.findIndex(item => isIdentityMatch(item, identity));
402
426
  }
427
+
428
+ /**
429
+ * 对外部传入的商品对象做归一化(v2 composite 语义):
430
+ * - 补全可选字段默认值(未传则使用兜底值,避免后续计算时因 undefined 导致异常)
431
+ * - 对 num 调用 getSafeProductNum 做安全处理
432
+ * - 保留 _origin 供后续业务流程(如促销规则)使用
433
+ *
434
+ * 价格字段语义(metadata 权威源 + composite 派生):
435
+ * - `metadata.source_product_price`:主商品/variant 基础价(已应用报价单),**不含 option**、
436
+ * **不含折扣**。是推导 main_product_* 的起点。variant 分支优先读 `metadata.origin.variant[vid].price`。
437
+ * - `metadata.main_product_original_price`:`source + Σ(option.price × option.num)`,**含 option**、
438
+ * **不含折扣**。
439
+ * - `metadata.main_product_selling_price`:`main_product_original_price - 主商品券 per-unit amount`,
440
+ * **含 option**、**含折扣**。
441
+ * - 行级 `selling_price` = `main_product_selling_price + Σ(bundle_selling_price × num)`。
442
+ * - 行级 `original_price` = `main_product_original_price + Σ(bundle 原价 × num)`。
443
+ *
444
+ * 迁移与幂等:
445
+ * - `metadata.price_schema_version === 2` → 已新语义归一化,保留 main_product_* 原值(保留折扣)。
446
+ * - 其它情况(v1 / 缺字段 / 无 metadata)→ 按"main_product_selling_price 曾是 main-only"的旧约定
447
+ * 反推 legacyDiscount,再以新 source + options 基准重算。最终统一打上 `price_schema_version: 2`。
448
+ * - 因此多次 normalize 不会重复叠加 option/bundle。
449
+ */
403
450
  function normalizeOrderProduct(product) {
404
- var _a, _b;
405
- const metadata = { ...product.metadata || {} };
406
- const resolvedIdentityKey = product.identity_key && String(product.identity_key).length > 0 ? String(product.identity_key) : (0, import_utils.createUuidV4)();
451
+ const metadata = {
452
+ ...(product.metadata || {})
453
+ };
454
+ // 不透明 identity 契约:每条订单行必须带 identity_key。
455
+ // 调用方未传时由 SDK 自动生成 UUID,后续 update/remove 只做严格比对,避免猜测合成 key。
456
+ const resolvedIdentityKey = product.identity_key && String(product.identity_key).length > 0 ? String(product.identity_key) : (0, _utils.createUuidV4)();
407
457
  if (!metadata.unique_identification_number) {
408
458
  metadata.unique_identification_number = resolvedIdentityKey;
409
459
  }
410
- const normalizedBundle = (product.product_bundle || []).map((item) => ({
460
+ const normalizedBundle = (product.product_bundle || []).map(item => ({
411
461
  ...item,
412
- bundle_selling_price: item.bundle_selling_price ?? item.price ?? "0.00"
462
+ bundle_selling_price: item.bundle_selling_price ?? item.price ?? '0.00'
413
463
  // custom_price: item.custom_price ?? item.bundle_selling_price ?? item.price ?? '0.00',
414
464
  }));
415
465
  const normalizedOptions = product.product_option_item || [];
416
- const optionSum = (0, import_utils.sumOptionUnitPrice)(normalizedOptions);
417
- const isV2 = metadata.price_schema_version === 2 || metadata.price_schema_version === "2";
466
+ const optionSum = (0, _utils.sumOptionUnitPrice)(normalizedOptions);
467
+
468
+ // 1) 解析 source_product_price。
469
+ // 优先级:
470
+ // 1) metadata.source_product_price(v2 权威)
471
+ // 2) variantPrice(命中 variant_id,从 metadata.origin.variant[vid].price 读)
472
+ // 3) _origin.price / _origin.base_price(后端语义的基础价)
473
+ // 4) v1 兼容:无 v2 标记但存在 metadata.main_product_original_price(v1 main-only,即旧 source)
474
+ // 5) 入参 original_price(v1 addProduct 约定:original_price 为 pre-discount 基础价)
475
+ // 6) 入参 selling_price(最末兜底,新添加路径 caller 只给 selling_price)
476
+ const isV2 = metadata.price_schema_version === 2 || metadata.price_schema_version === '2';
418
477
  const variantId = Number(product.product_variant_id || 0);
419
- const variantList = variantId ? (_a = metadata == null ? void 0 : metadata.origin) == null ? void 0 : _a.variant : null;
420
- const variantPrice = Array.isArray(variantList) ? (_b = variantList.find((v) => Number(v == null ? void 0 : v.id) === variantId)) == null ? void 0 : _b.price : void 0;
478
+ const variantList = variantId ? metadata?.origin?.variant : null;
479
+ const variantPrice = Array.isArray(variantList) ? variantList.find(v => Number(v?.id) === variantId)?.price : undefined;
421
480
  const resolvedSource = (() => {
422
- var _a2, _b2;
423
- if (metadata.source_product_price !== void 0) {
481
+ if (metadata.source_product_price !== undefined) {
424
482
  return String(metadata.source_product_price);
425
483
  }
426
- if (variantPrice !== void 0 && variantPrice !== null) {
484
+ if (variantPrice !== undefined && variantPrice !== null) {
427
485
  return String(variantPrice);
428
486
  }
429
- const originPrice = (_a2 = product._origin) == null ? void 0 : _a2.price;
430
- if (originPrice !== void 0 && originPrice !== null) {
487
+ const originPrice = product._origin?.price;
488
+ if (originPrice !== undefined && originPrice !== null) {
431
489
  return String(originPrice);
432
490
  }
433
- const originBasePrice = (_b2 = product._origin) == null ? void 0 : _b2.base_price;
434
- if (originBasePrice !== void 0 && originBasePrice !== null) {
491
+ const originBasePrice = product._origin?.base_price;
492
+ if (originBasePrice !== undefined && originBasePrice !== null) {
435
493
  return String(originBasePrice);
436
494
  }
437
- if (!isV2 && metadata.main_product_original_price !== void 0) {
495
+ if (!isV2 && metadata.main_product_original_price !== undefined) {
438
496
  return String(metadata.main_product_original_price);
439
497
  }
440
- return product.original_price ?? product.selling_price ?? "0.00";
498
+ return product.original_price ?? product.selling_price ?? '0.00';
441
499
  })();
442
- const mainOriginalDec = new import_decimal.default(Number(resolvedSource) || 0).plus(optionSum);
500
+
501
+ // 2) 派生 main_product_original_price(含 option、不含折扣)
502
+ const mainOriginalDec = new _decimal.default(Number(resolvedSource) || 0).plus(optionSum);
443
503
  const mainOriginalStr = mainOriginalDec.toDecimalPlaces(2).toFixed(2);
504
+
505
+ // 3) 派生 main_product_selling_price(含 option、含主商品折扣)
506
+ // - v2 数据:直接沿用 metadata.main_product_selling_price(保留折扣)
507
+ // - v1 metadata:main_product_selling_price 旧语义是 main-only(≈ source-level 折后价),
508
+ // 反推 legacyDiscount = main_original(旧) - main_selling(旧),再用新 main_original - legacyDiscount
509
+ // - v1 addProduct 入参:top-level selling_price < original_price 表示主商品折扣;
510
+ // 用差额作为 legacyDiscount 映射到新 main_selling
511
+ // - 其它:视为无折扣,main_selling = main_original
444
512
  let mainSellingDec;
445
513
  if (isV2 && metadata.main_product_selling_price != null) {
446
- mainSellingDec = new import_decimal.default(Number(metadata.main_product_selling_price) || 0);
514
+ mainSellingDec = new _decimal.default(Number(metadata.main_product_selling_price) || 0);
447
515
  } else if (metadata.main_product_selling_price != null && metadata.main_product_original_price != null) {
448
- const legacyOriginal = new import_decimal.default(Number(metadata.main_product_original_price) || 0);
449
- const legacySelling = new import_decimal.default(Number(metadata.main_product_selling_price) || 0);
516
+ const legacyOriginal = new _decimal.default(Number(metadata.main_product_original_price) || 0);
517
+ const legacySelling = new _decimal.default(Number(metadata.main_product_selling_price) || 0);
450
518
  const legacyDiscount = legacyOriginal.minus(legacySelling);
451
519
  mainSellingDec = mainOriginalDec.minus(legacyDiscount);
452
- } else if (product.original_price != null && product.selling_price != null && new import_decimal.default(Number(product.original_price) || 0).greaterThan(
453
- new import_decimal.default(Number(product.selling_price) || 0)
454
- )) {
455
- const topOriginal = new import_decimal.default(Number(product.original_price) || 0);
456
- const topSelling = new import_decimal.default(Number(product.selling_price) || 0);
520
+ } else if (product.original_price != null && product.selling_price != null && new _decimal.default(Number(product.original_price) || 0).greaterThan(new _decimal.default(Number(product.selling_price) || 0))) {
521
+ const topOriginal = new _decimal.default(Number(product.original_price) || 0);
522
+ const topSelling = new _decimal.default(Number(product.selling_price) || 0);
457
523
  const legacyDiscount = topOriginal.minus(topSelling);
458
524
  mainSellingDec = mainOriginalDec.minus(legacyDiscount);
459
525
  } else {
460
526
  mainSellingDec = mainOriginalDec;
461
527
  }
462
528
  const mainSellingStr = mainSellingDec.toDecimalPlaces(2).toFixed(2);
529
+
530
+ // 4) 落盘 metadata:三字段 + schema 版本 sentinel
463
531
  metadata.source_product_price = resolvedSource;
464
532
  metadata.main_product_original_price = mainOriginalStr;
465
533
  metadata.main_product_selling_price = mainSellingStr;
466
534
  metadata.price_schema_version = 2;
467
- const composedSellingPrice = (0, import_utils.composeLinePrice)({
535
+
536
+ // 5) 合成行级 composite(main 已含 option,本步只叠 bundle)
537
+ const composedSellingPrice = (0, _utils.composeLinePrice)({
468
538
  mainPrice: mainSellingStr,
469
539
  bundle: normalizedBundle
470
540
  });
471
- const composedOriginalPrice = (0, import_utils.composeLinePrice)({
541
+ const composedOriginalPrice = (0, _utils.composeLinePrice)({
472
542
  mainPrice: mainOriginalStr,
473
543
  bundle: normalizedBundle,
474
544
  useOriginalBundle: true
@@ -482,177 +552,148 @@ function normalizeOrderProduct(product) {
482
552
  product_option_item: normalizedOptions,
483
553
  selling_price: composedSellingPrice,
484
554
  original_price: composedOriginalPrice,
485
- tax_fee: product.tax_fee || "0.00",
555
+ tax_fee: product.tax_fee || '0.00',
486
556
  is_charge_tax: product.is_charge_tax ?? 0,
487
557
  discount_list: product.discount_list || [],
488
558
  product_bundle: normalizedBundle,
489
559
  metadata,
490
- note: product.note != null ? String(product.note) : "",
560
+ note: product.note != null ? String(product.note) : '',
491
561
  _origin: product._origin
492
562
  };
493
563
  }
564
+
565
+ /**
566
+ * 从 reservation.enabled_reservation_rules 中收集 type=link 的商品 id。
567
+ */
494
568
  function collectLinkProductIdsFromReservationRules(rules) {
495
- if (!Array.isArray(rules))
496
- return [];
569
+ if (!Array.isArray(rules)) return [];
497
570
  const ids = [];
498
571
  for (const entry of rules) {
499
- if (!entry || typeof entry !== "object")
500
- continue;
572
+ if (!entry || typeof entry !== 'object') continue;
501
573
  const rec = entry;
502
- if (rec.type !== "link")
503
- continue;
504
- if (!Array.isArray(rec.value))
505
- continue;
574
+ if (rec.type !== 'link') continue;
575
+ if (!Array.isArray(rec.value)) continue;
506
576
  for (const v of rec.value) {
507
577
  const n = Number(v);
508
- if (Number.isFinite(n) && n > 0)
509
- ids.push(Math.floor(n));
578
+ if (Number.isFinite(n) && n > 0) ids.push(Math.floor(n));
510
579
  }
511
580
  }
512
581
  return [...new Set(ids)];
513
582
  }
583
+
584
+ /**
585
+ * 找到第一个归属指定桌台的预约规则商品。
586
+ * 归属判断基于商品资源中的 default_resource / optional_resource。
587
+ */
514
588
  function findReservationRuleProductByResourceId(products, resourceId) {
515
- var _a;
516
589
  const numericResourceId = Number(resourceId);
517
- if (!Number.isFinite(numericResourceId) || numericResourceId <= 0)
518
- return void 0;
590
+ if (!Number.isFinite(numericResourceId) || numericResourceId <= 0) return undefined;
519
591
  for (const product of products) {
520
- const resources = (_a = product == null ? void 0 : product.product_resource) == null ? void 0 : _a.resources;
521
- if (!Array.isArray(resources))
522
- continue;
523
- const hasMatchedResource = resources.some((resource) => {
524
- const defaultResources = Array.isArray(resource == null ? void 0 : resource.default_resource) ? resource.default_resource : [];
525
- const optionalResources = Array.isArray(resource == null ? void 0 : resource.optional_resource) ? resource.optional_resource : [];
526
- return [...defaultResources, ...optionalResources].some(
527
- (id) => Number(id) === numericResourceId
528
- );
592
+ const resources = product?.product_resource?.resources;
593
+ if (!Array.isArray(resources)) continue;
594
+ const hasMatchedResource = resources.some(resource => {
595
+ const defaultResources = Array.isArray(resource?.default_resource) ? resource.default_resource : [];
596
+ const optionalResources = Array.isArray(resource?.optional_resource) ? resource.optional_resource : [];
597
+ return [...defaultResources, ...optionalResources].some(id => Number(id) === numericResourceId);
529
598
  });
530
- if (hasMatchedResource)
531
- return product;
599
+ if (hasMatchedResource) return product;
532
600
  }
533
- return void 0;
601
+ return undefined;
534
602
  }
603
+
604
+ /**
605
+ * 返回列表中第一个带有效 duration.value(分钟)的商品时长。
606
+ */
535
607
  function pickFirstDurationMinutesFromProducts(products) {
536
- var _a;
537
608
  for (const product of products) {
538
- const value = (_a = product == null ? void 0 : product.duration) == null ? void 0 : _a.value;
609
+ const value = product?.duration?.value;
539
610
  const n = Number(value);
540
- if (Number.isFinite(n) && n > 0)
541
- return Math.floor(n);
611
+ if (Number.isFinite(n) && n > 0) return Math.floor(n);
542
612
  }
543
- return void 0;
613
+ return undefined;
544
614
  }
615
+
616
+ /** 是否存在 capacity.type === 'custom' 的商品 */
545
617
  function hasCustomCapacityProduct(products) {
546
- return products.some((p) => {
547
- var _a;
548
- return ((_a = p == null ? void 0 : p.capacity) == null ? void 0 : _a.type) === "custom";
549
- });
618
+ return products.some(p => p?.capacity?.type === 'custom');
550
619
  }
620
+
621
+ /**
622
+ * 根据预约规则商品的 resource.type 计算桌台是否已被"占满"。
623
+ * - single:只要有 `lastOrderId` 即视为占用
624
+ * - multiple:当前时间落在 `capacity_list[i]` 的 `[start_at, end_at]`(inclusive)内的 pax 之和 >= 总容量
625
+ * - 其他('capacity' / undefined):返回 false,不施加限制
626
+ */
551
627
  function computeResourceIsFull(params) {
552
- const { resourceSelectType, lastOrderId, capacityList, capacity } = params;
553
- if (resourceSelectType === "single")
554
- return Boolean(lastOrderId);
555
- if (resourceSelectType !== "multiple")
556
- return false;
628
+ const {
629
+ resourceSelectType,
630
+ lastOrderId,
631
+ capacityList,
632
+ capacity
633
+ } = params;
634
+ if (resourceSelectType === 'single') return Boolean(lastOrderId);
635
+ if (resourceSelectType !== 'multiple') return false;
557
636
  const totalCapacity = Number(capacity);
558
- if (!Number.isFinite(totalCapacity) || totalCapacity <= 0)
559
- return false;
560
- const now = (0, import_dayjs.default)();
637
+ if (!Number.isFinite(totalCapacity) || totalCapacity <= 0) return false;
638
+ const now = (0, _dayjs.default)();
561
639
  let occupied = 0;
562
640
  for (const slot of capacityList || []) {
563
- const start = (0, import_dayjs.default)(slot == null ? void 0 : slot.start_at);
564
- const end = (0, import_dayjs.default)(slot == null ? void 0 : slot.end_at);
565
- if (!start.isValid() || !end.isValid())
566
- continue;
641
+ const start = (0, _dayjs.default)(slot?.start_at);
642
+ const end = (0, _dayjs.default)(slot?.end_at);
643
+ if (!start.isValid() || !end.isValid()) continue;
567
644
  if ((now.isAfter(start) || now.isSame(start)) && (now.isBefore(end) || now.isSame(end))) {
568
- const pax = Number(slot == null ? void 0 : slot.pax);
569
- if (Number.isFinite(pax) && pax > 0)
570
- occupied += pax;
645
+ const pax = Number(slot?.pax);
646
+ if (Number.isFinite(pax) && pax > 0) occupied += pax;
571
647
  }
572
648
  }
573
649
  return occupied >= totalCapacity;
574
650
  }
651
+
652
+ /**
653
+ * 在商品列表中找到第一个 `capacity.type === 'custom'` 的商品,取其 `custom` 数组第一项的 min/max。
654
+ * 仅返回有限数字字段;若均无法解析则返回 `undefined`。
655
+ */
575
656
  function pickFirstCustomCapacityPaxBounds(products) {
576
657
  for (const p of products) {
577
- const cap = p == null ? void 0 : p.capacity;
578
- if (!cap || cap.type !== "custom")
579
- continue;
580
- if (!Array.isArray(cap.custom) || cap.custom.length === 0)
581
- continue;
658
+ const cap = p?.capacity;
659
+ if (!cap || cap.type !== 'custom') continue;
660
+ if (!Array.isArray(cap.custom) || cap.custom.length === 0) continue;
582
661
  const row = cap.custom[0];
583
- if (!row || typeof row !== "object")
584
- continue;
662
+ if (!row || typeof row !== 'object') continue;
585
663
  const raw = row;
586
664
  const out = {};
587
- if (raw.min !== null && raw.min !== void 0 && raw.min !== "") {
665
+ if (raw.min !== null && raw.min !== undefined && raw.min !== '') {
588
666
  const min = Number(raw.min);
589
- if (Number.isFinite(min))
590
- out.min = min;
667
+ if (Number.isFinite(min)) out.min = min;
591
668
  }
592
- if (raw.max !== null && raw.max !== void 0 && raw.max !== "") {
669
+ if (raw.max !== null && raw.max !== undefined && raw.max !== '') {
593
670
  const max = Number(raw.max);
594
- if (Number.isFinite(max))
595
- out.max = max;
671
+ if (Number.isFinite(max)) out.max = max;
596
672
  }
597
- if (out.min !== void 0 || out.max !== void 0)
598
- return out;
673
+ if (out.min !== undefined || out.max !== undefined) return out;
599
674
  }
600
- return void 0;
675
+ return undefined;
601
676
  }
677
+
678
+ /**
679
+ * 第一个 `capacity.type === 'custom'` 的商品,取其 `custom[0].id`(提交 booking metadata.capacity 维度 id)。
680
+ * 无匹配时返回 `undefined`,调用方应回退为 `0`。
681
+ */
602
682
  function pickFirstCustomCapacityDimensionId(products) {
603
683
  for (const p of products) {
604
- const cap = p == null ? void 0 : p.capacity;
605
- if (!cap || cap.type !== "custom")
606
- continue;
607
- if (!Array.isArray(cap.custom) || cap.custom.length === 0)
608
- continue;
684
+ const cap = p?.capacity;
685
+ if (!cap || cap.type !== 'custom') continue;
686
+ if (!Array.isArray(cap.custom) || cap.custom.length === 0) continue;
609
687
  const row = cap.custom[0];
610
- if (!row || typeof row !== "object")
611
- continue;
688
+ if (!row || typeof row !== 'object') continue;
612
689
  const id = row.id;
613
- if (id === null || id === void 0 || id === "")
614
- continue;
615
- if (typeof id === "number" && Number.isFinite(id))
616
- return id;
617
- if (typeof id === "string") {
690
+ if (id === null || id === undefined || id === '') continue;
691
+ if (typeof id === 'number' && Number.isFinite(id)) return id;
692
+ if (typeof id === 'string') {
618
693
  const trimmed = id.trim();
619
- if (!trimmed)
620
- continue;
694
+ if (!trimmed) continue;
621
695
  return trimmed;
622
696
  }
623
697
  }
624
- return void 0;
625
- }
626
- // Annotate the CommonJS export names for ESM import in node:
627
- 0 && (module.exports = {
628
- aggregateItemRuleLimit,
629
- attachItemRuleLimitsToTopLevelProducts,
630
- buildItemRuleBusinessData,
631
- buildProductKey,
632
- buildProductLineFingerprint,
633
- buildQuantityLimitIndex,
634
- collectLinkProductIdsFromReservationRules,
635
- computeResourceIsFull,
636
- createEmptySummary,
637
- extractStrategyModelIdsFromTableConfig,
638
- findReservationRuleProductByResourceId,
639
- getProductIdentityIndex,
640
- getSafeProductNum,
641
- getTopLevelProductId,
642
- getTopLevelVariantId,
643
- hasCustomCapacityProduct,
644
- isIdentityMatch,
645
- isSkuOnlyDeleteIdentity,
646
- normalizeEnabledItemRuleIds,
647
- normalizeItemRuleStrategies,
648
- normalizeOrderProduct,
649
- pickFirstCustomCapacityDimensionId,
650
- pickFirstCustomCapacityPaxBounds,
651
- pickFirstDurationMinutesFromProducts,
652
- resolveSkuMatchedQuantityLimits,
653
- toBoolean,
654
- toNonNegativeInt,
655
- toNonNegativeNumber,
656
- toPositiveString,
657
- toPriceString
658
- });
698
+ return undefined;
699
+ }