@pisell/pisellos 2.3.73 → 2.3.75

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 (296) hide show
  1. package/dist/core/index.d.ts +7 -0
  2. package/dist/core/index.js +111 -71
  3. package/dist/model/strategy/adapter/promotion/index.js +9 -0
  4. package/dist/model/strategy/adapter/walletPass/example.js +3 -26
  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 +2 -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 +36 -64
  13. package/dist/modules/Payment/index.js +97 -162
  14. package/dist/modules/Quotation/index.d.ts +6 -0
  15. package/dist/modules/Quotation/index.js +75 -19
  16. package/dist/modules/Rules/index.js +1 -4
  17. package/dist/modules/Schedule/index.js +0 -1
  18. package/dist/modules/Schedule/utils.js +0 -1
  19. package/dist/modules/Step/index.js +0 -1
  20. package/dist/plugins/request.d.ts +1 -0
  21. package/dist/plugins/request.js +1 -4
  22. package/dist/plugins/window.js +2 -6
  23. package/dist/server/index.d.ts +8 -2
  24. package/dist/server/index.js +303 -269
  25. package/dist/server/modules/floor-plan/index.d.ts +4 -0
  26. package/dist/server/modules/floor-plan/index.js +240 -98
  27. package/dist/server/modules/menu/index.js +75 -59
  28. package/dist/server/modules/order/index.d.ts +17 -7
  29. package/dist/server/modules/order/index.js +421 -211
  30. package/dist/server/modules/products/index.d.ts +8 -2
  31. package/dist/server/modules/products/index.js +191 -117
  32. package/dist/server/modules/quotation/index.js +29 -38
  33. package/dist/server/modules/resource/index.js +24 -17
  34. package/dist/server/modules/schedule/index.js +27 -35
  35. package/dist/server/utils/product.js +0 -1
  36. package/dist/solution/BaseSales/index.js +38 -41
  37. package/dist/solution/BookingByStep/index.d.ts +1 -1
  38. package/dist/solution/BookingByStep/index.js +7 -59
  39. package/dist/solution/BookingByStep/utils/capacity.js +1 -11
  40. package/dist/solution/BookingByStep/utils/resources.js +1 -3
  41. package/dist/solution/BookingByStep/utils/timeslots.js +2 -43
  42. package/dist/solution/BookingTicket/index.js +0 -2
  43. package/dist/solution/BookingTicket/utils/scan/cloudSearch.js +6 -7
  44. package/dist/solution/BookingTicket/utils/scan/handleScan.js +25 -31
  45. package/dist/solution/BookingTicket/utils/scan/index.js +6 -10
  46. package/dist/solution/BuyTickets/index.js +9 -12
  47. package/dist/solution/Checkout/index.js +177 -252
  48. package/dist/solution/Checkout/utils/index.js +4 -16
  49. package/dist/solution/RegisterAndLogin/index.js +30 -76
  50. package/dist/solution/ScanOrder/index.js +50 -60
  51. package/dist/solution/ShopDiscount/index.js +2 -7
  52. package/dist/solution/VenueBooking/index.js +45 -55
  53. package/dist/utils/task.js +15 -18
  54. package/dist/utils/watch.js +0 -1
  55. package/lib/apis/picoding.js +2 -0
  56. package/lib/core/index.d.ts +7 -0
  57. package/lib/core/index.js +149 -131
  58. package/lib/effects/index.js +46 -57
  59. package/lib/index.js +82 -49
  60. package/lib/model/index.js +14 -21
  61. package/lib/model/strategy/adapter/dataVariant/adapter.js +38 -50
  62. package/lib/model/strategy/adapter/dataVariant/evaluator.js +162 -236
  63. package/lib/model/strategy/adapter/dataVariant/examples.js +229 -237
  64. package/lib/model/strategy/adapter/dataVariant/index.js +39 -34
  65. package/lib/model/strategy/adapter/dataVariant/type.js +57 -28
  66. package/lib/model/strategy/adapter/index.js +100 -64
  67. package/lib/model/strategy/adapter/itemRule/adapter.js +156 -144
  68. package/lib/model/strategy/adapter/itemRule/evaluator.js +36 -48
  69. package/lib/model/strategy/adapter/itemRule/examples.js +295 -225
  70. package/lib/model/strategy/adapter/itemRule/index.js +83 -57
  71. package/lib/model/strategy/adapter/itemRule/type.js +97 -36
  72. package/lib/model/strategy/adapter/promotion/adapter.js +91 -69
  73. package/lib/model/strategy/adapter/promotion/evaluator.js +444 -310
  74. package/lib/model/strategy/adapter/promotion/examples.js +222 -234
  75. package/lib/model/strategy/adapter/promotion/index.js +1 -0
  76. package/lib/model/strategy/adapter/promotion/type.js +243 -36
  77. package/lib/model/strategy/adapter/type.js +4 -16
  78. package/lib/model/strategy/adapter/walletPass/evaluator.js +180 -150
  79. package/lib/model/strategy/adapter/walletPass/example.js +217 -190
  80. package/lib/model/strategy/adapter/walletPass/index.js +75 -51
  81. package/lib/model/strategy/adapter/walletPass/locales.js +36 -58
  82. package/lib/model/strategy/adapter/walletPass/type.js +4 -16
  83. package/lib/model/strategy/adapter/walletPass/utils.js +588 -378
  84. package/lib/model/strategy/engine.js +270 -168
  85. package/lib/model/strategy/index.js +49 -34
  86. package/lib/model/strategy/strategy-example.js +243 -239
  87. package/lib/model/strategy/type.js +100 -40
  88. package/lib/modules/Account/index.js +39 -53
  89. package/lib/modules/Account/types.js +20 -33
  90. package/lib/modules/AccountList/index.js +116 -154
  91. package/lib/modules/AccountList/types.js +30 -31
  92. package/lib/modules/AccountList/utils.js +18 -30
  93. package/lib/modules/BaseModule.js +23 -42
  94. package/lib/modules/BookingContext/index.js +158 -136
  95. package/lib/modules/BookingContext/types.js +46 -32
  96. package/lib/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +138 -146
  97. package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +78 -83
  98. package/lib/modules/BookingContext/utils/cacheItemToBookingInput.js +170 -150
  99. package/lib/modules/BookingContext/utils/flexible.js +38 -55
  100. package/lib/modules/BookingContext/utils/formatResourceList.js +19 -34
  101. package/lib/modules/BookingContext/utils/index.js +124 -41
  102. package/lib/modules/BookingContext/utils/noResource.js +58 -70
  103. package/lib/modules/BookingContext/utils/orderLineDisplay.js +14 -41
  104. package/lib/modules/BookingContext/utils/productExtend.js +202 -155
  105. package/lib/modules/BookingContext/utils/resourceErrors.js +53 -53
  106. package/lib/modules/BookingContext/utils/resourceSelection.js +19 -41
  107. package/lib/modules/BookingContext/utils/resources.js +209 -167
  108. package/lib/modules/BookingContext/utils/serviceTimes.js +115 -116
  109. package/lib/modules/BookingContext/utils/timeSlices.js +76 -86
  110. package/lib/modules/Cart/index.js +170 -124
  111. package/lib/modules/Cart/types.js +46 -51
  112. package/lib/modules/Cart/utils/cartAccount.js +40 -39
  113. package/lib/modules/Cart/utils/cartDate.js +56 -61
  114. package/lib/modules/Cart/utils/cartDiscount.js +28 -33
  115. package/lib/modules/Cart/utils/cartNote.js +27 -32
  116. package/lib/modules/Cart/utils/cartProduct.js +321 -251
  117. package/lib/modules/Cart/utils/cartRelationForms.js +29 -35
  118. package/lib/modules/Cart/utils/cartResource.js +70 -78
  119. package/lib/modules/Cart/utils/changePrice.js +22 -50
  120. package/lib/modules/Cart/utils/index.js +106 -48
  121. package/lib/modules/Customer/constants.js +13 -34
  122. package/lib/modules/Customer/index.js +235 -172
  123. package/lib/modules/Customer/types.js +38 -35
  124. package/lib/modules/Date/index.js +116 -115
  125. package/lib/modules/Date/types.js +16 -28
  126. package/lib/modules/Date/utils.js +174 -123
  127. package/lib/modules/Discount/index.js +122 -127
  128. package/lib/modules/Discount/types.js +9 -31
  129. package/lib/modules/Guests/index.js +30 -56
  130. package/lib/modules/Guests/types.js +23 -33
  131. package/lib/modules/Holder/index.js +209 -189
  132. package/lib/modules/Holder/types.js +4 -16
  133. package/lib/modules/Holder/utils.js +20 -49
  134. package/lib/modules/OpenData/index.js +70 -76
  135. package/lib/modules/OpenData/types.js +4 -16
  136. package/lib/modules/OpenData/utils.js +44 -78
  137. package/lib/modules/Order/index.d.ts +1 -1
  138. package/lib/modules/Order/index.js +1713 -2118
  139. package/lib/modules/Order/payment-utils.js +77 -120
  140. package/lib/modules/Order/types.js +88 -35
  141. package/lib/modules/Order/utils/bundleDiscountHydration.js +41 -74
  142. package/lib/modules/Order/utils/discountProductLineIdentity.js +142 -100
  143. package/lib/modules/Order/utils/manualProductDiscount.js +124 -123
  144. package/lib/modules/Order/utils/orderCollectionIdentity.js +184 -244
  145. package/lib/modules/Order/utils.js +814 -647
  146. package/lib/modules/Payment/cash.js +20 -33
  147. package/lib/modules/Payment/cashRecommendationAlgorithm.js +145 -76
  148. package/lib/modules/Payment/eftpos.js +16 -30
  149. package/lib/modules/Payment/index.js +532 -399
  150. package/lib/modules/Payment/mx51.js +1 -0
  151. package/lib/modules/Payment/types.js +230 -108
  152. package/lib/modules/Payment/utils.js +52 -51
  153. package/lib/modules/Payment/walletpass.js +485 -660
  154. package/lib/modules/Product/index.js +51 -46
  155. package/lib/modules/Product/types.js +4 -16
  156. package/lib/modules/Product/utils.js +32 -33
  157. package/lib/modules/ProductList/index.js +113 -123
  158. package/lib/modules/ProductList/types.js +9 -31
  159. package/lib/modules/Quotation/index.d.ts +6 -0
  160. package/lib/modules/Quotation/index.js +110 -103
  161. package/lib/modules/Quotation/types.js +4 -16
  162. package/lib/modules/Resource/index.js +27 -59
  163. package/lib/modules/Resource/types.js +22 -32
  164. package/lib/modules/Resource/utils.js +30 -38
  165. package/lib/modules/ResourcePlanner/index.js +25 -23
  166. package/lib/modules/ResourcePlanner/localClock.js +54 -119
  167. package/lib/modules/ResourcePlanner/planner.js +589 -770
  168. package/lib/modules/ResourcePlanner/types.js +10 -29
  169. package/lib/modules/Rules/index.js +1218 -959
  170. package/lib/modules/Rules/types.js +17 -40
  171. package/lib/modules/SalesSummary/index.js +85 -90
  172. package/lib/modules/SalesSummary/types.js +4 -16
  173. package/lib/modules/SalesSummary/utils.js +355 -430
  174. package/lib/modules/ScanOrderLogger/index.js +59 -79
  175. package/lib/modules/ScanOrderLogger/providers/feishu.js +54 -78
  176. package/lib/modules/ScanOrderLogger/providers/grafana.js +13 -36
  177. package/lib/modules/ScanOrderLogger/types.js +4 -16
  178. package/lib/modules/Schedule/getDateIsInSchedule.js +334 -163
  179. package/lib/modules/Schedule/index.js +100 -114
  180. package/lib/modules/Schedule/type.js +4 -16
  181. package/lib/modules/Schedule/types.js +4 -16
  182. package/lib/modules/Schedule/utils.js +433 -291
  183. package/lib/modules/Step/index.js +40 -52
  184. package/lib/modules/Step/tyeps.js +4 -16
  185. package/lib/modules/Summary/index.js +66 -71
  186. package/lib/modules/Summary/types.js +4 -16
  187. package/lib/modules/Summary/utils.js +773 -418
  188. package/lib/modules/SurchargeList/index.js +46 -60
  189. package/lib/modules/SurchargeList/types.js +4 -16
  190. package/lib/modules/index.js +245 -63
  191. package/lib/plugins/app.js +4 -16
  192. package/lib/plugins/index.js +36 -25
  193. package/lib/plugins/request.d.ts +1 -0
  194. package/lib/plugins/request.js +137 -126
  195. package/lib/plugins/shopStore.js +4 -16
  196. package/lib/plugins/user.js +4 -16
  197. package/lib/plugins/window.js +171 -179
  198. package/lib/server/index.d.ts +8 -2
  199. package/lib/server/index.js +3083 -2663
  200. package/lib/server/modules/floor-plan/index.d.ts +4 -0
  201. package/lib/server/modules/floor-plan/index.js +159 -127
  202. package/lib/server/modules/floor-plan/types.js +15 -30
  203. package/lib/server/modules/index.js +106 -68
  204. package/lib/server/modules/menu/index.js +170 -124
  205. package/lib/server/modules/menu/types.js +18 -31
  206. package/lib/server/modules/order/index.d.ts +17 -7
  207. package/lib/server/modules/order/index.js +916 -977
  208. package/lib/server/modules/order/types.js +122 -33
  209. package/lib/server/modules/order/utils/filterBookings.js +219 -194
  210. package/lib/server/modules/order/utils/filterOrders.js +118 -92
  211. package/lib/server/modules/payment/index.js +59 -83
  212. package/lib/server/modules/payment/types.js +4 -16
  213. package/lib/server/modules/products/index.d.ts +8 -2
  214. package/lib/server/modules/products/index.js +656 -471
  215. package/lib/server/modules/products/types.js +44 -34
  216. package/lib/server/modules/quotation/index.js +137 -172
  217. package/lib/server/modules/quotation/types.js +21 -31
  218. package/lib/server/modules/resource/index.js +225 -186
  219. package/lib/server/modules/resource/types.js +42 -33
  220. package/lib/server/modules/schedule/index.js +135 -123
  221. package/lib/server/modules/schedule/types.js +14 -21
  222. package/lib/server/modules/schedule/utils.js +334 -163
  223. package/lib/server/types.js +4 -16
  224. package/lib/server/utils/index.js +25 -23
  225. package/lib/server/utils/product.js +196 -139
  226. package/lib/server/utils/schedule.js +34 -41
  227. package/lib/server/utils/small-ticket.js +479 -456
  228. package/lib/server/utils/time.js +40 -49
  229. package/lib/solution/BaseSales/index.js +1186 -1410
  230. package/lib/solution/BaseSales/types.js +42 -38
  231. package/lib/solution/BaseSales/utils/cartPromotion.js +642 -498
  232. package/lib/solution/BaseSales/utils/parseSalesResponse.js +85 -105
  233. package/lib/solution/BaseSales/utils/quotationPrice.js +56 -114
  234. package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +108 -145
  235. package/lib/solution/BaseSales/utils.js +158 -143
  236. package/lib/solution/BookingByStep/index.d.ts +1 -1
  237. package/lib/solution/BookingByStep/index.js +1527 -1340
  238. package/lib/solution/BookingByStep/types.js +40 -99
  239. package/lib/solution/BookingByStep/utils/capacity.js +192 -160
  240. package/lib/solution/BookingByStep/utils/products.js +42 -52
  241. package/lib/solution/BookingByStep/utils/resources.js +527 -330
  242. package/lib/solution/BookingByStep/utils/stock.js +65 -44
  243. package/lib/solution/BookingByStep/utils/timeslots.js +129 -131
  244. package/lib/solution/BookingTicket/index.js +607 -522
  245. package/lib/solution/BookingTicket/types.js +99 -77
  246. package/lib/solution/BookingTicket/utils/addProductDecision.js +226 -189
  247. package/lib/solution/BookingTicket/utils/addTimeAvailability.js +58 -89
  248. package/lib/solution/BookingTicket/utils/bookingStatus.js +107 -78
  249. package/lib/solution/BookingTicket/utils/cartView.js +114 -99
  250. package/lib/solution/BookingTicket/utils/exampleData.js +185 -0
  251. package/lib/solution/BookingTicket/utils/orderCustomer.js +18 -41
  252. package/lib/solution/BookingTicket/utils/resolveBestAddTimePlan.js +219 -86
  253. package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.js +168 -116
  254. package/lib/solution/BookingTicket/utils/scan/cloudSearch.js +79 -91
  255. package/lib/solution/BookingTicket/utils/scan/formatGlobalScan.js +54 -41
  256. package/lib/solution/BookingTicket/utils/scan/handleScan.js +84 -81
  257. package/lib/solution/BookingTicket/utils/scan/index.js +98 -104
  258. package/lib/solution/BookingTicket/utils/scan/scanCache.js +90 -43
  259. package/lib/solution/BookingTicket/utils/scan/scanProductValues.js +52 -64
  260. package/lib/solution/BookingTicket/utils/sessionCatalogStale.js +56 -58
  261. package/lib/solution/BookingTicket/utils/weighingProductSku.js +34 -48
  262. package/lib/solution/BuyTickets/index.js +67 -70
  263. package/lib/solution/BuyTickets/types.js +22 -38
  264. package/lib/solution/Checkout/index.js +1451 -1158
  265. package/lib/solution/Checkout/types.js +91 -61
  266. package/lib/solution/Checkout/utils/index.js +228 -156
  267. package/lib/solution/PlaceOrder/index.js +55 -0
  268. package/lib/solution/RegisterAndLogin/config.js +493 -460
  269. package/lib/solution/RegisterAndLogin/index.js +633 -630
  270. package/lib/solution/RegisterAndLogin/types.js +189 -76
  271. package/lib/solution/RegisterAndLogin/utils.js +183 -125
  272. package/lib/solution/Sales/index.js +328 -324
  273. package/lib/solution/Sales/types.js +27 -33
  274. package/lib/solution/ScanOrder/index.js +832 -864
  275. package/lib/solution/ScanOrder/types.js +33 -34
  276. package/lib/solution/ScanOrder/utils.js +409 -374
  277. package/lib/solution/ShopDiscount/index.js +226 -232
  278. package/lib/solution/ShopDiscount/types.js +15 -37
  279. package/lib/solution/ShopDiscount/utils.js +300 -172
  280. package/lib/solution/UnifiedBookingSales/index.js +1035 -1201
  281. package/lib/solution/UnifiedBookingSales/remoteOccupancyCache.js +99 -159
  282. package/lib/solution/UnifiedBookingSales/types.js +10 -32
  283. package/lib/solution/VenueBooking/index.js +811 -879
  284. package/lib/solution/VenueBooking/types.js +19 -39
  285. package/lib/solution/VenueBooking/utils/dateSummary.js +27 -56
  286. package/lib/solution/VenueBooking/utils/resource.js +31 -54
  287. package/lib/solution/VenueBooking/utils/slotMerge.js +94 -107
  288. package/lib/solution/VenueBooking/utils/timeSlot.js +132 -132
  289. package/lib/solution/VenueBooking/utils.js +130 -67
  290. package/lib/solution/index.js +124 -41
  291. package/lib/store/createStore.js +32 -29
  292. package/lib/types/index.js +4 -16
  293. package/lib/utils/payment-number.js +26 -46
  294. package/lib/utils/task.js +36 -36
  295. package/lib/utils/watch.js +81 -47
  296. package/package.json +2 -1
