@pisell/pisellos 2.2.230 → 2.2.232

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 (34) hide show
  1. package/dist/model/strategy/adapter/promotion/index.js +9 -0
  2. package/dist/modules/Order/index.js +44 -29
  3. package/dist/modules/Order/types.d.ts +14 -2
  4. package/dist/modules/Order/types.js +8 -1
  5. package/dist/modules/ProductList/index.d.ts +9 -12
  6. package/dist/modules/ProductList/index.js +122 -59
  7. package/dist/modules/ProductList/types.d.ts +14 -0
  8. package/dist/server/index.d.ts +21 -0
  9. package/dist/server/index.js +154 -34
  10. package/dist/server/utils/small-ticket.js +113 -29
  11. package/dist/solution/BookingByStep/index.d.ts +1 -1
  12. package/dist/solution/BookingTicket/index.d.ts +9 -1
  13. package/dist/solution/BookingTicket/index.js +198 -158
  14. package/dist/solution/BookingTicket/types.d.ts +4 -0
  15. package/dist/solution/BookingTicket/utils/cartView.js +20 -6
  16. package/dist/solution/BookingTicket/utils/resolveBestAddTimePlan.d.ts +189 -0
  17. package/dist/solution/BookingTicket/utils/resolveBestAddTimePlan.js +429 -0
  18. package/lib/model/strategy/adapter/promotion/index.js +0 -49
  19. package/lib/modules/Order/index.js +18 -5
  20. package/lib/modules/Order/types.d.ts +14 -2
  21. package/lib/modules/ProductList/index.d.ts +9 -12
  22. package/lib/modules/ProductList/index.js +32 -4
  23. package/lib/modules/ProductList/types.d.ts +14 -0
  24. package/lib/server/index.d.ts +21 -0
  25. package/lib/server/index.js +107 -9
  26. package/lib/server/utils/small-ticket.js +78 -1
  27. package/lib/solution/BookingByStep/index.d.ts +1 -1
  28. package/lib/solution/BookingTicket/index.d.ts +9 -1
  29. package/lib/solution/BookingTicket/index.js +20 -1
  30. package/lib/solution/BookingTicket/types.d.ts +4 -0
  31. package/lib/solution/BookingTicket/utils/cartView.js +14 -7
  32. package/lib/solution/BookingTicket/utils/resolveBestAddTimePlan.d.ts +189 -0
  33. package/lib/solution/BookingTicket/utils/resolveBestAddTimePlan.js +241 -0
  34. package/package.json +1 -1
@@ -50,6 +50,7 @@ var import_sessionCatalogStale = require("./utils/sessionCatalogStale");
50
50
  var import_buildNormalProductCacheItemFromOrderLine = require("../../modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine");
51
51
  var import_orderLineDisplay = require("../../modules/BookingContext/utils/orderLineDisplay");
52
52
  var import_bookingStatus = require("./utils/bookingStatus");
53
+ var import_resolveBestAddTimePlan = require("./utils/resolveBestAddTimePlan");
53
54
  __reExport(BookingTicket_exports, require("./types"), module.exports);
54
55
  var OPEN_DATA_SECTION_CODES = ["sale", "reservation", "fulfillment", "menu", "workflow"];
55
56
  var OPEN_DATA_CACHE_TTL = 5 * 60 * 1e3;
@@ -90,7 +91,9 @@ var BookingTicketImpl = class extends import_BaseSales.BaseSalesImpl {
90
91
  return [...super.getRegisteredModuleNames(), "customer", "bookingContext", "openData"];
91
92
  }
92
93
  getSubmitOrderSalesChannel() {
93
- return "pos";
94
+ var _a, _b, _c;
95
+ const channel = (_c = (_b = (_a = this.core) == null ? void 0 : _a.context) == null ? void 0 : _b.getChannel) == null ? void 0 : _c.call(_b);
96
+ return !channel || channel === "system" ? "pos" : channel;
94
97
  }
