@pisell/pisellos 2.1.126 → 2.1.128

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 (40) hide show
  1. package/dist/model/strategy/adapter/itemRule/adapter.js +6 -2
  2. package/dist/model/strategy/adapter/itemRule/examples.d.ts +15 -0
  3. package/dist/model/strategy/adapter/itemRule/examples.js +68 -1
  4. package/dist/model/strategy/adapter/itemRule/index.d.ts +1 -1
  5. package/dist/model/strategy/adapter/itemRule/index.js +1 -1
  6. package/dist/model/strategy/adapter/itemRule/type.d.ts +9 -0
  7. package/dist/modules/Order/index.d.ts +1 -1
  8. package/dist/modules/Order/index.js +9 -40
  9. package/dist/modules/Order/utils.d.ts +25 -1
  10. package/dist/modules/Order/utils.js +88 -12
  11. package/dist/modules/SalesSummary/types.d.ts +2 -1
  12. package/dist/modules/SalesSummary/utils.js +10 -10
  13. package/dist/solution/BookingByStep/index.d.ts +2 -2
  14. package/dist/solution/ScanOrder/index.d.ts +5 -0
  15. package/dist/solution/ScanOrder/index.js +181 -62
  16. package/dist/solution/ScanOrder/types.d.ts +19 -5
  17. package/dist/solution/ScanOrder/utils.d.ts +14 -0
  18. package/dist/solution/ScanOrder/utils.js +138 -63
  19. package/dist/solution/VenueBooking/index.js +42 -44
  20. package/lib/model/strategy/adapter/itemRule/adapter.js +10 -2
  21. package/lib/model/strategy/adapter/itemRule/examples.d.ts +15 -0
  22. package/lib/model/strategy/adapter/itemRule/examples.js +47 -0
  23. package/lib/model/strategy/adapter/itemRule/index.d.ts +1 -1
  24. package/lib/model/strategy/adapter/itemRule/index.js +2 -0
  25. package/lib/model/strategy/adapter/itemRule/type.d.ts +9 -0
  26. package/lib/model/strategy/adapter/promotion/index.js +0 -49
  27. package/lib/modules/Order/index.d.ts +1 -1
  28. package/lib/modules/Order/index.js +8 -41
  29. package/lib/modules/Order/utils.d.ts +25 -1
  30. package/lib/modules/Order/utils.js +66 -5
  31. package/lib/modules/SalesSummary/types.d.ts +2 -1
  32. package/lib/modules/SalesSummary/utils.js +2 -2
  33. package/lib/solution/BookingByStep/index.d.ts +2 -2
  34. package/lib/solution/ScanOrder/index.d.ts +5 -0
  35. package/lib/solution/ScanOrder/index.js +78 -19
  36. package/lib/solution/ScanOrder/types.d.ts +19 -5
  37. package/lib/solution/ScanOrder/utils.d.ts +14 -0
  38. package/lib/solution/ScanOrder/utils.js +82 -20
  39. package/lib/solution/VenueBooking/index.js +11 -16
  40. package/package.json +1 -1
