@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,375 +1,471 @@
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/walletPass/utils.ts
30
- var utils_exports = {};
31
- __export(utils_exports, {
32
- getApplicableProductIds: () => getApplicableProductIds,
33
- getBundleItemDiscountDifference: () => getBundleItemDiscountDifference,
34
- getBundleItemIsDiscountPrice: () => getBundleItemIsDiscountPrice,
35
- getBundleItemIsMarkupOrDiscountPrice: () => getBundleItemIsMarkupOrDiscountPrice,
36
- getBundleItemIsMarkupPrice: () => getBundleItemIsMarkupPrice,
37
- getBundleItemIsOriginalPrice: () => getBundleItemIsOriginalPrice,
38
- getBundleItemPrice: () => getBundleItemPrice,
39
- getBundleItemTaxAndFeeRoundingRemainder: () => getBundleItemTaxAndFeeRoundingRemainder,
40
- getMainProductPrice: () => getMainProductPrice,
41
- getProductDiscountDifference: () => getProductDiscountDifference,
42
- getProductQuantity: () => getProductQuantity,
43
- getTaxAndFeeRoundingRemainder: () => getTaxAndFeeRoundingRemainder,
44
- processVouchers: () => processVouchers,
45
- recalculateVouchers: () => recalculateVouchers,
46
- resolveWalletPassLineKey: () => resolveWalletPassLineKey
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
47
5
  });
