@pisell/pisellos 2.2.303 → 2.2.305

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.
@@ -557,7 +557,7 @@ export declare class OrderModule extends BaseModule implements Module, OrderModu
557
557
  generateIdempotencyToken(): string;
558
558
  syncPaymentsToOrder<T = any>(params: SyncPaymentsToOrderParams): Promise<SyncPaymentsToOrderResult<T>>;
559
559
  createOrder(params: CommitOrderParams['query']): {
560
- type: "virtual" | "appointment_booking";
560
+ type: "appointment_booking" | "virtual";
561
561
  platform: string;
562
562
  sales_channel: string;
563
563
  order_sales_channel: string;
@@ -2,10 +2,9 @@
2
2
  * 现金支付推荐算法
3
3
  *
4
4
  * 核心原理:
5
- * 1. 每个推荐金额都应该是独立的最优组合
6
- * 2. 不推荐在已有最优解基础上添加额外面额的组合
7
- * 3. 优先推荐使用不同数量币种的组合
8
- * 4. 根据组合判断去重,避免扩展组合
5
+ * 1. 第一项始终是精确应付金额
6
+ * 2. 其余推荐只能是严格大于应付金额的真实币种面额
7
+ * 3. 最多推荐三个更高面额
9
8
  */
10
9
  /**
11
10
  * 常见国家货币面额配置
@@ -13,7 +12,7 @@
13
12
  export declare const CURRENCY_DENOMINATIONS: Record<string, number[]>;
14
13
  /**
15
14
  * 最优支付金额推荐算法
16
- * 推荐通过不同数量面额组合刚好足够支付的最小金额
15
+ * 返回精确应付金额,以及最多三个更高的真实币种面额
17
16
  *
18
17
  * @param targetAmount 目标金额
19
18
  * @param denominations 币种面值数组
@@ -8,10 +8,9 @@ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len
8
8
  * 现金支付推荐算法
9
9
  *
10
10
  * 核心原理:
11
- * 1. 每个推荐金额都应该是独立的最优组合
12
- * 2. 不推荐在已有最优解基础上添加额外面额的组合
13
- * 3. 优先推荐使用不同数量币种的组合
14
- * 4. 根据组合判断去重,避免扩展组合
11
+ * 1. 第一项始终是精确应付金额
12
+ * 2. 其余推荐只能是严格大于应付金额的真实币种面额
13
+ * 3. 最多推荐三个更高面额
15
14
  */
16
15
 
17
16
  /**
@@ -47,377 +46,28 @@ export var CURRENCY_DENOMINATIONS = {
47
46
  'DEFAULT': [100, 50, 20, 10, 5, 1, 0.5, 0.25, 0.1, 0.05, 0.01]
48
47
  };
49
48
 
50
- /**
51
- * 支付组合类型定义
52
- */
53
-
54
49
  /**
55
50
  * 最优支付金额推荐算法
56
- * 推荐通过不同数量面额组合刚好足够支付的最小金额
51
+ * 返回精确应付金额,以及最多三个更高的真实币种面额
57
52
  *
58
53
  * @param targetAmount 目标金额
59
54
  * @param denominations 币种面值数组
60
55
  * @returns 推荐支付金额数组
61
56
  */
62
57
  export function recommendOptimalPayments(targetAmount, denominations) {
63
- // 参数验证
64
- if (targetAmount <= 0 || !isFinite(targetAmount) || isNaN(targetAmount)) {
58
+ if (targetAmount <= 0 || !Number.isFinite(targetAmount)) {
65
59
  return [];
66
60
  }
67
- if (!denominations || !Array.isArray(denominations) || denominations.length === 0) {
68
- return [Math.ceil(targetAmount)];
69
- }
70
-
71
- // 过滤有效面额并限制搜索空间以提高性能
72
- var maxReasonableDenom = targetAmount * 5; // 适度限制搜索空间
73
- var validDenoms = denominations.filter(function (denom) {
74
- return denom > 0 && isFinite(denom) && !isNaN(denom) && denom <= maxReasonableDenom;
75
- }).slice(0, 8); // 限制面额数量以提高性能
76
-
77
- if (validDenoms.length === 0) {
78
- // 如果没有合适的面额,返回最接近的整数金额
79
- return [Math.ceil(targetAmount)];
80
- }
81
- try {
82
- // 处理精度问题,将金额转换为整数计算
83
- var precision = 100; // 假设最小单位是0.01
84
- var target = Math.round(targetAmount * precision);
85
- var denoms = validDenoms.map(function (d) {
86
- return Math.round(d * precision);
87
- }).sort(function (a, b) {
88
- return b - a;
89
- });
90
-
91
- // 存储不同的支付组合方案
92
- var paymentOptions = [];
93
-
94
- // 首先尝试找到精确匹配的组合
95
- var exactCombination = findExactCombination(target, denoms, precision);
96
- if (exactCombination) {
97
- paymentOptions.push(exactCombination);
98
- }
99
-
100
- // 为了提高效率,我们将按币种数量来生成组合
101
- // 1币种组合:只用一种面额
102
- for (var i = 0; i < Math.min(denoms.length, 6); i++) {
103
- var denom = denoms[i];
104
- var count = Math.ceil(target / denom);
105
- var sum = count * denom;
106
- if (sum >= target && count <= 12) {
107
- // 限制硬币数量以提高性能
108
- paymentOptions.push({
109
- combination: Array(count).fill(denom / precision),
110
- sum: sum / precision,
111
- coinCount: count,
112
- denomTypes: 1
113
- });
114
- }
115
- }
116
-
117
- // 2币种组合:使用两种面额(大幅限制搜索范围以提高性能)
118
- for (var _i = 0; _i < Math.min(denoms.length - 1, 5); _i++) {
119
- for (var j = _i + 1; j < Math.min(denoms.length, 6); j++) {
120
- var denom1 = denoms[_i];
121
- var denom2 = denoms[j];
122
-
123
- // 大幅限制搜索范围,基于目标金额动态调整
124
- var maxCount1 = Math.min(Math.ceil(target / denom1) + 1, 8);
125
- var maxCount2 = Math.min(Math.ceil(target / denom2) + 1, 10);
126
- for (var count1 = 1; count1 <= maxCount1; count1++) {
127
- for (var count2 = 1; count2 <= maxCount2; count2++) {
128
- var _sum = count1 * denom1 + count2 * denom2;
129
- if (_sum >= target) {
130
- var combination = [].concat(_toConsumableArray(Array(count1).fill(denom1 / precision)), _toConsumableArray(Array(count2).fill(denom2 / precision)));
131
- paymentOptions.push({
132
- combination: combination,
133
- sum: _sum / precision,
134
- coinCount: count1 + count2,
135
- denomTypes: 2
136
- });
137
- }
138
- }
139
- }
140
- }
141
- }
142
-
143
- // 3币种组合:使用三种面额(大幅限制搜索以提高效率)
144
- for (var _i2 = 0; _i2 < Math.min(denoms.length - 2, 3); _i2++) {
145
- for (var _j = _i2 + 1; _j < Math.min(denoms.length - 1, 4); _j++) {
146
- for (var k = _j + 1; k < Math.min(denoms.length, 5); k++) {
147
- var _denom = denoms[_i2];
148
- var _denom2 = denoms[_j];
149
- var denom3 = denoms[k];
150
-
151
- // 大幅限制搜索范围
152
- var _maxCount = Math.min(Math.ceil(target / _denom) + 1, 5);
153
- var _maxCount2 = Math.min(Math.ceil(target / _denom2) + 1, 6);
154
- var maxCount3 = Math.min(Math.ceil(target / denom3) + 1, 8);
155
- for (var _count = 1; _count <= _maxCount; _count++) {
156
- for (var _count2 = 1; _count2 <= _maxCount2; _count2++) {
157
- for (var count3 = 1; count3 <= maxCount3; count3++) {
158
- var _sum2 = _count * _denom + _count2 * _denom2 + count3 * denom3;
159
- if (_sum2 >= target) {
160
- var _combination = [].concat(_toConsumableArray(Array(_count).fill(_denom / precision)), _toConsumableArray(Array(_count2).fill(_denom2 / precision)), _toConsumableArray(Array(count3).fill(denom3 / precision)));
161
- paymentOptions.push({
162
- combination: _combination,
163
- sum: _sum2 / precision,
164
- coinCount: _count + _count2 + count3,
165
- denomTypes: 3
166
- });
167
- }
168
- }
169
- }
170
- }
171
- }
172
- }
173
- }
174
-
175
- // 4币种组合:使用四种面额(极度限制搜索以提高性能)
176
- for (var _i3 = 0; _i3 < Math.min(denoms.length - 3, 2); _i3++) {
177
- for (var _j2 = _i3 + 1; _j2 < Math.min(denoms.length - 2, 3); _j2++) {
178
- for (var _k = _j2 + 1; _k < Math.min(denoms.length - 1, 4); _k++) {
179
- for (var l = _k + 1; l < Math.min(denoms.length, 5); l++) {
180
- var _denom3 = denoms[_i3];
181
- var _denom4 = denoms[_j2];
182
- var _denom5 = denoms[_k];
183
- var denom4 = denoms[l];
184
-
185
- // 极度限制搜索范围
186
- var _maxCount3 = Math.min(Math.ceil(target / _denom3) + 1, 3);
187
- var _maxCount4 = Math.min(Math.ceil(target / _denom4) + 1, 4);
188
- var _maxCount5 = Math.min(Math.ceil(target / _denom5) + 1, 5);
189
- var maxCount4 = Math.min(Math.ceil(target / denom4) + 1, 6);
190
- for (var _count3 = 1; _count3 <= _maxCount3; _count3++) {
191
- for (var _count4 = 1; _count4 <= _maxCount4; _count4++) {
192
- for (var _count5 = 1; _count5 <= _maxCount5; _count5++) {
193
- for (var count4 = 1; count4 <= maxCount4; count4++) {
194
- var _sum3 = _count3 * _denom3 + _count4 * _denom4 + _count5 * _denom5 + count4 * denom4;
195
- if (_sum3 >= target) {
196
- var _combination2 = [].concat(_toConsumableArray(Array(_count3).fill(_denom3 / precision)), _toConsumableArray(Array(_count4).fill(_denom4 / precision)), _toConsumableArray(Array(_count5).fill(_denom5 / precision)), _toConsumableArray(Array(count4).fill(denom4 / precision)));
197
- paymentOptions.push({
198
- combination: _combination2,
199
- sum: _sum3 / precision,
200
- coinCount: _count3 + _count4 + _count5 + count4,
201
- denomTypes: 4
202
- });
203
- }
204
- }
205
- }
206
- }
207
- }
208
- }
209
- }
210
- }
211
- }
212
-
213
- // 如果没有找到任何组合,提供默认建议
214
- if (paymentOptions.length === 0) {
215
- // 找到大于等于目标金额的最小面额
216
- var minValidDenom = validDenoms.find(function (denom) {
217
- return denom >= targetAmount;
218
- });
219
- if (minValidDenom) {
220
- return [minValidDenom];
221
- } else {
222
- // 使用最大面额的组合
223
- var maxDenom = Math.max.apply(Math, _toConsumableArray(validDenoms));
224
- var _count6 = Math.ceil(targetAmount / maxDenom);
225
- return [_count6 * maxDenom];
226
- }
227
- }
228
-
229
- // 移除重复和扩展组合
230
- var uniqueCombinations = removeDuplicateAndExtendedCombinations(paymentOptions, targetAmount);
231
-
232
- // 按币种类型数量排序,然后按总硬币数量排序,最后按金额排序
233
- uniqueCombinations.sort(function (a, b) {
234
- if (a.denomTypes !== b.denomTypes) {
235
- return a.denomTypes - b.denomTypes;
236
- }
237
- if (a.coinCount !== b.coinCount) {
238
- return a.coinCount - b.coinCount;
239
- }
240
- return a.sum - b.sum;
241
- });
242
-
243
- // 按金额去重并返回推荐的支付金额
244
- var uniqueAmounts = new Set();
245
- var finalResults = [];
246
- for (var _i4 = 0, _uniqueCombinations = uniqueCombinations; _i4 < _uniqueCombinations.length; _i4++) {
247
- var item = _uniqueCombinations[_i4];
248
- var roundedAmount = Math.round(item.sum * 100) / 100; // 处理浮点数精度
249
- if (!uniqueAmounts.has(roundedAmount) && finalResults.length < 10) {
250
- uniqueAmounts.add(roundedAmount);
251
- finalResults.push(roundedAmount);
252
- }
253
- }
254
- return finalResults.sort(function (a, b) {
255
- return a - b;
256
- });
257
- } catch (error) {
258
- // 发生错误时返回安全的默认值
259
- console.warn('推荐支付金额计算出错:', error);
260
- // 返回最接近的整数金额作为兜底
261
- var safeAmount = Math.ceil(targetAmount);
262
- return [targetAmount, safeAmount];
263
- }
264
- }
265
-
266
- /**
267
- * 尝试找到精确匹配目标金额的组合
268
- */
269
- function findExactCombination(target, denoms, precision) {
270
- // 简单的深度优先搜索,限制搜索深度
271
- function dfs(remaining, denomIndex, currentCombination) {
272
- if (remaining === 0) {
273
- return currentCombination;
274
- }
275
- if (remaining < 0 || denomIndex >= denoms.length || currentCombination.length > 10) {
276
- return null;
277
- }
278
- var denom = denoms[denomIndex];
279
-
280
- // 尝试使用当前面额 0 到 maxCount 次
281
- var maxCount = Math.min(Math.floor(remaining / denom), 8);
282
- for (var count = maxCount; count >= 0; count--) {
283
- var newCombination = [].concat(_toConsumableArray(currentCombination), _toConsumableArray(Array(count).fill(denom)));
284
- var result = dfs(remaining - count * denom, denomIndex + 1, newCombination);
285
- if (result) {
286
- return result;
287
- }
288
- }
289
- return null;
290
- }
291
- var exactMatch = dfs(target, 0, []);
292
- if (exactMatch && exactMatch.length > 0) {
293
- var denomTypes = new Set(exactMatch).size;
294
- return {
295
- combination: exactMatch.map(function (d) {
296
- return d / precision;
297
- }),
298
- sum: target / precision,
299
- coinCount: exactMatch.length,
300
- denomTypes: denomTypes
301
- };
302
- }
303
- return null;
304
- }
305
-
306
- /**
307
- * 移除重复和扩展组合
308
- * 核心原理:如果组合A包含组合B的所有硬币,并且还有额外硬币,则A是B的扩展,应该被移除
309
- */
310
- function removeDuplicateAndExtendedCombinations(combinations, targetAmount) {
311
- var result = [];
312
-
313
- // 按币种类型数量、硬币数量、金额排序,确保较简单的组合在前面
314
- combinations.sort(function (a, b) {
315
- if (a.denomTypes !== b.denomTypes) {
316
- return a.denomTypes - b.denomTypes;
317
- }
318
- if (a.coinCount !== b.coinCount) {
319
- return a.coinCount - b.coinCount;
320
- }
321
- return a.sum - b.sum;
322
- });
323
- for (var i = 0; i < combinations.length; i++) {
324
- var current = combinations[i];
325
- var shouldSkip = false;
326
-
327
- // 检查当前组合是否与已添加的组合重复或者是扩展
328
- for (var j = 0; j < result.length; j++) {
329
- var existing = result[j];
330
-
331
- // 如果完全相同,跳过
332
- if (isSameCombination(current.combination, existing.combination)) {
333
- shouldSkip = true;
334
- break;
335
- }
336
-
337
- // 如果当前组合是已存在组合的扩展,跳过
338
- if (isExtensionOf(current.combination, existing.combination)) {
339
- shouldSkip = true;
340
- break;
341
- }
342
- }
343
-
344
- // 检查是否有其他组合是当前组合的扩展,如果有,移除那些扩展
345
- if (!shouldSkip) {
346
- // 移除所有当前组合的扩展
347
- for (var _j3 = result.length - 1; _j3 >= 0; _j3--) {
348
- if (isExtensionOf(result[_j3].combination, current.combination)) {
349
- result.splice(_j3, 1);
350
- }
351
- }
352
- result.push(current);
353
- }
354
- }
355
- return result;
356
- }
357
-
358
- /**
359
- * 检查组合A是否是组合B的扩展(即A包含B的所有硬币,并且还有额外的硬币)
360
- */
361
- function isExtensionOf(combinationA, combinationB) {
362
- // 如果A的硬币数量少于等于B,A不可能是B的扩展
363
- if (combinationA.length <= combinationB.length) {
364
- return false;
365
- }
366
-
367
- // 统计每个面额的数量
368
- var countA = {};
369
- var countB = {};
370
- combinationA.forEach(function (coin) {
371
- countA[coin] = (countA[coin] || 0) + 1;
372
- });
373
- combinationB.forEach(function (coin) {
374
- countB[coin] = (countB[coin] || 0) + 1;
375
- });
376
-
377
- // 检查B的所有面额在A中是否都有足够的数量
378
- for (var coin in countB) {
379
- if (!countA[coin] || countA[coin] < countB[coin]) {
380
- return false;
381
- }
382
- }
383
-
384
- // 检查A是否有B没有的额外硬币
385
- var hasExtra = false;
386
- for (var _coin in countA) {
387
- if (countA[_coin] > (countB[_coin] || 0)) {
388
- hasExtra = true;
389
- break;
390
- }
391
- }
392
- return hasExtra;
393
- }
394
-
395
- /**
396
- * 检查两个组合是否本质相同(相同的面额组合)
397
- */
398
- function isSameCombination(combinationA, combinationB) {
399
- if (combinationA.length !== combinationB.length) {
400
- return false;
401
- }
402
- var countA = {};
403
- var countB = {};
404
- combinationA.forEach(function (coin) {
405
- countA[coin] = (countA[coin] || 0) + 1;
406
- });
407
- combinationB.forEach(function (coin) {
408
- countB[coin] = (countB[coin] || 0) + 1;
409
- });
410
-
411
- // 检查两个组合是否有相同的面额和数量
412
- for (var coin in countA) {
413
- if (countA[coin] !== (countB[coin] || 0)) {
414
- return false;
415
- }
416
- }
417
- for (var _coin2 in countB) {
418
- if (countB[_coin2] !== (countA[_coin2] || 0)) {
419
- return false;
420
- }
421
- }
422
- return true;
61
+ var precision = 100;
62
+ var normalizedTarget = Math.round(targetAmount * precision) / precision;
63
+ var higherDenominations = _toConsumableArray(new Set((Array.isArray(denominations) ? denominations : []).filter(function (denomination) {
64
+ return denomination > 0 && Number.isFinite(denomination);
65
+ }).map(function (denomination) {
66
+ return Math.round(denomination * precision) / precision;
67
+ }))).filter(function (denomination) {
68
+ return denomination > normalizedTarget;
69
+ }).sort(function (a, b) {
70
+ return a - b;
71
+ }).slice(0, 3);
72
+ return [normalizedTarget].concat(_toConsumableArray(higherDenominations));
423
73
  }
@@ -294,12 +294,14 @@ function evaluateCandidateTimeGate(params) {
294
294
  };
295
295
  }
296
296
  if (policy.type === 'before_start') {
297
- // Flexible-duration candidates and POS fixed-duration anchors start at
298
- // minute precision. Keep that current minute selectable instead of treating
299
- // the freshly generated candidate as already started because the query
300
- // clock still contains seconds.
297
+ // POS fixed-duration candidates use the context's stable opening-minute
298
+ // anchor. Keep that anchored range selectable while it is still active;
299
+ // otherwise a minute refresh turns it into 0 capacity and incorrectly
300
+ // advances a create flow to the next slot. The earlier `past` gate still
301
+ // rejects the anchor once its booking range has ended.
302
+ var isActivePosDurationAnchor = evaluationContext.useExactDurationCandidateMinute && product.kind === 'duration' && !product.isFlexibleDuration && evaluationContext.durationCandidateAnchorAtMs === candidateRange.start;
301
303
  var usesMinutePrecision = product.isFlexibleDuration || evaluationContext.useExactDurationCandidateMinute && product.kind === 'duration';
302
- var hasNotReachedStart = usesMinutePrecision ? floorToMinute(now) <= candidateRange.start : now < candidateRange.start;
304
+ var hasNotReachedStart = isActivePosDurationAnchor || (usesMinutePrecision ? floorToMinute(now) <= candidateRange.start : now < candidateRange.start);
303
305
  if (hasNotReachedStart) return null;
304
306
  return {
305
307
  status: 'unavailable',
@@ -1437,6 +1439,7 @@ function buildAvailabilityCellIndex(projection) {
1437
1439
  function buildEvaluationContext(params) {
1438
1440
  var _params$resourceIds2;
1439
1441
  var evaluatedAt = toDateTime(params.evaluatedAt || params.projection.meta.now, params.projection.meta.timezone);
1442
+ var durationCandidateAnchorAt = toDateTime(params.projection.meta.durationCandidateAnchorAt, params.projection.meta.timezone);
1440
1443
  var selectionsByGroupKey = new Map();
1441
1444
  (params.requirementSelections || []).forEach(function (selection) {
1442
1445
  var _selection$formId;
@@ -1449,6 +1452,7 @@ function buildEvaluationContext(params) {
1449
1452
  resourceIdSet: (_params$resourceIds2 = params.resourceIds) !== null && _params$resourceIds2 !== void 0 && _params$resourceIds2.length ? new Set(params.resourceIds.map(String)) : null,
1450
1453
  selectionsByGroupKey: selectionsByGroupKey,
1451
1454
  useExactDurationCandidateMinute: params.useExactDurationCandidateMinute === true,
1455
+ durationCandidateAnchorAtMs: Number.isFinite(durationCandidateAnchorAt) ? floorToMinute(durationCandidateAnchorAt) : null,
1452
1456
  allowExcludedScheduleDateOverride: params.allowExcludedScheduleDateOverride === true
1453
1457
  };
1454
1458
  }
@@ -253,7 +253,8 @@ export interface GetProductTimeRangesRuntimeOptions {
253
253
  /**
254
254
  * Internal POS capability: fixed-duration candidates start from the stable
255
255
  * context anchor's current minute instead of rounding up to ten minutes, and
256
- * that minute remains selectable during before-start cutoff evaluation.
256
+ * that anchored range remains selectable while active during before-start
257
+ * cutoff evaluation.
257
258
  * Other callers keep the legacy alignment and strict second-level cutoff.
258
259
  */
259
260
  useExactDurationCandidateMinute?: boolean;
@@ -326,7 +326,7 @@ export declare class BookingByStepImpl extends BaseModule implements Module {
326
326
  date: string;
327
327
  status: string;
328
328
  week: string;
329
- weekNum: 0 | 5 | 3 | 1 | 2 | 4 | 6;
329
+ weekNum: 0 | 2 | 3 | 1 | 5 | 6 | 4;
330
330
  }[]>;
331
331
  submitTimeSlot(timeSlots: TimeSliceItem): void;
332
332
  private getScheduleDataByIds;
@@ -1 +1,46 @@
1
- "use strict";
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ Object.defineProperty(exports, "BUY_X_GET_Y_FREE_STRATEGY", {
7
+ enumerable: true,
8
+ get: function () {
9
+ return _examples.BUY_X_GET_Y_FREE_STRATEGY;
10
+ }
11
+ });
12
+ Object.defineProperty(exports, "ITEM_REWARD_STRATEGY", {
13
+ enumerable: true,
14
+ get: function () {
15
+ return _examples.ITEM_REWARD_STRATEGY;
16
+ }
17
+ });
18
+ Object.defineProperty(exports, "PromotionAdapter", {
19
+ enumerable: true,
20
+ get: function () {
21
+ return _adapter.PromotionAdapter;
22
+ }
23
+ });
24
+ Object.defineProperty(exports, "PromotionEvaluator", {
25
+ enumerable: true,
26
+ get: function () {
27
+ return _evaluator.PromotionEvaluator;
28
+ }
29
+ });
30
+ Object.defineProperty(exports, "X_ITEMS_FOR_Y_PRICE_STRATEGY", {
31
+ enumerable: true,
32
+ get: function () {
33
+ return _examples.X_ITEMS_FOR_Y_PRICE_STRATEGY;
34
+ }
35
+ });
36
+ Object.defineProperty(exports, "default", {
37
+ enumerable: true,
38
+ get: function () {
39
+ return _adapter.default;
40
+ }
41
+ });
42
+ var _evaluator = require("./evaluator");
43
+ var _adapter = _interopRequireWildcard(require("./adapter"));
44
+ var _examples = require("./examples");
45
+ function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
46
+ function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
@@ -557,7 +557,7 @@ export declare class OrderModule extends BaseModule implements Module, OrderModu
557
557
  generateIdempotencyToken(): string;
558
558
  syncPaymentsToOrder<T = any>(params: SyncPaymentsToOrderParams): Promise<SyncPaymentsToOrderResult<T>>;
559
559
  createOrder(params: CommitOrderParams['query']): {
560
- type: "virtual" | "appointment_booking";
560
+ type: "appointment_booking" | "virtual";
561
561
  platform: string;
562
562
  sales_channel: string;
563
563
  order_sales_channel: string;
@@ -2,10 +2,9 @@
2
2
  * 现金支付推荐算法
3
3
  *
4
4
  * 核心原理:
5
- * 1. 每个推荐金额都应该是独立的最优组合
6
- * 2. 不推荐在已有最优解基础上添加额外面额的组合
7
- * 3. 优先推荐使用不同数量币种的组合
8
- * 4. 根据组合判断去重,避免扩展组合
5
+ * 1. 第一项始终是精确应付金额
6
+ * 2. 其余推荐只能是严格大于应付金额的真实币种面额
7
+ * 3. 最多推荐三个更高面额
9
8
  */
10
9
  /**
11
10
  * 常见国家货币面额配置
@@ -13,7 +12,7 @@
13
12
  export declare const CURRENCY_DENOMINATIONS: Record<string, number[]>;
14
13
  /**
15
14
  * 最优支付金额推荐算法
16
- * 推荐通过不同数量面额组合刚好足够支付的最小金额
15
+ * 返回精确应付金额,以及最多三个更高的真实币种面额
17
16
  *
18
17
  * @param targetAmount 目标金额
19
18
  * @param denominations 币种面值数组
@@ -9,10 +9,9 @@ exports.recommendOptimalPayments = recommendOptimalPayments;
9
9
  * 现金支付推荐算法
10
10
  *
11
11
  * 核心原理:
12
- * 1. 每个推荐金额都应该是独立的最优组合
13
- * 2. 不推荐在已有最优解基础上添加额外面额的组合
14
- * 3. 优先推荐使用不同数量币种的组合
15
- * 4. 根据组合判断去重,避免扩展组合
12
+ * 1. 第一项始终是精确应付金额
13
+ * 2. 其余推荐只能是严格大于应付金额的真实币种面额
14
+ * 3. 最多推荐三个更高面额
16
15
  */
17
16
 
18
17
  /**
@@ -48,364 +47,20 @@ const CURRENCY_DENOMINATIONS = exports.CURRENCY_DENOMINATIONS = {
48
47
  'DEFAULT': [100, 50, 20, 10, 5, 1, 0.5, 0.25, 0.1, 0.05, 0.01]
49
48
  };
50
49
 
51
- /**
52
- * 支付组合类型定义
53
- */
54
-
55
50
  /**
56
51
  * 最优支付金额推荐算法
57
- * 推荐通过不同数量面额组合刚好足够支付的最小金额
52
+ * 返回精确应付金额,以及最多三个更高的真实币种面额
58
53
  *
59
54
  * @param targetAmount 目标金额
60
55
  * @param denominations 币种面值数组
61
56
  * @returns 推荐支付金额数组
62
57
  */
63
58
  function recommendOptimalPayments(targetAmount, denominations) {
64
- // 参数验证
65
- if (targetAmount <= 0 || !isFinite(targetAmount) || isNaN(targetAmount)) {
59
+ if (targetAmount <= 0 || !Number.isFinite(targetAmount)) {
66
60
  return [];
67
61
  }
68
- if (!denominations || !Array.isArray(denominations) || denominations.length === 0) {
69
- return [Math.ceil(targetAmount)];
70
- }
71
-
72
- // 过滤有效面额并限制搜索空间以提高性能
73
- const maxReasonableDenom = targetAmount * 5; // 适度限制搜索空间
74
- const validDenoms = denominations.filter(denom => denom > 0 && isFinite(denom) && !isNaN(denom) && denom <= maxReasonableDenom).slice(0, 8); // 限制面额数量以提高性能
75
-
76
- if (validDenoms.length === 0) {
77
- // 如果没有合适的面额,返回最接近的整数金额
78
- return [Math.ceil(targetAmount)];
79
- }
80
- try {
81
- // 处理精度问题,将金额转换为整数计算
82
- const precision = 100; // 假设最小单位是0.01
83
- const target = Math.round(targetAmount * precision);
84
- const denoms = validDenoms.map(d => Math.round(d * precision)).sort((a, b) => b - a);
85
-
86
- // 存储不同的支付组合方案
87
- const paymentOptions = [];
88
-
89
- // 首先尝试找到精确匹配的组合
90
- const exactCombination = findExactCombination(target, denoms, precision);
91
- if (exactCombination) {
92
- paymentOptions.push(exactCombination);
93
- }
94
-
95
- // 为了提高效率,我们将按币种数量来生成组合
96
- // 1币种组合:只用一种面额
97
- for (let i = 0; i < Math.min(denoms.length, 6); i++) {
98
- const denom = denoms[i];
99
- const count = Math.ceil(target / denom);
100
- const sum = count * denom;
101
- if (sum >= target && count <= 12) {
102
- // 限制硬币数量以提高性能
103
- paymentOptions.push({
104
- combination: Array(count).fill(denom / precision),
105
- sum: sum / precision,
106
- coinCount: count,
107
- denomTypes: 1
108
- });
109
- }
110
- }
111
-
112
- // 2币种组合:使用两种面额(大幅限制搜索范围以提高性能)
113
- for (let i = 0; i < Math.min(denoms.length - 1, 5); i++) {
114
- for (let j = i + 1; j < Math.min(denoms.length, 6); j++) {
115
- const denom1 = denoms[i];
116
- const denom2 = denoms[j];
117
-
118
- // 大幅限制搜索范围,基于目标金额动态调整
119
- const maxCount1 = Math.min(Math.ceil(target / denom1) + 1, 8);
120
- const maxCount2 = Math.min(Math.ceil(target / denom2) + 1, 10);
121
- for (let count1 = 1; count1 <= maxCount1; count1++) {
122
- for (let count2 = 1; count2 <= maxCount2; count2++) {
123
- const sum = count1 * denom1 + count2 * denom2;
124
- if (sum >= target) {
125
- const combination = [...Array(count1).fill(denom1 / precision), ...Array(count2).fill(denom2 / precision)];
126
- paymentOptions.push({
127
- combination: combination,
128
- sum: sum / precision,
129
- coinCount: count1 + count2,
130
- denomTypes: 2
131
- });
132
- }
133
- }
134
- }
135
- }
136
- }
137
-
138
- // 3币种组合:使用三种面额(大幅限制搜索以提高效率)
139
- for (let i = 0; i < Math.min(denoms.length - 2, 3); i++) {
140
- for (let j = i + 1; j < Math.min(denoms.length - 1, 4); j++) {
141
- for (let k = j + 1; k < Math.min(denoms.length, 5); k++) {
142
- const denom1 = denoms[i];
143
- const denom2 = denoms[j];
144
- const denom3 = denoms[k];
145
-
146
- // 大幅限制搜索范围
147
- const maxCount1 = Math.min(Math.ceil(target / denom1) + 1, 5);
148
- const maxCount2 = Math.min(Math.ceil(target / denom2) + 1, 6);
149
- const maxCount3 = Math.min(Math.ceil(target / denom3) + 1, 8);
150
- for (let count1 = 1; count1 <= maxCount1; count1++) {
151
- for (let count2 = 1; count2 <= maxCount2; count2++) {
152
- for (let count3 = 1; count3 <= maxCount3; count3++) {
153
- const sum = count1 * denom1 + count2 * denom2 + count3 * denom3;
154
- if (sum >= target) {
155
- const combination = [...Array(count1).fill(denom1 / precision), ...Array(count2).fill(denom2 / precision), ...Array(count3).fill(denom3 / precision)];
156
- paymentOptions.push({
157
- combination: combination,
158
- sum: sum / precision,
159
- coinCount: count1 + count2 + count3,
160
- denomTypes: 3
161
- });
162
- }
163
- }
164
- }
165
- }
166
- }
167
- }
168
- }
169
-
170
- // 4币种组合:使用四种面额(极度限制搜索以提高性能)
171
- for (let i = 0; i < Math.min(denoms.length - 3, 2); i++) {
172
- for (let j = i + 1; j < Math.min(denoms.length - 2, 3); j++) {
173
- for (let k = j + 1; k < Math.min(denoms.length - 1, 4); k++) {
174
- for (let l = k + 1; l < Math.min(denoms.length, 5); l++) {
175
- const denom1 = denoms[i];
176
- const denom2 = denoms[j];
177
- const denom3 = denoms[k];
178
- const denom4 = denoms[l];
179
-
180
- // 极度限制搜索范围
181
- const maxCount1 = Math.min(Math.ceil(target / denom1) + 1, 3);
182
- const maxCount2 = Math.min(Math.ceil(target / denom2) + 1, 4);
183
- const maxCount3 = Math.min(Math.ceil(target / denom3) + 1, 5);
184
- const maxCount4 = Math.min(Math.ceil(target / denom4) + 1, 6);
185
- for (let count1 = 1; count1 <= maxCount1; count1++) {
186
- for (let count2 = 1; count2 <= maxCount2; count2++) {
187
- for (let count3 = 1; count3 <= maxCount3; count3++) {
188
- for (let count4 = 1; count4 <= maxCount4; count4++) {
189
- const sum = count1 * denom1 + count2 * denom2 + count3 * denom3 + count4 * denom4;
190
- if (sum >= target) {
191
- const combination = [...Array(count1).fill(denom1 / precision), ...Array(count2).fill(denom2 / precision), ...Array(count3).fill(denom3 / precision), ...Array(count4).fill(denom4 / precision)];
192
- paymentOptions.push({
193
- combination: combination,
194
- sum: sum / precision,
195
- coinCount: count1 + count2 + count3 + count4,
196
- denomTypes: 4
197
- });
198
- }
199
- }
200
- }
201
- }
202
- }
203
- }
204
- }
205
- }
206
- }
207
-
208
- // 如果没有找到任何组合,提供默认建议
209
- if (paymentOptions.length === 0) {
210
- // 找到大于等于目标金额的最小面额
211
- const minValidDenom = validDenoms.find(denom => denom >= targetAmount);
212
- if (minValidDenom) {
213
- return [minValidDenom];
214
- } else {
215
- // 使用最大面额的组合
216
- const maxDenom = Math.max(...validDenoms);
217
- const count = Math.ceil(targetAmount / maxDenom);
218
- return [count * maxDenom];
219
- }
220
- }
221
-
222
- // 移除重复和扩展组合
223
- const uniqueCombinations = removeDuplicateAndExtendedCombinations(paymentOptions, targetAmount);
224
-
225
- // 按币种类型数量排序,然后按总硬币数量排序,最后按金额排序
226
- uniqueCombinations.sort((a, b) => {
227
- if (a.denomTypes !== b.denomTypes) {
228
- return a.denomTypes - b.denomTypes;
229
- }
230
- if (a.coinCount !== b.coinCount) {
231
- return a.coinCount - b.coinCount;
232
- }
233
- return a.sum - b.sum;
234
- });
235
-
236
- // 按金额去重并返回推荐的支付金额
237
- const uniqueAmounts = new Set();
238
- const finalResults = [];
239
- for (const item of uniqueCombinations) {
240
- const roundedAmount = Math.round(item.sum * 100) / 100; // 处理浮点数精度
241
- if (!uniqueAmounts.has(roundedAmount) && finalResults.length < 10) {
242
- uniqueAmounts.add(roundedAmount);
243
- finalResults.push(roundedAmount);
244
- }
245
- }
246
- return finalResults.sort((a, b) => a - b);
247
- } catch (error) {
248
- // 发生错误时返回安全的默认值
249
- console.warn('推荐支付金额计算出错:', error);
250
- // 返回最接近的整数金额作为兜底
251
- const safeAmount = Math.ceil(targetAmount);
252
- return [targetAmount, safeAmount];
253
- }
254
- }
255
-
256
- /**
257
- * 尝试找到精确匹配目标金额的组合
258
- */
259
- function findExactCombination(target, denoms, precision) {
260
- // 简单的深度优先搜索,限制搜索深度
261
- function dfs(remaining, denomIndex, currentCombination) {
262
- if (remaining === 0) {
263
- return currentCombination;
264
- }
265
- if (remaining < 0 || denomIndex >= denoms.length || currentCombination.length > 10) {
266
- return null;
267
- }
268
- const denom = denoms[denomIndex];
269
-
270
- // 尝试使用当前面额 0 到 maxCount 次
271
- const maxCount = Math.min(Math.floor(remaining / denom), 8);
272
- for (let count = maxCount; count >= 0; count--) {
273
- const newCombination = [...currentCombination, ...Array(count).fill(denom)];
274
- const result = dfs(remaining - count * denom, denomIndex + 1, newCombination);
275
- if (result) {
276
- return result;
277
- }
278
- }
279
- return null;
280
- }
281
- const exactMatch = dfs(target, 0, []);
282
- if (exactMatch && exactMatch.length > 0) {
283
- const denomTypes = new Set(exactMatch).size;
284
- return {
285
- combination: exactMatch.map(d => d / precision),
286
- sum: target / precision,
287
- coinCount: exactMatch.length,
288
- denomTypes: denomTypes
289
- };
290
- }
291
- return null;
292
- }
293
-
294
- /**
295
- * 移除重复和扩展组合
296
- * 核心原理:如果组合A包含组合B的所有硬币,并且还有额外硬币,则A是B的扩展,应该被移除
297
- */
298
- function removeDuplicateAndExtendedCombinations(combinations, targetAmount) {
299
- const result = [];
300
-
301
- // 按币种类型数量、硬币数量、金额排序,确保较简单的组合在前面
302
- combinations.sort((a, b) => {
303
- if (a.denomTypes !== b.denomTypes) {
304
- return a.denomTypes - b.denomTypes;
305
- }
306
- if (a.coinCount !== b.coinCount) {
307
- return a.coinCount - b.coinCount;
308
- }
309
- return a.sum - b.sum;
310
- });
311
- for (let i = 0; i < combinations.length; i++) {
312
- const current = combinations[i];
313
- let shouldSkip = false;
314
-
315
- // 检查当前组合是否与已添加的组合重复或者是扩展
316
- for (let j = 0; j < result.length; j++) {
317
- const existing = result[j];
318
-
319
- // 如果完全相同,跳过
320
- if (isSameCombination(current.combination, existing.combination)) {
321
- shouldSkip = true;
322
- break;
323
- }
324
-
325
- // 如果当前组合是已存在组合的扩展,跳过
326
- if (isExtensionOf(current.combination, existing.combination)) {
327
- shouldSkip = true;
328
- break;
329
- }
330
- }
331
-
332
- // 检查是否有其他组合是当前组合的扩展,如果有,移除那些扩展
333
- if (!shouldSkip) {
334
- // 移除所有当前组合的扩展
335
- for (let j = result.length - 1; j >= 0; j--) {
336
- if (isExtensionOf(result[j].combination, current.combination)) {
337
- result.splice(j, 1);
338
- }
339
- }
340
- result.push(current);
341
- }
342
- }
343
- return result;
344
- }
345
-
346
- /**
347
- * 检查组合A是否是组合B的扩展(即A包含B的所有硬币,并且还有额外的硬币)
348
- */
349
- function isExtensionOf(combinationA, combinationB) {
350
- // 如果A的硬币数量少于等于B,A不可能是B的扩展
351
- if (combinationA.length <= combinationB.length) {
352
- return false;
353
- }
354
-
355
- // 统计每个面额的数量
356
- const countA = {};
357
- const countB = {};
358
- combinationA.forEach(coin => {
359
- countA[coin] = (countA[coin] || 0) + 1;
360
- });
361
- combinationB.forEach(coin => {
362
- countB[coin] = (countB[coin] || 0) + 1;
363
- });
364
-
365
- // 检查B的所有面额在A中是否都有足够的数量
366
- for (const coin in countB) {
367
- if (!countA[coin] || countA[coin] < countB[coin]) {
368
- return false;
369
- }
370
- }
371
-
372
- // 检查A是否有B没有的额外硬币
373
- let hasExtra = false;
374
- for (const coin in countA) {
375
- if (countA[coin] > (countB[coin] || 0)) {
376
- hasExtra = true;
377
- break;
378
- }
379
- }
380
- return hasExtra;
381
- }
382
-
383
- /**
384
- * 检查两个组合是否本质相同(相同的面额组合)
385
- */
386
- function isSameCombination(combinationA, combinationB) {
387
- if (combinationA.length !== combinationB.length) {
388
- return false;
389
- }
390
- const countA = {};
391
- const countB = {};
392
- combinationA.forEach(coin => {
393
- countA[coin] = (countA[coin] || 0) + 1;
394
- });
395
- combinationB.forEach(coin => {
396
- countB[coin] = (countB[coin] || 0) + 1;
397
- });
398
-
399
- // 检查两个组合是否有相同的面额和数量
400
- for (const coin in countA) {
401
- if (countA[coin] !== (countB[coin] || 0)) {
402
- return false;
403
- }
404
- }
405
- for (const coin in countB) {
406
- if (countB[coin] !== (countA[coin] || 0)) {
407
- return false;
408
- }
409
- }
410
- return true;
62
+ const precision = 100;
63
+ const normalizedTarget = Math.round(targetAmount * precision) / precision;
64
+ const higherDenominations = [...new Set((Array.isArray(denominations) ? denominations : []).filter(denomination => denomination > 0 && Number.isFinite(denomination)).map(denomination => Math.round(denomination * precision) / precision))].filter(denomination => denomination > normalizedTarget).sort((a, b) => a - b).slice(0, 3);
65
+ return [normalizedTarget, ...higherDenominations];
411
66
  }
@@ -281,12 +281,14 @@ function evaluateCandidateTimeGate(params) {
281
281
  };
282
282
  }
283
283
  if (policy.type === 'before_start') {
284
- // Flexible-duration candidates and POS fixed-duration anchors start at
285
- // minute precision. Keep that current minute selectable instead of treating
286
- // the freshly generated candidate as already started because the query
287
- // clock still contains seconds.
284
+ // POS fixed-duration candidates use the context's stable opening-minute
285
+ // anchor. Keep that anchored range selectable while it is still active;
286
+ // otherwise a minute refresh turns it into 0 capacity and incorrectly
287
+ // advances a create flow to the next slot. The earlier `past` gate still
288
+ // rejects the anchor once its booking range has ended.
289
+ const isActivePosDurationAnchor = evaluationContext.useExactDurationCandidateMinute && product.kind === 'duration' && !product.isFlexibleDuration && evaluationContext.durationCandidateAnchorAtMs === candidateRange.start;
288
290
  const usesMinutePrecision = product.isFlexibleDuration || evaluationContext.useExactDurationCandidateMinute && product.kind === 'duration';
289
- const hasNotReachedStart = usesMinutePrecision ? floorToMinute(now) <= candidateRange.start : now < candidateRange.start;
291
+ const hasNotReachedStart = isActivePosDurationAnchor || (usesMinutePrecision ? floorToMinute(now) <= candidateRange.start : now < candidateRange.start);
290
292
  if (hasNotReachedStart) return null;
291
293
  return {
292
294
  status: 'unavailable',
@@ -1249,6 +1251,7 @@ function buildAvailabilityCellIndex(projection) {
1249
1251
  }
1250
1252
  function buildEvaluationContext(params) {
1251
1253
  const evaluatedAt = toDateTime(params.evaluatedAt || params.projection.meta.now, params.projection.meta.timezone);
1254
+ const durationCandidateAnchorAt = toDateTime(params.projection.meta.durationCandidateAnchorAt, params.projection.meta.timezone);
1252
1255
  const selectionsByGroupKey = new Map();
1253
1256
  (params.requirementSelections || []).forEach(selection => {
1254
1257
  const key = selection.requirementGroupId || String(selection.formId ?? '');
@@ -1260,6 +1263,7 @@ function buildEvaluationContext(params) {
1260
1263
  resourceIdSet: params.resourceIds?.length ? new Set(params.resourceIds.map(String)) : null,
1261
1264
  selectionsByGroupKey,
1262
1265
  useExactDurationCandidateMinute: params.useExactDurationCandidateMinute === true,
1266
+ durationCandidateAnchorAtMs: Number.isFinite(durationCandidateAnchorAt) ? floorToMinute(durationCandidateAnchorAt) : null,
1263
1267
  allowExcludedScheduleDateOverride: params.allowExcludedScheduleDateOverride === true
1264
1268
  };
1265
1269
  }
@@ -253,7 +253,8 @@ export interface GetProductTimeRangesRuntimeOptions {
253
253
  /**
254
254
  * Internal POS capability: fixed-duration candidates start from the stable
255
255
  * context anchor's current minute instead of rounding up to ten minutes, and
256
- * that minute remains selectable during before-start cutoff evaluation.
256
+ * that anchored range remains selectable while active during before-start
257
+ * cutoff evaluation.
257
258
  * Other callers keep the legacy alignment and strict second-level cutoff.
258
259
  */
259
260
  useExactDurationCandidateMinute?: boolean;
@@ -326,7 +326,7 @@ export declare class BookingByStepImpl extends BaseModule implements Module {
326
326
  date: string;
327
327
  status: string;
328
328
  week: string;
329
- weekNum: 0 | 5 | 3 | 1 | 2 | 4 | 6;
329
+ weekNum: 0 | 2 | 3 | 1 | 5 | 6 | 4;
330
330
  }[]>;
331
331
  submitTimeSlot(timeSlots: TimeSliceItem): void;
332
332
  private getScheduleDataByIds;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "private": false,
3
3
  "name": "@pisell/pisellos",
4
- "version": "2.2.303",
4
+ "version": "2.2.305",
5
5
  "description": "一个可扩展的前端模块化SDK框架,支持插件系统",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",