@@ -170,28 +170,22 @@ export function buildQuantityLimitIndex(limits) {
170
170
  try {
171
171
  for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
172
172
  var limit = _step2.value;
173
- var _iterator3 = _createForOfIteratorHelper(limit.targets || []),
174
- _step3;
175
- try {
176
- for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
177
- var target = _step3.value;
178
- var productId = Number(target.product_id);
179
- if (!Number.isFinite(productId) || productId <= 0) continue;
180
- var variantId = Number(target.product_variant_id);
181
- var hasVariant = Number.isFinite(variantId) && variantId > 0;
182
- if (hasVariant) {
183
- var exactKey = buildProductKey(productId, variantId);
184
- var _existed = exactLimitMap.get(exactKey) || [];
185
- exactLimitMap.set(exactKey, [].concat(_toConsumableArray(_existed), [limit]));
186
- continue;
187
- }
188
- var existed = productLimitMap.get(productId) || [];
189
- productLimitMap.set(productId, [].concat(_toConsumableArray(existed), [limit]));
190
- }
191
- } catch (err) {
192
- _iterator3.e(err);
193
- } finally {
194
- _iterator3.f();
173
+ var targets = limit.targets || [];
174
+ // 多目标规则(OR 组)只做购物车聚合校验,不下发到单品维度,
175
+ // 避免 UI 把「组内至少 1 份」误当作每款商品各自的 min,锁死减号按钮。
176
+ if (targets.length !== 1) continue;
177
+ var target = targets[0];
178
+ var productId = Number(target.product_id);
179
+ if (!Number.isFinite(productId) || productId <= 0) continue;
180
+ var variantId = Number(target.product_variant_id);
181
+ var hasVariant = Number.isFinite(variantId) && variantId > 0;
182
+ if (hasVariant) {
183
+ var exactKey = buildProductKey(productId, variantId);
184
+ var existed = exactLimitMap.get(exactKey) || [];
185
+ exactLimitMap.set(exactKey, [].concat(_toConsumableArray(existed), [limit]));
186
+ } else {
187
+ var _existed = productLimitMap.get(productId) || [];
188
+ productLimitMap.set(productId, [].concat(_toConsumableArray(_existed), [limit]));
195
189
  }
196
190
  }
197
191
  } catch (err) {
@@ -219,40 +213,81 @@ export function aggregateItemRuleLimit(matchedLimits) {
219
213
  var remainingMax;
220
214
  var exact;
221
215
  var mustInclude = false;
222
- var _iterator4 = _createForOfIteratorHelper(matchedLimits),
223
- _step4;
216
+
217
+ // 聚合 validationMessage 按 min / max 两个方向分别输出:
218
+ // - maxValidationMessage:触发"当前 max / remainingMax / exact"的那条规则的文案
219
+ // - minValidationMessage:触发"当前 min / exact"的那条规则的文案
220
+ // 各自再回退到其他带文案的规则(最后回退到第一条非空 validationMessage)。
221
+ // 前端可根据"加号/减号被拦"的方向取对应文案。
222
+ var maxSourceLimit;
223
+ var remainingMaxSourceLimit;
224
+ var exactSourceLimit;
225
+ var minSourceLimit;
226
+ var firstMessagedLimit;
227
+ var _iterator3 = _createForOfIteratorHelper(matchedLimits),
228
+ _step3;
224
229
  try {
225
- for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
226
- var limit = _step4.value;
230
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
231
+ var limit = _step3.value;
227
232
  if (typeof limit.requiredMin === 'number' && Number.isFinite(limit.requiredMin)) {
228
- min = min === undefined ? limit.requiredMin : Math.max(min, limit.requiredMin);
233
+ if (min === undefined || limit.requiredMin > min) {
234
+ min = limit.requiredMin;
235
+ minSourceLimit = limit;
236
+ }
229
237
  }
230
238
  if (typeof limit.requiredMax === 'number' && Number.isFinite(limit.requiredMax)) {
231
- max = max === undefined ? limit.requiredMax : Math.min(max, limit.requiredMax);
239
+ if (max === undefined || limit.requiredMax < max) {
240
+ max = limit.requiredMax;
241
+ maxSourceLimit = limit;
242
+ }
232
243
  }
233
244
  if (typeof limit.remainingMax === 'number' && Number.isFinite(limit.remainingMax)) {
234
- remainingMax = remainingMax === undefined ? limit.remainingMax : Math.min(remainingMax, limit.remainingMax);
245
+ if (remainingMax === undefined || limit.remainingMax < remainingMax) {
246
+ remainingMax = limit.remainingMax;
247
+ remainingMaxSourceLimit = limit;
248
+ }
235
249
  }
236
250
  if (typeof limit.requiredExact === 'number' && Number.isFinite(limit.requiredExact)) {
237
251
  exact = limit.requiredExact;
252
+ exactSourceLimit = limit;
238
253
  }
239
254
  mustInclude = mustInclude || Boolean(limit.mustInclude);
255
+ if (!firstMessagedLimit && limit.validationMessage) {
256
+ firstMessagedLimit = limit;
257
+ }
240
258
  }
241
259
  } catch (err) {
242
- _iterator4.e(err);
260
+ _iterator3.e(err);
243
261
  } finally {
244
- _iterator4.f();
262
+ _iterator3.f();
245
263
  }
246
264
  if (typeof exact === 'number' && Number.isFinite(exact)) {
247
265
  min = exact;
248
266
  max = exact;
249
267
  }
268
+ var maxMessageSource = maxSourceLimit || remainingMaxSourceLimit || exactSourceLimit || firstMessagedLimit;
269
+ var minMessageSource = minSourceLimit || exactSourceLimit || firstMessagedLimit;
270
+ var maxValidationMessage = maxMessageSource === null || maxMessageSource === void 0 ? void 0 : maxMessageSource.validationMessage;
271
+ var rawMaxValidationMessage = maxMessageSource === null || maxMessageSource === void 0 ? void 0 : maxMessageSource.rawValidationMessage;
272
+ var minValidationMessage = minMessageSource === null || minMessageSource === void 0 ? void 0 : minMessageSource.validationMessage;
273
+ var rawMinValidationMessage = minMessageSource === null || minMessageSource === void 0 ? void 0 : minMessageSource.rawValidationMessage;
274
+
275
+ // validationMessage / rawValidationMessage 保留为"最严格限制"的聚合文案,
276
+ // 便于旧消费者(只关心 max 侧)不破坏。优先 max → min。
277
+ var validationMessage = maxValidationMessage !== null && maxValidationMessage !== void 0 ? maxValidationMessage : minValidationMessage;
278
+ var rawValidationMessage = rawMaxValidationMessage !== null && rawMaxValidationMessage !== void 0 ? rawMaxValidationMessage : rawMinValidationMessage;
250
279
  return {
251
280
  min: min,
252
281
  max: max,
253
282
  remainingMax: remainingMax,
254
283
  exact: exact,
255
- mustInclude: mustInclude
284
+ mustInclude: mustInclude,
285
+ validationMessage: validationMessage,
286
+ rawValidationMessage: rawValidationMessage,
287
+ maxValidationMessage: maxValidationMessage,
288
+ rawMaxValidationMessage: rawMaxValidationMessage,
289
+ minValidationMessage: minValidationMessage,
290
+ rawMinValidationMessage: rawMinValidationMessage
256
291
  };
257
292
  }