@@ -1,122 +1,128 @@
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);
28
-
29
- // src/model/strategy/adapter/promotion/evaluator.ts
30
- var evaluator_exports = {};
31
- __export(evaluator_exports, {
32
- PromotionEvaluator: () => PromotionEvaluator
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
33
5
  });
34
- module.exports = __toCommonJS(evaluator_exports);
35
- var import_decimal = __toESM(require("decimal.js"));
36
- var import_engine = require("../../engine");
37
- var import_adapter = require("./adapter");
38
- var import_type = require("./type");
39
- var defaultLocales = {
6
+ exports.PromotionEvaluator = void 0;
7
+ var _decimal = _interopRequireDefault(require("decimal.js"));
8
+ var _engine = require("../../engine");
9
+ var _adapter = require("./adapter");
10
+ var _type = require("./type");
11
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
12
+ // ============================================
13
+ // 类型定义
14
+ // ============================================
15
+
16
+ // ============================================
17
+ // 多语言定义
18
+ // ============================================
19
+
20
+ const defaultLocales = {
40
21
  en: {
41
- no_applicable_promotion: "No applicable promotion",
42
- promotion_not_in_time_range: "Promotion not in valid time range",
43
- product_not_in_promotion: "Product not in promotion scope"
22
+ no_applicable_promotion: 'No applicable promotion',
23
+ promotion_not_in_time_range: 'Promotion not in valid time range',
24
+ product_not_in_promotion: 'Product not in promotion scope'
44
25
  },
45
- "zh-CN": {
46
- no_applicable_promotion: "无适用的促销活动",
47
- promotion_not_in_time_range: "促销活动不在有效时间范围内",
48
- product_not_in_promotion: "商品不在促销范围内"
26
+ 'zh-CN': {
27
+ no_applicable_promotion: '无适用的促销活动',
28
+ promotion_not_in_time_range: '促销活动不在有效时间范围内',
29
+ product_not_in_promotion: '商品不在促销范围内'
49
30
  },
50
- "zh-HK": {
51
- no_applicable_promotion: "無適用的促銷活動",
52
- promotion_not_in_time_range: "促銷活動不在有效時間範圍內",
53
- product_not_in_promotion: "商品不在促銷範圍內"
31
+ 'zh-HK': {
32
+ no_applicable_promotion: '無適用的促銷活動',
33
+ promotion_not_in_time_range: '促銷活動不在有效時間範圍內',
34
+ product_not_in_promotion: '商品不在促銷範圍內'
54
35
  }
55
36
  };
56
- var PromotionEvaluator = class {
37
+
38
+ // ============================================
39
+ // PromotionEvaluator 实现
40
+ // ============================================
41
+
42
+ /**
43
+ * Promotion 评估器
44
+ *
45
+ * 用于评估商品适用的促销活动
46
+ * 封装了 StrategyEngine 和 PromotionAdapter 的调用逻辑
47
+ */
48
+ class PromotionEvaluator {
49
+ engine;
50
+ adapter;
51
+ strategyConfigs = [];
52
+ locale = 'en';
53
+ locales = defaultLocales;
57
54
  constructor() {
58
- this.strategyConfigs = [];
59
- this.locale = "en";
60
- this.locales = defaultLocales;
61
- this.engine = new import_engine.StrategyEngine({
55
+ this.engine = new _engine.StrategyEngine({
62
56
  debug: false,
63
57
  enableTrace: false
64
58
  });
65
- this.adapter = new import_adapter.PromotionAdapter();
59
+ this.adapter = new _adapter.PromotionAdapter();
66
60
  }
61
+
67
62
  // ============================================
68
63
  // 配置管理
69
64
  // ============================================
65
+
70
66
  /**
71
67
  * 设置策略配置列表
72
68
  */
73
69
  setStrategyConfigs(strategyConfigs) {
74
- const newStrategyConfigs = strategyConfigs.filter((item) => item.metadata.type === "promotion");
70
+ const newStrategyConfigs = strategyConfigs.filter(item => item.metadata.type === 'promotion');
75
71
  this.strategyConfigs = newStrategyConfigs;
76
72
  }
73
+
77
74
  /**
78
75
  * 获取策略配置列表
79
76
  */
80
77
  getStrategyConfigs() {
81
78
  return this.strategyConfigs;
82
79
  }
80
+
83
81
  /**
84
82
  * 添加策略配置
85
83
  */
86
84
  addStrategyConfig(strategyConfig) {
87
85
  this.strategyConfigs.push(strategyConfig);
88
86
  }
87
+
89
88
  /**
90
89
  * 设置语言
91
90
  */
92
91
  setLocale(locale) {
93
92
  this.locale = locale;
94
93
  }
94
+
95
95
  /**
96
96
  * 设置自定义多语言文案
97
97
  */
98
98
  setLocales(locales) {
99
- this.locales = { ...defaultLocales, ...locales };
99
+ this.locales = {
100
+ ...defaultLocales,
101
+ ...locales
102
+ };
100
103
  }
104
+
101
105
  /**
102
106
  * 获取多语言文案
103
107
  */
104
108
  getText(key) {
105
- var _a, _b;
106
- return ((_b = (_a = this.locales) == null ? void 0 : _a[this.locale]) == null ? void 0 : _b[key]) || key;
109
+ return this.locales?.[this.locale]?.[key] || key;
107
110
  }
111
+
108
112
  /**
109
113
  * 获取多语言名称
110
114
  */
111
115
  getLocalizedName(name) {
112
- if (typeof name === "string") {
116
+ if (typeof name === 'string') {
113
117
  return name;
114
118
  }
115
- return name[this.locale] || name["en"] || Object.values(name)[0] || "";
119
+ return name[this.locale] || name['en'] || Object.values(name)[0] || '';
116
120
  }
121
+
117
122
  // ============================================
118
123
  // 核心评估方法
119
124
  // ============================================
125
+
120
126
  /**
121
127
  * 评估商品列表
122
128
  *
@@ -127,22 +133,35 @@ var PromotionEvaluator = class {
127
133
  * @returns 每个商品的促销评估结果
128
134
  */
129
135
  evaluateProducts(input) {
130
- const { products, strategyConfigs, channel } = input;
136
+ const {
137
+ products,
138
+ strategyConfigs,
139
+ channel
140
+ } = input;
131
141
  const configs = strategyConfigs || this.strategyConfigs;
132
142
  const results = [];
133
143
  for (const product of products) {
134
144
  const applicablePromotions = [];
145
+
146
+ // 遍历所有策略,检查此商品是否适用
135
147
  for (const config of configs) {
136
148
  if (this.shouldSkipStrategyForProduct(product, config)) {
137
149
  continue;
138
150
  }
151
+
152
+ // 1. 先用 getProductMatchInfo 检查商品是否在策略的适用范围内(包含 bundle 子商品检查)
139
153
  const matchInfo = this.getProductMatchInfo(product, config);
140
154
  if (!matchInfo.isMatch) {
155
+ // 商品不在策略适用范围内,跳过
141
156
  continue;
142
157
  }
158
+
159
+ // 2. 准备上下文(如果是 bundle 子商品匹配,需要用 bundle 子商品的 ID)
143
160
  let contextProduct = product;
144
- if (matchInfo.matchedBundleIndex !== void 0 && product.bundle) {
161
+ if (matchInfo.matchedBundleIndex !== undefined && product.bundle) {
145
162
  const bundleItem = product.bundle[matchInfo.matchedBundleIndex];
163
+ // 创建一个临时的"虚拟商品"用于策略引擎评估
164
+ // 这样策略引擎的 product_match 条件能正确通过
146
165
  contextProduct = {
147
166
  ...product,
148
167
  product_id: bundleItem._bundle_product_id,
@@ -154,6 +173,8 @@ var PromotionEvaluator = class {
154
173
  currentProduct: contextProduct,
155
174
  channel
156
175
  });
176
+
177
+ // 3. 评估策略(检查其他条件如时间范围等)
157
178
  const result = this.engine.evaluate(config, context);
158
179
  if (result.applicable && result.matchedActions.length > 0) {
159
180
  const action = result.matchedActions[0];
@@ -184,6 +205,7 @@ var PromotionEvaluator = class {
184
205
  }
185
206
  return results;
186
207
  }
208
+
187
209
  /**
188
210
  * 评估购物车
189
211
  *
@@ -194,29 +216,42 @@ var PromotionEvaluator = class {
194
216
  * @returns 购物车评估结果
195
217
  */
196
218
  evaluateCart(input) {
197
- const { products, strategyConfigs, channel } = input;
219
+ const {
220
+ products,
221
+ strategyConfigs,
222
+ channel
223
+ } = input;
198
224
  const configs = strategyConfigs || this.strategyConfigs;
225
+
226
+ // 1. 先获取每个商品的促销结果
199
227
  const productResults = this.evaluateProducts(input);
200
- const promotionMap = /* @__PURE__ */ new Map();
228
+
229
+ // 2. 收集所有适用的促销(去重)
230
+ const promotionMap = new Map();
201
231
  for (const productResult of productResults) {
202
232
  for (const promo of productResult.applicablePromotions) {
203
233
  const existing = promotionMap.get(promo.strategyId);
204
234
  const product = productResult.product;
235
+
236
+ // 计算促销数量:如果是 bundle 子商品匹配,数量 = 主商品数量 × 子商品数量
205
237
  let promoQuantity = product.quantity;
206
- if (promo.matchedBundleIndex !== void 0 && product.bundle) {
238
+ if (promo.matchedBundleIndex !== undefined && product.bundle) {
207
239
  const bundleItem = product.bundle[promo.matchedBundleIndex];
208
240
  promoQuantity = product.quantity * (bundleItem.quantity || 1);
209
241
  }
210
242
  if (existing) {
243
+ // 已存在,添加商品到列表
211
244
  existing.applicableProducts.push(product);
212
245
  existing.totalQuantity += promoQuantity;
213
246
  existing.totalAmount += product.price * product.quantity;
247
+ // 记录每个商品的 matchedBundleIndex
214
248
  if (!existing.productMatchedBundleIndexMap) {
215
- existing.productMatchedBundleIndexMap = /* @__PURE__ */ new Map();
249
+ existing.productMatchedBundleIndexMap = new Map();
216
250
  }
217
251
  existing.productMatchedBundleIndexMap.set(product.id, promo.matchedBundleIndex);
218
252
  } else {
219
- const productMatchedBundleIndexMap = /* @__PURE__ */ new Map();
253
+ // 新促销
254
+ const productMatchedBundleIndexMap = new Map();
220
255
  productMatchedBundleIndexMap.set(product.id, promo.matchedBundleIndex);
221
256
  promotionMap.set(promo.strategyId, {
222
257
  ...promo,
@@ -229,30 +264,28 @@ var PromotionEvaluator = class {
229
264
  }
230
265
  }
231
266
  const applicablePromotions = Array.from(promotionMap.values());
232
- const promotionGroups = applicablePromotions.map(
233
- (promo) => {
234
- var _a;
235
- return {
236
- strategyId: promo.strategyId,
237
- strategyName: promo.strategyName,
238
- strategyMetadata: (_a = promo.strategyConfig) == null ? void 0 : _a.metadata,
239
- strategyConfig: promo.strategyConfig,
240
- actionType: promo.actionType,
241
- actionDetail: promo.actionDetail,
242
- products: promo.applicableProducts,
243
- totalQuantity: promo.totalQuantity,
244
- totalAmount: promo.totalAmount,
245
- // 传递 productMatchedBundleIndexMap 到 group
246
- productMatchedBundleIndexMap: promo.productMatchedBundleIndexMap
247
- };
248
- }
249
- );
267
+
268
+ // 3. 按促销分组
269
+ const promotionGroups = applicablePromotions.map(promo => ({
270
+ strategyId: promo.strategyId,
271
+ strategyName: promo.strategyName,
272
+ strategyMetadata: promo.strategyConfig?.metadata,
273
+ strategyConfig: promo.strategyConfig,
274
+ actionType: promo.actionType,
275
+ actionDetail: promo.actionDetail,
276
+ products: promo.applicableProducts,
277
+ totalQuantity: promo.totalQuantity,
278
+ totalAmount: promo.totalAmount,
279
+ // 传递 productMatchedBundleIndexMap 到 group
280
+ productMatchedBundleIndexMap: promo.productMatchedBundleIndexMap
281
+ }));
250
282
  return {
251
283
  productResults,
252
284
  applicablePromotions,
253
285
  promotionGroups
254
286
  };
255
287
  }
288
+
256
289
  /**
257
290
  * 评估购物车并计算定价
258
291
  *
@@ -264,70 +297,76 @@ var PromotionEvaluator = class {
264
297
  * @returns 带定价的购物车评估结果
265
298
  */
266
299
  evaluateCartWithPricing(input) {
267
- const { products } = input;
300
+ const {
301
+ products
302
+ } = input;
303
+
304
+ // 1. 先调用 evaluateCart 获取分组结果
268
305
  const cartResult = this.evaluateCart(input);
269
- const sortedGroups = this.sortPromotionGroupsByPriority(
270
- cartResult.promotionGroups,
271
- cartResult.applicablePromotions
272
- );
273
- const processedQuantityMap = /* @__PURE__ */ new Map();
306
+
307
+ // 2. 按 action priority 排序促销组(从 strategyConfig 获取 priority)
308
+ const sortedGroups = this.sortPromotionGroupsByPriority(cartResult.promotionGroups, cartResult.applicablePromotions);
309
+
310
+ // 3. 追踪已处理的商品(避免重复处理)
311
+ // key: `${product_id}_${product_variant_id}`
312
+ const processedQuantityMap = new Map();
313
+
314
+ // 4. 处理每个促销组,收集结果
274
315
  const pricedProducts = [];
275
316
  const gifts = [];
276
- let totalOriginalAmount = new import_decimal.default(0);
277
- let totalFinalAmount = new import_decimal.default(0);
317
+ let totalOriginalAmount = new _decimal.default(0);
318
+ let totalFinalAmount = new _decimal.default(0);
278
319
  for (const group of sortedGroups) {
279
- const { actionType } = group;
280
- const matchedBundleIndexMap = group.productMatchedBundleIndexMap || /* @__PURE__ */ new Map();
281
- if (actionType === import_type.PROMOTION_ACTION_TYPES.X_ITEMS_FOR_Y_PRICE) {
282
- const result = this.processXItemsForYPrice(
283
- group,
284
- processedQuantityMap,
285
- matchedBundleIndexMap
286
- );
320
+ const {
321
+ actionType
322
+ } = group;
323
+
324
+ // 直接使用 evaluateCart 阶段计算好的 matchedBundleIndexMap
325
+ const matchedBundleIndexMap = group.productMatchedBundleIndexMap || new Map();
326
+ if (actionType === _type.PROMOTION_ACTION_TYPES.X_ITEMS_FOR_Y_PRICE) {
327
+ // X件Y元处理
328
+ const result = this.processXItemsForYPrice(group, processedQuantityMap, matchedBundleIndexMap);
287
329
  pricedProducts.push(...result.products);
288
330
  totalOriginalAmount = totalOriginalAmount.plus(result.originalAmount);
289
331
  totalFinalAmount = totalFinalAmount.plus(result.finalAmount);
290
- } else if (actionType === import_type.PROMOTION_ACTION_TYPES.BUY_X_GET_Y_FREE) {
291
- const result = this.processBuyXGetYFree(
292
- group,
293
- processedQuantityMap,
294
- matchedBundleIndexMap
295
- );
332
+ } else if (actionType === _type.PROMOTION_ACTION_TYPES.BUY_X_GET_Y_FREE) {
333
+ // 买X送Y处理
334
+ const result = this.processBuyXGetYFree(group, processedQuantityMap, matchedBundleIndexMap);
296
335
  pricedProducts.push(...result.products);
297
336
  totalOriginalAmount = totalOriginalAmount.plus(result.originalAmount);
298
337
  totalFinalAmount = totalFinalAmount.plus(result.finalAmount);
338
+
339
+ // 添加赠品信息
299
340
  if (result.giftInfo) {
300
341
  gifts.push(result.giftInfo);
301
342
  }
302
- } else if (actionType === import_type.PROMOTION_ACTION_TYPES.ITEM_REWARD) {
303
- const result = this.processItemReward(
304
- group,
305
- processedQuantityMap,
306
- products,
307
- matchedBundleIndexMap
308
- );
343
+ } else if (actionType === _type.PROMOTION_ACTION_TYPES.ITEM_REWARD) {
344
+ // 商品奖励处理:条件商品触发,目标商品行享优惠,不自动新增商品
345
+ const result = this.processItemReward(group, processedQuantityMap, products, matchedBundleIndexMap);
309
346
  pricedProducts.push(...result.products);
310
347
  totalOriginalAmount = totalOriginalAmount.plus(result.originalAmount);
311
348
  totalFinalAmount = totalFinalAmount.plus(result.finalAmount);
312
349
  }
313
350
  }
314
- const remainingProducts = this.getRemainingProducts(
315
- products,
316
- processedQuantityMap
317
- );
351
+
352
+ // 5. 处理未参与任何促销的商品
353
+ const remainingProducts = this.getRemainingProducts(products, processedQuantityMap);
318
354
  for (const product of remainingProducts) {
319
- const amount = new import_decimal.default(product.price).mul(product.quantity);
355
+ const amount = new _decimal.default(product.price).mul(product.quantity);
320
356
  totalOriginalAmount = totalOriginalAmount.plus(amount);
321
357
  totalFinalAmount = totalFinalAmount.plus(amount);
322
358
  }
323
359
  pricedProducts.push(...remainingProducts);
360
+
361
+ // 6. 计算总优惠金额
324
362
  const totalDiscount = totalOriginalAmount.minus(totalFinalAmount);
325
363
  return {
326
364
  products: pricedProducts,
327
365
  gifts,
328
- totalDiscount: import_decimal.default.max(0, totalDiscount).toNumber()
366
+ totalDiscount: _decimal.default.max(0, totalDiscount).toNumber()
329
367
  };
330
368
  }
369
+
331
370
  /**
332
371
  * 获取商品适用的促销列表
333
372
  *
@@ -338,13 +377,13 @@ var PromotionEvaluator = class {
338
377
  * @returns 适用的促销列表
339
378
  */
340
379
  getProductPromotions(product, strategyConfigs) {
341
- var _a;
342
380
  const results = this.evaluateProducts({
343
381
  products: [product],
344
382
  strategyConfigs
345
383
  });
346
- return ((_a = results[0]) == null ? void 0 : _a.applicablePromotions) || [];
384
+ return results[0]?.applicablePromotions || [];
347
385
  }
386
+
348
387
  /**
349
388
  * 获取商品的促销标签
350
389
  *
@@ -356,12 +395,13 @@ var PromotionEvaluator = class {
356
395
  */
357
396
  getProductPromotionTags(product, strategyConfigs) {
358
397
  const promotions = this.getProductPromotions(product, strategyConfigs);
359
- return promotions.filter((promo) => promo.display).map((promo) => ({
398
+ return promotions.filter(promo => promo.display).map(promo => ({
360
399
  text: this.getLocalizedName(promo.display.text),
361
400
  type: promo.display.type,
362
401
  strategyId: promo.strategyId
363
402
  }));
364
403
  }
404
+
365
405
  /**
366
406
  * 查找商品适用的策略配置
367
407
  *
@@ -371,10 +411,9 @@ var PromotionEvaluator = class {
371
411
  */
372
412
  findApplicableStrategies(product, strategyConfigs) {
373
413
  const configs = strategyConfigs || this.strategyConfigs;
374
- return configs.filter(
375
- (config) => this.isProductInStrategy(product, config)
376
- );
414
+ return configs.filter(config => this.isProductInStrategy(product, config));
377
415
  }
416
+
378
417
  /**
379
418
  * 批量获取商品列表的适用策略信息
380
419
  *
@@ -386,26 +425,33 @@ var PromotionEvaluator = class {
386
425
  * @returns 每个商品的适用策略完整数据
387
426
  */
388
427
  getProductsApplicableStrategies(input, matchVariant = true) {
389
- const { products, strategyConfigs, channel } = input;
428
+ const {
429
+ products,
430
+ strategyConfigs,
431
+ channel
432
+ } = input;
390
433
  const configs = strategyConfigs || this.strategyConfigs;
391
434
  const results = [];
392
435
  for (const product of products) {
393
436
  const applicableStrategies = [];
437
+
438
+ // 遍历所有策略,检查此商品是否适用
394
439
  for (const config of configs) {
395
440
  if (this.shouldSkipStrategyForProduct(product, config)) {
396
441
  continue;
397
442
  }
398
443
  let contextProduct = product;
399
444
  if (!matchVariant) {
445
+ // 只匹配 product_id,忽略 product_variant_id
400
446
  const productMatchRule = this.findProductMatchRule(config);
401
447
  if (productMatchRule) {
402
448
  const configProducts = productMatchRule.value;
403
- const matchedConfig = configProducts.find(
404
- (item) => item.product_id === product.product_id
405
- );
449
+ const matchedConfig = configProducts.find(item => item.product_id === product.product_id);
406
450
  if (!matchedConfig) {
451
+ // product_id 不在策略适用范围内,跳过
407
452
  continue;
408
453
  }
454
+ // 用配置中的 variant_id 构造上下文,确保引擎 product_match 通过
409
455
  if (matchedConfig.product_variant_id !== 0 && matchedConfig.product_variant_id !== product.product_variant_id) {
410
456
  contextProduct = {
411
457
  ...product,
@@ -419,6 +465,8 @@ var PromotionEvaluator = class {
419
465
  currentProduct: contextProduct,
420
466
  channel
421
467
  });
468
+
469
+ // 评估策略
422
470
  const result = this.engine.evaluate(config, context);
423
471
  if (result.applicable && result.matchedActions.length > 0) {
424
472
  const action = result.matchedActions[0];
@@ -428,11 +476,11 @@ var PromotionEvaluator = class {
428
476
  channel
429
477
  });
430
478
  if (transformedResult.actionDetail) {
431
- const { requiredQuantity, eligibleProducts } = this.getPromotionRequirements(
432
- action.type,
433
- transformedResult.actionDetail,
434
- config
435
- );
479
+ // 计算所需数量和可参与商品列表
480
+ const {
481
+ requiredQuantity,
482
+ eligibleProducts
483
+ } = this.getPromotionRequirements(action.type, transformedResult.actionDetail, config);
436
484
  applicableStrategies.push({
437
485
  strategyId: config.metadata.id,
438
486
  strategyName: config.metadata.name,
@@ -455,6 +503,7 @@ var PromotionEvaluator = class {
455
503
  }
456
504
  return results;
457
505
  }
506
+
458
507
  /**
459
508
  * 获取促销所需数量和可参与商品列表
460
509
  *
@@ -465,44 +514,53 @@ var PromotionEvaluator = class {
465
514
  */
466
515
  getPromotionRequirements(actionType, actionDetail, config) {
467
516
  let requiredQuantity = 0;
468
- if (actionType === "BUY_X_GET_Y_FREE") {
517
+
518
+ // 根据促销类型获取所需数量
519
+ if (actionType === 'BUY_X_GET_Y_FREE') {
469
520
  const detail = actionDetail;
470
521
  requiredQuantity = detail.buyQuantity || 0;
471
- } else if (actionType === "X_ITEMS_FOR_Y_PRICE") {
522
+ } else if (actionType === 'X_ITEMS_FOR_Y_PRICE') {
472
523
  const detail = actionDetail;
473
524
  requiredQuantity = detail.x || 0;
474
- } else if (actionType === "ITEM_REWARD") {
525
+ } else if (actionType === 'ITEM_REWARD') {
475
526
  const detail = actionDetail;
476
527
  requiredQuantity = detail.triggerQuantity || 1;
477
528
  }
529
+
530
+ // 获取可参与商品列表
478
531
  const productMatchRule = this.findProductMatchRule(config);
479
- const eligibleProducts = (productMatchRule == null ? void 0 : productMatchRule.value) || [];
532
+ const eligibleProducts = productMatchRule?.value || [];
480
533
  return {
481
534
  requiredQuantity,
482
535
  eligibleProducts
483
536
  };
484
537
  }
538
+
485
539
  // ============================================
486
540
  // 私有辅助方法
487
541
  // ============================================
542
+
488
543
  /**
489
544
  * 按优先级排序促销组
490
545
  * 优先级从 strategyConfig.actions[0].priority 获取,数值越小优先级越高
491
546
  */
492
547
  sortPromotionGroupsByPriority(groups, promotions) {
493
- var _a, _b;
494
- const priorityMap = /* @__PURE__ */ new Map();
548
+ // 创建策略ID到优先级的映射
549
+ const priorityMap = new Map();
495
550
  for (const promo of promotions) {
496
- const actions = ((_a = promo.strategyConfig) == null ? void 0 : _a.actions) || [];
497
- const priority = ((_b = actions[0]) == null ? void 0 : _b.priority) ?? 999;
551
+ const actions = promo.strategyConfig?.actions || [];
552
+ const priority = actions[0]?.priority ?? 999;
498
553
  priorityMap.set(promo.strategyId, priority);
499
554
  }
555
+
556
+ // 按优先级排序(数值越小优先级越高)
500
557
  return [...groups].sort((a, b) => {
501
558
  const priorityA = priorityMap.get(a.strategyId) ?? 999;
502
559
  const priorityB = priorityMap.get(b.strategyId) ?? 999;
503
560
  return priorityA - priorityB;
504
561
  });
505
562
  }
563
+
506
564
  /**
507
565
  * 处理 X件Y元 促销
508
566
  *
@@ -514,19 +572,32 @@ var PromotionEvaluator = class {
514
572
  * 注意:每个商品按 id 独立处理,不会合并不同 id 的商品
515
573
  */
516
574
  processXItemsForYPrice(group, processedQuantityMap, matchedBundleIndexMap) {
517
- const { strategyId, strategyMetadata, actionDetail, products: groupProducts } = group;
575
+ const {
576
+ strategyId,
577
+ strategyMetadata,
578
+ actionDetail,
579
+ products: groupProducts
580
+ } = group;
518
581
  const detail = actionDetail;
519
- const { x, price: groupPrice, cumulative } = detail;
582
+ const {
583
+ x,
584
+ price: groupPrice,
585
+ cumulative
586
+ } = detail;
520
587
  const result = [];
521
- let originalAmount = new import_decimal.default(0);
522
- let finalAmount = new import_decimal.default(0);
588
+ let originalAmount = new _decimal.default(0);
589
+ let finalAmount = new _decimal.default(0);
590
+
591
+ // 1. 展开所有商品为单件列表(排除已处理的数量)
592
+ // 每个单件记录来自哪个原商品(通过 product.id)
523
593
  const unitProducts = [];
524
594
  for (const product of groupProducts) {
525
595
  const key = this.getProductKey(product);
526
596
  const processedQty = processedQuantityMap.get(key) || 0;
527
597
  const availableQty = product.quantity - processedQty;
528
- if (availableQty <= 0)
529
- continue;
598
+ if (availableQty <= 0) continue;
599
+
600
+ // 展开为单件,每个单件都记录来源商品
530
601
  for (let i = 0; i < availableQty; i++) {
531
602
  unitProducts.push({
532
603
  product,
@@ -537,47 +608,67 @@ var PromotionEvaluator = class {
537
608
  }
538
609
  }
539
610
  if (unitProducts.length === 0) {
540
- return { products: [], originalAmount: 0, finalAmount: 0 };
611
+ return {
612
+ products: [],
613
+ originalAmount: 0,
614
+ finalAmount: 0
615
+ };
541
616
  }
617
+
618
+ // 2. 排序:优先选择来源数量是 x 倍数的商品(可完整凑组不拆分),然后按价格从高到低
542
619
  unitProducts.sort((a, b) => {
620
+ // 优先级1:来源购物车项数量是 x 的倍数的排前面(这些商品可以完整凑组,避免拆分)
543
621
  const aIsMultiple = a.sourceAvailableQty > 0 && a.sourceAvailableQty % x === 0 ? 1 : 0;
544
622
  const bIsMultiple = b.sourceAvailableQty > 0 && b.sourceAvailableQty % x === 0 ? 1 : 0;
545
- if (aIsMultiple !== bIsMultiple)
546
- return bIsMultiple - aIsMultiple;
623
+ if (aIsMultiple !== bIsMultiple) return bIsMultiple - aIsMultiple;
624
+ // 优先级2:按价格从高到低(优先使用高价商品)
547
625
  return b.price - a.price;
548
626
  });
627
+
628
+ // 3. 计算可以凑成多少组(cumulative 缺省视为 true,与 adapter 一致)
549
629
  const totalUnits = unitProducts.length;
550
630
  const isCumulative = cumulative !== false;
551
631
  const groupCount = isCumulative ? Math.floor(totalUnits / x) : totalUnits >= x ? 1 : 0;
552
632
  const promotionUnits = groupCount * x;
633
+
634
+ // 4. 分离参与促销的和不参与的商品
553
635
  const inPromotionUnits = unitProducts.slice(0, promotionUnits);
554
636
  const notInPromotionUnits = unitProducts.slice(promotionUnits);
555
- const promotionQtyByProductId = /* @__PURE__ */ new Map();
637
+
638
+ // 5. 按原商品 id 统计参与促销和不参与的数量
639
+ const promotionQtyByProductId = new Map();
556
640
  for (const unit of inPromotionUnits) {
557
641
  const id = unit.productId;
558
642
  promotionQtyByProductId.set(id, (promotionQtyByProductId.get(id) || 0) + 1);
559
643
  }
560
- const remainingQtyByProductId = /* @__PURE__ */ new Map();
644
+ const remainingQtyByProductId = new Map();
561
645
  for (const unit of notInPromotionUnits) {
562
646
  const id = unit.productId;
563
647
  remainingQtyByProductId.set(id, (remainingQtyByProductId.get(id) || 0) + 1);
564
648
  }
565
- const allocatedPricesByProductId = /* @__PURE__ */ new Map();
649
+
650
+ // 6. 为每个参与促销的单件计算均摊价格
651
+ // 按组内原价比例均摊
652
+ // 记录每个原商品的均摊价格累加(用于后续聚合)
653
+ const allocatedPricesByProductId = new Map();
566
654
  for (let g = 0; g < groupCount; g++) {
567
655
  const startIdx = g * x;
568
656
  const endIdx = startIdx + x;
569
657
  const groupUnits = inPromotionUnits.slice(startIdx, endIdx);
570
- const groupOriginalTotal = groupUnits.reduce(
571
- (sum, u) => sum.plus(u.price),
572
- new import_decimal.default(0)
573
- );
574
- const groupPriceDecimal = new import_decimal.default(groupPrice);
575
- let allocatedSum = new import_decimal.default(0);
658
+
659
+ // 计算组内原价总和
660
+ const groupOriginalTotal = groupUnits.reduce((sum, u) => sum.plus(u.price), new _decimal.default(0));
661
+
662
+ // 按原价比例均摊
663
+ const groupPriceDecimal = new _decimal.default(groupPrice);
664
+ let allocatedSum = new _decimal.default(0);
576
665
  for (let i = 0; i < groupUnits.length; i++) {
577
666
  const unit = groupUnits[i];
578
667
  const isLastUnit = i === groupUnits.length - 1;
579
- const ratio = groupOriginalTotal.eq(0) ? new import_decimal.default(0) : new import_decimal.default(unit.price).div(groupOriginalTotal);
668
+ const ratio = groupOriginalTotal.eq(0) ? new _decimal.default(0) : new _decimal.default(unit.price).div(groupOriginalTotal);
580
669
  const allocatedDecimal = isLastUnit ? groupPriceDecimal.minus(allocatedSum) : groupPriceDecimal.mul(ratio);
670
+
671
+ // 记录这个单件的均摊价格
581
672
  const productId = unit.productId;
582
673
  const prices = allocatedPricesByProductId.get(productId) || [];
583
674
  prices.push(allocatedDecimal);
@@ -585,23 +676,25 @@ var PromotionEvaluator = class {
585
676
  allocatedSum = allocatedSum.plus(allocatedDecimal);
586
677
  }
587
678
  }
679
+
680
+ // 7. 为每个原商品生成结果
588
681
  for (const product of groupProducts) {
589
682
  const key = this.getProductKey(product);
590
683
  const processedQty = processedQuantityMap.get(key) || 0;
591
684
  const availableQty = product.quantity - processedQty;
592
- if (availableQty <= 0)
593
- continue;
685
+ if (availableQty <= 0) continue;
594
686
  const promoQty = promotionQtyByProductId.get(product.id) || 0;
595
687
  const remainingQty = remainingQtyByProductId.get(product.id) || 0;
596
688
  const allocatedPrices = allocatedPricesByProductId.get(product.id) || [];
597
- const matchedBundleIndex = matchedBundleIndexMap == null ? void 0 : matchedBundleIndexMap.get(product.id);
689
+
690
+ // 获取当前商品的 matchedBundleIndex
691
+ const matchedBundleIndex = matchedBundleIndexMap?.get(product.id);
598
692
  if (promoQty > 0 && remainingQty === 0) {
599
- const totalAllocated = allocatedPrices.reduce(
600
- (sum, p) => sum.plus(p),
601
- new import_decimal.default(0)
602
- );
603
- const productOriginalAmount = new import_decimal.default(product.price).mul(promoQty);
604
- const actualTotal = import_decimal.default.min(totalAllocated, productOriginalAmount);
693
+ // 全部参与促销:保留原 id,计算总 finalPrice
694
+ const totalAllocated = allocatedPrices.reduce((sum, p) => sum.plus(p), new _decimal.default(0));
695
+ // 取促销价和原价的最小值,避免促销反而加价
696
+ const productOriginalAmount = new _decimal.default(product.price).mul(promoQty);
697
+ const actualTotal = _decimal.default.min(totalAllocated, productOriginalAmount);
605
698
  const finalPricePerUnit = this.formatPrice(actualTotal.div(promoQty));
606
699
  result.push({
607
700
  ...product,
@@ -616,25 +709,26 @@ var PromotionEvaluator = class {
616
709
  originalAmount = originalAmount.plus(productOriginalAmount);
617
710
  finalAmount = finalAmount.plus(actualTotal);
618
711
  } else if (promoQty === 0 && remainingQty > 0) {
712
+ // 全部不参与促销:保留原 id,finalPrice = 原价
619
713
  result.push({
620
714
  ...product,
621
715
  quantity: remainingQty,
622
716
  finalPrice: this.formatPrice(product.price),
623
- strategyId: void 0,
624
- strategyMetadata: void 0,
717
+ strategyId: undefined,
718
+ strategyMetadata: undefined,
625
719
  inPromotion: false,
626
720
  isSplit: false
627
721
  });
628
- const amount = new import_decimal.default(product.price).mul(remainingQty);
722
+ const amount = new _decimal.default(product.price).mul(remainingQty);
629
723
  originalAmount = originalAmount.plus(amount);
630
724
  finalAmount = finalAmount.plus(amount);
631
725
  } else if (promoQty > 0 && remainingQty > 0) {
632
- const totalAllocated = allocatedPrices.reduce(
633
- (sum, p) => sum.plus(p),
634
- new import_decimal.default(0)
635
- );
636
- const promoOriginalAmount = new import_decimal.default(product.price).mul(promoQty);
637
- const actualTotal = import_decimal.default.min(totalAllocated, promoOriginalAmount);
726
+ // 部分参与促销,部分不参与:需要拆分
727
+ // 参与促销的部分:新 id
728
+ const totalAllocated = allocatedPrices.reduce((sum, p) => sum.plus(p), new _decimal.default(0));
729
+ // 取促销价和原价的最小值,避免促销反而加价
730
+ const promoOriginalAmount = new _decimal.default(product.price).mul(promoQty);
731
+ const actualTotal = _decimal.default.min(totalAllocated, promoOriginalAmount);
638
732
  const finalPricePerUnit = this.formatPrice(actualTotal.div(promoQty));
639
733
  result.push({
640
734
  ...product,
@@ -650,44 +744,50 @@ var PromotionEvaluator = class {
650
744
  });
651
745
  originalAmount = originalAmount.plus(promoOriginalAmount);
652
746
  finalAmount = finalAmount.plus(actualTotal);
747
+
748
+ // 不参与促销的部分:保留原 id
653
749
  result.push({
654
750
  ...product,
655
751
  quantity: remainingQty,
656
752
  finalPrice: this.formatPrice(product.price),
657
- strategyId: void 0,
658
- strategyMetadata: void 0,
753
+ strategyId: undefined,
754
+ strategyMetadata: undefined,
659
755
  inPromotion: false,
660
756
  isSplit: true
661
757
  });
662
- const remainingAmount = new import_decimal.default(product.price).mul(remainingQty);
758
+ const remainingAmount = new _decimal.default(product.price).mul(remainingQty);
663
759
  originalAmount = originalAmount.plus(remainingAmount);
664
760
  finalAmount = finalAmount.plus(remainingAmount);
665
761
  }
762
+
763
+ // 更新已处理数量
666
764
  processedQuantityMap.set(key, processedQty + availableQty);
667
765
  }
766
+
767
+ // 8. 尾差调整:确保促销商品的 finalPrice × quantity 之和精确等于 groupPrice × groupCount
668
768
  if (groupCount > 0) {
669
- const expectedPromoTotal = new import_decimal.default(groupPrice).mul(groupCount);
670
- const promoItems = result.filter((p) => p.inPromotion);
671
- let actualPromoTotal = new import_decimal.default(0);
769
+ const expectedPromoTotal = new _decimal.default(groupPrice).mul(groupCount);
770
+ const promoItems = result.filter(p => p.inPromotion);
771
+ let actualPromoTotal = new _decimal.default(0);
672
772
  for (const p of promoItems) {
673
- actualPromoTotal = actualPromoTotal.plus(
674
- new import_decimal.default(p.finalPrice).mul(p.quantity)
675
- );
773
+ actualPromoTotal = actualPromoTotal.plus(new _decimal.default(p.finalPrice).mul(p.quantity));
676
774
  }
677
775
  const roundingDiff = actualPromoTotal.minus(expectedPromoTotal);
678
776
  if (!roundingDiff.eq(0)) {
679
- const adjustTarget = promoItems.find((p) => p.quantity === 1);
777
+ // 优先找 quantity=1 的促销商品调整(差额直接加减到单价上)
778
+ const adjustTarget = promoItems.find(p => p.quantity === 1);
680
779
  if (adjustTarget) {
681
- adjustTarget.finalPrice = this.formatPrice(
682
- new import_decimal.default(adjustTarget.finalPrice).minus(roundingDiff)
683
- );
780
+ adjustTarget.finalPrice = this.formatPrice(new _decimal.default(adjustTarget.finalPrice).minus(roundingDiff));
684
781
  } else if (promoItems.length > 0) {
782
+ // 没有 quantity=1 的促销商品,从最后一个促销商品中拆出1件做尾差调整
685
783
  const lastPromo = promoItems[promoItems.length - 1];
686
- const adjustedUnitPrice = this.formatPrice(
687
- new import_decimal.default(lastPromo.finalPrice).minus(roundingDiff)
688
- );
784
+ const adjustedUnitPrice = this.formatPrice(new _decimal.default(lastPromo.finalPrice).minus(roundingDiff));
785
+
786
+ // 原商品减少1件
689
787
  lastPromo.quantity -= 1;
690
788
  lastPromo.isSplit = true;
789
+
790
+ // 拆出1件用调整后的价格
691
791
  result.push({
692
792
  ...lastPromo,
693
793
  id: this.generateRandomId(),
@@ -697,11 +797,11 @@ var PromotionEvaluator = class {
697
797
  isSplit: true
698
798
  });
699
799
  }
700
- finalAmount = new import_decimal.default(0);
800
+
801
+ // 重新计算 finalAmount
802
+ finalAmount = new _decimal.default(0);
701
803
  for (const p of result) {
702
- finalAmount = finalAmount.plus(
703
- new import_decimal.default(p.finalPrice).mul(p.quantity)
704
- );
804
+ finalAmount = finalAmount.plus(new _decimal.default(p.finalPrice).mul(p.quantity));
705
805
  }
706
806
  }
707
807
  }
@@ -711,6 +811,7 @@ var PromotionEvaluator = class {
711
811
  finalAmount: finalAmount.toNumber()
712
812
  };
713
813
  }
814
+
714
815
  /**
715
816
  * 处理 买X送Y 促销
716
817
  *
@@ -726,34 +827,56 @@ var PromotionEvaluator = class {
726
827
  products: groupProducts
727
828
  } = group;
728
829
  const detail = actionDetail;
729
- const { buyQuantity, freeQuantity, cumulative, giftProducts } = detail;
830
+ const {
831
+ buyQuantity,
832
+ freeQuantity,
833
+ cumulative,
834
+ giftProducts
835
+ } = detail;
730
836
  const result = [];
731
- let originalAmount = new import_decimal.default(0);
732
- let finalAmount = new import_decimal.default(0);
837
+ let originalAmount = new _decimal.default(0);
838
+ let finalAmount = new _decimal.default(0);
839
+
840
+ // 1. 先计算可用商品总数量(第一次遍历)
841
+ // 对于 bundle 子商品匹配,促销数量 = 主商品数量 × bundle子商品数量
733
842
  let totalPromoQty = 0;
734
- const productInfoMap = /* @__PURE__ */ new Map();
843
+ const productInfoMap = new Map();
735
844
  for (const product of groupProducts) {
736
845
  const key = this.getProductKey(product);
737
846
  const processedQty = processedQuantityMap.get(key) || 0;
738
847
  const availableQty = product.quantity - processedQty;
739
- if (availableQty <= 0)
740
- continue;
741
- const matchedBundleIndex = matchedBundleIndexMap == null ? void 0 : matchedBundleIndexMap.get(product.id);
848
+ if (availableQty <= 0) continue;
849
+
850
+ // 获取 matchedBundleIndex
851
+ const matchedBundleIndex = matchedBundleIndexMap?.get(product.id);
852
+
853
+ // 计算用于促销的数量
742
854
  let promoQty = availableQty;
743
- if (matchedBundleIndex !== void 0 && product.bundle) {
855
+ if (matchedBundleIndex !== undefined && product.bundle) {
744
856
  const bundleItem = product.bundle[matchedBundleIndex];
857
+ // 促销数量 = 主商品数量 × bundle子商品数量
745
858
  promoQty = availableQty * (bundleItem.quantity || 1);
746
859
  }
747
860
  totalPromoQty += promoQty;
748
- productInfoMap.set(key, { availableQty, promoQty, matchedBundleIndex });
861
+ productInfoMap.set(key, {
862
+ availableQty,
863
+ promoQty,
864
+ matchedBundleIndex
865
+ });
749
866
  }
867
+
868
+ // 2. 判断是否满足促销条件
750
869
  const isFulfilled = totalPromoQty >= buyQuantity;
870
+
871
+ // 3. 生成商品结果(第二次遍历)
751
872
  for (const product of groupProducts) {
752
873
  const key = this.getProductKey(product);
753
874
  const info = productInfoMap.get(key);
754
- if (!info || info.availableQty <= 0)
755
- continue;
875
+ if (!info || info.availableQty <= 0) continue;
756
876
  const processedQty = processedQuantityMap.get(key) || 0;
877
+
878
+ // 商品本身价格不变,只是触发赠品
879
+ // inPromotion 只有在满足条件时才为 true
757
880
  result.push({
758
881
  ...product,
759
882
  quantity: info.availableQty,
@@ -765,15 +888,21 @@ var PromotionEvaluator = class {
765
888
  isSplit: info.availableQty < product.quantity,
766
889
  matchedBundleIndex: info.matchedBundleIndex
767
890
  });
768
- const amount = new import_decimal.default(product.price).mul(info.availableQty);
891
+ const amount = new _decimal.default(product.price).mul(info.availableQty);
769
892
  originalAmount = originalAmount.plus(amount);
770
893
  finalAmount = finalAmount.plus(amount);
894
+
895
+ // 更新已处理数量(使用主商品数量)
771
896
  processedQuantityMap.set(key, processedQty + info.availableQty);
772
897
  }
898
+
899
+ // 4. 计算赠品数量(使用促销数量)
773
900
  let giftCount = 0;
774
901
  if (isFulfilled) {
775
902
  giftCount = cumulative ? Math.floor(totalPromoQty / buyQuantity) * freeQuantity : freeQuantity;
776
903
  }
904
+
905
+ // 5. 构建赠品信息
777
906
  const giftInfo = giftCount > 0 ? {
778
907
  strategyId,
779
908
  strategyName,
@@ -787,6 +916,7 @@ var PromotionEvaluator = class {
787
916
  giftInfo
788
917
  };
789
918
  }
919
+
790
920
  /**
791
921
  * 处理 商品奖励 促销
792
922
  *
@@ -806,42 +936,37 @@ var PromotionEvaluator = class {
806
936
  const detail = actionDetail;
807
937
  const triggerQuantity = this.normalizePositiveInteger(detail.triggerQuantity, 1);
808
938
  const rewardQuantity = this.normalizePositiveInteger(detail.rewardQuantity, 1);
809
- const conditionQuantity = this.getAvailablePromotionQuantity(
810
- triggerProducts,
811
- processedQuantityMap,
812
- matchedBundleIndexMap
813
- );
939
+ const conditionQuantity = this.getAvailablePromotionQuantity(triggerProducts, processedQuantityMap, matchedBundleIndexMap);
814
940
  if (conditionQuantity < triggerQuantity) {
815
- return { products: [], originalAmount: 0, finalAmount: 0 };
941
+ return {
942
+ products: [],
943
+ originalAmount: 0,
944
+ finalAmount: 0
945
+ };
816
946
  }
817
- const targetProducts = allProducts.filter(
818
- (product) => this.isProductMatch(product, detail.rewardTargets || [])
819
- );
820
- const targetUnitCandidates = this.buildItemRewardTargetUnits(
821
- targetProducts,
822
- processedQuantityMap,
823
- detail
824
- );
947
+ const targetProducts = allProducts.filter(product => this.isProductMatch(product, detail.rewardTargets || []));
948
+ const targetUnitCandidates = this.buildItemRewardTargetUnits(targetProducts, processedQuantityMap, detail);
825
949
  if (targetUnitCandidates.length === 0) {
826
- return { products: [], originalAmount: 0, finalAmount: 0 };
950
+ return {
951
+ products: [],
952
+ originalAmount: 0,
953
+ finalAmount: 0
954
+ };
827
955
  }
828
- const rewardLimit = this.resolveItemRewardLimit(
829
- detail,
830
- conditionQuantity,
831
- triggerQuantity,
832
- rewardQuantity,
833
- targetUnitCandidates.length
834
- );
956
+ const rewardLimit = this.resolveItemRewardLimit(detail, conditionQuantity, triggerQuantity, rewardQuantity, targetUnitCandidates.length);
835
957
  if (rewardLimit <= 0) {
836
- return { products: [], originalAmount: 0, finalAmount: 0 };
958
+ return {
959
+ products: [],
960
+ originalAmount: 0,
961
+ finalAmount: 0
962
+ };
837
963
  }
838
964
  const selectedUnits = [...targetUnitCandidates].sort((a, b) => {
839
965
  const discountDiff = b.discountAmount.minus(a.discountAmount).toNumber();
840
- if (discountDiff !== 0)
841
- return discountDiff;
966
+ if (discountDiff !== 0) return discountDiff;
842
967
  return b.originalPrice.minus(a.originalPrice).toNumber();
843
968
  }).slice(0, rewardLimit);
844
- const selectedByProductId = /* @__PURE__ */ new Map();
969
+ const selectedByProductId = new Map();
845
970
  for (const unit of selectedUnits) {
846
971
  const existing = selectedByProductId.get(unit.product.id);
847
972
  if (existing) {
@@ -855,19 +980,17 @@ var PromotionEvaluator = class {
855
980
  }
856
981
  }
857
982
  const result = [];
858
- let originalAmount = new import_decimal.default(0);
859
- let finalAmount = new import_decimal.default(0);
983
+ let originalAmount = new _decimal.default(0);
984
+ let finalAmount = new _decimal.default(0);
860
985
  for (const product of targetProducts) {
861
986
  const key = this.getProductKey(product);
862
987
  const processedQty = processedQuantityMap.get(key) || 0;
863
988
  const availableQty = product.quantity - processedQty;
864
- if (availableQty <= 0)
865
- continue;
989
+ if (availableQty <= 0) continue;
866
990
  const selected = selectedByProductId.get(product.id);
867
- const selectedQty = (selected == null ? void 0 : selected.quantity) || 0;
991
+ const selectedQty = selected?.quantity || 0;
868
992
  const remainingQty = availableQty - selectedQty;
869
- if (selectedQty <= 0)
870
- continue;
993
+ if (selectedQty <= 0) continue;
871
994
  if (selectedQty > 0) {
872
995
  result.push({
873
996
  ...product,
@@ -880,9 +1003,7 @@ var PromotionEvaluator = class {
880
1003
  inPromotion: true,
881
1004
  isSplit: selectedQty !== product.quantity
882
1005
  });
883
- originalAmount = originalAmount.plus(
884
- selected.originalPrice.mul(selectedQty)
885
- );
1006
+ originalAmount = originalAmount.plus(selected.originalPrice.mul(selectedQty));
886
1007
  finalAmount = finalAmount.plus(selected.finalPrice.mul(selectedQty));
887
1008
  }
888
1009
  if (remainingQty > 0) {
@@ -890,12 +1011,12 @@ var PromotionEvaluator = class {
890
1011
  ...product,
891
1012
  quantity: remainingQty,
892
1013
  finalPrice: this.formatPrice(product.price),
893
- strategyId: void 0,
894
- strategyMetadata: void 0,
1014
+ strategyId: undefined,
1015
+ strategyMetadata: undefined,
895
1016
  inPromotion: false,
896
1017
  isSplit: selectedQty > 0
897
1018
  });
898
- const remainingAmount = new import_decimal.default(product.price).mul(remainingQty);
1019
+ const remainingAmount = new _decimal.default(product.price).mul(remainingQty);
899
1020
  originalAmount = originalAmount.plus(remainingAmount);
900
1021
  finalAmount = finalAmount.plus(remainingAmount);
901
1022
  }
@@ -917,12 +1038,11 @@ var PromotionEvaluator = class {
917
1038
  const key = this.getProductKey(product);
918
1039
  const processedQty = processedQuantityMap.get(key) || 0;
919
1040
  const availableQty = product.quantity - processedQty;
920
- if (availableQty <= 0)
921
- continue;
922
- const matchedBundleIndex = matchedBundleIndexMap == null ? void 0 : matchedBundleIndexMap.get(product.id);
923
- if (matchedBundleIndex !== void 0 && product.bundle) {
1041
+ if (availableQty <= 0) continue;
1042
+ const matchedBundleIndex = matchedBundleIndexMap?.get(product.id);
1043
+ if (matchedBundleIndex !== undefined && product.bundle) {
924
1044
  const bundleItem = product.bundle[matchedBundleIndex];
925
- total += availableQty * ((bundleItem == null ? void 0 : bundleItem.quantity) || 1);
1045
+ total += availableQty * (bundleItem?.quantity || 1);
926
1046
  } else {
927
1047
  total += availableQty;
928
1048
  }
@@ -935,13 +1055,11 @@ var PromotionEvaluator = class {
935
1055
  const key = this.getProductKey(product);
936
1056
  const processedQty = processedQuantityMap.get(key) || 0;
937
1057
  const availableQty = product.quantity - processedQty;
938
- if (availableQty <= 0)
939
- continue;
940
- const originalPrice = new import_decimal.default(product.price || 0);
1058
+ if (availableQty <= 0) continue;
1059
+ const originalPrice = new _decimal.default(product.price || 0);
941
1060
  const finalPrice = this.resolveItemRewardFinalPrice(originalPrice, detail);
942
1061
  const discountAmount = originalPrice.minus(finalPrice);
943
- if (discountAmount.lte(0))
944
- continue;
1062
+ if (discountAmount.lte(0)) continue;
945
1063
  for (let i = 0; i < availableQty; i++) {
946
1064
  units.push({
947
1065
  product,
@@ -954,46 +1072,45 @@ var PromotionEvaluator = class {
954
1072
  return units;
955
1073
  }
956
1074
  resolveItemRewardFinalPrice(originalPrice, detail) {
957
- const rewardValue = new import_decimal.default(Number(detail.rewardValue) || 0);
1075
+ const rewardValue = new _decimal.default(Number(detail.rewardValue) || 0);
958
1076
  let finalPrice;
959
1077
  switch (detail.rewardMethod) {
960
- case "percent_off": {
961
- const percent = import_decimal.default.min(100, import_decimal.default.max(0, rewardValue));
962
- finalPrice = originalPrice.mul(new import_decimal.default(100).minus(percent)).div(100);
963
- break;
964
- }
965
- case "amount_off":
966
- finalPrice = originalPrice.minus(import_decimal.default.max(0, rewardValue));
1078
+ case 'percent_off':
1079
+ {
1080
+ const percent = _decimal.default.min(100, _decimal.default.max(0, rewardValue));
1081
+ finalPrice = originalPrice.mul(new _decimal.default(100).minus(percent)).div(100);
1082
+ break;
1083
+ }
1084
+ case 'amount_off':
1085
+ finalPrice = originalPrice.minus(_decimal.default.max(0, rewardValue));
967
1086
  break;
968
- case "fixed_price":
969
- finalPrice = import_decimal.default.max(0, rewardValue);
1087
+ case 'fixed_price':
1088
+ finalPrice = _decimal.default.max(0, rewardValue);
970
1089
  break;
971
- case "free":
1090
+ case 'free':
972
1091
  default:
973
- finalPrice = new import_decimal.default(0);
1092
+ finalPrice = new _decimal.default(0);
974
1093
  break;
975
1094
  }
976
- return import_decimal.default.max(0, import_decimal.default.min(originalPrice, finalPrice)).toDecimalPlaces(2, import_decimal.default.ROUND_HALF_UP);
1095
+ return _decimal.default.max(0, _decimal.default.min(originalPrice, finalPrice)).toDecimalPlaces(2, _decimal.default.ROUND_HALF_UP);
977
1096
  }
978
1097
  resolveItemRewardLimit(detail, conditionQuantity, triggerQuantity, rewardQuantity, targetUnitCount) {
979
1098
  let limit = 0;
980
1099
  switch (detail.quantityRule) {
981
- case "all_targets":
1100
+ case 'all_targets':
982
1101
  limit = targetUnitCount;
983
1102
  break;
984
- case "max_per_order":
985
- limit = this.normalizePositiveInteger(
986
- detail.maxRewardQuantity ?? detail.rewardQuantity,
987
- rewardQuantity
988
- );
1103
+ case 'max_per_order':
1104
+ limit = this.normalizePositiveInteger(detail.maxRewardQuantity ?? detail.rewardQuantity, rewardQuantity);
989
1105
  break;
990
- case "per_condition_quantity":
1106
+ case 'per_condition_quantity':
991
1107
  default:
992
1108
  limit = Math.floor(conditionQuantity / triggerQuantity) * rewardQuantity;
993
1109
  break;
994
1110
  }
995
1111
  return Math.min(Math.max(0, limit), targetUnitCount);
996
1112
  }
1113
+
997
1114
  /**
998
1115
  * 获取未参与任何促销的商品
999
1116
  */
@@ -1008,7 +1125,7 @@ var PromotionEvaluator = class {
1008
1125
  ...product,
1009
1126
  quantity: remainingQty,
1010
1127
  finalPrice: this.formatPrice(product.price),
1011
- strategyId: void 0,
1128
+ strategyId: undefined,
1012
1129
  inPromotion: false,
1013
1130
  isSplit: remainingQty < product.quantity
1014
1131
  });
@@ -1016,12 +1133,14 @@ var PromotionEvaluator = class {
1016
1133
  }
1017
1134
  return result;
1018
1135
  }
1136
+
1019
1137
  /**
1020
1138
  * 价格统一保留两位小数
1021
1139
  */
1022
1140
  formatPrice(value) {
1023
- return new import_decimal.default(value).toDecimalPlaces(2, import_decimal.default.ROUND_HALF_UP).toNumber();
1141
+ return new _decimal.default(value).toDecimalPlaces(2, _decimal.default.ROUND_HALF_UP).toNumber();
1024
1142
  }
1143
+
1025
1144
  /**
1026
1145
  * 获取商品唯一标识 key
1027
1146
  * 使用商品的 id 作为唯一标识,不同 id 的商品不会被合并
@@ -1029,38 +1148,51 @@ var PromotionEvaluator = class {
1029
1148
  getProductKey(product) {
1030
1149
  return String(product.id);
1031
1150
  }
1151
+
1032
1152
  /**
1033
1153
  * 生成随机ID
1034
1154
  */
1035
1155
  generateRandomId() {
1036
- return Math.floor(Math.random() * 1e9) + Date.now();
1156
+ return Math.floor(Math.random() * 1000000000) + Date.now();
1037
1157
  }
1158
+
1038
1159
  /**
1039
1160
  * 商品匹配结果信息
1040
1161
  */
1041
1162
  getProductMatchInfo(product, config) {
1042
1163
  const productMatchRule = this.findProductMatchRule(config);
1043
1164
  if (!productMatchRule) {
1044
- return { isMatch: true };
1165
+ // 没有商品范围条件,默认适用所有商品(匹配主商品)
1166
+ return {
1167
+ isMatch: true
1168
+ };
1045
1169
  }
1046
1170
  const configProducts = productMatchRule.value;
1171
+
1172
+ // 1. 先检查主商品是否匹配
1047
1173
  if (this.isProductIdMatch(product.product_id, product.product_variant_id, configProducts)) {
1048
- return { isMatch: true };
1174
+ return {
1175
+ isMatch: true
1176
+ };
1049
1177
  }
1178
+
1179
+ // 2. 主商品不匹配,检查 bundle 子商品
1050
1180
  if (product.bundle && product.bundle.length > 0) {
1051
1181
  for (let i = 0; i < product.bundle.length; i++) {
1052
1182
  const bundleItem = product.bundle[i];
1053
- if (this.isProductIdMatch(
1054
- bundleItem._bundle_product_id,
1055
- bundleItem.product_variant_id,
1056
- configProducts
1057
- )) {
1058
- return { isMatch: true, matchedBundleIndex: i };
1183
+ if (this.isProductIdMatch(bundleItem._bundle_product_id, bundleItem.product_variant_id, configProducts)) {
1184
+ return {
1185
+ isMatch: true,
1186
+ matchedBundleIndex: i
1187
+ };
1059
1188
  }
1060
1189
  }
1061
1190
  }
1062
- return { isMatch: false };
1191
+ return {
1192
+ isMatch: false
1193
+ };
1063
1194
  }
1195
+
1064
1196
  /**
1065
1197
  * 检查商品是否在策略的适用范围内
1066
1198
  */
@@ -1071,59 +1203,61 @@ var PromotionEvaluator = class {
1071
1203
  return this.getProductMatchInfo(product, config).isMatch;
1072
1204
  }
1073
1205
  shouldSkipStrategyForProduct(product, config) {
1074
- return Boolean(product._promotionOnlyForItemReward) && !this.hasActionType(config, import_type.PROMOTION_ACTION_TYPES.ITEM_REWARD);
1206
+ return Boolean(product._promotionOnlyForItemReward) && !this.hasActionType(config, _type.PROMOTION_ACTION_TYPES.ITEM_REWARD);
1075
1207
  }
1076
1208
  hasActionType(config, actionType) {
1077
- return (config.actions || []).some((action) => (action == null ? void 0 : action.type) === actionType);
1209
+ return (config.actions || []).some(action => action?.type === actionType);
1078
1210
  }
1211
+
1079
1212
  /**
1080
1213
  * 查找商品匹配规则
1081
1214
  */
1082
1215
  findProductMatchRule(config) {
1083
- var _a;
1084
- if (!((_a = config == null ? void 0 : config.conditions) == null ? void 0 : _a.rules)) {
1216
+ if (!config?.conditions?.rules) {
1085
1217
  return null;
1086
1218
  }
1087
- return config.conditions.rules.find(
1088
- (rule) => rule.field === "productIdAndVariantId" && (rule.operator === "product_match" || rule.operator === "object_in")
1089
- );
1219
+ return config.conditions.rules.find(rule => rule.field === 'productIdAndVariantId' && (rule.operator === 'product_match' || rule.operator === 'object_in'));
1090
1220
  }
1221
+
1091
1222
  /**
1092
1223
  * 检查商品ID是否匹配配置列表
1093
1224
  */
1094
1225
  isProductIdMatch(productId, productVariantId, configProducts) {
1095
- return configProducts.some((config) => {
1226
+ return configProducts.some(config => {
1227
+ // 首先检查 product_id
1096
1228
  if (config.product_id !== productId) {
1097
1229
  return false;
1098
1230
  }
1231
+
1232
+ // 如果配置的 product_variant_id = 0,只需要 product_id 匹配
1099
1233
  if (config.product_variant_id === 0) {
1100
1234
  return true;
1101
1235
  }
1236
+
1237
+ // 否则需要 product_variant_id 精确匹配
1102
1238
  return config.product_variant_id === productVariantId;
1103
1239
  });
1104
1240
  }
1241
+
1105
1242
  /**
1106
1243
  * 检查商品是否匹配配置(兼容旧方法)
1107
1244
  */
1108
1245
  isProductMatch(product, configProducts) {
1109
1246
  return this.isProductIdMatch(product.product_id, product.product_variant_id, configProducts);
1110
1247
  }
1248
+
1111
1249
  /**
1112
1250
  * 获取展示配置
1113
1251
  */
1114
1252
  getDisplayConfig(config) {
1115
- var _a;
1116
1253
  const custom = config.metadata.custom;
1117
- if ((_a = custom == null ? void 0 : custom.display) == null ? void 0 : _a.product_card) {
1254
+ if (custom?.display?.product_card) {
1118
1255
  return {
1119
1256
  text: custom.display.product_card.text,
1120
1257
  type: custom.display.product_card.type
1121
1258
  };
1122
1259
  }
1123
- return void 0;
1260
+ return undefined;
1124
1261
  }
1125
- };
1126
- // Annotate the CommonJS export names for ESM import in node:
1127
- 0 && (module.exports = {
1128
- PromotionEvaluator
1129
- });
1262
+ }
1263
+ exports.PromotionEvaluator = PromotionEvaluator;