48
- module.exports = __toCommonJS(utils_exports);
49
- var import_decimal = __toESM(require("decimal.js"));
50
- var getProductQuantity = (product) => {
6
+ exports.getTaxAndFeeRoundingRemainder = exports.getProductQuantity = exports.getProductDiscountDifference = exports.getMainProductPrice = exports.getBundleItemTaxAndFeeRoundingRemainder = exports.getBundleItemPrice = exports.getBundleItemIsOriginalPrice = exports.getBundleItemIsMarkupPrice = exports.getBundleItemIsMarkupOrDiscountPrice = exports.getBundleItemIsDiscountPrice = exports.getBundleItemDiscountDifference = exports.getApplicableProductIds = void 0;
7
+ exports.processVouchers = processVouchers;
8
+ exports.recalculateVouchers = recalculateVouchers;
9
+ exports.resolveWalletPassLineKey = resolveWalletPassLineKey;
10
+ var _decimal = _interopRequireDefault(require("decimal.js"));
11
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
12
+ /** 订单商品数量 */
13
+ const getProductQuantity = product => {
51
14
  return product.quantity || product.product_quantity || 1;
52
15
  };
16
+
17
+ /**
18
+ * 订单商品行唯一键,用于 maxPassesPerItem 按行、按件配额(同 SPU 不同规格视为不同行)。
19
+ */
20
+ exports.getProductQuantity = getProductQuantity;
53
21
  function resolveWalletPassLineKey(product, indexInOrder) {
54
- const m = (product == null ? void 0 : product.metadata) || {};
22
+ const m = product?.metadata || {};
55
23
  const u1 = m.product_unique;
56
- if (u1 != null && String(u1).length > 0)
57
- return String(u1);
24
+ if (u1 != null && String(u1).length > 0) return String(u1);
58
25
  const u2 = m.unique_identification_number;
59
- if (u2 != null && String(u2).length > 0)
60
- return String(u2);
61
- if ((product == null ? void 0 : product.id) != null && String(product.id).length > 0)
62
- return `order_item:${product.id}`;
63
- if ((product == null ? void 0 : product.product_unique_string) != null && String(product.product_unique_string).length > 0) {
26
+ if (u2 != null && String(u2).length > 0) return String(u2);
27
+ if (product?.id != null && String(product.id).length > 0) return `order_item:${product.id}`;
28
+ if (product?.product_unique_string != null && String(product.product_unique_string).length > 0) {
64
29
  return String(product.product_unique_string);
65
30
  }
66
31
  return `order_line_${indexInOrder}`;
67
32
  }
68
33
  function getExpandedOrderLineQuantity(p) {
69
- if ((p == null ? void 0 : p._orderLineQuantity) != null && p._orderLineQuantity > 0)
70
- return p._orderLineQuantity;
34
+ if (p?._orderLineQuantity != null && p._orderLineQuantity > 0) return p._orderLineQuantity;
71
35
  return getProductQuantity(p);
72
36
  }
37
+
38
+ /** 该行允许消耗的「券次」上限:maxPassesPerItem × 订单行件数 */
73
39
  function getMaxPassSlotsForExpandedLine(maxPassesPerItem, p) {
74
- if (maxPassesPerItem <= 0)
75
- return Infinity;
40
+ if (maxPassesPerItem <= 0) return Infinity;
76
41
  return maxPassesPerItem * getExpandedOrderLineQuantity(p);
77
42
  }
43
+
44
+ /**
45
+ * 本次抵扣占用多少「券次」配额:有抵扣时至少占 1,并与按金额折算的件数挂钩,不超过剩余额度。
46
+ */
78
47
  function computePassSlotsIncrement(deductAmount, deductQty, maxPassesPerItem, orderLineQuantity, currentUsage) {
79
- if (maxPassesPerItem <= 0 || !deductAmount.greaterThan(0))
80
- return 0;
48
+ if (maxPassesPerItem <= 0 || !deductAmount.greaterThan(0)) return 0;
81
49
  const cap = maxPassesPerItem * orderLineQuantity;
82
50
  const room = Math.max(0, cap - currentUsage);
83
- if (room <= 0)
84
- return 0;
51
+ if (room <= 0) return 0;
85
52
  const raw = Math.max(1, Math.ceil(Number(deductQty)) || 1);
86
53
  return Math.min(raw, room);
87
54
  }
88
55
  function applyMaxPassUsageIncrements(usageMap, walletPassProductId, maxPassesPerItem, deductionDetails) {
89
- deductionDetails.forEach((detail) => {
90
- var _a;
56
+ deductionDetails.forEach(detail => {
91
57
  const lineKey = detail.lineKey;
92
- if (!lineKey || maxPassesPerItem <= 0)
93
- return;
58
+ if (!lineKey || maxPassesPerItem <= 0) return;
94
59
  const orderLineQty = detail.orderLineQuantity != null && detail.orderLineQuantity > 0 ? detail.orderLineQuantity : 1;
95
- const cur = ((_a = usageMap.get(walletPassProductId)) == null ? void 0 : _a.get(lineKey)) || 0;
96
- const inc = computePassSlotsIncrement(
97
- new import_decimal.default(detail.deductAmount),
98
- detail.deductQuantity,
99
- maxPassesPerItem,
100
- orderLineQty,
101
- cur
102
- );
103
- if (inc <= 0)
104
- return;
105
- if (!usageMap.has(walletPassProductId))
106
- usageMap.set(walletPassProductId, /* @__PURE__ */ new Map());
60
+ const cur = usageMap.get(walletPassProductId)?.get(lineKey) || 0;
61
+ const inc = computePassSlotsIncrement(new _decimal.default(detail.deductAmount), detail.deductQuantity, maxPassesPerItem, orderLineQty, cur);
62
+ if (inc <= 0) return;
63
+ if (!usageMap.has(walletPassProductId)) usageMap.set(walletPassProductId, new Map());
107
64
  const inner = usageMap.get(walletPassProductId);
108
65
  inner.set(lineKey, cur + inc);
109
66
  });
110
67
  }
111
- var getRecommendedAmount = (voucher) => {
112
- console.log("voucher312", voucher);
113
- const { config, recommended_usage_amount, recommended_pure_product_usage_amount } = voucher;
114
- const deductTaxAndFee = (config == null ? void 0 : config.deductTaxAndFee) ?? true;
68
+
69
+ // 辅助函数:根据 deductTaxAndFee 配置获取推荐使用金额
70
+ const getRecommendedAmount = voucher => {
71
+ const {
72
+ config,
73
+ recommended_usage_amount,
74
+ recommended_pure_product_usage_amount
75
+ } = voucher;
76
+ const deductTaxAndFee = config?.deductTaxAndFee ?? true; // 默认开启抵扣税费
77
+
78
+ // 如果开启抵扣税费,使用 recommended_usage_amount(包含税费)
79
+ // 如果关闭抵扣税费,使用 recommended_pure_product_usage_amount(仅商品金额)
115
80
  return deductTaxAndFee ? recommended_usage_amount : recommended_pure_product_usage_amount ?? recommended_usage_amount;
116
81
  };
117
- var getApplicableProductIds = (voucher) => {
118
- const { available_product_type, available_product_ids } = voucher;
119
- const productType = available_product_type || "product_all";
120
- if (productType === "product_all") {
82
+
83
+ // 辅助函数:获取适用商品ID列表
84
+ const getApplicableProductIds = voucher => {
85
+ const {
86
+ available_product_type,
87
+ available_product_ids
88
+ } = voucher;
89
+
90
+ // 如果没有 available_product_type,默认为 product_all
91
+ const productType = available_product_type || 'product_all';
92
+
93
+ // product_all 时,返回 null 表示适用所有商品
94
+ if (productType === 'product_all') {
121
95
  return null;
122
96
  }
123
- if (productType === "product_collection" || productType === "products") {
97
+
98
+ // product_collection 或 products 时,返回指定的商品ID列表
99
+ if (productType === 'product_collection' || productType === 'products') {
124
100
  return available_product_ids || [];
125
101
  }
102
+
103
+ // 其他情况默认返回空数组(不适用任何商品)
126
104
  return [];
127
105
  };
128
- var getApplicableProductsAmount = (voucher, productsData) => {
106
+
107
+ // 辅助函数:计算适用商品的总金额(基于剩余金额)
108
+ exports.getApplicableProductIds = getApplicableProductIds;
109
+ const getApplicableProductsAmount = (voucher, productsData) => {
129
110
  const applicableProductIds = getApplicableProductIds(voucher);
130
- const { config } = voucher;
131
- const deductTaxAndFee = (config == null ? void 0 : config.deductTaxAndFee) ?? true;
132
- const amountField = deductTaxAndFee ? "remainingAmountWithTax" : "remainingAmountPure";
111
+ const {
112
+ config
113
+ } = voucher;
114
+ const deductTaxAndFee = config?.deductTaxAndFee ?? true;
115
+
116
+ // 根据券的配置选择使用哪个金额字段
117
+ const amountField = deductTaxAndFee ? 'remainingAmountWithTax' : 'remainingAmountPure';
118
+
119
+ // 如果为 null,适用于所有商品
133
120
  if (applicableProductIds === null) {
134
- return productsData.filter((p) => p[amountField].greaterThan(0)).reduce((sum, p) => sum.plus(p[amountField]), new import_decimal.default(0));
121
+ return productsData.filter(p => p[amountField].greaterThan(0)).reduce((sum, p) => sum.plus(p[amountField]), new _decimal.default(0));
135
122
  }
123
+
124
+ // 如果为空数组,不适用任何商品
136
125
  if (applicableProductIds.length === 0) {
137
- return new import_decimal.default(0);
126
+ return new _decimal.default(0);
138
127
  }
139
- return productsData.filter((p) => applicableProductIds.includes(p.product_id) && p[amountField].greaterThan(0)).reduce((sum, p) => sum.plus(p[amountField]), new import_decimal.default(0));
128
+
129
+ // 计算指定商品的总金额
130
+ return productsData.filter(p => applicableProductIds.includes(p.product_id) && p[amountField].greaterThan(0)).reduce((sum, p) => sum.plus(p[amountField]), new _decimal.default(0));
140
131
  };
141
- var getApplicableProducts = (voucher, productsData) => {
132
+
133
+ // 辅助函数:获取适用的商品列表
134
+ const getApplicableProducts = (voucher, productsData) => {
142
135
  const applicableProductIds = getApplicableProductIds(voucher);
136
+
137
+ // 如果为 null,适用于所有商品
143
138
  if (applicableProductIds === null) {
144
139
  return productsData;
145
140
  }
141
+
142
+ // 如果为空数组,不适用任何商品
146
143
  if (applicableProductIds.length === 0) {
147
144
  return [];
148
145
  }
149
- return productsData.filter((p) => applicableProductIds.includes(p.product_id));
146
+
147
+ // 返回指定的商品列表
148
+ return productsData.filter(p => applicableProductIds.includes(p.product_id));
150
149
  };
150
+
151
+ /**
152
+ * 优惠券处理函数
153
+ * @param applicableVouchers 可用的券列表
154
+ * @param orderTotalAmount 订单总金额
155
+ * @param products 订单商品列表
156
+ * @returns 返回推荐券列表和全部列表,每个券包含 _available_max_amount 和 _unified_available_status
157
+ */
151
158
  function processVouchers(applicableVouchers, orderTotalAmount, products) {
152
- console.log(products, "products123");
159
+ // 拆分商品数据,同时维护含税和不含税两个金额池
160
+ // remainingAmountWithTax: 含税费的剩余可抵扣金额
161
+ // remainingAmountPure: 纯商品金额(不含税费)的剩余可抵扣金额
162
+ // 在抵扣时,会根据券的 deductTaxAndFee 配置选择从哪个金额池扣除
153
163
  const productsCopy = expandProductsWithBundleItems(products, true);
154
- let remainingOrderAmount = new import_decimal.default(orderTotalAmount);
164
+ let remainingOrderAmount = new _decimal.default(orderTotalAmount); // 订单剩余应付金额
165
+
166
+ // ========== 辅助工具:maxPassesPerItem(按订单行 + 件数)追踪 ==========
155
167
  const getItemPassUsage = (usageMap, walletPassProductId, lineKey) => {
156
- var _a;
157
- return ((_a = usageMap.get(walletPassProductId)) == null ? void 0 : _a.get(lineKey)) || 0;
168
+ return usageMap.get(walletPassProductId)?.get(lineKey) || 0;
158
169
  };
159
- const filterByMaxPassesPerItem = (products2, usageMap, walletPassProductId, maxPassesPerItem) => {
160
- if (maxPassesPerItem <= 0)
161
- return products2;
162
- return products2.filter((p) => {
170
+
171
+ // 按 maxPassesPerItem 过滤:排除已达到该行「券次」上限的展开行(按 _walletPassLineKey)
172
+ const filterByMaxPassesPerItem = (products, usageMap, walletPassProductId, maxPassesPerItem) => {
173
+ if (maxPassesPerItem <= 0) return products;
174
+ return products.filter(p => {
163
175
  const lineKey = p._walletPassLineKey;
164
- if (!lineKey)
165
- return true;
176
+ if (!lineKey) return true;
166
177
  return getItemPassUsage(usageMap, walletPassProductId, lineKey) < getMaxPassSlotsForExpandedLine(maxPassesPerItem, p);
167
178
  });
168
179
  };
180
+ // ================================================================
181
+
182
+ // 辅助函数:计算单张券的 _available_max_amount
169
183
  const calculateAvailableMaxAmount = (voucher, productsData, itemPassUsage) => {
170
- const { config } = voucher;
171
- const { maxDeductionAmount = 0, allowCrossProduct = true, applicableProductLimit = 0, deductTaxAndFee = true, maxPassesPerItem = 0 } = config ?? {};
184
+ const {
185
+ config
186
+ } = voucher;
187
+ const {
188
+ maxDeductionAmount = 0,
189
+ allowCrossProduct = true,
190
+ applicableProductLimit = 0,
191
+ deductTaxAndFee = true,
192
+ maxPassesPerItem = 0
193
+ } = config ?? {};
194
+
195
+ // 根据 deductTaxAndFee 配置获取推荐金额
172
196
  const recommendedAmount = getRecommendedAmount(voucher);
173
- const baseAmount = import_decimal.default.min(
174
- new import_decimal.default(recommendedAmount),
175
- new import_decimal.default(maxDeductionAmount)
176
- );
177
- const unitPriceField = deductTaxAndFee ? "unitPriceWithTax" : "unitPricePure";
178
- const amountField = deductTaxAndFee ? "remainingAmountWithTax" : "remainingAmountPure";
179
- let applicableProducts = getApplicableProducts(voucher, productsData).filter((p) => p[amountField].greaterThan(0));
197
+
198
+ // 基础值 = min(recommendedAmount, maxDeductionAmount)
199
+ const baseAmount = _decimal.default.min(new _decimal.default(recommendedAmount), new _decimal.default(maxDeductionAmount));
200
+
201
+ // 根据券的配置选择使用哪个单价字段和金额字段
202
+ const unitPriceField = deductTaxAndFee ? 'unitPriceWithTax' : 'unitPricePure';
203
+ const amountField = deductTaxAndFee ? 'remainingAmountWithTax' : 'remainingAmountPure';
204
+
205
+ // 获取适用商品列表(只筛选有剩余金额的商品)
206
+ let applicableProducts = getApplicableProducts(voucher, productsData).filter(p => p[amountField].greaterThan(0));
207
+
208
+ // 按 maxPassesPerItem 过滤:排除已达到单商品可用卡券上限的商品
180
209
  if (itemPassUsage) {
181
210
  applicableProducts = filterByMaxPassesPerItem(applicableProducts, itemPassUsage, voucher.product_id, maxPassesPerItem);
182
211
  }
183
212
  if (applicableProducts.length === 0) {
184
- return new import_decimal.default(0);
213
+ return new _decimal.default(0);
185
214
  }
186
- let finalApplicableAmount = new import_decimal.default(0);
215
+
216
+ // 根据跨商品配置计算适用商品金额
217
+ let finalApplicableAmount = new _decimal.default(0);
187
218
  if (allowCrossProduct) {
219
+ // 跨商品券:可以抵扣多个商品
188
220
  if (applicableProductLimit > 0) {
221
+ // 有数量限制:按剩余金额从高到低排序,按动态计算的可抵扣数量累计直到达到 limit
189
222
  const sortedProducts = [...applicableProducts].sort((a, b) => a[amountField].comparedTo(b[amountField]) > 0 ? -1 : 1);
190
223
  let remainingLimit = applicableProductLimit;
191
224
  for (const product of sortedProducts) {
192
- if (remainingLimit <= 0)
193
- break;
225
+ if (remainingLimit <= 0) break;
226
+ // 动态计算当前可抵扣数量 = ceil(剩余金额 / 单价)
194
227
  const currentAvailableQty = Math.ceil(product[amountField].dividedBy(product[unitPriceField]).toNumber());
195
228
  const deductQty = Math.min(currentAvailableQty, remainingLimit);
196
- const deductAmount = import_decimal.default.min(
197
- product[unitPriceField].times(deductQty),
198
- product[amountField]
199
- );
229
+ // 实际可抵扣金额 = min(数量 * 单价, 剩余金额)
230
+ const deductAmount = _decimal.default.min(product[unitPriceField].times(deductQty), product[amountField]);
200
231
  finalApplicableAmount = finalApplicableAmount.plus(deductAmount);
201
232
  remainingLimit -= deductQty;
202
233
  }
203
234
  } else {
204
- finalApplicableAmount = applicableProducts.reduce(
205
- (sum, p) => sum.plus(p[amountField]),
206
- new import_decimal.default(0)
207
- );
235
+ // 无数量限制:所有适用商品的剩余金额总和
236
+ finalApplicableAmount = applicableProducts.reduce((sum, p) => sum.plus(p[amountField]), new _decimal.default(0));
208
237
  }
209
238
  } else {
210
- const maxProduct = applicableProducts.reduce(
211
- (max, p) => p[unitPriceField].greaterThan(max[unitPriceField]) ? p : max
212
- );
239
+ // 非跨商品券:选择单价最高的商品(优先抵扣高价商品)
240
+ const maxProduct = applicableProducts.reduce((max, p) => p[unitPriceField].greaterThan(max[unitPriceField]) ? p : max);
241
+ // maxPassesPerItem 限制每张券最多抵扣的单位数
213
242
  if (maxPassesPerItem > 0) {
214
- finalApplicableAmount = import_decimal.default.min(
215
- maxProduct[unitPriceField].times(maxPassesPerItem),
216
- maxProduct[amountField]
217
- );
243
+ finalApplicableAmount = _decimal.default.min(maxProduct[unitPriceField].times(maxPassesPerItem), maxProduct[amountField]);
218
244
  } else {
219
245
  finalApplicableAmount = maxProduct[amountField];
220
246
  }
221
247
  }
222
- return import_decimal.default.min(baseAmount, finalApplicableAmount, remainingOrderAmount);
248
+
249
+ // 返回最小值
250
+ return _decimal.default.min(baseAmount, finalApplicableAmount, remainingOrderAmount);
223
251
  };
224
- const isVoucherAvailable = (voucher, productsData, usedVoucherCounts2, itemPassUsage) => {
225
- const { config, id, product_id } = voucher;
252
+
253
+ // 辅助函数:判断券是否可用,返回可用性和原因代码
254
+ const isVoucherAvailable = (voucher, productsData, usedVoucherCounts, itemPassUsage) => {
255
+ const {
256
+ config,
257
+ id,
258
+ product_id
259
+ } = voucher;
260
+
261
+ // 根据 deductTaxAndFee 配置获取推荐金额
226
262
  const recommendedAmount = getRecommendedAmount(voucher);
263
+
264
+ // 余额为 0
227
265
  if (recommendedAmount <= 0) {
228
- return { isAvailable: false, reasonCode: "not_meet_the_required_conditions" };
266
+ return {
267
+ isAvailable: false,
268
+ reasonCode: 'not_meet_the_required_conditions'
269
+ };
229
270
  }
271
+
272
+ // 订单金额已抵扣完
230
273
  if (remainingOrderAmount.lessThanOrEqualTo(0)) {
231
- return { isAvailable: false, reasonCode: "exceeds_the_maximum_deduction_limit" };
274
+ return {
275
+ isAvailable: false,
276
+ reasonCode: 'exceeds_the_maximum_deduction_limit'
277
+ };
232
278
  }
279
+
280
+ // 适用商品金额已被抵扣完
233
281
  const applicableAmount = getApplicableProductsAmount(voucher, productsData);
234
282
  if (applicableAmount.lessThanOrEqualTo(0)) {
235
- return { isAvailable: false, reasonCode: "not_meet_the_required_conditions" };
283
+ return {
284
+ isAvailable: false,
285
+ reasonCode: 'not_meet_the_required_conditions'
286
+ };
236
287
  }
237
- if (((config == null ? void 0 : config.maxUsagePerOrder) || 0) > 0) {
238
- const usedCount = usedVoucherCounts2.get(product_id) || 0;
239
- if (usedCount >= ((config == null ? void 0 : config.maxUsagePerOrder) || 0)) {
240
- return { isAvailable: false, reasonCode: "usage_limit_reached" };
288
+
289
+ // 检查 maxUsagePerOrder(同 product_id 的券),0 表示不限制
290
+ if ((config?.maxUsagePerOrder || 0) > 0) {
291
+ const usedCount = usedVoucherCounts.get(product_id) || 0;
292
+ if (usedCount >= (config?.maxUsagePerOrder || 0)) {
293
+ return {
294
+ isAvailable: false,
295
+ reasonCode: 'usage_limit_reached'
296
+ };
241
297
  }
242
298
  }
243
- const maxPassesPerItem = (config == null ? void 0 : config.maxPassesPerItem) || 0;
299
+
300
+ // 检查 maxPassesPerItem:如果所有适用商品都已达到单商品可用卡券上限,则不可用
301
+ const maxPassesPerItem = config?.maxPassesPerItem || 0;
244
302
  if (maxPassesPerItem > 0 && itemPassUsage) {
245
- const deductTaxAndFee = (config == null ? void 0 : config.deductTaxAndFee) ?? true;
246
- const amountField = deductTaxAndFee ? "remainingAmountWithTax" : "remainingAmountPure";
247
- const availableAfterPassLimit = getApplicableProducts(voucher, productsData).filter((p) => p[amountField].greaterThan(0)).filter((p) => {
303
+ const deductTaxAndFee = config?.deductTaxAndFee ?? true;
304
+ const amountField = deductTaxAndFee ? 'remainingAmountWithTax' : 'remainingAmountPure';
305
+ const availableAfterPassLimit = getApplicableProducts(voucher, productsData).filter(p => p[amountField].greaterThan(0)).filter(p => {
248
306
  const lineKey = p._walletPassLineKey;
249
- if (!lineKey)
250
- return true;
307
+ if (!lineKey) return true;
251
308
  return getItemPassUsage(itemPassUsage, product_id, lineKey) < getMaxPassSlotsForExpandedLine(maxPassesPerItem, p);
252
309
  });
253
310
  if (availableAfterPassLimit.length === 0) {
254
- return { isAvailable: false, reasonCode: "max_passes_per_item_reached" };
311
+ return {
312
+ isAvailable: false,
313
+ reasonCode: 'max_passes_per_item_reached'
314
+ };
255
315
  }
256
316
  }
257
- return { isAvailable: true };
317
+ return {
318
+ isAvailable: true
319
+ };
258
320
  };
259
- const usedVoucherCountsForAll = /* @__PURE__ */ new Map();
260
- const allVouchersWithStatus = applicableVouchers.map((voucher) => {
261
- const _available_max_amount = calculateAvailableMaxAmount(
262
- voucher,
263
- productsCopy
264
- );
265
- const availabilityResult = isVoucherAvailable(
266
- voucher,
267
- productsCopy,
268
- usedVoucherCountsForAll
269
- );
321
+
322
+ // ========== 第一步:为所有券计算初始的 _available_max_amount 和 _unified_available_status ==========
323
+ const usedVoucherCountsForAll = new Map();
324
+ const allVouchersWithStatus = applicableVouchers.map(voucher => {
325
+ const _available_max_amount = calculateAvailableMaxAmount(voucher, productsCopy);
326
+ const availabilityResult = isVoucherAvailable(voucher, productsCopy, usedVoucherCountsForAll);
270
327
  const _unified_available_status = availabilityResult.isAvailable ? 1 : 0;
271
328
  return {
272
329
  ...voucher,
273
330
  _available_max_amount: _available_max_amount.toNumber(),
274
331
  // 转换为数字
275
332
  _unified_available_status,
276
- ...availabilityResult.reasonCode && { reasonCode: availabilityResult.reasonCode }
333
+ ...(availabilityResult.reasonCode && {
334
+ reasonCode: availabilityResult.reasonCode
335
+ })
277
336
  };
278
337
  });
338
+
339
+ // ========== 第二步:计算推荐券列表 ==========
279
340
  const recommendedVouchers = [];
280
- const usedVoucherCounts = /* @__PURE__ */ new Map();
341
+ const usedVoucherCounts = new Map(); // 跟踪每个券 ID 的使用次数
342
+
343
+ // 重置商品余额追踪(同时维护含税和不含税两个金额池)
281
344
  const productsForRecommendation = expandProductsWithBundleItems(products, true);
282
- remainingOrderAmount = new import_decimal.default(orderTotalAmount);
283
- const itemPassUsageMap = /* @__PURE__ */ new Map();
284
- const applyVoucher = (voucher) => {
285
- const availabilityCheck = isVoucherAvailable(
286
- voucher,
287
- productsForRecommendation,
288
- usedVoucherCounts,
289
- itemPassUsageMap
290
- );
345
+ remainingOrderAmount = new _decimal.default(orderTotalAmount);
346
+ // 追踪推荐阶段每个 Wallet Pass 商品对每个订单商品行的已使用卡券次数(按行唯一键)
347
+ const itemPassUsageMap = new Map();
348
+
349
+ /**
350
+ // 分类券:未跨商品券 vs 跨商品券
351
+ const nonCrossProductVouchers = applicableVouchers.filter(
352
+ (v) => !v.config?.allowCrossProduct,
353
+ );
354
+ const crossProductVouchers = applicableVouchers.filter(
355
+ (v) => v.config?.allowCrossProduct,
356
+ );
357
+ // 按 tag 分组
358
+ const groupByType = (vouchers: any[]) => {
359
+ const groups = new Map<string, any[]>();
360
+ vouchers.forEach((v) => {
361
+ const tag = v.tag;
362
+ if (!groups.has(tag)) {
363
+ groups.set(tag, []);
364
+ }
365
+ groups.get(tag)!.push(v);
366
+ });
367
+ return groups;
368
+ };
369
+ // 对每组券按金额(balance)从大到小排序
370
+ const sortVouchersByAmount = (vouchers: Voucher[]) => {
371
+ return [...vouchers].sort((a, b) => getRecommendedAmount(b) - getRecommendedAmount(a));
372
+ };
373
+ */
374
+
375
+ // 处理单张券的抵扣
376
+ const applyVoucher = voucher => {
377
+ // 检查是否可用
378
+ const availabilityCheck = isVoucherAvailable(voucher, productsForRecommendation, usedVoucherCounts, itemPassUsageMap);
291
379
  if (!availabilityCheck.isAvailable) {
292
380
  return false;
293
381
  }
294
- const { config, id, product_id } = voucher;
295
- const { maxDeductionAmount = 0, allowCrossProduct = true, applicableProductLimit = 0, deductTaxAndFee = true, maxPassesPerItem = 0 } = config ?? {};
296
- const unitPriceField = deductTaxAndFee ? "unitPriceWithTax" : "unitPricePure";
297
- const amountField = deductTaxAndFee ? "remainingAmountWithTax" : "remainingAmountPure";
298
- let applicableProducts = getApplicableProducts(
299
- voucher,
300
- productsForRecommendation
301
- ).filter((p) => p[amountField].greaterThan(0));
382
+ const {
383
+ config,
384
+ id,
385
+ product_id
386
+ } = voucher;
387
+ const {
388
+ maxDeductionAmount = 0,
389
+ allowCrossProduct = true,
390
+ applicableProductLimit = 0,
391
+ deductTaxAndFee = true,
392
+ maxPassesPerItem = 0
393
+ } = config ?? {};
394
+
395
+ // 根据券的配置选择使用哪个单价字段和金额字段
396
+ const unitPriceField = deductTaxAndFee ? 'unitPriceWithTax' : 'unitPricePure';
397
+ const amountField = deductTaxAndFee ? 'remainingAmountWithTax' : 'remainingAmountPure';
398
+
399
+ // 获取适用商品(只筛选有剩余金额的商品)
400
+ let applicableProducts = getApplicableProducts(voucher, productsForRecommendation).filter(p => p[amountField].greaterThan(0));
401
+
402
+ // 按 maxPassesPerItem 过滤:排除已达到单商品可用卡券上限的商品
302
403
  applicableProducts = filterByMaxPassesPerItem(applicableProducts, itemPassUsageMap, product_id, maxPassesPerItem);
303
- if (applicableProducts.length === 0)
304
- return false;
305
- const usageAmount = typeof voucher.edit_current_amount === "number" ? voucher.edit_current_amount : getRecommendedAmount(voucher);
306
- const baseAmount = import_decimal.default.min(
307
- new import_decimal.default(usageAmount),
308
- new import_decimal.default(maxDeductionAmount)
309
- );
310
- let calculatedAvailableMaxAmount = new import_decimal.default(0);
404
+ if (applicableProducts.length === 0) return false;
405
+
406
+ // ========== 关键修改:在应用券之前,基于当前剩余金额计算 _available_max_amount ==========
407
+ // 优先使用用户手动编辑的金额,否则根据 deductTaxAndFee 配置获取推荐金额
408
+ const usageAmount = typeof voucher.edit_current_amount === 'number' ? voucher.edit_current_amount : getRecommendedAmount(voucher);
409
+ const baseAmount = _decimal.default.min(new _decimal.default(usageAmount), new _decimal.default(maxDeductionAmount));
410
+ let calculatedAvailableMaxAmount = new _decimal.default(0);
311
411
  if (allowCrossProduct) {
412
+ // 跨商品券:按 quantity 限制计算可抵扣金额
312
413
  if (applicableProductLimit > 0) {
414
+ // 按剩余金额从高到低排序
313
415
  const sortedProducts = [...applicableProducts].sort((a, b) => a[amountField].comparedTo(b[amountField]) > 0 ? -1 : 1);
314
416
  let remainingLimit = applicableProductLimit;
315
417
  for (const product of sortedProducts) {
316
- if (remainingLimit <= 0)
317
- break;
418
+ if (remainingLimit <= 0) break;
419
+ // 动态计算当前可抵扣数量 = ceil(剩余金额 / 单价)
318
420
  const currentAvailableQty = Math.ceil(product[amountField].dividedBy(product[unitPriceField]).toNumber());
319
421
  const deductQty = Math.min(currentAvailableQty, remainingLimit);
320
- const deductAmount = import_decimal.default.min(
321
- product[unitPriceField].times(deductQty),
322
- product[amountField]
323
- );
422
+ // 实际可抵扣金额 = min(数量 * 单价, 剩余金额)
423
+ const deductAmount = _decimal.default.min(product[unitPriceField].times(deductQty), product[amountField]);
324
424
  calculatedAvailableMaxAmount = calculatedAvailableMaxAmount.plus(deductAmount);
325
425
  remainingLimit -= deductQty;
326
426
  }
327
427
  } else {
328
- calculatedAvailableMaxAmount = applicableProducts.reduce(
329
- (sum, p) => sum.plus(p[amountField]),
330
- new import_decimal.default(0)
331
- );
428
+ // 无数量限制:所有适用商品的剩余金额总和
429
+ calculatedAvailableMaxAmount = applicableProducts.reduce((sum, p) => sum.plus(p[amountField]), new _decimal.default(0));
332
430
  }
333
431
  } else {
334
- const maxProduct = applicableProducts.reduce(
335
- (max, p) => p[unitPriceField].greaterThan(max[unitPriceField]) ? p : max
336
- );
432
+ // 非跨商品券:选择单价最高的商品(优先抵扣高价商品)
433
+ const maxProduct = applicableProducts.reduce((max, p) => p[unitPriceField].greaterThan(max[unitPriceField]) ? p : max);
434
+ // maxPassesPerItem 限制每张券最多抵扣的单位数
337
435
  if (maxPassesPerItem > 0) {
338
- calculatedAvailableMaxAmount = import_decimal.default.min(
339
- maxProduct[unitPriceField].times(maxPassesPerItem),
340
- maxProduct[amountField]
341
- );
436
+ calculatedAvailableMaxAmount = _decimal.default.min(maxProduct[unitPriceField].times(maxPassesPerItem), maxProduct[amountField]);
342
437
  } else {
343
438
  calculatedAvailableMaxAmount = maxProduct[amountField];
344
439
  }
345
440
  }
346
- const availableMaxAmount = import_decimal.default.min(
347
- baseAmount,
348
- calculatedAvailableMaxAmount,
349
- remainingOrderAmount
350
- );
351
- const maxDeduction = import_decimal.default.min(
352
- new import_decimal.default(usageAmount),
353
- new import_decimal.default(maxDeductionAmount)
354
- );
441
+
442
+ // 取最小值:min(recommended_usage_amount, maxDeductionAmount, 适用商品金额, 订单剩余金额)
443
+ const availableMaxAmount = _decimal.default.min(baseAmount, calculatedAvailableMaxAmount, remainingOrderAmount);
444
+ // ======================================================================================
445
+
446
+ // 计算本次抵扣金额(使用同样的 usageAmount)
447
+ const maxDeduction = _decimal.default.min(new _decimal.default(usageAmount), new _decimal.default(maxDeductionAmount));
355
448
  let deductionLeft = maxDeduction;
356
449
  const deductionDetails = [];
357
450
  if (allowCrossProduct) {
358
- const sortedProducts = [...applicableProducts].sort(
359
- (a, b) => a[amountField].comparedTo(b[amountField]) > 0 ? -1 : 1
360
- );
451
+ // 跨商品券:按剩余金额从高到低抵扣,受 applicableProductLimit 限制
452
+ const sortedProducts = [...applicableProducts].sort((a, b) => a[amountField].comparedTo(b[amountField]) > 0 ? -1 : 1);
361
453
  let remainingLimit = applicableProductLimit > 0 ? applicableProductLimit : Infinity;
362
454
  for (const product of sortedProducts) {
363
- if (deductionLeft.lessThanOrEqualTo(0) || remainingLimit <= 0)
364
- break;
455
+ if (deductionLeft.lessThanOrEqualTo(0) || remainingLimit <= 0) break;
456
+
457
+ // 动态计算当前可抵扣数量 = ceil(剩余金额 / 单价)
365
458
  const currentAvailableQty = Math.ceil(product[amountField].dividedBy(product[unitPriceField]).toNumber());
366
459
  const availableQty = Math.min(currentAvailableQty, remainingLimit);
367
- const maxDeductForProduct = import_decimal.default.min(
368
- product[unitPriceField].times(availableQty),
369
- product[amountField]
370
- );
371
- const actualDeductAmount = import_decimal.default.min(deductionLeft, maxDeductForProduct);
460
+
461
+ // 计算本商品最大可抵扣金额 = min(数量 * 单价, 剩余金额)
462
+ const maxDeductForProduct = _decimal.default.min(product[unitPriceField].times(availableQty), product[amountField]);
463
+ const actualDeductAmount = _decimal.default.min(deductionLeft, maxDeductForProduct);
464
+
465
+ // 计算实际抵扣的数量(用于记录和配额计算)
372
466
  const actualDeductQty = Math.ceil(actualDeductAmount.dividedBy(product[unitPriceField]).toNumber());
467
+
468
+ // 只更新商品的剩余金额,不更新 remainingQuantity
373
469
  product[amountField] = product[amountField].minus(actualDeductAmount);
374
470
  deductionLeft = deductionLeft.minus(actualDeductAmount);
375
471
  remainingLimit -= actualDeductQty;
@@ -381,23 +477,24 @@ function processVouchers(applicableVouchers, orderTotalAmount, products) {
381
477
  orderLineQuantity: getExpandedOrderLineQuantity(product),
382
478
  deductAmount: actualDeductAmount.toNumber(),
383
479
  // 转换为数字
384
- deductQuantity: actualDeductQty
385
- // 抵扣涉及的数量(用于记录)
480
+ deductQuantity: actualDeductQty // 抵扣涉及的数量(用于记录)
386
481
  });
387
482
  }
388
483
  } else {
389
- const targetProduct = applicableProducts.reduce(
390
- (max, p) => p[unitPriceField].greaterThan(max[unitPriceField]) ? p : max
391
- );
484
+ // 非跨商品券:选择单价最高的商品(优先抵扣高价商品)
485
+ const targetProduct = applicableProducts.reduce((max, p) => p[unitPriceField].greaterThan(max[unitPriceField]) ? p : max);
486
+
487
+ // maxPassesPerItem 限制每张券最多抵扣的单位数
392
488
  let maxDeductForProduct = targetProduct[amountField];
393
489
  if (maxPassesPerItem > 0) {
394
- maxDeductForProduct = import_decimal.default.min(
395
- targetProduct[unitPriceField].times(maxPassesPerItem),
396
- targetProduct[amountField]
397
- );
490
+ maxDeductForProduct = _decimal.default.min(targetProduct[unitPriceField].times(maxPassesPerItem), targetProduct[amountField]);
398
491
  }
399
- const actualDeductAmount = import_decimal.default.min(deductionLeft, maxDeductForProduct);
492
+ const actualDeductAmount = _decimal.default.min(deductionLeft, maxDeductForProduct);
493
+
494
+ // 计算实际抵扣的数量
400
495
  const actualDeductQty = Math.ceil(actualDeductAmount.dividedBy(targetProduct[unitPriceField]).toNumber());
496
+
497
+ // 只更新商品的剩余金额,不更新 remainingQuantity
401
498
  targetProduct[amountField] = targetProduct[amountField].minus(actualDeductAmount);
402
499
  deductionLeft = deductionLeft.minus(actualDeductAmount);
403
500
  deductionDetails.push({
@@ -412,9 +509,14 @@ function processVouchers(applicableVouchers, orderTotalAmount, products) {
412
509
  }
413
510
  const totalDeducted = maxDeduction.minus(deductionLeft);
414
511
  if (totalDeducted.greaterThan(0)) {
512
+ // 更新订单剩余金额
415
513
  remainingOrderAmount = remainingOrderAmount.minus(totalDeducted);
514
+
515
+ // 更新券使用次数(按 product_id 统计)
416
516
  usedVoucherCounts.set(product_id, (usedVoucherCounts.get(product_id) || 0) + 1);
417
517
  applyMaxPassUsageIncrements(itemPassUsageMap, product_id, maxPassesPerItem, deductionDetails);
518
+
519
+ // 添加到推荐列表(包含基于当前剩余金额计算的 available_max_amount)
418
520
  recommendedVouchers.push({
419
521
  ...voucher,
420
522
  actualDeduction: totalDeducted.toNumber(),
@@ -428,17 +530,47 @@ function processVouchers(applicableVouchers, orderTotalAmount, products) {
428
530
  }
429
531
  return false;
430
532
  };
431
- applicableVouchers.forEach((voucher) => {
533
+
534
+ /**
535
+ // 第一轮:处理未跨商品券
536
+ const nonCrossGroups = groupByType(nonCrossProductVouchers);
537
+ nonCrossGroups.forEach((vouchersInGroup) => {
538
+ const sortedVouchers = sortVouchersByAmount(vouchersInGroup);
539
+ sortedVouchers.forEach((voucher) => {
540
+ applyVoucher(voucher);
541
+ });
542
+ });
543
+ // 第二轮:处理跨商品券
544
+ const crossGroups = groupByType(crossProductVouchers);
545
+ crossGroups.forEach((vouchersInGroup) => {
546
+ const sortedVouchers = sortVouchersByAmount(vouchersInGroup);
547
+ sortedVouchers.forEach((voucher) => {
548
+ applyVoucher(voucher);
549
+ });
550
+ });
551
+ */
552
+
553
+ // 直接按 applicableVouchers 的顺序处理所有券
554
+ applicableVouchers.forEach(voucher => {
432
555
  applyVoucher(voucher);
433
556
  });
434
- const recommendedMap = /* @__PURE__ */ new Map();
435
- recommendedVouchers.forEach((v) => {
557
+
558
+ // recommendedVouchers 已经包含了基于应用时计算的 _available_max_amount 和 _unified_available_status
559
+
560
+ // ========== 第三步:构建 all 数组,以 recommended 为基础 ==========
561
+ // 创建 recommended 券的 Map,以 id 为 key
562
+ const recommendedMap = new Map();
563
+ recommendedVouchers.forEach(v => {
436
564
  recommendedMap.set(v.id, v);
437
565
  });
438
- const allWithEnhancedData = allVouchersWithStatus.map((voucher) => {
566
+
567
+ // 遍历所有券,如果在 recommended 中则使用增强数据,否则使用基础数据
568
+ const allWithEnhancedData = allVouchersWithStatus.map(voucher => {
439
569
  if (recommendedMap.has(voucher.id)) {
570
+ // 使用 recommended 中的增强数据
440
571
  return recommendedMap.get(voucher.id);
441
572
  } else {
573
+ // 不在 recommended 中的券,补充基础字段
442
574
  return {
443
575
  ...voucher,
444
576
  actualDeduction: 0,
@@ -452,70 +584,96 @@ function processVouchers(applicableVouchers, orderTotalAmount, products) {
452
584
  transformList: allWithEnhancedData
453
585
  };
454
586
  }
587
+
588
+ /**
589
+ * 重新计算优惠券状态(基于已选券的增量计算)
590
+ * @param allVouchers 所有原始券列表
591
+ * @param selectedVouchers 已选中的券列表(按选中顺序)
592
+ * @param orderTotalAmount 订单总金额
593
+ * @param products 订单商品列表
594
+ * @returns 返回更新后的所有券列表和已选券的详细抵扣信息
595
+ */
455
596
  function recalculateVouchers(allVouchers, selectedVouchers, orderTotalAmount, products) {
597
+ // 深拷贝并拆分商品列表(包含主商品和原价子商品,同时维护含税和不含税两个金额池),用于计算
456
598
  const productsForCalc = expandProductsWithBundleItems(products, true);
457
- let remainingOrderAmount = new import_decimal.default(orderTotalAmount);
599
+ let remainingOrderAmount = new _decimal.default(orderTotalAmount);
458
600
  const selectedWithDetails = [];
459
- const itemPassUsageMap = /* @__PURE__ */ new Map();
601
+
602
+ // 追踪每个 Wallet Pass 商品对每个订单商品行的已使用卡券次数(按行唯一键)
603
+ const itemPassUsageMap = new Map();
460
604
  const getItemPassUsageRecalc = (walletPassProductId, lineKey) => {
461
- var _a;
462
- return ((_a = itemPassUsageMap.get(walletPassProductId)) == null ? void 0 : _a.get(lineKey)) || 0;
605
+ return itemPassUsageMap.get(walletPassProductId)?.get(lineKey) || 0;
463
606
  };
464
- const filterByMaxPassesPerItemRecalc = (products2, walletPassProductId, maxPassesPerItem) => {
465
- if (maxPassesPerItem <= 0)
466
- return products2;
467
- return products2.filter((p) => {
607
+ const filterByMaxPassesPerItemRecalc = (products, walletPassProductId, maxPassesPerItem) => {
608
+ if (maxPassesPerItem <= 0) return products;
609
+ return products.filter(p => {
468
610
  const lineKey = p._walletPassLineKey;
469
- if (!lineKey)
470
- return true;
611
+ if (!lineKey) return true;
471
612
  return getItemPassUsageRecalc(walletPassProductId, lineKey) < getMaxPassSlotsForExpandedLine(maxPassesPerItem, p);
472
613
  });
473
614
  };
474
- selectedVouchers.forEach((selectedVoucher) => {
475
- const { config, id } = selectedVoucher;
476
- const { maxDeductionAmount, allowCrossProduct, applicableProductLimit, deductTaxAndFee = true, maxPassesPerItem = 0 } = config;
477
- const unitPriceField = deductTaxAndFee ? "unitPriceWithTax" : "unitPricePure";
478
- const amountField = deductTaxAndFee ? "remainingAmountWithTax" : "remainingAmountPure";
479
- let applicableProducts = getApplicableProducts(
480
- selectedVoucher,
481
- productsForCalc
482
- ).filter((p) => p[amountField].greaterThan(0));
615
+
616
+ // 第一步:按顺序应用已选中的券,计算实际抵扣
617
+ selectedVouchers.forEach(selectedVoucher => {
618
+ const {
619
+ config,
620
+ id
621
+ } = selectedVoucher;
622
+ const {
623
+ maxDeductionAmount,
624
+ allowCrossProduct,
625
+ applicableProductLimit,
626
+ deductTaxAndFee = true,
627
+ maxPassesPerItem = 0
628
+ } = config;
629
+
630
+ // 根据券的配置选择使用哪个单价字段和金额字段
631
+ const unitPriceField = deductTaxAndFee ? 'unitPriceWithTax' : 'unitPricePure';
632
+ const amountField = deductTaxAndFee ? 'remainingAmountWithTax' : 'remainingAmountPure';
633
+
634
+ // 获取适用商品(只筛选有剩余金额的商品)
635
+ let applicableProducts = getApplicableProducts(selectedVoucher, productsForCalc).filter(p => p[amountField].greaterThan(0));
636
+
637
+ // 按 maxPassesPerItem 过滤:排除已达到该行券次上限的展开行
483
638
  applicableProducts = filterByMaxPassesPerItemRecalc(applicableProducts, selectedVoucher.product_id, maxPassesPerItem);
484
639
  if (applicableProducts.length === 0) {
640
+ // 无适用商品,跳过
485
641
  selectedWithDetails.push({
486
642
  ...selectedVoucher,
487
643
  actualDeduction: 0,
488
644
  deductionDetails: [],
489
645
  _available_max_amount: 0,
490
646
  _unified_available_status: 0,
491
- reasonCode: "not_meet_the_required_conditions"
647
+ reasonCode: 'not_meet_the_required_conditions'
492
648
  });
493
649
  return;
494
650
  }
495
- const usageAmount = typeof selectedVoucher.edit_current_amount === "number" ? selectedVoucher.edit_current_amount : getRecommendedAmount(selectedVoucher);
496
- const maxDeduction = import_decimal.default.min(
497
- new import_decimal.default(usageAmount),
498
- new import_decimal.default(maxDeductionAmount),
499
- remainingOrderAmount
500
- );
651
+
652
+ // 计算本次抵扣金额
653
+ // 优先使用用户手动编辑的金额,否则根据 deductTaxAndFee 配置获取推荐金额
654
+ const usageAmount = typeof selectedVoucher.edit_current_amount === 'number' ? selectedVoucher.edit_current_amount : getRecommendedAmount(selectedVoucher);
655
+ const maxDeduction = _decimal.default.min(new _decimal.default(usageAmount), new _decimal.default(maxDeductionAmount), remainingOrderAmount);
501
656
  let deductionLeft = maxDeduction;
502
657
  const deductionDetails = [];
503
658
  if (allowCrossProduct) {
504
- const sortedProducts = [...applicableProducts].sort(
505
- (a, b) => a[amountField].comparedTo(b[amountField]) > 0 ? -1 : 1
506
- );
659
+ // 跨商品券:按剩余金额从高到低抵扣,受 applicableProductLimit 限制
660
+ const sortedProducts = [...applicableProducts].sort((a, b) => a[amountField].comparedTo(b[amountField]) > 0 ? -1 : 1);
507
661
  let remainingLimit = applicableProductLimit > 0 ? applicableProductLimit : Infinity;
508
662
  for (const product of sortedProducts) {
509
- if (deductionLeft.lessThanOrEqualTo(0) || remainingLimit <= 0)
510
- break;
663
+ if (deductionLeft.lessThanOrEqualTo(0) || remainingLimit <= 0) break;
664
+
665
+ // 动态计算当前可抵扣数量 = ceil(剩余金额 / 单价)
511
666
  const currentAvailableQty = Math.ceil(product[amountField].dividedBy(product[unitPriceField]).toNumber());
512
667
  const availableQty = Math.min(currentAvailableQty, remainingLimit);
513
- const maxDeductForProduct = import_decimal.default.min(
514
- product[unitPriceField].times(availableQty),
515
- product[amountField]
516
- );
517
- const actualDeductAmount = import_decimal.default.min(deductionLeft, maxDeductForProduct);
668
+
669
+ // 计算本商品最大可抵扣金额 = min(数量 * 单价, 剩余金额)
670
+ const maxDeductForProduct = _decimal.default.min(product[unitPriceField].times(availableQty), product[amountField]);
671
+ const actualDeductAmount = _decimal.default.min(deductionLeft, maxDeductForProduct);
672
+
673
+ // 计算实际抵扣的数量(用于记录和配额计算)
518
674
  const actualDeductQty = Math.ceil(actualDeductAmount.dividedBy(product[unitPriceField]).toNumber());
675
+
676
+ // 只更新商品的剩余金额,不更新 remainingQuantity
519
677
  product[amountField] = product[amountField].minus(actualDeductAmount);
520
678
  deductionLeft = deductionLeft.minus(actualDeductAmount);
521
679
  remainingLimit -= actualDeductQty;
@@ -527,23 +685,24 @@ function recalculateVouchers(allVouchers, selectedVouchers, orderTotalAmount, pr
527
685
  orderLineQuantity: getExpandedOrderLineQuantity(product),
528
686
  deductAmount: actualDeductAmount.toNumber(),
529
687
  // 转换为数字
530
- deductQuantity: actualDeductQty
531
- // 抵扣涉及的数量(用于记录)
688
+ deductQuantity: actualDeductQty // 抵扣涉及的数量(用于记录)
532
689
  });
533
690
  }
534
691
  } else {
535
- const targetProduct = applicableProducts.reduce(
536
- (max, p) => p[unitPriceField].greaterThan(max[unitPriceField]) ? p : max
537
- );
692
+ // 非跨商品券:选择单价最高的商品(优先抵扣高价商品)
693
+ const targetProduct = applicableProducts.reduce((max, p) => p[unitPriceField].greaterThan(max[unitPriceField]) ? p : max);
694
+
695
+ // maxPassesPerItem 限制每张券最多抵扣的单位数
538
696
  let maxDeductForProduct = targetProduct[amountField];
539
697
  if (maxPassesPerItem > 0) {
540
- maxDeductForProduct = import_decimal.default.min(
541
- targetProduct[unitPriceField].times(maxPassesPerItem),
542
- targetProduct[amountField]
543
- );
698
+ maxDeductForProduct = _decimal.default.min(targetProduct[unitPriceField].times(maxPassesPerItem), targetProduct[amountField]);
544
699
  }
545
- const actualDeductAmount = import_decimal.default.min(deductionLeft, maxDeductForProduct);
700
+ const actualDeductAmount = _decimal.default.min(deductionLeft, maxDeductForProduct);
701
+
702
+ // 计算实际抵扣的数量
546
703
  const actualDeductQty = Math.ceil(actualDeductAmount.dividedBy(targetProduct[unitPriceField]).toNumber());
704
+
705
+ // 只更新商品的剩余金额,不更新 remainingQuantity
547
706
  targetProduct[amountField] = targetProduct[amountField].minus(actualDeductAmount);
548
707
  deductionLeft = deductionLeft.minus(actualDeductAmount);
549
708
  deductionDetails.push({
@@ -557,6 +716,8 @@ function recalculateVouchers(allVouchers, selectedVouchers, orderTotalAmount, pr
557
716
  });
558
717
  }
559
718
  const totalDeducted = maxDeduction.minus(deductionLeft);
719
+
720
+ // 更新订单剩余金额
560
721
  remainingOrderAmount = remainingOrderAmount.minus(totalDeducted);
561
722
  applyMaxPassUsageIncrements(itemPassUsageMap, selectedVoucher.product_id, maxPassesPerItem, deductionDetails);
562
723
  selectedWithDetails.push({
@@ -569,97 +730,105 @@ function recalculateVouchers(allVouchers, selectedVouchers, orderTotalAmount, pr
569
730
  _unified_available_status: totalDeducted.greaterThan(0) ? 1 : 0
570
731
  });
571
732
  });
572
- const selectedIds = new Set(selectedVouchers.map((v) => v.id));
573
- const usedVoucherCounts = /* @__PURE__ */ new Map();
574
- selectedVouchers.forEach((v) => {
733
+
734
+ // 第二步:为所有券重新计算 _available_max_amount 和 _unified_available_status
735
+ const selectedIds = new Set(selectedVouchers.map(v => v.id));
736
+ const usedVoucherCounts = new Map();
737
+ selectedVouchers.forEach(v => {
575
738
  usedVoucherCounts.set(v.product_id, (usedVoucherCounts.get(v.product_id) || 0) + 1);
576
739
  });
577
- const allWithUpdatedStatus = allVouchers.map((voucher) => {
740
+ const allWithUpdatedStatus = allVouchers.map(voucher => {
741
+ // 如果是已选中的券,使用详细计算结果
578
742
  if (selectedIds.has(voucher.id)) {
579
- const selectedDetail = selectedWithDetails.find(
580
- (v) => v.id === voucher.id
581
- );
743
+ const selectedDetail = selectedWithDetails.find(v => v.id === voucher.id);
582
744
  return selectedDetail || voucher;
583
745
  }
584
- const { config, id, product_id } = voucher;
585
- const { maxDeductionAmount, allowCrossProduct, applicableProductLimit, deductTaxAndFee = true, maxPassesPerItem = 0 } = config;
586
- const unitPriceField = deductTaxAndFee ? "unitPriceWithTax" : "unitPricePure";
587
- const amountField = deductTaxAndFee ? "remainingAmountWithTax" : "remainingAmountPure";
746
+
747
+ // 未选中的券,重新计算其可用状态
748
+ const {
749
+ config,
750
+ id,
751
+ product_id
752
+ } = voucher;
753
+ const {
754
+ maxDeductionAmount,
755
+ allowCrossProduct,
756
+ applicableProductLimit,
757
+ deductTaxAndFee = true,
758
+ maxPassesPerItem = 0
759
+ } = config;
760
+
761
+ // 根据券的配置选择使用哪个单价字段和金额字段
762
+ const unitPriceField = deductTaxAndFee ? 'unitPriceWithTax' : 'unitPricePure';
763
+ const amountField = deductTaxAndFee ? 'remainingAmountWithTax' : 'remainingAmountPure';
764
+
765
+ // 根据 deductTaxAndFee 配置获取推荐金额
588
766
  const recommendedAmount = getRecommendedAmount(voucher);
767
+
768
+ // 检查基本可用性
589
769
  let isAvailable = true;
590
- let calculatedMaxAmount = new import_decimal.default(0);
770
+ let calculatedMaxAmount = new _decimal.default(0);
591
771
  let reasonCode;
592
772
  if (recommendedAmount <= 0) {
593
773
  isAvailable = false;
594
- reasonCode = "not_meet_the_required_conditions";
774
+ reasonCode = 'not_meet_the_required_conditions';
595
775
  } else if (remainingOrderAmount.lessThanOrEqualTo(0)) {
596
776
  isAvailable = false;
597
- reasonCode = "exceeds_the_maximum_deduction_limit";
777
+ reasonCode = 'exceeds_the_maximum_deduction_limit';
598
778
  } else {
779
+ // 检查 maxUsagePerOrder(按 product_id 统计),0 表示不限制
599
780
  if (config.maxUsagePerOrder > 0) {
600
781
  const usedCount = usedVoucherCounts.get(product_id) || 0;
601
782
  if (usedCount >= config.maxUsagePerOrder) {
602
783
  isAvailable = false;
603
- reasonCode = "usage_limit_reached";
784
+ reasonCode = 'usage_limit_reached';
604
785
  }
605
786
  }
606
787
  if (isAvailable) {
607
- let applicableProducts = getApplicableProducts(
608
- voucher,
609
- productsForCalc
610
- ).filter((p) => p[amountField].greaterThan(0));
788
+ // 获取适用商品(只筛选有剩余金额的商品)
789
+ let applicableProducts = getApplicableProducts(voucher, productsForCalc).filter(p => p[amountField].greaterThan(0));
790
+
791
+ // 按 maxPassesPerItem 过滤:排除已达到单商品可用卡券上限的商品
611
792
  applicableProducts = filterByMaxPassesPerItemRecalc(applicableProducts, product_id, maxPassesPerItem);
612
793
  if (applicableProducts.length === 0) {
613
794
  isAvailable = false;
614
- reasonCode = "not_meet_the_required_conditions";
795
+ reasonCode = 'not_meet_the_required_conditions';
615
796
  } else {
616
- const baseAmount = import_decimal.default.min(
617
- new import_decimal.default(recommendedAmount),
618
- new import_decimal.default(maxDeductionAmount)
619
- );
797
+ // 计算 _available_max_amount
798
+ const baseAmount = _decimal.default.min(new _decimal.default(recommendedAmount), new _decimal.default(maxDeductionAmount));
620
799
  if (allowCrossProduct) {
621
800
  if (applicableProductLimit > 0) {
801
+ // 按剩余金额从高到低排序,按动态计算的可抵扣数量累计直到达到 limit
622
802
  const sortedProducts = [...applicableProducts].sort((a, b) => a[amountField].comparedTo(b[amountField]) > 0 ? -1 : 1);
623
803
  let remainingLimit = applicableProductLimit;
624
804
  for (const product of sortedProducts) {
625
- if (remainingLimit <= 0)
626
- break;
805
+ if (remainingLimit <= 0) break;
806
+ // 动态计算当前可抵扣数量 = ceil(剩余金额 / 单价)
627
807
  const currentAvailableQty = Math.ceil(product[amountField].dividedBy(product[unitPriceField]).toNumber());
628
808
  const deductQty = Math.min(currentAvailableQty, remainingLimit);
629
- const deductAmount = import_decimal.default.min(
630
- product[unitPriceField].times(deductQty),
631
- product[amountField]
632
- );
809
+ // 实际可抵扣金额 = min(数量 * 单价, 剩余金额)
810
+ const deductAmount = _decimal.default.min(product[unitPriceField].times(deductQty), product[amountField]);
633
811
  calculatedMaxAmount = calculatedMaxAmount.plus(deductAmount);
634
812
  remainingLimit -= deductQty;
635
813
  }
636
814
  } else {
637
- calculatedMaxAmount = applicableProducts.reduce(
638
- (sum, p) => sum.plus(p[amountField]),
639
- new import_decimal.default(0)
640
- );
815
+ // 无数量限制:所有适用商品的剩余金额总和
816
+ calculatedMaxAmount = applicableProducts.reduce((sum, p) => sum.plus(p[amountField]), new _decimal.default(0));
641
817
  }
642
818
  } else {
643
- const maxProduct = applicableProducts.reduce(
644
- (max, p) => p[unitPriceField].greaterThan(max[unitPriceField]) ? p : max
645
- );
819
+ // 非跨商品券:选择单价最高的商品(优先抵扣高价商品)
820
+ const maxProduct = applicableProducts.reduce((max, p) => p[unitPriceField].greaterThan(max[unitPriceField]) ? p : max);
821
+ // maxPassesPerItem 限制每张券最多抵扣的单位数
646
822
  if (maxPassesPerItem > 0) {
647
- calculatedMaxAmount = import_decimal.default.min(
648
- maxProduct[unitPriceField].times(maxPassesPerItem),
649
- maxProduct[amountField]
650
- );
823
+ calculatedMaxAmount = _decimal.default.min(maxProduct[unitPriceField].times(maxPassesPerItem), maxProduct[amountField]);
651
824
  } else {
652
825
  calculatedMaxAmount = maxProduct[amountField];
653
826
  }
654
827
  }
655
- calculatedMaxAmount = import_decimal.default.min(
656
- baseAmount,
657
- calculatedMaxAmount,
658
- remainingOrderAmount
659
- );
828
+ calculatedMaxAmount = _decimal.default.min(baseAmount, calculatedMaxAmount, remainingOrderAmount);
660
829
  if (calculatedMaxAmount.lessThanOrEqualTo(0)) {
661
830
  isAvailable = false;
662
- reasonCode = "exceeds_the_maximum_deduction_limit";
831
+ reasonCode = 'exceeds_the_maximum_deduction_limit';
663
832
  }
664
833
  }
665
834
  }
@@ -669,7 +838,9 @@ function recalculateVouchers(allVouchers, selectedVouchers, orderTotalAmount, pr
669
838
  _available_max_amount: calculatedMaxAmount.toNumber(),
670
839
  // 转换为数字
671
840
  _unified_available_status: isAvailable ? 1 : 0,
672
- ...reasonCode && { reasonCode }
841
+ ...(reasonCode && {
842
+ reasonCode
843
+ })
673
844
  };
674
845
  });
675
846
  return {
@@ -677,81 +848,158 @@ function recalculateVouchers(allVouchers, selectedVouchers, orderTotalAmount, pr
677
848
  selectedWithDetails
678
849
  };
679
850
  }
680
- var getTaxAndFeeRoundingRemainder = (product, isDeductTaxAndFee) => {
681
- var _a, _b;
851
+
852
+ /**
853
+ * 获取税费和附加费的舍入余数
854
+ * @param product 商品
855
+ * @param isDeductTaxAndFee 是否抵扣税费与附加费
856
+ * @returns 舍入余数(税费 + 附加费的舍入余数)
857
+ */
858
+ const getTaxAndFeeRoundingRemainder = (product, isDeductTaxAndFee) => {
682
859
  if (!isDeductTaxAndFee) {
683
860
  return 0;
684
861
  }
685
- let taxFeeRoundingRemainder = new import_decimal.default(0);
862
+
863
+ // 税费舍入余数(如果价格包含税则为0)
864
+ let taxFeeRoundingRemainder = new _decimal.default(0);
686
865
  if (product.is_price_include_tax !== 1) {
687
- taxFeeRoundingRemainder = new import_decimal.default(((_a = product == null ? void 0 : product.metadata) == null ? void 0 : _a.tax_fee_rounding_remainder) || 0);
866
+ taxFeeRoundingRemainder = new _decimal.default(product?.metadata?.tax_fee_rounding_remainder || 0);
688
867
  }
689
- const surchargeFeeRoundingRemainder = new import_decimal.default(((_b = product == null ? void 0 : product.metadata) == null ? void 0 : _b.surcharge_rounding_remainder) || 0);
868
+
869
+ // 附加费舍入余数
870
+ const surchargeFeeRoundingRemainder = new _decimal.default(product?.metadata?.surcharge_rounding_remainder || 0);
690
871
  return taxFeeRoundingRemainder.add(surchargeFeeRoundingRemainder).toNumber();
691
872
  };
692
- var getProductDiscountDifference = (product) => {
693
- var _a;
694
- return new import_decimal.default(((_a = product == null ? void 0 : product.metadata) == null ? void 0 : _a.product_discount_difference) || 0).toNumber();
873
+
874
+ /**
875
+ * 获取主商品的商品差额
876
+ * @param product 商品
877
+ * @returns 商品差额(与数量无关,计算总价时需要减去)
878
+ */
879
+ exports.getTaxAndFeeRoundingRemainder = getTaxAndFeeRoundingRemainder;
880
+ const getProductDiscountDifference = product => {
881
+ return new _decimal.default(product?.metadata?.product_discount_difference || 0).toNumber();
695
882
  };
696
- var getMainProductPrice = (product, isDeductTaxAndFee) => {
697
- var _a, _b, _c, _d, _e;
698
- let mainProductPrice = new import_decimal.default((product == null ? void 0 : product.main_product_selling_price) || ((_a = product.metadata) == null ? void 0 : _a.main_product_selling_price) || 0);
699
- for (let bundleItem of (product == null ? void 0 : product.product_bundle) || []) {
883
+
884
+ /**
885
+ * 获取主商品价格(单价,不含舍入余数)
886
+ * @param product 商品
887
+ * @param isDeductTaxAndFee 是否抵扣税费与附加费
888
+ * @returns 商品单价
889
+ */
890
+ exports.getProductDiscountDifference = getProductDiscountDifference;
891
+ const getMainProductPrice = (product, isDeductTaxAndFee) => {
892
+ let mainProductPrice = new _decimal.default(product?.main_product_selling_price || product.metadata?.main_product_selling_price || 0);
893
+ for (let bundleItem of product?.product_bundle || []) {
894
+ // 子商品是加价或减价才需要计算
700
895
  if (getBundleItemIsMarkupOrDiscountPrice(bundleItem)) {
701
- const bundleItemPrice = new import_decimal.default(bundleItem.bundle_selling_price ?? 0);
896
+ // 子商品价格
897
+ const bundleItemPrice = new _decimal.default(bundleItem.bundle_selling_price ?? 0);
702
898
  mainProductPrice = mainProductPrice.add(bundleItemPrice.times(bundleItem.num));
703
899
  }
704
900
  }
705
- let taxFee = new import_decimal.default((product == null ? void 0 : product.tax_fee) || ((_b = product == null ? void 0 : product.metadata) == null ? void 0 : _b.main_product_attached_bundle_tax_fee) || 0).add(((_c = product == null ? void 0 : product.metadata) == null ? void 0 : _c.tax_fee_rounding_remainder) || 0);
901
+ let taxFee = new _decimal.default(product?.tax_fee || product?.metadata?.main_product_attached_bundle_tax_fee || 0).add(product?.metadata?.tax_fee_rounding_remainder || 0);
706
902
  if (product.is_price_include_tax === 1) {
707
- taxFee = new import_decimal.default(0);
903
+ taxFee = new _decimal.default(0);
708
904
  }
709
- const surchargeFee = new import_decimal.default(((_d = product == null ? void 0 : product.metadata) == null ? void 0 : _d.main_product_attached_bundle_surcharge_fee) || 0).add(((_e = product == null ? void 0 : product.metadata) == null ? void 0 : _e.surcharge_rounding_remainder) || 0);
905
+
906
+ // 税费
907
+ // 附加费
908
+ const surchargeFee = new _decimal.default(product?.metadata?.main_product_attached_bundle_surcharge_fee || 0).add(product?.metadata?.surcharge_rounding_remainder || 0);
909
+
910
+ // 税费附加费总额
710
911
  const taxAndFeeTotal = taxFee.add(surchargeFee);
912
+
913
+ // 如果需要抵扣税费与附加费,则加上税费附加费总额
711
914
  if (isDeductTaxAndFee) {
712
915
  mainProductPrice = mainProductPrice.add(taxAndFeeTotal);
713
916
  }
714
917
  return mainProductPrice.toNumber();
715
918
  };
716
- var getBundleItemTaxAndFeeRoundingRemainder = (bundleItem, isDeductTaxAndFee) => {
717
- var _a, _b;
919
+
920
+ /**
921
+ * 获取套餐子商品税费和附加费的舍入余数
922
+ * @param bundleItem 套餐子商品
923
+ * @param isDeductTaxAndFee 是否抵扣税费与附加费
924
+ * @returns 舍入余数(税费 + 附加费的舍入余数)
925
+ */
926
+ exports.getMainProductPrice = getMainProductPrice;
927
+ const getBundleItemTaxAndFeeRoundingRemainder = (bundleItem, isDeductTaxAndFee) => {
718
928
  if (!isDeductTaxAndFee) {
719
929
  return 0;
720
930
  }
721
- let taxFeeRoundingRemainder = new import_decimal.default(0);
931
+
932
+ // 税费舍入余数(如果价格包含税则为0)
933
+ let taxFeeRoundingRemainder = new _decimal.default(0);
722
934
  if (bundleItem.is_price_include_tax !== 1) {
723
- taxFeeRoundingRemainder = new import_decimal.default(((_a = bundleItem.metadata) == null ? void 0 : _a.tax_fee_rounding_remainder) || 0);
935
+ taxFeeRoundingRemainder = new _decimal.default(bundleItem.metadata?.tax_fee_rounding_remainder || 0);
724
936
  }
725
- const surchargeFeeRoundingRemainder = new import_decimal.default(((_b = bundleItem.metadata) == null ? void 0 : _b.surcharge_rounding_remainder) || 0);
937
+
938
+ // 附加费舍入余数
939
+ const surchargeFeeRoundingRemainder = new _decimal.default(bundleItem.metadata?.surcharge_rounding_remainder || 0);
726
940
  return taxFeeRoundingRemainder.add(surchargeFeeRoundingRemainder).toNumber();
727
941
  };
728
- var getBundleItemDiscountDifference = (bundleItem) => {
729
- var _a;
730
- return new import_decimal.default(((_a = bundleItem.metadata) == null ? void 0 : _a.product_discount_difference) || 0).toNumber();
942
+
943
+ /**
944
+ * 获取套餐子商品的商品差额
945
+ * @param bundleItem 套餐子商品
946
+ * @returns 商品差额(与数量无关,计算总价时需要减去)
947
+ */
948
+ exports.getBundleItemTaxAndFeeRoundingRemainder = getBundleItemTaxAndFeeRoundingRemainder;
949
+ const getBundleItemDiscountDifference = bundleItem => {
950
+ return new _decimal.default(bundleItem.metadata?.product_discount_difference || 0).toNumber();
731
951
  };
732
- var getBundleItemPrice = (bundleItem, parentQuantity, isDeductTaxAndFee) => {
733
- var _a;
952
+
953
+ /**
954
+ * 获取套餐子商品价格(不含舍入余数)
955
+ * @param bundleItem 套餐子商品
956
+ * @param parentQuantity 父商品数量
957
+ * @param isDeductTaxAndFee 是否抵扣税费与附加费
958
+ * @returns 子商品总价格(不含舍入余数)
959
+ */
960
+ exports.getBundleItemDiscountDifference = getBundleItemDiscountDifference;
961
+ const getBundleItemPrice = (bundleItem, parentQuantity, isDeductTaxAndFee) => {
962
+ // 子商品基础价格 = bundle_selling_price * num * 主商品数量
734
963
  const totalQuantity = bundleItem.num * parentQuantity;
735
- let bundleItemPrice = new import_decimal.default(bundleItem.bundle_selling_price ?? 0).times(totalQuantity);
964
+ let bundleItemPrice = new _decimal.default(bundleItem.bundle_selling_price ?? 0).times(totalQuantity);
965
+
966
+ // 根据 deductTaxAndFee 配置决定是否加上税费和附加费
736
967
  if (isDeductTaxAndFee) {
737
- let taxFee = new import_decimal.default(bundleItem.tax_fee ?? 0).times(totalQuantity);
968
+ // 税费
969
+ let taxFee = new _decimal.default(bundleItem.tax_fee ?? 0).times(totalQuantity);
738
970
  if (bundleItem.is_price_include_tax === 1) {
739
- taxFee = new import_decimal.default(0);
971
+ taxFee = new _decimal.default(0);
740
972
  }
741
- const surchargeFee = new import_decimal.default(((_a = bundleItem.metadata) == null ? void 0 : _a.surcharge_fee) ?? 0).times(totalQuantity);
973
+ // 附加费
974
+ const surchargeFee = new _decimal.default(bundleItem.metadata?.surcharge_fee ?? 0).times(totalQuantity);
742
975
  bundleItemPrice = bundleItemPrice.add(taxFee).add(surchargeFee);
743
976
  }
744
977
  return bundleItemPrice.toNumber();
745
978
  };
746
- var expandProductsWithBundleItems = (products, deductTaxAndFee) => {
979
+
980
+ /**
981
+ * 将商品数据拆分,包含主商品和原价子商品
982
+ * @param products 原始商品列表
983
+ * @param deductTaxAndFee 是否抵扣税费与附加费(已废弃,保留参数以兼容旧代码)
984
+ * @returns 拆分后的商品数据(包含主商品和子商品,同时维护含税和不含税两个金额池,以及单价和剩余数量)
985
+ */
986
+ exports.getBundleItemPrice = getBundleItemPrice;
987
+ const expandProductsWithBundleItems = (products, deductTaxAndFee) => {
747
988
  const expandedProducts = [];
748
989
  products.forEach((product, indexInOrder) => {
749
990
  const productQuantity = getProductQuantity(product);
750
991
  const parentLineKey = resolveWalletPassLineKey(product, indexInOrder);
992
+
993
+ // 计算主商品单价(含税和不含税)
751
994
  const unitPriceWithTax = getMainProductPrice(product, true);
752
995
  const unitPricePure = getMainProductPrice(product, false);
996
+
997
+ // 获取舍入余数(与数量无关,只加一次)
753
998
  const roundingRemainderWithTax = getTaxAndFeeRoundingRemainder(product, true);
999
+ // 获取商品差额(与数量无关,计算总价时需要减去)
754
1000
  const discountDifference = getProductDiscountDifference(product);
1001
+
1002
+ // 1. 添加主商品(同时计算含税和不含税两个金额,以及单价和剩余数量)
755
1003
  expandedProducts.push({
756
1004
  ...product,
757
1005
  is_bundle_item: false,
@@ -759,24 +1007,32 @@ var expandProductsWithBundleItems = (products, deductTaxAndFee) => {
759
1007
  _walletPassLineKey: parentLineKey,
760
1008
  _orderLineQuantity: productQuantity,
761
1009
  // 单价(用于按 quantity 计算抵扣)
762
- unitPriceWithTax: new import_decimal.default(unitPriceWithTax),
763
- unitPricePure: new import_decimal.default(unitPricePure),
1010
+ unitPriceWithTax: new _decimal.default(unitPriceWithTax),
1011
+ unitPricePure: new _decimal.default(unitPricePure),
764
1012
  // 剩余可抵扣数量
765
1013
  remainingQuantity: productQuantity,
766
1014
  // 含税费的剩余金额(单价 * 数量 + 舍入余数 - 商品差额)
767
- remainingAmountWithTax: new import_decimal.default(unitPriceWithTax).times(productQuantity).add(roundingRemainderWithTax).sub(discountDifference),
1015
+ remainingAmountWithTax: new _decimal.default(unitPriceWithTax).times(productQuantity).add(roundingRemainderWithTax).sub(discountDifference),
768
1016
  // 纯商品金额(不含税费,也需要减去商品差额)
769
- remainingAmountPure: new import_decimal.default(unitPricePure).times(productQuantity).sub(discountDifference)
1017
+ remainingAmountPure: new _decimal.default(unitPricePure).times(productQuantity).sub(discountDifference)
770
1018
  });
1019
+
1020
+ // 2. 添加原价子商品(作为独立商品项)
771
1021
  if (product.product_bundle && product.product_bundle.length > 0) {
772
- product.product_bundle.forEach((bundleItem) => {
1022
+ product.product_bundle.forEach(bundleItem => {
773
1023
  if (getBundleItemIsOriginalPrice(bundleItem)) {
774
1024
  const bundleQuantity = bundleItem.num * productQuantity;
775
1025
  const bundleLineKey = `${parentLineKey}#bundle:${bundleItem.bundle_id}:${bundleItem.bundle_product_id}`;
776
- const bundleUnitPriceWithTax = new import_decimal.default(getBundleItemPrice(bundleItem, 1, true)).dividedBy(bundleItem.num);
777
- const bundleUnitPricePure = new import_decimal.default(getBundleItemPrice(bundleItem, 1, false)).dividedBy(bundleItem.num);
1026
+ // 计算子商品单价(注意:getBundleItemPrice 返回的是总价,需要除以数量得到单价)
1027
+ const bundleUnitPriceWithTax = new _decimal.default(getBundleItemPrice(bundleItem, 1, true)).dividedBy(bundleItem.num);
1028
+ const bundleUnitPricePure = new _decimal.default(getBundleItemPrice(bundleItem, 1, false)).dividedBy(bundleItem.num);
1029
+
1030
+ // 获取子商品舍入余数(与数量无关,只加一次)
778
1031
  const bundleRoundingRemainder = getBundleItemTaxAndFeeRoundingRemainder(bundleItem, true);
1032
+ // 获取子商品差额(与数量无关,计算总价时需要减去)
779
1033
  const bundleDiscountDifference = getBundleItemDiscountDifference(bundleItem);
1034
+
1035
+ // 原价子商品作为独立商品
780
1036
  expandedProducts.push({
781
1037
  ...bundleItem,
782
1038
  product_id: bundleItem.bundle_product_id,
@@ -793,9 +1049,9 @@ var expandProductsWithBundleItems = (products, deductTaxAndFee) => {
793
1049
  // 剩余可抵扣数量
794
1050
  remainingQuantity: bundleQuantity,
795
1051
  // 含税费的剩余金额(总价 + 舍入余数 - 商品差额)
796
- remainingAmountWithTax: new import_decimal.default(getBundleItemPrice(bundleItem, productQuantity, true)).add(bundleRoundingRemainder).sub(bundleDiscountDifference),
1052
+ remainingAmountWithTax: new _decimal.default(getBundleItemPrice(bundleItem, productQuantity, true)).add(bundleRoundingRemainder).sub(bundleDiscountDifference),
797
1053
  // 纯商品金额(不含税费,也需要减去商品差额)
798
- remainingAmountPure: new import_decimal.default(getBundleItemPrice(bundleItem, productQuantity, false)).sub(bundleDiscountDifference)
1054
+ remainingAmountPure: new _decimal.default(getBundleItemPrice(bundleItem, productQuantity, false)).sub(bundleDiscountDifference)
799
1055
  });
800
1056
  }
801
1057
  });
@@ -803,33 +1059,27 @@ var expandProductsWithBundleItems = (products, deductTaxAndFee) => {
803
1059
  });
804
1060
  return expandedProducts;
805
1061
  };
806
- var getBundleItemIsOriginalPrice = (item) => {
807
- return (item == null ? void 0 : item.price_type) === "markup" && (item == null ? void 0 : item.price_type_ext) === "product_price";
1062
+
1063
+ // bundle商品是否是原价
1064
+ const getBundleItemIsOriginalPrice = item => {
1065
+ return item?.price_type === 'markup' && item?.price_type_ext === 'product_price';
808
1066
  };
809
- var getBundleItemIsMarkupPrice = (item) => {
810
- return (item == null ? void 0 : item.price_type) === "markup" && ((item == null ? void 0 : item.price_type_ext) === "" || !(item == null ? void 0 : item.price_type_ext));
1067
+
1068
+ // bundle商品是否是加价
1069
+ exports.getBundleItemIsOriginalPrice = getBundleItemIsOriginalPrice;
1070
+ const getBundleItemIsMarkupPrice = item => {
1071
+ return item?.price_type === 'markup' && (item?.price_type_ext === '' || !item?.price_type_ext);
811
1072
  };
812
- var getBundleItemIsDiscountPrice = (item) => {
813
- return (item == null ? void 0 : item.price_type) === "markdown" && ((item == null ? void 0 : item.price_type_ext) === "" || !(item == null ? void 0 : item.price_type_ext));
1073
+
1074
+ // bundle商品是否是减价
1075
+ exports.getBundleItemIsMarkupPrice = getBundleItemIsMarkupPrice;
1076
+ const getBundleItemIsDiscountPrice = item => {
1077
+ return item?.price_type === 'markdown' && (item?.price_type_ext === '' || !item?.price_type_ext);
814
1078
  };
815
- var getBundleItemIsMarkupOrDiscountPrice = (item) => {
1079
+
1080
+ // bundle商品是否是加价或减价
1081
+ exports.getBundleItemIsDiscountPrice = getBundleItemIsDiscountPrice;
1082
+ const getBundleItemIsMarkupOrDiscountPrice = item => {
816
1083
  return getBundleItemIsMarkupPrice(item) || getBundleItemIsDiscountPrice(item);
817
1084
  };
818
- // Annotate the CommonJS export names for ESM import in node:
819
- 0 && (module.exports = {
820
- getApplicableProductIds,
821
- getBundleItemDiscountDifference,
822
- getBundleItemIsDiscountPrice,
823
- getBundleItemIsMarkupOrDiscountPrice,
824
- getBundleItemIsMarkupPrice,
825
- getBundleItemIsOriginalPrice,
826
- getBundleItemPrice,
827
- getBundleItemTaxAndFeeRoundingRemainder,
828
- getMainProductPrice,
829
- getProductDiscountDifference,
830
- getProductQuantity,
831
- getTaxAndFeeRoundingRemainder,
832
- processVouchers,
833
- recalculateVouchers,
834
- resolveWalletPassLineKey
835
- });
1085
+ exports.getBundleItemIsMarkupOrDiscountPrice = getBundleItemIsMarkupOrDiscountPrice;