@pisell/pisellos 2.3.74 → 2.3.76

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 (283) hide show
  1. package/dist/core/index.js +6 -10
  2. package/dist/model/strategy/adapter/promotion/index.js +9 -0
  3. package/dist/model/strategy/adapter/walletPass/example.js +3 -26
  4. package/dist/model/strategy/strategy-example.js +5 -19
  5. package/dist/modules/Cart/index.js +0 -3
  6. package/dist/modules/Cart/utils/cartProduct.js +0 -1
  7. package/dist/modules/Date/index.js +1 -5
  8. package/dist/modules/Discount/index.js +2 -4
  9. package/dist/modules/OpenData/index.js +0 -2
  10. package/dist/modules/Order/index.d.ts +1 -1
  11. package/dist/modules/Order/index.js +75 -91
  12. package/dist/modules/Order/types.d.ts +3 -1
  13. package/dist/modules/Payment/index.js +97 -162
  14. package/dist/modules/Rules/index.js +1 -4
  15. package/dist/modules/Schedule/index.js +0 -1
  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 +97 -129
  21. package/dist/server/modules/menu/index.js +32 -41
  22. package/dist/server/modules/order/index.js +2 -4
  23. package/dist/server/modules/products/index.js +24 -42
  24. package/dist/server/modules/quotation/index.js +29 -38
  25. package/dist/server/modules/resource/index.js +3 -4
  26. package/dist/server/modules/schedule/index.js +27 -35
  27. package/dist/server/utils/product.js +0 -1
  28. package/dist/solution/BaseSales/index.js +100 -72
  29. package/dist/solution/BaseSales/utils/cartPromotion.d.ts +5 -0
  30. package/dist/solution/BaseSales/utils/cartPromotion.js +15 -6
  31. package/dist/solution/BookingByStep/index.js +7 -59
  32. package/dist/solution/BookingByStep/utils/capacity.js +1 -11
  33. package/dist/solution/BookingByStep/utils/resources.js +1 -3
  34. package/dist/solution/BookingByStep/utils/timeslots.js +2 -43
  35. package/dist/solution/BookingTicket/index.js +0 -2
  36. package/dist/solution/BookingTicket/utils/scan/cloudSearch.js +6 -7
  37. package/dist/solution/BookingTicket/utils/scan/handleScan.js +25 -31
  38. package/dist/solution/BookingTicket/utils/scan/index.js +6 -10
  39. package/dist/solution/BuyTickets/index.js +9 -12
  40. package/dist/solution/Checkout/index.js +177 -252
  41. package/dist/solution/Checkout/utils/index.js +4 -16
  42. package/dist/solution/RegisterAndLogin/index.js +30 -76
  43. package/dist/solution/ScanOrder/index.js +50 -60
  44. package/dist/solution/ShopDiscount/index.js +2 -7
  45. package/dist/solution/VenueBooking/index.js +45 -55
  46. package/dist/utils/task.js +15 -18
  47. package/dist/utils/watch.js +0 -1
  48. package/lib/apis/picoding.js +2 -0
  49. package/lib/core/index.js +134 -134
  50. package/lib/effects/index.js +46 -57
  51. package/lib/index.js +82 -49
  52. package/lib/model/index.js +14 -21
  53. package/lib/model/strategy/adapter/dataVariant/adapter.js +38 -50
  54. package/lib/model/strategy/adapter/dataVariant/evaluator.js +162 -236
  55. package/lib/model/strategy/adapter/dataVariant/examples.js +229 -237
  56. package/lib/model/strategy/adapter/dataVariant/index.js +39 -34
  57. package/lib/model/strategy/adapter/dataVariant/type.js +57 -28
  58. package/lib/model/strategy/adapter/index.js +100 -64
  59. package/lib/model/strategy/adapter/itemRule/adapter.js +156 -144
  60. package/lib/model/strategy/adapter/itemRule/evaluator.js +36 -48
  61. package/lib/model/strategy/adapter/itemRule/examples.js +295 -225
  62. package/lib/model/strategy/adapter/itemRule/index.js +83 -57
  63. package/lib/model/strategy/adapter/itemRule/type.js +97 -36
  64. package/lib/model/strategy/adapter/promotion/adapter.js +91 -69
  65. package/lib/model/strategy/adapter/promotion/evaluator.js +444 -310
  66. package/lib/model/strategy/adapter/promotion/examples.js +222 -234
  67. package/lib/model/strategy/adapter/promotion/index.js +1 -0
  68. package/lib/model/strategy/adapter/promotion/type.js +243 -36
  69. package/lib/model/strategy/adapter/type.js +4 -16
  70. package/lib/model/strategy/adapter/walletPass/evaluator.js +180 -150
  71. package/lib/model/strategy/adapter/walletPass/example.js +217 -190
  72. package/lib/model/strategy/adapter/walletPass/index.js +75 -51
  73. package/lib/model/strategy/adapter/walletPass/locales.js +36 -58
  74. package/lib/model/strategy/adapter/walletPass/type.js +4 -16
  75. package/lib/model/strategy/adapter/walletPass/utils.js +588 -378
  76. package/lib/model/strategy/engine.js +270 -168
  77. package/lib/model/strategy/index.js +49 -34
  78. package/lib/model/strategy/strategy-example.js +243 -239
  79. package/lib/model/strategy/type.js +100 -40
  80. package/lib/modules/Account/index.js +39 -53
  81. package/lib/modules/Account/types.js +20 -33
  82. package/lib/modules/AccountList/index.js +116 -154
  83. package/lib/modules/AccountList/types.js +30 -31
  84. package/lib/modules/AccountList/utils.js +18 -30
  85. package/lib/modules/BaseModule.js +23 -42
  86. package/lib/modules/BookingContext/index.js +158 -136
  87. package/lib/modules/BookingContext/types.js +46 -32
  88. package/lib/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +138 -146
  89. package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +78 -83
  90. package/lib/modules/BookingContext/utils/cacheItemToBookingInput.js +170 -150
  91. package/lib/modules/BookingContext/utils/flexible.js +38 -55
  92. package/lib/modules/BookingContext/utils/formatResourceList.js +19 -34
  93. package/lib/modules/BookingContext/utils/index.js +124 -41
  94. package/lib/modules/BookingContext/utils/noResource.js +58 -70
  95. package/lib/modules/BookingContext/utils/orderLineDisplay.js +14 -41
  96. package/lib/modules/BookingContext/utils/productExtend.js +202 -155
  97. package/lib/modules/BookingContext/utils/resourceErrors.js +53 -53
  98. package/lib/modules/BookingContext/utils/resourceSelection.js +19 -41
  99. package/lib/modules/BookingContext/utils/resources.js +209 -167
  100. package/lib/modules/BookingContext/utils/serviceTimes.js +115 -116
  101. package/lib/modules/BookingContext/utils/timeSlices.js +76 -86
  102. package/lib/modules/Cart/index.js +170 -124
  103. package/lib/modules/Cart/types.js +46 -51
  104. package/lib/modules/Cart/utils/cartAccount.js +40 -39
  105. package/lib/modules/Cart/utils/cartDate.js +56 -61
  106. package/lib/modules/Cart/utils/cartDiscount.js +28 -33
  107. package/lib/modules/Cart/utils/cartNote.js +27 -32
  108. package/lib/modules/Cart/utils/cartProduct.js +321 -251
  109. package/lib/modules/Cart/utils/cartRelationForms.js +29 -35
  110. package/lib/modules/Cart/utils/cartResource.js +70 -78
  111. package/lib/modules/Cart/utils/changePrice.js +22 -50
  112. package/lib/modules/Cart/utils/index.js +106 -48
  113. package/lib/modules/Customer/constants.js +13 -34
  114. package/lib/modules/Customer/index.js +235 -172
  115. package/lib/modules/Customer/types.js +38 -35
  116. package/lib/modules/Date/index.js +116 -115
  117. package/lib/modules/Date/types.js +16 -28
  118. package/lib/modules/Date/utils.js +174 -123
  119. package/lib/modules/Discount/index.js +122 -127
  120. package/lib/modules/Discount/types.js +9 -31
  121. package/lib/modules/Guests/index.js +30 -56
  122. package/lib/modules/Guests/types.js +23 -33
  123. package/lib/modules/Holder/index.js +209 -189
  124. package/lib/modules/Holder/types.js +4 -16
  125. package/lib/modules/Holder/utils.js +20 -49
  126. package/lib/modules/OpenData/index.js +70 -76
  127. package/lib/modules/OpenData/types.js +4 -16
  128. package/lib/modules/OpenData/utils.js +44 -78
  129. package/lib/modules/Order/index.d.ts +1 -1
  130. package/lib/modules/Order/index.js +1733 -2121
  131. package/lib/modules/Order/payment-utils.js +77 -120
  132. package/lib/modules/Order/types.d.ts +3 -1
  133. package/lib/modules/Order/types.js +88 -35
  134. package/lib/modules/Order/utils/bundleDiscountHydration.js +41 -74
  135. package/lib/modules/Order/utils/discountProductLineIdentity.js +142 -100
  136. package/lib/modules/Order/utils/manualProductDiscount.js +124 -123
  137. package/lib/modules/Order/utils/orderCollectionIdentity.js +184 -244
  138. package/lib/modules/Order/utils.js +814 -647
  139. package/lib/modules/Payment/cash.js +20 -33
  140. package/lib/modules/Payment/cashRecommendationAlgorithm.js +145 -76
  141. package/lib/modules/Payment/eftpos.js +16 -30
  142. package/lib/modules/Payment/index.js +532 -399
  143. package/lib/modules/Payment/mx51.js +1 -0
  144. package/lib/modules/Payment/types.js +230 -108
  145. package/lib/modules/Payment/utils.js +52 -51
  146. package/lib/modules/Payment/walletpass.js +485 -660
  147. package/lib/modules/Product/index.js +51 -46
  148. package/lib/modules/Product/types.js +4 -16
  149. package/lib/modules/Product/utils.js +32 -33
  150. package/lib/modules/ProductList/index.js +113 -123
  151. package/lib/modules/ProductList/types.js +9 -31
  152. package/lib/modules/Quotation/index.js +81 -118
  153. package/lib/modules/Quotation/types.js +4 -16
  154. package/lib/modules/Resource/index.js +27 -59
  155. package/lib/modules/Resource/types.js +22 -32
  156. package/lib/modules/Resource/utils.js +30 -38
  157. package/lib/modules/ResourcePlanner/index.js +25 -23
  158. package/lib/modules/ResourcePlanner/localClock.js +54 -119
  159. package/lib/modules/ResourcePlanner/planner.js +589 -770
  160. package/lib/modules/ResourcePlanner/types.js +10 -29
  161. package/lib/modules/Rules/index.js +1218 -959
  162. package/lib/modules/Rules/types.js +17 -40
  163. package/lib/modules/SalesSummary/index.js +85 -90
  164. package/lib/modules/SalesSummary/types.js +4 -16
  165. package/lib/modules/SalesSummary/utils.js +355 -430
  166. package/lib/modules/ScanOrderLogger/index.js +59 -79
  167. package/lib/modules/ScanOrderLogger/providers/feishu.js +54 -78
  168. package/lib/modules/ScanOrderLogger/providers/grafana.js +13 -36
  169. package/lib/modules/ScanOrderLogger/types.js +4 -16
  170. package/lib/modules/Schedule/getDateIsInSchedule.js +334 -163
  171. package/lib/modules/Schedule/index.js +100 -114
  172. package/lib/modules/Schedule/type.js +4 -16
  173. package/lib/modules/Schedule/types.js +4 -16
  174. package/lib/modules/Schedule/utils.js +433 -291
  175. package/lib/modules/Step/index.js +40 -52
  176. package/lib/modules/Step/tyeps.js +4 -16
  177. package/lib/modules/Summary/index.js +66 -71
  178. package/lib/modules/Summary/types.js +4 -16
  179. package/lib/modules/Summary/utils.js +773 -418
  180. package/lib/modules/SurchargeList/index.js +46 -60
  181. package/lib/modules/SurchargeList/types.js +4 -16
  182. package/lib/modules/index.js +245 -63
  183. package/lib/plugins/app.js +4 -16
  184. package/lib/plugins/index.js +36 -25
  185. package/lib/plugins/request.js +137 -126
  186. package/lib/plugins/shopStore.js +4 -16
  187. package/lib/plugins/user.js +4 -16
  188. package/lib/plugins/window.js +171 -179
  189. package/lib/server/index.js +3050 -2665
  190. package/lib/server/modules/floor-plan/index.js +118 -134
  191. package/lib/server/modules/floor-plan/types.js +15 -30
  192. package/lib/server/modules/index.js +106 -68
  193. package/lib/server/modules/menu/index.js +142 -122
  194. package/lib/server/modules/menu/types.js +18 -31
  195. package/lib/server/modules/order/index.js +784 -1002
  196. package/lib/server/modules/order/types.js +122 -33
  197. package/lib/server/modules/order/utils/filterBookings.js +219 -194
  198. package/lib/server/modules/order/utils/filterOrders.js +118 -92
  199. package/lib/server/modules/payment/index.js +59 -83
  200. package/lib/server/modules/payment/types.js +4 -16
  201. package/lib/server/modules/products/index.js +583 -471
  202. package/lib/server/modules/products/types.js +44 -34
  203. package/lib/server/modules/quotation/index.js +137 -172
  204. package/lib/server/modules/quotation/types.js +21 -31
  205. package/lib/server/modules/resource/index.js +220 -186
  206. package/lib/server/modules/resource/types.js +42 -33
  207. package/lib/server/modules/schedule/index.js +135 -123
  208. package/lib/server/modules/schedule/types.js +14 -21
  209. package/lib/server/modules/schedule/utils.js +334 -163
  210. package/lib/server/types.js +4 -16
  211. package/lib/server/utils/index.js +25 -23
  212. package/lib/server/utils/product.js +196 -139
  213. package/lib/server/utils/schedule.js +34 -41
  214. package/lib/server/utils/small-ticket.js +479 -456
  215. package/lib/server/utils/time.js +40 -49
  216. package/lib/solution/BaseSales/index.js +1220 -1412
  217. package/lib/solution/BaseSales/types.js +42 -38
  218. package/lib/solution/BaseSales/utils/cartPromotion.d.ts +5 -0
  219. package/lib/solution/BaseSales/utils/cartPromotion.js +652 -498
  220. package/lib/solution/BaseSales/utils/parseSalesResponse.js +85 -105
  221. package/lib/solution/BaseSales/utils/quotationPrice.js +56 -114
  222. package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +108 -145
  223. package/lib/solution/BaseSales/utils.js +158 -143
  224. package/lib/solution/BookingByStep/index.js +1527 -1340
  225. package/lib/solution/BookingByStep/types.js +40 -99
  226. package/lib/solution/BookingByStep/utils/capacity.js +192 -160
  227. package/lib/solution/BookingByStep/utils/products.js +42 -52
  228. package/lib/solution/BookingByStep/utils/resources.js +527 -330
  229. package/lib/solution/BookingByStep/utils/stock.js +65 -44
  230. package/lib/solution/BookingByStep/utils/timeslots.js +129 -131
  231. package/lib/solution/BookingTicket/index.js +607 -522
  232. package/lib/solution/BookingTicket/types.js +99 -77
  233. package/lib/solution/BookingTicket/utils/addProductDecision.js +226 -189
  234. package/lib/solution/BookingTicket/utils/addTimeAvailability.js +58 -89
  235. package/lib/solution/BookingTicket/utils/bookingStatus.js +107 -78
  236. package/lib/solution/BookingTicket/utils/cartView.js +114 -99
  237. package/lib/solution/BookingTicket/utils/exampleData.js +185 -0
  238. package/lib/solution/BookingTicket/utils/orderCustomer.js +18 -41
  239. package/lib/solution/BookingTicket/utils/resolveBestAddTimePlan.js +219 -86
  240. package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.js +168 -116
  241. package/lib/solution/BookingTicket/utils/scan/cloudSearch.js +79 -91
  242. package/lib/solution/BookingTicket/utils/scan/formatGlobalScan.js +54 -41
  243. package/lib/solution/BookingTicket/utils/scan/handleScan.js +84 -81
  244. package/lib/solution/BookingTicket/utils/scan/index.js +98 -104
  245. package/lib/solution/BookingTicket/utils/scan/scanCache.js +90 -43
  246. package/lib/solution/BookingTicket/utils/scan/scanProductValues.js +52 -64
  247. package/lib/solution/BookingTicket/utils/sessionCatalogStale.js +56 -58
  248. package/lib/solution/BookingTicket/utils/weighingProductSku.js +34 -48
  249. package/lib/solution/BuyTickets/index.js +67 -70
  250. package/lib/solution/BuyTickets/types.js +22 -38
  251. package/lib/solution/Checkout/index.js +1451 -1158
  252. package/lib/solution/Checkout/types.js +91 -61
  253. package/lib/solution/Checkout/utils/index.js +228 -156
  254. package/lib/solution/PlaceOrder/index.js +55 -0
  255. package/lib/solution/RegisterAndLogin/config.js +493 -460
  256. package/lib/solution/RegisterAndLogin/index.js +633 -630
  257. package/lib/solution/RegisterAndLogin/types.js +189 -76
  258. package/lib/solution/RegisterAndLogin/utils.js +183 -125
  259. package/lib/solution/Sales/index.js +328 -324
  260. package/lib/solution/Sales/types.js +27 -33
  261. package/lib/solution/ScanOrder/index.js +832 -864
  262. package/lib/solution/ScanOrder/types.js +33 -34
  263. package/lib/solution/ScanOrder/utils.js +409 -374
  264. package/lib/solution/ShopDiscount/index.js +226 -232
  265. package/lib/solution/ShopDiscount/types.js +15 -37
  266. package/lib/solution/ShopDiscount/utils.js +300 -172
  267. package/lib/solution/UnifiedBookingSales/index.js +1035 -1201
  268. package/lib/solution/UnifiedBookingSales/remoteOccupancyCache.js +99 -159
  269. package/lib/solution/UnifiedBookingSales/types.js +10 -32
  270. package/lib/solution/VenueBooking/index.js +811 -879
  271. package/lib/solution/VenueBooking/types.js +19 -39
  272. package/lib/solution/VenueBooking/utils/dateSummary.js +27 -56
  273. package/lib/solution/VenueBooking/utils/resource.js +31 -54
  274. package/lib/solution/VenueBooking/utils/slotMerge.js +94 -107
  275. package/lib/solution/VenueBooking/utils/timeSlot.js +132 -132
  276. package/lib/solution/VenueBooking/utils.js +130 -67
  277. package/lib/solution/index.js +124 -41
  278. package/lib/store/createStore.js +32 -29
  279. package/lib/types/index.js +4 -16
  280. package/lib/utils/payment-number.js +26 -46
  281. package/lib/utils/task.js +36 -36
  282. package/lib/utils/watch.js +81 -47
  283. package/package.json +2 -1