258
293
  export function buildItemRuleBusinessData(params) {
@@ -300,11 +335,11 @@ export function buildItemRuleBusinessData(params) {
300
335
  export function attachItemRuleLimitsToTopLevelProducts(productList, limits) {
301
336
  if (!Array.isArray(productList)) return productList;
302
337
  var limitIndex = buildQuantityLimitIndex(limits || []);
303
- var _iterator5 = _createForOfIteratorHelper(productList),
304
- _step5;
338
+ var _iterator4 = _createForOfIteratorHelper(productList),
339
+ _step4;
305
340
  try {
306
- for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
307
- var item = _step5.value;
341
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
342
+ var item = _step4.value;
308
343
  if (!item || _typeof(item) !== 'object') continue;
309
344
  var productItem = item;
310
345
  var productId = getTopLevelProductId(productItem);
@@ -323,9 +358,9 @@ export function attachItemRuleLimitsToTopLevelProducts(productList, limits) {
323
358
  }
324
359
  }
325
360
  } catch (err) {
326
- _iterator5.e(err);
361
+ _iterator4.e(err);
327
362
  } finally {
328
- _iterator5.f();
363
+ _iterator4.f();
329
364
  }
330
365
  return productList;
331
366
  }
@@ -363,8 +398,14 @@ export function normalizeOrderProduct(product) {
363
398
  if (product.identity_key && !metadata.unique_identification_number) {
364
399
  metadata.unique_identification_number = product.identity_key;
365
400
  }
366
- var resolvedOriginalPrice = product.original_price || '0.00';
401
+
402
+ // selling_price:券后成交单价;original_price:券前店铺售价。
403
+ // 初次加购时两者相等(入口层应保证 caller 传的 original_price == selling_price);
404
+ // 券应用后由 Rules 引擎只改动 selling_price,保留 original_price 以便还原 / 展示划线对比。
405
+ // 注意:这里 caller 若显式传入了 original_price(例如 Rules 回写或单测场景),要尊重该值,
406
+ // 让券后状态能正确通过再次 normalize。
367
407
  var resolvedSellingPrice = product.selling_price || '0.00';
408
+ var resolvedOriginalPrice = product.original_price || resolvedSellingPrice;
368
409
  if (metadata.main_product_original_price === undefined) {
369
410
  metadata.main_product_original_price = resolvedOriginalPrice;
370
411
  }
@@ -372,14 +413,14 @@ export function normalizeOrderProduct(product) {
372
413
  metadata.main_product_selling_price = resolvedSellingPrice;
373
414
  }
374
415
  if (metadata.source_product_price === undefined) {
375
- var _product$_origin$orig, _product$_origin;
376
- metadata.source_product_price = (_product$_origin$orig = (_product$_origin = product._origin) === null || _product$_origin === void 0 ? void 0 : _product$_origin.original_price) !== null && _product$_origin$orig !== void 0 ? _product$_origin$orig : resolvedOriginalPrice;
416
+ var _ref, _product$_origin$pric, _product$_origin, _product$_origin2;
417
+ metadata.source_product_price = (_ref = (_product$_origin$pric = (_product$_origin = product._origin) === null || _product$_origin === void 0 ? void 0 : _product$_origin.price) !== null && _product$_origin$pric !== void 0 ? _product$_origin$pric : (_product$_origin2 = product._origin) === null || _product$_origin2 === void 0 ? void 0 : _product$_origin2.base_price) !== null && _ref !== void 0 ? _ref : resolvedSellingPrice;
377
418
  }
378
419
  var normalizedBundle = (product.product_bundle || []).map(function (item) {
379
- var _ref, _item$bundle_selling_, _ref2, _ref3, _item$custom_price;
420
+ var _ref2, _item$bundle_selling_, _ref3, _ref4, _item$custom_price;
380
421
  return _objectSpread(_objectSpread({}, item), {}, {
381
- bundle_selling_price: (_ref = (_item$bundle_selling_ = item.bundle_selling_price) !== null && _item$bundle_selling_ !== void 0 ? _item$bundle_selling_ : item.price) !== null && _ref !== void 0 ? _ref : '0.00',
382
- custom_price: (_ref2 = (_ref3 = (_item$custom_price = item.custom_price) !== null && _item$custom_price !== void 0 ? _item$custom_price : item.bundle_selling_price) !== null && _ref3 !== void 0 ? _ref3 : item.price) !== null && _ref2 !== void 0 ? _ref2 : '0.00'
422
+ bundle_selling_price: (_ref2 = (_item$bundle_selling_ = item.bundle_selling_price) !== null && _item$bundle_selling_ !== void 0 ? _item$bundle_selling_ : item.price) !== null && _ref2 !== void 0 ? _ref2 : '0.00',
423
+ custom_price: (_ref3 = (_ref4 = (_item$custom_price = item.custom_price) !== null && _item$custom_price !== void 0 ? _item$custom_price : item.bundle_selling_price) !== null && _ref4 !== void 0 ? _ref4 : item.price) !== null && _ref3 !== void 0 ? _ref3 : '0.00'
383
424
  });
384
425
  });
385
426
  return {
@@ -391,7 +432,6 @@ export function normalizeOrderProduct(product) {
391
432
  product_option_item: product.product_option_item || [],
392
433
  selling_price: resolvedSellingPrice,
393
434
  original_price: resolvedOriginalPrice,
394
- payment_price: product.payment_price || '0.00',
395
435
  tax_fee: product.tax_fee || '0.00',
396
436
  is_charge_tax: (_product$is_charge_ta = product.is_charge_tax) !== null && _product$is_charge_ta !== void 0 ? _product$is_charge_ta : 0,
397
437
  discount_list: product.discount_list || [],
@@ -407,33 +447,33 @@ export function normalizeOrderProduct(product) {
407
447
  export function collectLinkProductIdsFromReservationRules(rules) {
408
448
  if (!Array.isArray(rules)) return [];
409
449
  var ids = [];
410
- var _iterator6 = _createForOfIteratorHelper(rules),
411
- _step6;
450
+ var _iterator5 = _createForOfIteratorHelper(rules),
451
+ _step5;
412
452
  try {
413
- for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
414
- var entry = _step6.value;
453
+ for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
454
+ var entry = _step5.value;
415
455
  if (!entry || _typeof(entry) !== 'object') continue;
416
456
  var rec = entry;
417
457
  if (rec.type !== 'link') continue;
418
458
  if (!Array.isArray(rec.value)) continue;
419
- var _iterator7 = _createForOfIteratorHelper(rec.value),
420
- _step7;
459
+ var _iterator6 = _createForOfIteratorHelper(rec.value),
460
+ _step6;
421
461
  try {
422
- for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) {
423
- var v = _step7.value;
462
+ for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
463
+ var v = _step6.value;
424
464
  var n = Number(v);
425
465
  if (Number.isFinite(n) && n > 0) ids.push(Math.floor(n));
426
466
  }
427
467
  } catch (err) {
428
- _iterator7.e(err);
468
+ _iterator6.e(err);
429
469
  } finally {
430
- _iterator7.f();
470
+ _iterator6.f();
431
471
  }
432
472
  }
433
473
  } catch (err) {
434
- _iterator6.e(err);
474
+ _iterator5.e(err);
435
475
  } finally {
436
- _iterator6.f();
476
+ _iterator5.f();
437
477
  }
438
478
  return _toConsumableArray(new Set(ids));
439
479
  }
@@ -442,20 +482,20 @@ export function collectLinkProductIdsFromReservationRules(rules) {
442
482
  * 返回列表中第一个带有效 duration.value(分钟)的商品时长。
443
483
  */
444
484
  export function pickFirstDurationMinutesFromProducts(products) {
445
- var _iterator8 = _createForOfIteratorHelper(products),
446
- _step8;
485
+ var _iterator7 = _createForOfIteratorHelper(products),
486
+ _step7;
447
487
  try {
448
- for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) {
488
+ for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) {
449
489
  var _product$duration;
450
- var product = _step8.value;
490
+ var product = _step7.value;
451
491
  var value = product === null || product === void 0 || (_product$duration = product.duration) === null || _product$duration === void 0 ? void 0 : _product$duration.value;
452
492
  var n = Number(value);
453
493
  if (Number.isFinite(n) && n > 0) return Math.floor(n);
454
494
  }
455
495
  } catch (err) {
456
- _iterator8.e(err);
496
+ _iterator7.e(err);
457
497
  } finally {
458
- _iterator8.f();
498
+ _iterator7.f();
459
499
  }
460
500
  return undefined;
461
501
  }
@@ -466,4 +506,39 @@ export function hasCustomCapacityProduct(products) {
466
506
  var _p$capacity;
467
507
  return (p === null || p === void 0 || (_p$capacity = p.capacity) === null || _p$capacity === void 0 ? void 0 : _p$capacity.type) === 'custom';
468
508
  });
509
+ }
510
+
511
+ /**
512
+ * 在商品列表中找到第一个 `capacity.type === 'custom'` 的商品,取其 `custom` 数组第一项的 min/max。
513
+ * 仅返回有限数字字段;若均无法解析则返回 `undefined`。
514
+ */
515
+ export function pickFirstCustomCapacityPaxBounds(products) {
516
+ var _iterator8 = _createForOfIteratorHelper(products),
517
+ _step8;
518
+ try {
519
+ for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) {
520
+ var p = _step8.value;
521
+ var cap = p === null || p === void 0 ? void 0 : p.capacity;
522
+ if (!cap || cap.type !== 'custom') continue;
523
+ if (!Array.isArray(cap.custom) || cap.custom.length === 0) continue;
524
+ var row = cap.custom[0];
525
+ if (!row || _typeof(row) !== 'object') continue;
526
+ var raw = row;
527
+ var out = {};
528
+ if (raw.min !== null && raw.min !== undefined && raw.min !== '') {
529
+ var min = Number(raw.min);
530
+ if (Number.isFinite(min)) out.min = min;
531
+ }
532
+ if (raw.max !== null && raw.max !== undefined && raw.max !== '') {
533
+ var max = Number(raw.max);
534
+ if (Number.isFinite(max)) out.max = max;
535
+ }
536
+ if (out.min !== undefined || out.max !== undefined) return out;
537
+ }
538
+ } catch (err) {
539
+ _iterator8.e(err);
540
+ } finally {
541
+ _iterator8.f();
542
+ }
543
+ return undefined;
469
544
  }