95
98
  /**
96
99
  * 业务侧子模块工厂:BookingTicket 自带的 createBookingTicketModule 优先匹配;
@@ -241,6 +244,7 @@ var BookingTicketImpl = class extends import_BaseSales.BaseSalesImpl {
241
244
  * 让现有 setActiveCustomer / getActiveCustomer 调用方零成本拿到详情态客户。
242
245
  */
243
246
  async loadSalesDetail(orderIdOrParams) {
247
+ debugger;
244
248
  const sales = await super.loadSalesDetail(orderIdOrParams);
245
249
  const customerModule = this.store.customer;
246
250
  const detailCustomer = sales == null ? void 0 : sales.customer;
@@ -508,6 +512,18 @@ var BookingTicketImpl = class extends import_BaseSales.BaseSalesImpl {
508
512
  throw error;
509
513
  }
510
514
  }
515
+ /**
516
+ * 获取加时商品列表。
517
+ *
518
+ * @example
519
+ * const products = await bookingTicket.getAddTimeProducts({ schedule_date: '2026-06-16' });
520
+ */
521
+ async getAddTimeProducts(params = {}) {
522
+ return this.store.products.getAddTimeProducts({
523
+ ...params,
524
+ cacheId: this.cacheId
525
+ });
526
+ }
511
527
  /**
512
528
  * 只读查询单商品详情:走 ProductList 报价查询,但不写 productCatalog、
513
529
  * 不 emit onProductsLoaded、不修改 BookingDate。
@@ -1284,6 +1300,9 @@ var BookingTicketImpl = class extends import_BaseSales.BaseSalesImpl {
1284
1300
  return { status: "failed", reason: "not_found" };
1285
1301
  }
1286
1302
  }
1303
+ resolveBestAddTimePlan(addTimeProducts, targetMinutes) {
1304
+ return (0, import_resolveBestAddTimePlan.resolveBestAddTimePlan)(addTimeProducts, targetMinutes);
1305
+ }
1287
1306
  /**
1288
1307
  * 销毁模块:先调用父类(销毁所有 store 内子模块 + emit destroy + super.destroy()),
1289
1308
  * 再清理 BookingTicket 自有资源(scan 监听 + scan 内存缓存)。
@@ -79,6 +79,10 @@ export interface ILoadProductsParams {
79
79
  with_count?: string[];
80
80
  /** 是否返回日程信息 */
81
81
  with_schedule?: number;
82
+ /** 扩展商品类型过滤 */
83
+ extension_type?: string[];
84
+ /** 商品发布状态 */
85
+ status?: string;
82
86
  }
83
87
  /**
84
88
  * 加载商品详情的参数
@@ -51,34 +51,41 @@ function indexProductsByUid(lines) {
51
51
  return acc;
52
52
  }, {});
53
53
  }
54
+ function isGiftProductLine(line) {
55
+ var _a;
56
+ return Boolean((_a = line == null ? void 0 : line.metadata) == null ? void 0 : _a._giftInfo);
57
+ }
54
58
  function buildCartView(lines, bookings) {
55
59
  const productLines = Array.isArray(lines) ? lines : [];
56
60
  const bookingList = Array.isArray(bookings) ? bookings : [];
57
61
  const linesByUid = indexProductsByUid(productLines);
58
62
  const claimedUids = /* @__PURE__ */ new Set();