@@ -1,39 +1,173 @@
1
- var __defProp = Object.defineProperty;
2
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
- var __getOwnPropNames = Object.getOwnPropertyNames;
4
- var __hasOwnProp = Object.prototype.hasOwnProperty;
5
- var __export = (target, all) => {
6
- for (var name in all)
7
- __defProp(target, name, { get: all[name], enumerable: true });
8
- };
9
- var __copyProps = (to, from, except, desc) => {
10
- if (from && typeof from === "object" || typeof from === "function") {
11
- for (let key of __getOwnPropNames(from))
12
- if (!__hasOwnProp.call(to, key) && key !== except)
13
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
- }
15
- return to;
16
- };
17
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
1
+ "use strict";
18
2
 
19
- // src/solution/BookingTicket/utils/resolveBestAddTimePlan.ts
20
- var resolveBestAddTimePlan_exports = {};
21
- __export(resolveBestAddTimePlan_exports, {
22
- ADD_TIME_UNIT_MINUTES: () => ADD_TIME_UNIT_MINUTES,
23
- buildAddTimeCandidates: () => buildAddTimeCandidates,
24
- resolveBestAddTimePlan: () => resolveBestAddTimePlan
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
25
5
  });
26
- module.exports = __toCommonJS(resolveBestAddTimePlan_exports);
27
- var ADD_TIME_UNIT_MINUTES = [10, 15, 20, 30, 45, 60];
28
- var toPositiveNumber = (value) => {
6
+ exports.ADD_TIME_UNIT_MINUTES = void 0;
7
+ exports.buildAddTimeCandidates = buildAddTimeCandidates;
8
+ exports.resolveBestAddTimePlan = resolveBestAddTimePlan;
9
+ /**
10
+ * 计算指定加时时长下的最优购买方案。
11
+ *
12
+ * 输入的商品结构来自旧版 booking addTimeModal 的加时商品配置映射:
13
+ * - `minMinutes`:该商品第一次可选择的最小时长。
14
+ * - `maxMinutes`:单次购买该商品可覆盖的最大时长。
15
+ * - `unitMinutes`:超过最小时长后,每次递增的时长切片。
16
+ * - `price`:每个 `unitMinutes` 切片的价格。
17
+ * - `unlimited`:固定价格商品,可覆盖任意目标时长。
18
+ *
19
+ * 算法流程:
20
+ *
21
+ * ```
22
+ * products
23
+ * |
24
+ * v
25
+ * 将每个普通商品展开成有限候选档位
26
+ * 商品 A: min=30, max=60, unit=10, price=10
27
+ * => 30m/$30, 40m/$40, 50m/$50, 60m/$60
28
+ * |
29
+ * v
30
+ * 单独加入 unlimited 候选
31
+ * unlimited 商品 => targetMinutes / 固定商品价格
32
+ * |
33
+ * v
34
+ * 按 covered minutes 做动态规划
35
+ * dp[minutes] = 正好覆盖 `minutes` 时的最低价候选组合
36
+ * |
37
+ * v
38
+ * 从所有 dp[minutes >= targetMinutes] 中挑选最优方案
39
+ * ```
40
+ *
41
+ * 候选档位生成刻意对齐旧版 addTimeModal:
42
+ * `totalPrice = minutes / unitMinutes * price`.
43
+ * 例如 `minMinutes=30`、`unitMinutes=10`、`price=10` 时:
44
+ * - 30 分钟 => 30 / 10 * 10 = 30
45
+ * - 60 分钟 => 60 / 10 * 10 = 60
46
+ *
47
+ * 目标时长不要求精确命中某个档位,只需要覆盖即可。例如目标是 51 分钟时,
48
+ * 60 分钟档位可以作为候选。动态规划也允许跨商品组合,例如商品 A 的 30m
49
+ * 加上商品 B 的 30m。最终方案按以下优先级比较:
50
+ * 1. 总价更低;
51
+ * 2. 超出时长更少,即 `coveredMinutes - targetMinutes` 更小;
52
+ * 3. 结果行数更少,让购买方案尽量简单。
53
+ *
54
+ * 典型匹配示例:
55
+ *
56
+ * 示例 1:精确命中,直接选择更便宜的商品。
57
+ * ```
58
+ * target = 30
59
+ * 商品 A: min=30, max=30, unit=30, price=10 => 30m/$10
60
+ * 商品 B: min=30, max=30, unit=30, price=8 => 30m/$8
61
+ * 结果:选择商品 B,coveredMinutes=30,totalPrice=8
62
+ * ```
63
+ *
64
+ * 示例 2:目标时长落在两个档位之间,向上选择可覆盖的档位。
65
+ * ```
66
+ * target = 51
67
+ * 商品 A: min=30, max=120, unit=10, price=10
68
+ * 候选:30m/$30, 40m/$40, 50m/$50, 60m/$60 ...
69
+ * 结果:50m 不足以覆盖 51m,因此选择 60m/$60
70
+ * ```
71
+ *
72
+ * 示例 3:不同商品都能覆盖同一目标时长,比较最终价格。
73
+ * ```
74
+ * target = 51
75
+ * 商品 A: min=30, max=120, unit=10, price=10 => 60m/$60
76
+ * 商品 B: min=30, max=120, unit=30, price=25 => 60m/$50
77
+ * 结果:两个方案都覆盖 60m,但商品 B 更便宜,所以选择商品 B
78
+ * ```
79
+ *
80
+ * 示例 4:允许跨商品组合,组合价更低时会选择组合。
81
+ * ```
82
+ * target = 90
83
+ * 商品 A: min=30, max=30, unit=30, price=5 => 30m/$5
84
+ * 商品 B: min=60, max=60, unit=60, price=8 => 60m/$8
85
+ * 商品 C: min=90, max=90, unit=90, price=20 => 90m/$20
86
+ * 结果:商品 A + 商品 B = 90m/$13,比商品 C 更便宜
87
+ * ```
88
+ *
89
+ * 示例 5:允许重复购买同一个候选档位。
90
+ * ```
91
+ * target = 90
92
+ * 商品 A: min=30, max=30, unit=30, price=5 => 30m/$5
93
+ * 结果:购买 3 份商品 A,coveredMinutes=90,totalPrice=15,
94
+ * lines 中会合并为一行:product_add_schedule_time=30,num=3
95
+ * ```
96
+ *
97
+ * 示例 6:unlimited 可以覆盖任意目标时长,并参与价格比较。
98
+ * ```
99
+ * target = 180
100
+ * 商品 A: min=30, max=120, unit=30, price=10
101
+ * 商品 B: unlimited=true, price=35
102
+ * 普通商品可以组合成 180m,例如 120m + 60m,总价 $60
103
+ * unlimited 商品固定 $35
104
+ * 结果:选择 unlimited 商品,coveredMinutes=180,totalPrice=35
105
+ * ```
106
+ *
107
+ * 示例 7:unlimited 不一定优先,普通商品更便宜时选择普通商品。
108
+ * ```
109
+ * target = 60
110
+ * 商品 A: min=30, max=60, unit=30, price=10 => 60m/$20
111
+ * 商品 B: unlimited=true, price=50
112
+ * 结果:选择商品 A 的 60m 档位
113
+ * ```
114
+ *
115
+ * 示例 8:价格相同,优先选择超出时长更少的方案。
116
+ * ```
117
+ * target = 51
118
+ * 商品 A: 60m/$20
119
+ * 商品 B: 90m/$20
120
+ * 结果:选择 60m,因为 overage=9,小于 90m 的 overage=39
121
+ * ```
122
+ *
123
+ * 示例 9:价格和覆盖时长都相同,优先选择结果行数更少的方案。
124
+ * ```
125
+ * target = 60
126
+ * 商品 A: 60m/$20
127
+ * 商品 B: 30m/$10
128
+ * 商品 C: 30m/$10
129
+ * 结果:商品 A 单行方案和 B+C 两行方案价格、时长相同,选择商品 A
130
+ * ```
131
+ *
132
+ * 示例 10:没有任何有效商品时返回 `NO_PRODUCTS`。
133
+ * ```
134
+ * target = 30
135
+ * products = []
136
+ * 结果:available=false,unavailableReason='NO_PRODUCTS'
137
+ * ```
138
+ *
139
+ * 示例 11:有商品但没有可用候选时返回 `NO_COVERAGE`。
140
+ * ```
141
+ * target = 30
142
+ * 商品 A: price=0,或 minMinutes 缺失/无效
143
+ * 结果:available=false,unavailableReason='NO_COVERAGE'
144
+ * ```
145
+ *
146
+ * 示例 12:目标时长小于等于 0 时无需购买。
147
+ * ```
148
+ * target = 0
149
+ * 结果:available=true,coveredMinutes=0,totalPrice=0,lines=[]
150
+ * ```
151
+ *
152
+ * 搜索边界:
153
+ * 有限档位只需要搜索到 `targetMinutes + maxFiniteCandidateMinutes`。
154
+ * 这里的 `maxFiniteCandidateMinutes` 是所有普通候选档位里的最大时长。
155
+ * 例如目标是 51 分钟,最大普通档位是 120 分钟,则只搜索到 171 分钟。
156
+ * 如果某个组合覆盖了 180 分钟,那么移除其中任意一个普通档位后,剩余时长
157
+ * 仍然有机会覆盖 51 分钟,而且价格一定不会更高,所以 180 分钟这个组合不
158
+ * 可能是最优解。
159
+ */
160
+
161
+ const ADD_TIME_UNIT_MINUTES = exports.ADD_TIME_UNIT_MINUTES = [10, 15, 20, 30, 45, 60];
162
+ const toPositiveNumber = value => {
29
163
  const n = Number(value);
30
164
  return Number.isFinite(n) && n > 0 ? n : null;
31
165
  };
32
- var normalizeTargetMinutes = (minutes) => {
166
+ const normalizeTargetMinutes = minutes => {
33
167
  const n = Number(minutes);
34
168
  return Number.isFinite(n) ? Math.max(0, Math.ceil(n)) : 0;
35
169
  };
36
- var gcd = (a, b) => {
170
+ const gcd = (a, b) => {
37
171
  let x = Math.abs(a);
38
172
  let y = Math.abs(b);
39
173
  while (y !== 0) {
@@ -43,26 +177,28 @@ var gcd = (a, b) => {
43
177
  }
44
178
  return x;
45
179
  };
46
- var getDpStepMinutes = (candidates) => {
47
- return candidates.reduce(
48
- (current, candidate) => gcd(current, candidate.product_add_schedule_time),
49
- 0
50
- ) || 1;
180
+ const getDpStepMinutes = candidates => {
181
+ return candidates.reduce((current, candidate) => gcd(current, candidate.product_add_schedule_time), 0) || 1;
51
182
  };
52
- var comparePlans = (left, right) => {
53
- if (!left)
54
- return right;
183
+ const comparePlans = (left, right) => {
184
+ if (!left) return right;
185
+
186
+ // 第一优先级:总价更低。例:同样覆盖 60 分钟,$50 优先于 $60。
55
187
  if (right.totalPrice !== left.totalPrice) {
56
188
  return right.totalPrice < left.totalPrice ? right : left;
57
189
  }
190
+
191
+ // 第二优先级:价格相同时,选择超出目标更少的方案。例:target=51 时,60m 优先于 90m。
58
192
  const leftOverage = left.coveredMinutes - left.targetMinutes;
59
193
  const rightOverage = right.coveredMinutes - right.targetMinutes;
60
194
  if (rightOverage !== leftOverage) {
61
195
  return rightOverage < leftOverage ? right : left;
62
196
  }
197
+
198
+ // 第三优先级:价格和超出时长都相同时,选择购买行数更少的方案,减少下单复杂度。
63
199
  return right.lines.length < left.lines.length ? right : left;
64
200
  };
65
- var toPlanLine = (candidate) => ({
201
+ const toPlanLine = candidate => ({
66
202
  product_id: candidate.product_id,
67
203
  product_title: candidate.product_title,
68
204
  product_add_schedule_time: Number.isFinite(candidate.product_add_schedule_time) ? candidate.product_add_schedule_time : 0,
@@ -70,10 +206,11 @@ var toPlanLine = (candidate) => ({
70
206
  price: candidate.price,
71
207
  _originalProduct: candidate._originalProduct
72
208
  });
73
- var mergePlanLines = (candidates) => {
74
- const map = /* @__PURE__ */ new Map();
209
+ const mergePlanLines = candidates => {
210
+ const map = new Map();
75
211
  for (const candidate of candidates) {
76
- const key = `${candidate.product_id}-${candidate.product_add_schedule_time}-${candidate.unlimited ? "u" : "n"}`;
212
+ // 同一个商品、同一个档位可以重复购买,合并成 num。例:30m 档买 3 => 一行 num=3。
213
+ const key = `${candidate.product_id}-${candidate.product_add_schedule_time}-${candidate.unlimited ? 'u' : 'n'}`;
77
214
  const existing = map.get(key);
78
215
  if (existing) {
79
216
  existing.num += candidate.num;
@@ -83,17 +220,13 @@ var mergePlanLines = (candidates) => {
83
220
  }
84
221
  return Array.from(map.values());
85
222
  };
86
- var sumPlanLineMinutes = (lines) => lines.reduce(
87
- (total, line) => total + line.product_add_schedule_time * line.num,
88
- 0
89
- );
90
- var restoreDpCandidates = (dp, finiteCandidates, index) => {
223
+ const sumPlanLineMinutes = lines => lines.reduce((total, line) => total + line.product_add_schedule_time * line.num, 0);
224
+ const restoreDpCandidates = (dp, finiteCandidates, index) => {
91
225
  const candidates = [];
92
226
  let currentIndex = index;
93
227
  while (currentIndex > 0) {
94
228
  const current = dp[currentIndex];
95
- if (!current || current.candidateIndex < 0)
96
- break;
229
+ if (!current || current.candidateIndex < 0) break;
97
230
  candidates.push(finiteCandidates[current.candidateIndex]);
98
231
  currentIndex = current.prevIndex;
99
232
  }
@@ -102,10 +235,11 @@ var restoreDpCandidates = (dp, finiteCandidates, index) => {
102
235
  function buildAddTimeCandidates(products) {
103
236
  const candidates = [];
104
237
  for (const product of products) {
238
+ // 价格无效的商品无法参与比价,直接跳过,避免生成 0 元或 NaN 候选。
105
239
  const price = toPositiveNumber(product.price);
106
- if (price == null)
107
- continue;
240
+ if (price == null) continue;
108
241
  if (product.unlimited) {
242
+ // unlimited 是固定价商品,可覆盖任意目标时长;这里先用 Infinity 标记,最终按 targetMinutes 输出。
109
243
  candidates.push({
110
244
  product_id: product.id,
111
245
  product_title: product.title,
@@ -118,12 +252,18 @@ function buildAddTimeCandidates(products) {
118
252
  });
119
253
  continue;
120
254
  }
255
+
256
+ // 普通商品必须有有效的起购时长,否则无法知道第一档应该从哪里开始。
121
257
  const minMinutes = toPositiveNumber(product.minMinutes);
122
- if (minMinutes == null)
123
- continue;
258
+ if (minMinutes == null) continue;
259
+
260
+ // 旧版配置中 max/unit 可能缺失:缺失时退化为只有 minMinutes 这一档。
124
261
  const maxMinutes = toPositiveNumber(product.maxMinutes) ?? minMinutes;
125
262
  const unitMinutes = toPositiveNumber(product.unitMinutes) ?? minMinutes;
263
+ // 当 maxMinutes 小于 minMinutes 时,至少保留 minMinutes 这一档,避免错误配置导致无候选。
126
264
  const safeMax = Math.max(minMinutes, maxMinutes);
265
+
266
+ // 将一个商品展开为多个可购买档位。例:min=30,max=60,unit=10 => 30/40/50/60 四个候选。
127
267
  for (let minutes = minMinutes; minutes <= safeMax; minutes += unitMinutes) {
128
268
  candidates.push({
129
269
  product_id: product.id,
@@ -139,8 +279,10 @@ function buildAddTimeCandidates(products) {
139
279
  return candidates;
140
280
  }
141
281
  function resolveBestAddTimePlan(products, targetMinutes) {
282
+ // 目标时长按分钟向上取整,避免 50.2 分钟被当成 50 分钟而覆盖不足。
142
283
  const normalizedTarget = normalizeTargetMinutes(targetMinutes);
143
284
  if (normalizedTarget <= 0) {
285
+ // 目标时长为 0 或负数时表示无需加时,直接返回 0 元可用方案。
144
286
  return {
145
287
  available: true,
146
288
  targetMinutes: 0,
@@ -152,7 +294,7 @@ function resolveBestAddTimePlan(products, targetMinutes) {
152
294
  if (!products.length) {
153
295
  return {
154
296
  available: false,
155
- unavailableReason: "NO_PRODUCTS",
297
+ unavailableReason: 'NO_PRODUCTS',
156
298
  targetMinutes: normalizedTarget,
157
299
  coveredMinutes: 0,
158
300
  totalPrice: 0,
@@ -160,23 +302,19 @@ function resolveBestAddTimePlan(products, targetMinutes) {
160
302
  };
161
303
  }
162
304
  const candidates = buildAddTimeCandidates(products);
163
- const finiteCandidates = candidates.filter(
164
- (item) => Number.isFinite(item.product_add_schedule_time)
165
- );
166
- const unlimitedCandidates = candidates.filter((item) => item.unlimited);
167
- const maxFiniteMinutes = Math.max(
168
- 0,
169
- ...finiteCandidates.map((item) => item.product_add_schedule_time)
170
- );
305
+ // 普通候选参与 DP 组合;unlimited 单独比较,因为它本身可以覆盖任意时长,不需要组合。
306
+ const finiteCandidates = candidates.filter(item => Number.isFinite(item.product_add_schedule_time));
307
+ const unlimitedCandidates = candidates.filter(item => item.unlimited);
308
+ // 搜索上限只需要多看一个最大档位。超过这个范围的组合,去掉其中一个档位后通常仍可覆盖且更便宜。
309
+ const maxFiniteMinutes = Math.max(0, ...finiteCandidates.map(item => item.product_add_schedule_time));
171
310
  const searchLimit = normalizedTarget + maxFiniteMinutes;
172
311
  let best = null;
173
312
  for (const unlimited of unlimitedCandidates) {
174
- const lines = [
175
- toPlanLine({
176
- ...unlimited,
177
- product_add_schedule_time: normalizedTarget
178
- })
179
- ];
313
+ // unlimited 按目标时长输出 lineItem 时长,但价格保持商品固定价,并参与普通方案的统一比价。
314
+ const lines = [toPlanLine({
315
+ ...unlimited,
316
+ product_add_schedule_time: normalizedTarget
317
+ })];
180
318
  best = comparePlans(best, {
181
319
  available: true,
182
320
  targetMinutes: normalizedTarget,
@@ -189,9 +327,9 @@ function resolveBestAddTimePlan(products, targetMinutes) {
189
327
  const stepMinutes = getDpStepMinutes(finiteCandidates);
190
328
  const searchLimitIndex = Math.ceil(searchLimit / stepMinutes);
191
329
  const targetIndex = Math.ceil(normalizedTarget / stepMinutes);
192
- const finiteCandidateSteps = finiteCandidates.map(
193
- (candidate) => candidate.product_add_schedule_time / stepMinutes
194
- );
330
+ const finiteCandidateSteps = finiteCandidates.map(candidate => candidate.product_add_schedule_time / stepMinutes);
331
+
332
+ // dp[x] 表示“刚好覆盖 x * stepMinutes 分钟”的当前最低价状态;状态只存前驱,最后再回溯生成明细。
195
333
  const dp = [];
196
334
  dp[0] = {
197
335
  price: 0,
@@ -201,16 +339,18 @@ function resolveBestAddTimePlan(products, targetMinutes) {
201
339
  };
202
340
  for (let index = 0; index <= searchLimitIndex; index += 1) {
203
341
  const current = dp[index];
204
- if (!current)
205
- continue;
342
+ // 当前压缩状态不可达时跳过。例:只有 30m 档且 step=30 时,dp[1] 才代表 30 分钟。
343
+ if (!current) continue;
206
344
  for (let candidateIndex = 0; candidateIndex < finiteCandidates.length; candidateIndex += 1) {
345
+ // 尝试在当前组合后再买一个候选档位,形成新的覆盖时长。
207
346
  const nextIndex = index + finiteCandidateSteps[candidateIndex];
208
- if (nextIndex > searchLimitIndex)
209
- continue;
347
+ if (nextIndex > searchLimitIndex) continue;
210
348
  const candidate = finiteCandidates[candidateIndex];
211
349
  const nextPrice = current.price + candidate.totalPrice;
212
350
  const nextItemCount = current.itemCount + 1;
213
351
  const prev = dp[nextIndex];
352
+
353
+ // 更新 dp[nextIndex]:价格更低则替换;价格相同则用更少档位,便于后续输出更简单的方案。
214
354
  if (!prev || nextPrice < prev.price || nextPrice === prev.price && nextItemCount < prev.itemCount) {
215
355
  dp[nextIndex] = {
216
356
  price: nextPrice,
@@ -221,13 +361,12 @@ function resolveBestAddTimePlan(products, targetMinutes) {
221
361
  }
222
362
  }
223
363
  }
364
+
365
+ // 目标不要求精确命中:从 target 到 searchLimit 的所有可达方案里选最优。
224
366
  for (let index = targetIndex; index <= searchLimitIndex; index += 1) {
225
367
  const current = dp[index];
226
- if (!current)
227
- continue;
228
- const lines = mergePlanLines(
229
- restoreDpCandidates(dp, finiteCandidates, index)
230
- );
368
+ if (!current) continue;
369
+ const lines = mergePlanLines(restoreDpCandidates(dp, finiteCandidates, index));
231
370
  best = comparePlans(best, {
232
371
  available: true,
233
372
  targetMinutes: normalizedTarget,
@@ -239,16 +378,10 @@ function resolveBestAddTimePlan(products, targetMinutes) {
239
378
  }
240
379
  return best ?? {
241
380
  available: false,
242
- unavailableReason: "NO_COVERAGE",
381
+ unavailableReason: 'NO_COVERAGE',
243
382
  targetMinutes: normalizedTarget,
244
383
  coveredMinutes: 0,
245
384
  totalPrice: 0,
246
385
  lines: []
247
386
  };
248
- }
249
- // Annotate the CommonJS export names for ESM import in node:
250
- 0 && (module.exports = {
251
- ADD_TIME_UNIT_MINUTES,
252
- buildAddTimeCandidates,
253
- resolveBestAddTimePlan
254
- });
387
+ }