@@ -1446,7 +1446,6 @@ export var VenueBookingImpl = /*#__PURE__*/function (_BaseModule) {
1446
1446
  num: 1,
1447
1447
  selling_price: group.totalPrice,
1448
1448
  original_price: group.totalPrice,
1449
- payment_price: group.totalPrice,
1450
1449
  is_charge_tax: (_venueProduct$is_char = venueProduct === null || venueProduct === void 0 ? void 0 : venueProduct.is_charge_tax) !== null && _venueProduct$is_char !== void 0 ? _venueProduct$is_char : 0,
1451
1450
  metadata: {
1452
1451
  venue_booking: true,
@@ -1699,7 +1698,7 @@ export var VenueBookingImpl = /*#__PURE__*/function (_BaseModule) {
1699
1698
  var merged = mergeConsecutiveSlots(updatedSlots);
1700
1699
  if (merged.length === 1) {
1701
1700
  product.selling_price = merged[0].totalPrice;
1702
- product.payment_price = merged[0].totalPrice;
1701
+ product.original_price = merged[0].totalPrice;
1703
1702
  product.metadata.price_breakdown = buildPriceBreakdown({
1704
1703
  group: merged[0],
1705
1704
  productId: mapping.productId,
@@ -1715,7 +1714,7 @@ export var VenueBookingImpl = /*#__PURE__*/function (_BaseModule) {
1715
1714
  });
1716
1715
  if (quotationPrice !== null) {
1717
1716
  product.selling_price = quotationPrice;
1718
- product.payment_price = quotationPrice;
1717
+ product.original_price = quotationPrice;
1719
1718
  }
1720
1719
  }
1721
1720
  };
@@ -1793,7 +1792,7 @@ export var VenueBookingImpl = /*#__PURE__*/function (_BaseModule) {
1793
1792
  key: "setDiscountSelected",
1794
1793
  value: (function () {
1795
1794
  var _setDiscountSelected = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee21(params) {
1796
- var _tempOrder$holder, _this$store$order$get, _this$store$order$get2, list, beforeTarget, updated, updatedTarget, tempOrder, orderStore, discountModule, rulesModule, holders, nextDiscountList, _tempOrder$holder2, result, beforeSelectedIds, _iterator9, _step9, d, selectedResourceIds, _iterator10, _step10, _product$discount_lis, product, before, totalDiscountAmount, newPaymentPrice, paymentStr, afterApplyTarget, finalSummary, finalProduct, finalTarget;
1795
+ var _tempOrder$holder, _this$store$order$get, _this$store$order$get2, list, beforeTarget, updated, updatedTarget, tempOrder, orderStore, discountModule, rulesModule, holders, nextDiscountList, _tempOrder$holder2, result, beforeSelectedIds, _iterator9, _step9, d, selectedResourceIds, _iterator10, _step10, _product$discount_lis, product, before, totalPerUnitDiscount, newSellingPrice, afterApplyTarget, finalSummary, finalProduct, finalTarget;
1797
1796
  return _regeneratorRuntime().wrap(function _callee21$(_context21) {
1798
1797
  while (1) switch (_context21.prev = _context21.next) {
1799
1798
  case 0:
@@ -1896,14 +1895,15 @@ export var VenueBookingImpl = /*#__PURE__*/function (_BaseModule) {
1896
1895
  return rid != null && selectedResourceIds.has(rid);
1897
1896
  });
1898
1897
  if (product.discount_list.length < before) {
1899
- totalDiscountAmount = product.discount_list.reduce(function (sum, pd) {
1898
+ // 基于剩余的 discount_list 重算 selling_price。
1899
+ // 约定:pd.amount 为 per-unit 折扣额,remaining 加总即单品券后总折扣。
1900
+ totalPerUnitDiscount = product.discount_list.reduce(function (sum, pd) {
1900
1901
  return sum + (pd.amount || 0);
1901
1902
  }, 0);
1902
- newPaymentPrice = new Decimal(product.selling_price || 0).minus(totalDiscountAmount).toNumber();
1903
- paymentStr = toPriceString(newPaymentPrice);
1904
- product.payment_price = paymentStr;
1903
+ newSellingPrice = new Decimal(product.original_price || product.selling_price || 0).minus(totalPerUnitDiscount).toDecimalPlaces(2).toString();
1904
+ product.selling_price = newSellingPrice;
1905
1905
  if (product.metadata) {
1906
- product.metadata.main_product_selling_price = paymentStr;
1906
+ product.metadata.main_product_selling_price = newSellingPrice;
1907
1907
  }
1908
1908
  }
1909
1909
  case 28:
@@ -2123,10 +2123,9 @@ export var VenueBookingImpl = /*#__PURE__*/function (_BaseModule) {
2123
2123
  });
2124
2124
  if (quotationPrice !== null) {
2125
2125
  product.selling_price = quotationPrice;
2126
- if (product.original_price == null) {
2127
- product.original_price = quotationPrice;
2128
- }
2129
- product.payment_price = quotationPrice;
2126
+ product.original_price = quotationPrice;
2127
+ } else if (product.selling_price != null) {
2128
+ product.original_price = product.selling_price;
2130
2129
  }
2131
2130
  }
2132
2131
  _context25.next = 7;
@@ -2619,7 +2618,7 @@ export var VenueBookingImpl = /*#__PURE__*/function (_BaseModule) {
2619
2618
  key: "applyPrefillByItemRule",
2620
2619
  value: function () {
2621
2620
  var _applyPrefillByItemRule = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee36() {
2622
- var strategyConfigs, businessData, prefillItems, productSourceMap, tempOrder, hasChanges, _iterator12, _step12, _prefillItem$product_, _targetProduct$_origi, prefillItem, productId, productVariantId, targetQuantity, sourceItem, productIndex, _sourceItem$price, _ref5, _sourceItem$original_, _ref6, _sourceItem$payment_p, sellingPrice, originalPrice, paymentPrice, targetProduct, existedQuantity, delta, nextProduct;
2621
+ var strategyConfigs, businessData, prefillItems, productSourceMap, tempOrder, hasChanges, _iterator12, _step12, _prefillItem$product_, _targetProduct$_origi, prefillItem, productId, productVariantId, targetQuantity, sourceItem, productIndex, _sourceItem$price, sellingPrice, targetProduct, existedQuantity, delta, nextProduct;
2623
2622
  return _regeneratorRuntime().wrap(function _callee36$(_context36) {
2624
2623
  while (1) switch (_context36.prev = _context36.next) {
2625
2624
  case 0:
@@ -2669,7 +2668,7 @@ export var VenueBookingImpl = /*#__PURE__*/function (_BaseModule) {
2669
2668
  _iterator12.s();
2670
2669
  case 22:
2671
2670
  if ((_step12 = _iterator12.n()).done) {
2672
- _context36.next = 47;
2671
+ _context36.next = 45;
2673
2672
  break;
2674
2673
  }
2675
2674
  prefillItem = _step12.value;
@@ -2680,13 +2679,13 @@ export var VenueBookingImpl = /*#__PURE__*/function (_BaseModule) {
2680
2679
  _context36.next = 29;
2681
2680
  break;
2682
2681
  }
2683
- return _context36.abrupt("continue", 45);
2682
+ return _context36.abrupt("continue", 43);
2684
2683
  case 29:
2685
2684
  if (!Number.isNaN(productVariantId)) {
2686
2685
  _context36.next = 31;
2687
2686
  break;
2688
2687
  }
2689
- return _context36.abrupt("continue", 45);
2688
+ return _context36.abrupt("continue", 43);
2690
2689
  case 31:
2691
2690
  sourceItem = productSourceMap.get(buildProductKey(productId, productVariantId)) || productSourceMap.get(buildProductKey(productId, 0)) || null;
2692
2691
  productIndex = getProductIdentityIndex(tempOrder.products, {
@@ -2694,19 +2693,16 @@ export var VenueBookingImpl = /*#__PURE__*/function (_BaseModule) {
2694
2693
  product_variant_id: productVariantId
2695
2694
  });
2696
2695
  if (!(productIndex === -1)) {
2697
- _context36.next = 40;
2696
+ _context36.next = 38;
2698
2697
  break;
2699
2698
  }
2700
2699
  sellingPrice = toPriceString((_sourceItem$price = sourceItem === null || sourceItem === void 0 ? void 0 : sourceItem.price) !== null && _sourceItem$price !== void 0 ? _sourceItem$price : sourceItem === null || sourceItem === void 0 ? void 0 : sourceItem.selling_price, '0.00');
2701
- originalPrice = toPriceString((_ref5 = (_sourceItem$original_ = sourceItem === null || sourceItem === void 0 ? void 0 : sourceItem.original_price) !== null && _sourceItem$original_ !== void 0 ? _sourceItem$original_ : sourceItem === null || sourceItem === void 0 ? void 0 : sourceItem.price) !== null && _ref5 !== void 0 ? _ref5 : sellingPrice, sellingPrice);
2702
- paymentPrice = toPriceString((_ref6 = (_sourceItem$payment_p = sourceItem === null || sourceItem === void 0 ? void 0 : sourceItem.payment_price) !== null && _sourceItem$payment_p !== void 0 ? _sourceItem$payment_p : sourceItem === null || sourceItem === void 0 ? void 0 : sourceItem.price) !== null && _ref6 !== void 0 ? _ref6 : sellingPrice, sellingPrice);
2703
2700
  tempOrder.products.push(normalizeOrderProduct({
2704
2701
  product_id: productId,
2705
2702
  product_variant_id: productVariantId,
2706
2703
  num: targetQuantity,
2707
2704
  selling_price: sellingPrice,
2708
- original_price: originalPrice,
2709
- payment_price: paymentPrice,
2705
+ original_price: sellingPrice,
2710
2706
  metadata: {
2711
2707
  item_rule_prefill: true,
2712
2708
  item_rule_id: prefillItem.ruleId
@@ -2717,8 +2713,8 @@ export var VenueBookingImpl = /*#__PURE__*/function (_BaseModule) {
2717
2713
  })
2718
2714
  }));
2719
2715
  hasChanges = true;
2720
- return _context36.abrupt("continue", 45);
2721
- case 40:
2716
+ return _context36.abrupt("continue", 43);
2717
+ case 38:
2722
2718
  targetProduct = tempOrder.products[productIndex];
2723
2719
  existedQuantity = toNonNegativeInt(targetProduct.num);
2724
2720
  delta = targetQuantity - existedQuantity;
@@ -2735,45 +2731,47 @@ export var VenueBookingImpl = /*#__PURE__*/function (_BaseModule) {
2735
2731
  tempOrder.products[productIndex] = nextProduct;
2736
2732
  hasChanges = true;
2737
2733
  }
2738
- case 45:
2734
+ case 43:
2739
2735
  _context36.next = 22;
2740
2736
  break;
2741
- case 47:
2742
- _context36.next = 52;
2737
+ case 45:
2738
+ _context36.next = 50;
2743
2739
  break;
2744
- case 49:
2745
- _context36.prev = 49;
2740
+ case 47:
2741
+ _context36.prev = 47;
2746
2742
  _context36.t0 = _context36["catch"](20);
2747
2743
  _iterator12.e(_context36.t0);
2748
- case 52:
2749
- _context36.prev = 52;
2744
+ case 50:
2745
+ _context36.prev = 50;
2750
2746
  _iterator12.f();
2751
- return _context36.finish(52);
2752
- case 55:
2747
+ return _context36.finish(50);
2748
+ case 53:
2753
2749
  if (!hasChanges) {
2754
- _context36.next = 59;
2750
+ _context36.next = 58;
2755
2751
  break;
2756
2752
  }
2757
- _context36.next = 58;
2753
+ // Prefill 属于 products CRUD 的一种形式:新增后让 Rules 重算,以支持自动应用可用优惠券。
2754
+ this.store.order.applyDiscount();
2755
+ _context36.next = 57;
2758
2756
  return this.store.order.recalculateSummary({
2759
2757
  createIfMissing: true
2760
2758
  });
2761
- case 58:
2759
+ case 57:
2762
2760
  this.store.order.persistTempOrder();
2763
- case 59:
2764
- _context36.next = 61;
2761
+ case 58:
2762
+ _context36.next = 60;
2765
2763
  return this.refreshItemRuleQuantityLimits();
2766
- case 61:
2764
+ case 60:
2767
2765
  this.itemRulePrefillApplied = true;
2768
2766
  this.logMethodSuccess('applyPrefillByItemRule', {
2769
2767
  prefillCount: prefillItems.length,
2770
2768
  hasChanges: hasChanges
2771
2769
  });
2772
- case 63:
2770
+ case 62:
2773
2771
  case "end":
2774
2772
  return _context36.stop();
2775
2773
  }
2776
- }, _callee36, this, [[20, 49, 52, 55]]);
2774
+ }, _callee36, this, [[20, 47, 50, 53]]);
2777
2775
  }));
2778
2776
  function applyPrefillByItemRule() {
2779
2777
  return _applyPrefillByItemRule.apply(this, arguments);
@@ -3078,14 +3076,14 @@ export var VenueBookingImpl = /*#__PURE__*/function (_BaseModule) {
3078
3076
  key: "setOtherParams",
3079
3077
  value: function () {
3080
3078
  var _setOtherParams = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee43(params) {
3081
- var _ref7,
3082
- _ref7$cover,
3079
+ var _ref5,
3080
+ _ref5$cover,
3083
3081
  cover,
3084
3082
  _args43 = arguments;
3085
3083
  return _regeneratorRuntime().wrap(function _callee43$(_context43) {
3086
3084
  while (1) switch (_context43.prev = _context43.next) {
3087
3085
  case 0:
3088
- _ref7 = _args43.length > 1 && _args43[1] !== undefined ? _args43[1] : {}, _ref7$cover = _ref7.cover, cover = _ref7$cover === void 0 ? false : _ref7$cover;
3086
+ _ref5 = _args43.length > 1 && _args43[1] !== undefined ? _args43[1] : {}, _ref5$cover = _ref5.cover, cover = _ref5$cover === void 0 ? false : _ref5$cover;
3089
3087
  if (cover) {
3090
3088
  this.otherParams = params;
3091
3089
  } else {
@@ -250,7 +250,7 @@ var ItemRuleAdapter = class {
250
250
  const config = action.config;
251
251
  if (!config)
252
252
  return null;
253
- const { scope, quantityRules, targetType, targets } = config;
253
+ const { scope, quantityRules, targetType, targets, validationMessage } = config;
254
254
  if (!this.shouldEvaluateForScope(scope, businessData.submissionIndex ?? 0)) {
255
255
  return null;
256
256
  }
@@ -289,6 +289,12 @@ var ItemRuleAdapter = class {
289
289
  targets,
290
290
  businessData
291
291
  );
292
+ const formattedValidationMessage = this.formatValidationMessage(
293
+ validationMessage,
294
+ requiredMin,
295
+ requiredMax,
296
+ requiredExact
297
+ );
292
298
  return {
293
299
  ruleId: action.id,
294
300
  targetType,
@@ -298,7 +304,9 @@ var ItemRuleAdapter = class {
298
304
  remainingMax,
299
305
  requiredExact,
300
306
  mustInclude,
301
- scope
307
+ scope,
308
+ validationMessage: formattedValidationMessage,
309
+ rawValidationMessage: validationMessage
302
310
  };
303
311
  }
304
312
  /**
@@ -25,6 +25,21 @@ export declare const MAX_BUNS_PER_TABLE_STRATEGY: StrategyConfig;
25
25
  * - Prefill Cart: 自动添加默认锅底 x 1
26
26
  */
27
27
  export declare const HOTPOT_BASE_REQUIRED_STRATEGY: StrategyConfig;
28
+ /**
29
+ * 场景:4 款锅底里任选至少 1 款,不自动预填,不限制多选上限
30
+ *
31
+ * WHEN: Always active
32
+ * THEN:
33
+ * - Quantity Check: 4 个锅底商品合计数量 >= 1 (OR 组语义)
34
+ *
35
+ * 说明:
36
+ * - targets 里放多个 product_id 时,ItemRuleAdapter 会把它们视为 OR 组,
37
+ * 仅校验「合计数量」,不会要求每款各自满足 min。
38
+ * - ScanOrder 工具层 buildQuantityLimitIndex 会跳过多目标规则的
39
+ * 单品索引,避免 UI 把组级 min 误当作每张商品卡的减号下限,
40
+ * 锁死用户切换锅底的操作。
41
+ */
42
+ export declare const HOTPOT_BASE_PICK_ONE_STRATEGY: StrategyConfig;
28
43
  /**
29
44
  * ItemRuleEvaluator 简洁调用示例
30
45
  *
@@ -19,6 +19,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
19
19
  // src/model/strategy/adapter/itemRule/examples.ts
20
20
  var examples_exports = {};
21
21
  __export(examples_exports, {
22
+ HOTPOT_BASE_PICK_ONE_STRATEGY: () => HOTPOT_BASE_PICK_ONE_STRATEGY,
22
23
  HOTPOT_BASE_REQUIRED_STRATEGY: () => HOTPOT_BASE_REQUIRED_STRATEGY,
23
24
  MAX_BUNS_PER_TABLE_STRATEGY: () => MAX_BUNS_PER_TABLE_STRATEGY,
24
25
  MIN_CONDIMENT_PER_PERSON_STRATEGY: () => MIN_CONDIMENT_PER_PERSON_STRATEGY,
@@ -190,6 +191,51 @@ var HOTPOT_BASE_REQUIRED_STRATEGY = {
190
191
  }
191
192
  ]
192
193
  };
194
+ var HOTPOT_BASE_PICK_ONE_STRATEGY = {
195
+ metadata: {
196
+ id: "RULE_HOTPOT_BASE_PICK_ONE",
197
+ name: {
198
+ "zh-CN": "锅底必选(四选一)",
199
+ en: "Hotpot base required (pick any)"
200
+ },
201
+ type: "item_rule",
202
+ description: {
203
+ "zh-CN": "4 款锅底任选至少 1 份",
204
+ en: "Pick at least 1 hotpot base from the available options"
205
+ }
206
+ },
207
+ conditions: {
208
+ operator: "and",
209
+ rules: [],
210
+ actionIds: ["quantity_check_hotpot_base_group"]
211
+ },
212
+ actions: [
213
+ {
214
+ id: "quantity_check_hotpot_base_group",
215
+ type: import_type.ITEM_RULE_ACTION_TYPES.QUANTITY_CHECK,
216
+ value: null,
217
+ target: "cart",
218
+ priority: 10,
219
+ config: {
220
+ targetType: "product",
221
+ targets: [
222
+ { product_id: 30001 },
223
+ { product_id: 30002 },
224
+ { product_id: 30003 },
225
+ { product_id: 30004 }
226
+ ],
227
+ quantityRules: [
228
+ { comparison: "minimum", source: "fixed", value: 1 }
229
+ ],
230
+ scope: "first_submission_only",
231
+ validationMessage: {
232
+ "zh-CN": "请至少选择 {min} 款锅底",
233
+ en: "Please select at least {min} hotpot base"
234
+ }
235
+ }
236
+ }
237
+ ]
238
+ };
193
239
  function quickUseItemRuleEvaluator() {
194
240
  const evaluator = new import_evaluator.ItemRuleEvaluator();
195
241
  evaluator.setStrategyConfigs([
@@ -261,6 +307,7 @@ function runItemRuleEvaluatorDemo() {
261
307
  }
262
308
  // Annotate the CommonJS export names for ESM import in node:
263
309
  0 && (module.exports = {
310
+ HOTPOT_BASE_PICK_ONE_STRATEGY,
264
311
  HOTPOT_BASE_REQUIRED_STRATEGY,
265
312
  MAX_BUNS_PER_TABLE_STRATEGY,
266
313
  MIN_CONDIMENT_PER_PERSON_STRATEGY,