59
- const cartBookings = bookingList.filter((booking) => booking.parent_id !== 0).map((booking) => {
63
+ const cartBookings = bookingList.filter((booking) => booking.parent_id !== 0).reduce((acc, booking) => {
60
64
  const uid = booking == null ? void 0 : booking.product_uid;
61
65
  let product = null;
62
66
  if (uid !== void 0 && uid !== null && uid !== "") {
63
67
  const key = String(uid);
64
68
  if (!claimedUids.has(key)) {
65
69
  const line = linesByUid[key];
70
+ if (isGiftProductLine(line))
71
+ return acc;
66
72
  if (line) {
67
73
  claimedUids.add(key);
68
74
  product = line;
69
75
  }
70
76
  }
71
77
  }
72
- return {
78
+ acc.push({
73
79
  ...booking,
74
80
  _extend: { product }
75
- };
76
- });
81
+ });
82
+ return acc;
83
+ }, []);
77
84
  const items = productLines.filter((line) => {
78
- var _a, _b;
79
- if ((_a = line.metadata) == null ? void 0 : _a._giftInfo)
85
+ var _a;
86
+ if (isGiftProductLine(line))
80
87
  return false;
81
- const uid = (_b = line.metadata) == null ? void 0 : _b.unique_identification_number;
88
+ const uid = (_a = line.metadata) == null ? void 0 : _a.unique_identification_number;
82
89
  if (uid === void 0 || uid === null || uid === "")
83
90
  return true;
84
91
  return !claimedUids.has(String(uid));
@@ -0,0 +1,189 @@
1
+ /**
2
+ * 计算指定加时时长下的最优购买方案。
3
+ *
4
+ * 输入的商品结构来自旧版 booking addTimeModal 的加时商品配置映射:
5
+ * - `minMinutes`:该商品第一次可选择的最小时长。
6
+ * - `maxMinutes`:单次购买该商品可覆盖的最大时长。
7
+ * - `unitMinutes`:超过最小时长后,每次递增的时长切片。
8
+ * - `price`:每个 `unitMinutes` 切片的价格。
9
+ * - `unlimited`:固定价格商品,可覆盖任意目标时长。
10
+ *
11
+ * 算法流程:
12
+ *
13
+ * ```
14
+ * products
15
+ * |
16
+ * v
17
+ * 将每个普通商品展开成有限候选档位
18
+ * 商品 A: min=30, max=60, unit=10, price=10
19
+ * => 30m/$30, 40m/$40, 50m/$50, 60m/$60
20
+ * |
21
+ * v
22
+ * 单独加入 unlimited 候选
23
+ * unlimited 商品 => targetMinutes / 固定商品价格
24
+ * |
25
+ * v
26
+ * 按 covered minutes 做动态规划
27
+ * dp[minutes] = 正好覆盖 `minutes` 时的最低价候选组合
28
+ * |
29
+ * v
30
+ * 从所有 dp[minutes >= targetMinutes] 中挑选最优方案
31
+ * ```
32
+ *
33
+ * 候选档位生成刻意对齐旧版 addTimeModal:
34
+ * `totalPrice = minutes / unitMinutes * price`.
35
+ * 例如 `minMinutes=30`、`unitMinutes=10`、`price=10` 时:
36
+ * - 30 分钟 => 30 / 10 * 10 = 30
37
+ * - 60 分钟 => 60 / 10 * 10 = 60
38
+ *
39
+ * 目标时长不要求精确命中某个档位,只需要覆盖即可。例如目标是 51 分钟时,
40
+ * 60 分钟档位可以作为候选。动态规划也允许跨商品组合,例如商品 A 的 30m
41
+ * 加上商品 B 的 30m。最终方案按以下优先级比较:
42
+ * 1. 总价更低;
43
+ * 2. 超出时长更少,即 `coveredMinutes - targetMinutes` 更小;
44
+ * 3. 结果行数更少,让购买方案尽量简单。
45
+ *
46
+ * 典型匹配示例:
47
+ *
48
+ * 示例 1:精确命中,直接选择更便宜的商品。
49
+ * ```
50
+ * target = 30
51
+ * 商品 A: min=30, max=30, unit=30, price=10 => 30m/$10
52
+ * 商品 B: min=30, max=30, unit=30, price=8 => 30m/$8
53
+ * 结果:选择商品 B,coveredMinutes=30,totalPrice=8
54
+ * ```
55
+ *
56
+ * 示例 2:目标时长落在两个档位之间,向上选择可覆盖的档位。
57
+ * ```
58
+ * target = 51
59
+ * 商品 A: min=30, max=120, unit=10, price=10
60
+ * 候选:30m/$30, 40m/$40, 50m/$50, 60m/$60 ...
61
+ * 结果:50m 不足以覆盖 51m,因此选择 60m/$60
62
+ * ```
63
+ *
64
+ * 示例 3:不同商品都能覆盖同一目标时长,比较最终价格。
65
+ * ```
66
+ * target = 51
67
+ * 商品 A: min=30, max=120, unit=10, price=10 => 60m/$60
68
+ * 商品 B: min=30, max=120, unit=30, price=25 => 60m/$50
69
+ * 结果:两个方案都覆盖 60m,但商品 B 更便宜,所以选择商品 B
70
+ * ```
71
+ *
72
+ * 示例 4:允许跨商品组合,组合价更低时会选择组合。
73
+ * ```
74
+ * target = 90
75
+ * 商品 A: min=30, max=30, unit=30, price=5 => 30m/$5
76
+ * 商品 B: min=60, max=60, unit=60, price=8 => 60m/$8
77
+ * 商品 C: min=90, max=90, unit=90, price=20 => 90m/$20
78
+ * 结果:商品 A + 商品 B = 90m/$13,比商品 C 更便宜
79
+ * ```
80
+ *
81
+ * 示例 5:允许重复购买同一个候选档位。
82
+ * ```
83
+ * target = 90
84
+ * 商品 A: min=30, max=30, unit=30, price=5 => 30m/$5
85
+ * 结果:购买 3 份商品 A,coveredMinutes=90,totalPrice=15,
86
+ * lines 中会合并为一行:minutes=30,quantity=3
87
+ * ```
88
+ *
89
+ * 示例 6:unlimited 可以覆盖任意目标时长,并参与价格比较。
90
+ * ```
91
+ * target = 180
92
+ * 商品 A: min=30, max=120, unit=30, price=10
93
+ * 商品 B: unlimited=true, price=35
94
+ * 普通商品可以组合成 180m,例如 120m + 60m,总价 $60
95
+ * unlimited 商品固定 $35
96
+ * 结果:选择 unlimited 商品,coveredMinutes=180,totalPrice=35
97
+ * ```
98
+ *
99
+ * 示例 7:unlimited 不一定优先,普通商品更便宜时选择普通商品。
100
+ * ```
101
+ * target = 60
102
+ * 商品 A: min=30, max=60, unit=30, price=10 => 60m/$20
103
+ * 商品 B: unlimited=true, price=50
104
+ * 结果:选择商品 A 的 60m 档位
105
+ * ```
106
+ *
107
+ * 示例 8:价格相同,优先选择超出时长更少的方案。
108
+ * ```
109
+ * target = 51
110
+ * 商品 A: 60m/$20
111
+ * 商品 B: 90m/$20
112
+ * 结果:选择 60m,因为 overage=9,小于 90m 的 overage=39
113
+ * ```
114
+ *
115
+ * 示例 9:价格和覆盖时长都相同,优先选择结果行数更少的方案。
116
+ * ```
117
+ * target = 60
118
+ * 商品 A: 60m/$20
119
+ * 商品 B: 30m/$10
120
+ * 商品 C: 30m/$10
121
+ * 结果:商品 A 单行方案和 B+C 两行方案价格、时长相同,选择商品 A
122
+ * ```
123
+ *
124
+ * 示例 10:没有任何有效商品时返回 `NO_PRODUCTS`。
125
+ * ```
126
+ * target = 30
127
+ * products = []
128
+ * 结果:available=false,unavailableReason='NO_PRODUCTS'
129
+ * ```
130
+ *
131
+ * 示例 11:有商品但没有可用候选时返回 `NO_COVERAGE`。
132
+ * ```
133
+ * target = 30
134
+ * 商品 A: price=0,或 minMinutes 缺失/无效
135
+ * 结果:available=false,unavailableReason='NO_COVERAGE'
136
+ * ```
137
+ *
138
+ * 示例 12:目标时长小于等于 0 时无需购买。
139
+ * ```
140
+ * target = 0
141
+ * 结果:available=true,coveredMinutes=0,totalPrice=0,lines=[]
142
+ * ```
143
+ *
144
+ * 搜索边界:
145
+ * 有限档位只需要搜索到 `targetMinutes + maxFiniteCandidateMinutes`。
146
+ * 这里的 `maxFiniteCandidateMinutes` 是所有普通候选档位里的最大时长。
147
+ * 例如目标是 51 分钟,最大普通档位是 120 分钟,则只搜索到 171 分钟。
148
+ * 如果某个组合覆盖了 180 分钟,那么移除其中任意一个普通档位后,剩余时长
149
+ * 仍然有机会覆盖 51 分钟,而且价格一定不会更高,所以 180 分钟这个组合不
150
+ * 可能是最优解。
151
+ */
152
+ export interface AddTimeProduct {
153
+ id: string | number;
154
+ title?: string;
155
+ price: number;
156
+ minMinutes?: number;
157
+ maxMinutes?: number;
158
+ unitMinutes?: number;
159
+ unlimited?: boolean;
160
+ }
161
+ export interface AddTimePlanLine {
162
+ productId: string | number;
163
+ productTitle?: string;
164
+ minutes: number;
165
+ quantity: number;
166
+ unitPrice: number;
167
+ totalPrice: number;
168
+ unlimited?: boolean;
169
+ }
170
+ export interface AddTimePlan {
171
+ available: boolean;
172
+ unavailableReason?: 'NO_PRODUCTS' | 'NO_COVERAGE';
173
+ targetMinutes: number;
174
+ coveredMinutes: number;
175
+ totalPrice: number;
176
+ lines: AddTimePlanLine[];
177
+ }
178
+ interface AddTimeCandidate {
179
+ productId: string | number;
180
+ productTitle?: string;
181
+ minutes: number;
182
+ unitPrice: number;
183
+ totalPrice: number;
184
+ unlimited?: boolean;
185
+ }
186
+ export declare const ADD_TIME_UNIT_MINUTES: readonly [10, 15, 20, 30, 45, 60];
187
+ export declare function buildAddTimeCandidates(products: AddTimeProduct[]): AddTimeCandidate[];
188
+ export declare function resolveBestAddTimePlan(products: AddTimeProduct[], targetMinutes: number): AddTimePlan;
189
+ export {};
@@ -0,0 +1,241 @@
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);
18
+
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
25
+ });
26
+ module.exports = __toCommonJS(resolveBestAddTimePlan_exports);
27
+ var ADD_TIME_UNIT_MINUTES = [10, 15, 20, 30, 45, 60];
28
+ var toPositiveNumber = (value) => {
29
+ const n = Number(value);
30
+ return Number.isFinite(n) && n > 0 ? n : null;
31
+ };
32
+ var normalizeTargetMinutes = (minutes) => {
33
+ const n = Number(minutes);
34
+ return Number.isFinite(n) ? Math.max(0, Math.ceil(n)) : 0;
35
+ };
36
+ var gcd = (a, b) => {
37
+ let x = Math.abs(a);
38
+ let y = Math.abs(b);
39
+ while (y !== 0) {
40
+ const next = x % y;
41
+ x = y;
42
+ y = next;
43
+ }
44
+ return x;
45
+ };
46
+ var getDpStepMinutes = (candidates) => {
47
+ return candidates.reduce(
48
+ (current, candidate) => gcd(current, candidate.minutes),
49
+ 0
50
+ ) || 1;
51
+ };
52
+ var comparePlans = (left, right) => {
53
+ if (!left)
54
+ return right;
55
+ if (right.totalPrice !== left.totalPrice) {
56
+ return right.totalPrice < left.totalPrice ? right : left;
57
+ }
58
+ const leftOverage = left.coveredMinutes - left.targetMinutes;
59
+ const rightOverage = right.coveredMinutes - right.targetMinutes;
60
+ if (rightOverage !== leftOverage) {
61
+ return rightOverage < leftOverage ? right : left;
62
+ }
63
+ return right.lines.length < left.lines.length ? right : left;
64
+ };
65
+ var toPlanLine = (candidate) => ({
66
+ productId: candidate.productId,
67
+ productTitle: candidate.productTitle,
68
+ minutes: Number.isFinite(candidate.minutes) ? candidate.minutes : 0,
69
+ quantity: 1,
70
+ unitPrice: candidate.unitPrice,
71
+ totalPrice: candidate.totalPrice,
72
+ unlimited: candidate.unlimited
73
+ });
74
+ var mergePlanLines = (candidates) => {
75
+ const map = /* @__PURE__ */ new Map();
76
+ for (const candidate of candidates) {
77
+ const key = `${candidate.productId}-${candidate.minutes}-${candidate.unlimited ? "u" : "n"}`;
78
+ const existing = map.get(key);
79
+ if (existing) {
80
+ existing.quantity += 1;
81
+ existing.totalPrice += candidate.totalPrice;
82
+ continue;
83
+ }
84
+ map.set(key, toPlanLine(candidate));
85
+ }
86
+ return Array.from(map.values());
87
+ };
88
+ var restoreDpCandidates = (dp, finiteCandidates, index) => {
89
+ const candidates = [];
90
+ let currentIndex = index;
91
+ while (currentIndex > 0) {
92
+ const current = dp[currentIndex];
93
+ if (!current || current.candidateIndex < 0)
94
+ break;
95
+ candidates.push(finiteCandidates[current.candidateIndex]);
96
+ currentIndex = current.prevIndex;
97
+ }
98
+ return candidates.reverse();
99
+ };
100
+ function buildAddTimeCandidates(products) {
101
+ const candidates = [];
102
+ for (const product of products) {
103
+ const price = toPositiveNumber(product.price);
104
+ if (price == null)
105
+ continue;
106
+ if (product.unlimited) {
107
+ candidates.push({
108
+ productId: product.id,
109
+ productTitle: product.title,
110
+ minutes: Number.POSITIVE_INFINITY,
111
+ unitPrice: price,
112
+ totalPrice: price,
113
+ unlimited: true
114
+ });
115
+ continue;
116
+ }
117
+ const minMinutes = toPositiveNumber(product.minMinutes);
118
+ if (minMinutes == null)
119
+ continue;
120
+ const maxMinutes = toPositiveNumber(product.maxMinutes) ?? minMinutes;
121
+ const unitMinutes = toPositiveNumber(product.unitMinutes) ?? minMinutes;
122
+ const safeMax = Math.max(minMinutes, maxMinutes);
123
+ for (let minutes = minMinutes; minutes <= safeMax; minutes += unitMinutes) {
124
+ candidates.push({
125
+ productId: product.id,
126
+ productTitle: product.title,
127
+ minutes,
128
+ unitPrice: price,
129
+ // 对齐旧版 addTimeModal:每个 unit_duration 切片收一次 price。例:60/10*10=$60。
130
+ totalPrice: minutes / unitMinutes * price
131
+ });
132
+ }
133
+ }
134
+ return candidates;
135
+ }
136
+ function resolveBestAddTimePlan(products, targetMinutes) {
137
+ const normalizedTarget = normalizeTargetMinutes(targetMinutes);
138
+ if (normalizedTarget <= 0) {
139
+ return {
140
+ available: true,
141
+ targetMinutes: 0,
142
+ coveredMinutes: 0,
143
+ totalPrice: 0,
144
+ lines: []
145
+ };
146
+ }
147
+ if (!products.length) {
148
+ return {
149
+ available: false,
150
+ unavailableReason: "NO_PRODUCTS",
151
+ targetMinutes: normalizedTarget,
152
+ coveredMinutes: 0,
153
+ totalPrice: 0,
154
+ lines: []
155
+ };
156
+ }
157
+ const candidates = buildAddTimeCandidates(products);
158
+ const finiteCandidates = candidates.filter(
159
+ (item) => Number.isFinite(item.minutes)
160
+ );
161
+ const unlimitedCandidates = candidates.filter((item) => item.unlimited);
162
+ const maxFiniteMinutes = Math.max(
163
+ 0,
164
+ ...finiteCandidates.map((item) => item.minutes)
165
+ );
166
+ const searchLimit = normalizedTarget + maxFiniteMinutes;
167
+ let best = null;
168
+ for (const unlimited of unlimitedCandidates) {
169
+ best = comparePlans(best, {
170
+ available: true,
171
+ targetMinutes: normalizedTarget,
172
+ coveredMinutes: normalizedTarget,
173
+ totalPrice: unlimited.totalPrice,
174
+ lines: [toPlanLine({ ...unlimited, minutes: normalizedTarget })]
175
+ });
176
+ }
177
+ if (finiteCandidates.length && searchLimit > 0) {
178
+ const stepMinutes = getDpStepMinutes(finiteCandidates);
179
+ const searchLimitIndex = Math.ceil(searchLimit / stepMinutes);
180
+ const targetIndex = Math.ceil(normalizedTarget / stepMinutes);
181
+ const finiteCandidateSteps = finiteCandidates.map(
182
+ (candidate) => candidate.minutes / stepMinutes
183
+ );
184
+ const dp = [];
185
+ dp[0] = {
186
+ price: 0,
187
+ itemCount: 0,
188
+ prevIndex: -1,
189
+ candidateIndex: -1
190
+ };
191
+ for (let index = 0; index <= searchLimitIndex; index += 1) {
192
+ const current = dp[index];
193
+ if (!current)
194
+ continue;
195
+ for (let candidateIndex = 0; candidateIndex < finiteCandidates.length; candidateIndex += 1) {
196
+ const nextIndex = index + finiteCandidateSteps[candidateIndex];
197
+ if (nextIndex > searchLimitIndex)
198
+ continue;
199
+ const candidate = finiteCandidates[candidateIndex];
200
+ const nextPrice = current.price + candidate.totalPrice;
201
+ const nextItemCount = current.itemCount + 1;
202
+ const prev = dp[nextIndex];
203
+ if (!prev || nextPrice < prev.price || nextPrice === prev.price && nextItemCount < prev.itemCount) {
204
+ dp[nextIndex] = {
205
+ price: nextPrice,
206
+ itemCount: nextItemCount,
207
+ prevIndex: index,
208
+ candidateIndex
209
+ };
210
+ }
211
+ }
212
+ }
213
+ for (let index = targetIndex; index <= searchLimitIndex; index += 1) {
214
+ const current = dp[index];
215
+ if (!current)
216
+ continue;
217
+ const coveredMinutes = index * stepMinutes;
218
+ best = comparePlans(best, {
219
+ available: true,
220
+ targetMinutes: normalizedTarget,
221
+ coveredMinutes,
222
+ totalPrice: current.price,
223
+ lines: mergePlanLines(restoreDpCandidates(dp, finiteCandidates, index))
224
+ });
225
+ }
226
+ }
227
+ return best ?? {
228
+ available: false,
229
+ unavailableReason: "NO_COVERAGE",
230
+ targetMinutes: normalizedTarget,
231
+ coveredMinutes: 0,
232
+ totalPrice: 0,
233
+ lines: []
234
+ };
235
+ }
236
+ // Annotate the CommonJS export names for ESM import in node:
237
+ 0 && (module.exports = {
238
+ ADD_TIME_UNIT_MINUTES,
239
+ buildAddTimeCandidates,
240
+ resolveBestAddTimePlan
241
+ });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "private": false,
3
3
  "name": "@pisell/pisellos",
4
- "version": "2.2.230",
4
+ "version": "2.2.232",
5
5
  "description": "一个可扩展的前端模块化SDK框架,支持插件系统",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",