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