@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.
- package/dist/core/index.js +6 -10
- package/dist/model/strategy/adapter/promotion/index.js +9 -0
- package/dist/model/strategy/adapter/walletPass/example.js +3 -26
- package/dist/model/strategy/strategy-example.js +5 -19
- package/dist/modules/Cart/index.js +0 -3
- package/dist/modules/Cart/utils/cartProduct.js +0 -1
- package/dist/modules/Date/index.js +1 -5
- package/dist/modules/Discount/index.js +2 -4
- package/dist/modules/OpenData/index.js +0 -2
- package/dist/modules/Order/index.d.ts +1 -1
- package/dist/modules/Order/index.js +75 -91
- package/dist/modules/Order/types.d.ts +3 -1
- package/dist/modules/Payment/index.js +97 -162
- package/dist/modules/Rules/index.js +1 -4
- package/dist/modules/Schedule/index.js +0 -1
- package/dist/modules/Schedule/utils.js +0 -1
- package/dist/modules/Step/index.js +0 -1
- package/dist/plugins/request.js +1 -4
- package/dist/plugins/window.js +2 -6
- package/dist/server/index.js +97 -129
- package/dist/server/modules/menu/index.js +32 -41
- package/dist/server/modules/order/index.js +2 -4
- package/dist/server/modules/products/index.js +24 -42
- package/dist/server/modules/quotation/index.js +29 -38
- package/dist/server/modules/resource/index.js +3 -4
- package/dist/server/modules/schedule/index.js +27 -35
- package/dist/server/utils/product.js +0 -1
- package/dist/solution/BaseSales/index.js +100 -72
- package/dist/solution/BaseSales/utils/cartPromotion.d.ts +5 -0
- package/dist/solution/BaseSales/utils/cartPromotion.js +15 -6
- package/dist/solution/BookingByStep/index.js +7 -59
- package/dist/solution/BookingByStep/utils/capacity.js +1 -11
- package/dist/solution/BookingByStep/utils/resources.js +1 -3
- package/dist/solution/BookingByStep/utils/timeslots.js +2 -43
- package/dist/solution/BookingTicket/index.js +0 -2
- package/dist/solution/BookingTicket/utils/scan/cloudSearch.js +6 -7
- package/dist/solution/BookingTicket/utils/scan/handleScan.js +25 -31
- package/dist/solution/BookingTicket/utils/scan/index.js +6 -10
- package/dist/solution/BuyTickets/index.js +9 -12
- package/dist/solution/Checkout/index.js +177 -252
- package/dist/solution/Checkout/utils/index.js +4 -16
- package/dist/solution/RegisterAndLogin/index.js +30 -76
- package/dist/solution/ScanOrder/index.js +50 -60
- package/dist/solution/ShopDiscount/index.js +2 -7
- package/dist/solution/VenueBooking/index.js +45 -55
- package/dist/utils/task.js +15 -18
- package/dist/utils/watch.js +0 -1
- package/lib/apis/picoding.js +2 -0
- package/lib/core/index.js +134 -134
- package/lib/effects/index.js +46 -57
- package/lib/index.js +82 -49
- package/lib/model/index.js +14 -21
- package/lib/model/strategy/adapter/dataVariant/adapter.js +38 -50
- package/lib/model/strategy/adapter/dataVariant/evaluator.js +162 -236
- package/lib/model/strategy/adapter/dataVariant/examples.js +229 -237
- package/lib/model/strategy/adapter/dataVariant/index.js +39 -34
- package/lib/model/strategy/adapter/dataVariant/type.js +57 -28
- package/lib/model/strategy/adapter/index.js +100 -64
- package/lib/model/strategy/adapter/itemRule/adapter.js +156 -144
- package/lib/model/strategy/adapter/itemRule/evaluator.js +36 -48
- package/lib/model/strategy/adapter/itemRule/examples.js +295 -225
- package/lib/model/strategy/adapter/itemRule/index.js +83 -57
- package/lib/model/strategy/adapter/itemRule/type.js +97 -36
- package/lib/model/strategy/adapter/promotion/adapter.js +91 -69
- package/lib/model/strategy/adapter/promotion/evaluator.js +444 -310
- package/lib/model/strategy/adapter/promotion/examples.js +222 -234
- package/lib/model/strategy/adapter/promotion/index.js +1 -0
- package/lib/model/strategy/adapter/promotion/type.js +243 -36
- package/lib/model/strategy/adapter/type.js +4 -16
- package/lib/model/strategy/adapter/walletPass/evaluator.js +180 -150
- package/lib/model/strategy/adapter/walletPass/example.js +217 -190
- package/lib/model/strategy/adapter/walletPass/index.js +75 -51
- package/lib/model/strategy/adapter/walletPass/locales.js +36 -58
- package/lib/model/strategy/adapter/walletPass/type.js +4 -16
- package/lib/model/strategy/adapter/walletPass/utils.js +588 -378
- package/lib/model/strategy/engine.js +270 -168
- package/lib/model/strategy/index.js +49 -34
- package/lib/model/strategy/strategy-example.js +243 -239
- package/lib/model/strategy/type.js +100 -40
- package/lib/modules/Account/index.js +39 -53
- package/lib/modules/Account/types.js +20 -33
- package/lib/modules/AccountList/index.js +116 -154
- package/lib/modules/AccountList/types.js +30 -31
- package/lib/modules/AccountList/utils.js +18 -30
- package/lib/modules/BaseModule.js +23 -42
- package/lib/modules/BookingContext/index.js +158 -136
- package/lib/modules/BookingContext/types.js +46 -32
- package/lib/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +138 -146
- package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +78 -83
- package/lib/modules/BookingContext/utils/cacheItemToBookingInput.js +170 -150
- package/lib/modules/BookingContext/utils/flexible.js +38 -55
- package/lib/modules/BookingContext/utils/formatResourceList.js +19 -34
- package/lib/modules/BookingContext/utils/index.js +124 -41
- package/lib/modules/BookingContext/utils/noResource.js +58 -70
- package/lib/modules/BookingContext/utils/orderLineDisplay.js +14 -41
- package/lib/modules/BookingContext/utils/productExtend.js +202 -155
- package/lib/modules/BookingContext/utils/resourceErrors.js +53 -53
- package/lib/modules/BookingContext/utils/resourceSelection.js +19 -41
- package/lib/modules/BookingContext/utils/resources.js +209 -167
- package/lib/modules/BookingContext/utils/serviceTimes.js +115 -116
- package/lib/modules/BookingContext/utils/timeSlices.js +76 -86
- package/lib/modules/Cart/index.js +170 -124
- package/lib/modules/Cart/types.js +46 -51
- package/lib/modules/Cart/utils/cartAccount.js +40 -39
- package/lib/modules/Cart/utils/cartDate.js +56 -61
- package/lib/modules/Cart/utils/cartDiscount.js +28 -33
- package/lib/modules/Cart/utils/cartNote.js +27 -32
- package/lib/modules/Cart/utils/cartProduct.js +321 -251
- package/lib/modules/Cart/utils/cartRelationForms.js +29 -35
- package/lib/modules/Cart/utils/cartResource.js +70 -78
- package/lib/modules/Cart/utils/changePrice.js +22 -50
- package/lib/modules/Cart/utils/index.js +106 -48
- package/lib/modules/Customer/constants.js +13 -34
- package/lib/modules/Customer/index.js +235 -172
- package/lib/modules/Customer/types.js +38 -35
- package/lib/modules/Date/index.js +116 -115
- package/lib/modules/Date/types.js +16 -28
- package/lib/modules/Date/utils.js +174 -123
- package/lib/modules/Discount/index.js +122 -127
- package/lib/modules/Discount/types.js +9 -31
- package/lib/modules/Guests/index.js +30 -56
- package/lib/modules/Guests/types.js +23 -33
- package/lib/modules/Holder/index.js +209 -189
- package/lib/modules/Holder/types.js +4 -16
- package/lib/modules/Holder/utils.js +20 -49
- package/lib/modules/OpenData/index.js +70 -76
- package/lib/modules/OpenData/types.js +4 -16
- package/lib/modules/OpenData/utils.js +44 -78
- package/lib/modules/Order/index.d.ts +1 -1
- package/lib/modules/Order/index.js +1733 -2121
- package/lib/modules/Order/payment-utils.js +77 -120
- package/lib/modules/Order/types.d.ts +3 -1
- package/lib/modules/Order/types.js +88 -35
- package/lib/modules/Order/utils/bundleDiscountHydration.js +41 -74
- package/lib/modules/Order/utils/discountProductLineIdentity.js +142 -100
- package/lib/modules/Order/utils/manualProductDiscount.js +124 -123
- package/lib/modules/Order/utils/orderCollectionIdentity.js +184 -244
- package/lib/modules/Order/utils.js +814 -647
- package/lib/modules/Payment/cash.js +20 -33
- package/lib/modules/Payment/cashRecommendationAlgorithm.js +145 -76
- package/lib/modules/Payment/eftpos.js +16 -30
- package/lib/modules/Payment/index.js +532 -399
- package/lib/modules/Payment/mx51.js +1 -0
- package/lib/modules/Payment/types.js +230 -108
- package/lib/modules/Payment/utils.js +52 -51
- package/lib/modules/Payment/walletpass.js +485 -660
- package/lib/modules/Product/index.js +51 -46
- package/lib/modules/Product/types.js +4 -16
- package/lib/modules/Product/utils.js +32 -33
- package/lib/modules/ProductList/index.js +113 -123
- package/lib/modules/ProductList/types.js +9 -31
- package/lib/modules/Quotation/index.js +81 -118
- package/lib/modules/Quotation/types.js +4 -16
- package/lib/modules/Resource/index.js +27 -59
- package/lib/modules/Resource/types.js +22 -32
- package/lib/modules/Resource/utils.js +30 -38
- package/lib/modules/ResourcePlanner/index.js +25 -23
- package/lib/modules/ResourcePlanner/localClock.js +54 -119
- package/lib/modules/ResourcePlanner/planner.js +589 -770
- package/lib/modules/ResourcePlanner/types.js +10 -29
- package/lib/modules/Rules/index.js +1218 -959
- package/lib/modules/Rules/types.js +17 -40
- package/lib/modules/SalesSummary/index.js +85 -90
- package/lib/modules/SalesSummary/types.js +4 -16
- package/lib/modules/SalesSummary/utils.js +355 -430
- package/lib/modules/ScanOrderLogger/index.js +59 -79
- package/lib/modules/ScanOrderLogger/providers/feishu.js +54 -78
- package/lib/modules/ScanOrderLogger/providers/grafana.js +13 -36
- package/lib/modules/ScanOrderLogger/types.js +4 -16
- package/lib/modules/Schedule/getDateIsInSchedule.js +334 -163
- package/lib/modules/Schedule/index.js +100 -114
- package/lib/modules/Schedule/type.js +4 -16
- package/lib/modules/Schedule/types.js +4 -16
- package/lib/modules/Schedule/utils.js +433 -291
- package/lib/modules/Step/index.js +40 -52
- package/lib/modules/Step/tyeps.js +4 -16
- package/lib/modules/Summary/index.js +66 -71
- package/lib/modules/Summary/types.js +4 -16
- package/lib/modules/Summary/utils.js +773 -418
- package/lib/modules/SurchargeList/index.js +46 -60
- package/lib/modules/SurchargeList/types.js +4 -16
- package/lib/modules/index.js +245 -63
- package/lib/plugins/app.js +4 -16
- package/lib/plugins/index.js +36 -25
- package/lib/plugins/request.js +137 -126
- package/lib/plugins/shopStore.js +4 -16
- package/lib/plugins/user.js +4 -16
- package/lib/plugins/window.js +171 -179
- package/lib/server/index.js +3050 -2665
- package/lib/server/modules/floor-plan/index.js +118 -134
- package/lib/server/modules/floor-plan/types.js +15 -30
- package/lib/server/modules/index.js +106 -68
- package/lib/server/modules/menu/index.js +142 -122
- package/lib/server/modules/menu/types.js +18 -31
- package/lib/server/modules/order/index.js +784 -1002
- package/lib/server/modules/order/types.js +122 -33
- package/lib/server/modules/order/utils/filterBookings.js +219 -194
- package/lib/server/modules/order/utils/filterOrders.js +118 -92
- package/lib/server/modules/payment/index.js +59 -83
- package/lib/server/modules/payment/types.js +4 -16
- package/lib/server/modules/products/index.js +583 -471
- package/lib/server/modules/products/types.js +44 -34
- package/lib/server/modules/quotation/index.js +137 -172
- package/lib/server/modules/quotation/types.js +21 -31
- package/lib/server/modules/resource/index.js +220 -186
- package/lib/server/modules/resource/types.js +42 -33
- package/lib/server/modules/schedule/index.js +135 -123
- package/lib/server/modules/schedule/types.js +14 -21
- package/lib/server/modules/schedule/utils.js +334 -163
- package/lib/server/types.js +4 -16
- package/lib/server/utils/index.js +25 -23
- package/lib/server/utils/product.js +196 -139
- package/lib/server/utils/schedule.js +34 -41
- package/lib/server/utils/small-ticket.js +479 -456
- package/lib/server/utils/time.js +40 -49
- package/lib/solution/BaseSales/index.js +1220 -1412
- package/lib/solution/BaseSales/types.js +42 -38
- package/lib/solution/BaseSales/utils/cartPromotion.d.ts +5 -0
- package/lib/solution/BaseSales/utils/cartPromotion.js +652 -498
- package/lib/solution/BaseSales/utils/parseSalesResponse.js +85 -105
- package/lib/solution/BaseSales/utils/quotationPrice.js +56 -114
- package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +108 -145
- package/lib/solution/BaseSales/utils.js +158 -143
- package/lib/solution/BookingByStep/index.js +1527 -1340
- package/lib/solution/BookingByStep/types.js +40 -99
- package/lib/solution/BookingByStep/utils/capacity.js +192 -160
- package/lib/solution/BookingByStep/utils/products.js +42 -52
- package/lib/solution/BookingByStep/utils/resources.js +527 -330
- package/lib/solution/BookingByStep/utils/stock.js +65 -44
- package/lib/solution/BookingByStep/utils/timeslots.js +129 -131
- package/lib/solution/BookingTicket/index.js +607 -522
- package/lib/solution/BookingTicket/types.js +99 -77
- package/lib/solution/BookingTicket/utils/addProductDecision.js +226 -189
- package/lib/solution/BookingTicket/utils/addTimeAvailability.js +58 -89
- package/lib/solution/BookingTicket/utils/bookingStatus.js +107 -78
- package/lib/solution/BookingTicket/utils/cartView.js +114 -99
- package/lib/solution/BookingTicket/utils/exampleData.js +185 -0
- package/lib/solution/BookingTicket/utils/orderCustomer.js +18 -41
- package/lib/solution/BookingTicket/utils/resolveBestAddTimePlan.js +219 -86
- package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.js +168 -116
- package/lib/solution/BookingTicket/utils/scan/cloudSearch.js +79 -91
- package/lib/solution/BookingTicket/utils/scan/formatGlobalScan.js +54 -41
- package/lib/solution/BookingTicket/utils/scan/handleScan.js +84 -81
- package/lib/solution/BookingTicket/utils/scan/index.js +98 -104
- package/lib/solution/BookingTicket/utils/scan/scanCache.js +90 -43
- package/lib/solution/BookingTicket/utils/scan/scanProductValues.js +52 -64
- package/lib/solution/BookingTicket/utils/sessionCatalogStale.js +56 -58
- package/lib/solution/BookingTicket/utils/weighingProductSku.js +34 -48
- package/lib/solution/BuyTickets/index.js +67 -70
- package/lib/solution/BuyTickets/types.js +22 -38
- package/lib/solution/Checkout/index.js +1451 -1158
- package/lib/solution/Checkout/types.js +91 -61
- package/lib/solution/Checkout/utils/index.js +228 -156
- package/lib/solution/PlaceOrder/index.js +55 -0
- package/lib/solution/RegisterAndLogin/config.js +493 -460
- package/lib/solution/RegisterAndLogin/index.js +633 -630
- package/lib/solution/RegisterAndLogin/types.js +189 -76
- package/lib/solution/RegisterAndLogin/utils.js +183 -125
- package/lib/solution/Sales/index.js +328 -324
- package/lib/solution/Sales/types.js +27 -33
- package/lib/solution/ScanOrder/index.js +832 -864
- package/lib/solution/ScanOrder/types.js +33 -34
- package/lib/solution/ScanOrder/utils.js +409 -374
- package/lib/solution/ShopDiscount/index.js +226 -232
- package/lib/solution/ShopDiscount/types.js +15 -37
- package/lib/solution/ShopDiscount/utils.js +300 -172
- package/lib/solution/UnifiedBookingSales/index.js +1035 -1201
- package/lib/solution/UnifiedBookingSales/remoteOccupancyCache.js +99 -159
- package/lib/solution/UnifiedBookingSales/types.js +10 -32
- package/lib/solution/VenueBooking/index.js +811 -879
- package/lib/solution/VenueBooking/types.js +19 -39
- package/lib/solution/VenueBooking/utils/dateSummary.js +27 -56
- package/lib/solution/VenueBooking/utils/resource.js +31 -54
- package/lib/solution/VenueBooking/utils/slotMerge.js +94 -107
- package/lib/solution/VenueBooking/utils/timeSlot.js +132 -132
- package/lib/solution/VenueBooking/utils.js +130 -67
- package/lib/solution/index.js +124 -41
- package/lib/store/createStore.js +32 -29
- package/lib/types/index.js +4 -16
- package/lib/utils/payment-number.js +26 -46
- package/lib/utils/task.js +36 -36
- package/lib/utils/watch.js +81 -47
- package/package.json +2 -1
|
@@ -1,39 +1,173 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
20
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
53
|
-
if (!left)
|
|
54
|
-
|
|
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
|
-
|
|
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
|
-
|
|
74
|
-
const map =
|
|
209
|
+
const mergePlanLines = candidates => {
|
|
210
|
+
const map = new Map();
|
|
75
211
|
for (const candidate of candidates) {
|
|
76
|
-
|
|
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
|
-
|
|
87
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
164
|
-
|
|
165
|
-
);
|
|
166
|
-
|
|
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
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
-
|
|
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
|
-
|
|
205
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
+
}
|