@pisell/pisellos 0.10.64 → 0.10.65

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 (32) hide show
  1. package/dist/model/strategy/adapter/promotion/index.js +0 -9
  2. package/dist/modules/Order/index.d.ts +6 -1
  3. package/dist/modules/Order/index.js +564 -475
  4. package/dist/modules/Order/types.d.ts +2 -0
  5. package/dist/modules/Order/utils/discountProductLineIdentity.js +2 -2
  6. package/dist/modules/Order/utils/productVoucherGroups.d.ts +15 -0
  7. package/dist/modules/Order/utils/productVoucherGroups.js +115 -0
  8. package/dist/modules/Product/index.d.ts +1 -1
  9. package/dist/modules/Rules/index.d.ts +2 -0
  10. package/dist/modules/Rules/index.js +29 -21
  11. package/dist/solution/BaseSales/index.d.ts +8 -1
  12. package/dist/solution/BaseSales/index.js +53 -41
  13. package/dist/solution/BookingByStep/index.d.ts +1 -1
  14. package/dist/solution/BookingTicket/index.d.ts +13 -1
  15. package/dist/solution/BookingTicket/index.js +36 -11
  16. package/dist/solution/BookingTicket/types.d.ts +2 -0
  17. package/lib/modules/Order/index.d.ts +6 -1
  18. package/lib/modules/Order/index.js +49 -6
  19. package/lib/modules/Order/types.d.ts +2 -0
  20. package/lib/modules/Order/utils/discountProductLineIdentity.js +1 -1
  21. package/lib/modules/Order/utils/productVoucherGroups.d.ts +15 -0
  22. package/lib/modules/Order/utils/productVoucherGroups.js +64 -0
  23. package/lib/modules/Product/index.d.ts +1 -1
  24. package/lib/modules/Rules/index.d.ts +2 -0
  25. package/lib/modules/Rules/index.js +6 -0
  26. package/lib/solution/BaseSales/index.d.ts +8 -1
  27. package/lib/solution/BaseSales/index.js +9 -2
  28. package/lib/solution/BookingByStep/index.d.ts +1 -1
  29. package/lib/solution/BookingTicket/index.d.ts +13 -1
  30. package/lib/solution/BookingTicket/index.js +14 -0
  31. package/lib/solution/BookingTicket/types.d.ts +2 -0
  32. package/package.json +1 -1
@@ -20,6 +20,7 @@ var _weighedProduct = require("./utils/weighedProduct");
20
20
  var _pickupTiming = require("./pickupTiming");
21
21
  var _dayjs = _interopRequireDefault(require("dayjs"));
22
22
  var _availability = require("../Discount/availability");
23
+ var _productVoucherGroups = require("./utils/productVoucherGroups");
23
24
  var _osPrivatePayload = require("../../utils/os-private-payload");
24
25
  var _salesNotes = require("./salesNotes");
25
26
  var _paymentUtils = require("./payment-utils");
@@ -148,7 +149,7 @@ class OrderModule extends _BaseModule.BaseModule {
148
149
  orderHooks;
149
150
  cartCommandQueue = Promise.resolve();
150
151
  queueCartCommand(run) {
151
- if (!this.orderHooks?.onBeforeCartCommit) return run();
152
+ if (!this.orderHooks?.onBeforeCartCommit && !this.store.tempOrder?._extend?.productVoucherGroups?.length) return run();
152
153
  const pending = this.cartCommandQueue.then(run, run);
153
154
  this.cartCommandQueue = pending.catch(() => undefined);
154
155
  return pending;
@@ -158,7 +159,7 @@ class OrderModule extends _BaseModule.BaseModule {
158
159
  beginCartMutation() {
159
160
  const current = this.ensureTempOrder();
160
161
  const guard = this.orderHooks?.onBeforeCartCommit;
161
- if (!guard) return {
162
+ if (!guard && !current._extend?.productVoucherGroups?.length) return {
162
163
  current,
163
164
  draft: current,
164
165
  commit: undefined
@@ -169,7 +170,8 @@ class OrderModule extends _BaseModule.BaseModule {
169
170
  current,
170
171
  draft,
171
172
  commit: async beforeCommit => {
172
- await guard({
173
+ (0, _productVoucherGroups.validateProductVoucherGroups)(draft, this.getDiscountList(), before);
174
+ await guard?.({
173
175
  currentItems: (0, _lodashEs.cloneDeep)(before.products),
174
176
  proposedItems: (0, _lodashEs.cloneDeep)(draft.products)
175
177
  });
@@ -190,6 +192,7 @@ class OrderModule extends _BaseModule.BaseModule {
190
192
  async prepareCheckout(prepare) {
191
193
  const run = async () => {
192
194
  const order = this.ensureTempOrder();
195
+ (0, _productVoucherGroups.validateProductVoucherGroups)(order, this.getDiscountList(), undefined, true);
193
196
  const items = (0, _lodashEs.cloneDeep)(order.products);
194
197
  const result = await prepare(items);
195
198
  if (this.store.tempOrder !== order || !(0, _lodashEs.isEqual)(order.products, items)) {
@@ -197,6 +200,7 @@ class OrderModule extends _BaseModule.BaseModule {
197
200
  code: 'SALES_GUARD_REJECTED'
198
201
  });
199
202
  }
203
+ (0, _productVoucherGroups.validateProductVoucherGroups)(order, this.getDiscountList(), undefined, true);
200
204
  return result;
201
205
  };
202
206
  const pending = this.cartCommandQueue.then(run, run);
@@ -601,6 +605,26 @@ class OrderModule extends _BaseModule.BaseModule {
601
605
  hooks: this.rulesHooksOverride || this.createDefaultRulesHooks()
602
606
  });
603
607
  this.store.rules = rules;
608
+ rules.setDiscountScope((product, discount) => (0, _productVoucherGroups.allowsProductVoucher)(this.store.tempOrder, product, discount));
609
+ }
610
+ setProductVoucherGroups(groups) {
611
+ const order = this.ensureTempOrder();
612
+ const ids = new Set();
613
+ const keys = new Set();
614
+ const known = new Map(this.getDiscountList().map(discount => [String(discount.id), discount]));
615
+ for (const group of groups) {
616
+ if (!group.key || keys.has(group.key)) throw (0, _productVoucherGroups.voucherGroupError)();
617
+ keys.add(group.key);
618
+ for (const id of group.discountIds) {
619
+ const discount = known.get(String(id));
620
+ if (!discount || (discount.tag || discount.type) !== 'good_pass' || ids.has(String(id))) throw (0, _productVoucherGroups.voucherGroupError)();
621
+ ids.add(String(id));
622
+ }
623
+ }
624
+ this.ensureExtend(order).productVoucherGroups = (0, _lodashEs.cloneDeep)(groups);
625
+ }
626
+ validateProductVoucherSelection() {
627
+ (0, _productVoucherGroups.validateProductVoucherGroups)(this.ensureTempOrder(), this.getDiscountList(), undefined, true);
604
628
  }
605
629
  createDefaultRulesHooks() {
606
630
  return (0, _utils.createDefaultOrderRulesHooks)(() => this.store.tempOrder?.bookings || [], () => this.store.tempOrder?.holder_relations);
@@ -752,7 +776,7 @@ class OrderModule extends _BaseModule.BaseModule {
752
776
  apply: false
753
777
  });
754
778
  }
755
- async scanCode(code, customerId) {
779
+ async scanCode(code, customerId, options) {
756
780
  const beforeCommit = this.createCartIntentCommitGuard('scan_promotion');
757
781
  if (this.isActiveVirtualSettlementOrder(this.store.tempOrder)) {
758
782
  return {
@@ -764,16 +788,22 @@ class OrderModule extends _BaseModule.BaseModule {
764
788
  unavailableReason: _types2.UnavailableReason.ProductNotApplicable
765
789
  };
766
790
  }
791
+ const scanOrderKey = this.getDiscountConfigOrderIdentityKey(this.store.tempOrder);
767
792
  const resultDiscountWithReason = (await this.store.discount?.batchSearch(code, {
768
793
  customerId
769
794
  })) || {
770
795
  discountList: [],
771
796
  unavailableReasonKey: null
772
797
  };
798
+ if (scanOrderKey !== this.getDiscountConfigOrderIdentityKey(this.store.tempOrder)) {
799
+ throw Object.assign(new Error('The scan session ended.'), {
800
+ code: 'SALES_GUARD_REJECTED'
801
+ });
802
+ }
773
803
  const {
774
- discountList: resultDiscountList,
775
804
  unavailableReasonKey
776
805
  } = resultDiscountWithReason;
806
+ const resultDiscountList = resultDiscountWithReason.discountList.filter(discount => !options?.productVouchersOnly || (discount.tag || discount.type) === 'good_pass');
777
807
  const rulesModule = this.store.rules;
778
808
  if (!rulesModule) {
779
809
  return {
@@ -800,7 +830,7 @@ class OrderModule extends _BaseModule.BaseModule {
800
830
  isScan: true
801
831
  }));
802
832
  const currentSelected = this.getDiscountList().filter(n => n.isSelected);
803
- if (currentSelected.length && currentSelected.some(n => withScanList.some(m => m.id === n.id))) {
833
+ if (currentSelected.length && (options?.productVouchersOnly ? withScanList.every(scanned => currentSelected.some(selected => selected.id === scanned.id)) : currentSelected.some(n => withScanList.some(m => m.id === n.id)))) {
804
834
  beforeCommit();
805
835
  await this.syncScannedDiscountsToOriginalDiscountList(this.getDiscountList());
806
836
  return {
@@ -2307,8 +2337,12 @@ class OrderModule extends _BaseModule.BaseModule {
2307
2337
  ...existed,
2308
2338
  ...(origin !== undefined ? {
2309
2339
  origin
2340
+ } : {}),
2341
+ ...(rawProduct._productVoucherGroupKey ? {
2342
+ productVoucherGroupKey: rawProduct._productVoucherGroupKey
2310
2343
  } : {})
2311
2344
  };
2345
+ delete normalizedProduct._productVoucherGroupKey;
2312
2346
  delete normalizedProduct._origin;
2313
2347
  delete normalizedProduct.unique_identification_number;
2314
2348
  }
@@ -3602,6 +3636,7 @@ class OrderModule extends _BaseModule.BaseModule {
3602
3636
  const isIncomingCustomItem = productToAdd.metadata?.origin?.isCustomItem === true || productToAdd._origin?.isCustomItem === true;
3603
3637
  const shouldForceNewLine = disableMerge || !!booking || hasIncomingGoodPass || isIncomingPersistedEntitlementPass || hasIncomingBookingUid || hasIncomingHolder || isIncomingCustomItem || isWeighingProduct;
3604
3638
  const matchedIndex = shouldForceNewLine ? -1 : tempOrder.products.findIndex(item => {
3639
+ if ((0, _productVoucherGroups.productVoucherGroupKey)(tempOrder, item) !== productToAdd._productVoucherGroupKey) return false;
3605
3640
  if (this.hasGoodPassDiscount(item)) return false;
3606
3641
  if ((0, _holderRelations.getProductHolderRelations)(tempOrder, item).length > 0 || (0, _utils.hasAssignedHolderId)(item.metadata?.holder_id)) return false;
3607
3642
  const isRuntimeEntitlementPass = this.isRuntimeEntitlementAppend(item, tempOrder);
@@ -3791,6 +3826,13 @@ class OrderModule extends _BaseModule.BaseModule {
3791
3826
  const mutation = this.beginCartMutation();
3792
3827
  let tempOrder = mutation.draft;
3793
3828
  const beforeCommit = this.createCartIntentCommitGuard('add_product', mutation.current);
3829
+ if (options?.productVoucherGroupKey) {
3830
+ if (!tempOrder._extend?.productVoucherGroups?.some(group => group.key === options.productVoucherGroupKey)) throw (0, _productVoucherGroups.voucherGroupError)();
3831
+ product = {
3832
+ ...product,
3833
+ _productVoucherGroupKey: options.productVoucherGroupKey
3834
+ };
3835
+ }
3794
3836
  const salesNoteDrafts = (options?.salesNotes || []).map(draft => ({
3795
3837
  note_type: String(draft.note_type || '').trim(),
3796
3838
  text: {
@@ -4734,6 +4776,7 @@ class OrderModule extends _BaseModule.BaseModule {
4734
4776
  }
4735
4777
  async runSubmitTempOrder(params) {
4736
4778
  const tempOrder = this.ensureTempOrder();
4779
+ (0, _productVoucherGroups.validateProductVoucherGroups)(tempOrder, this.getDiscountList(), undefined, true);
4737
4780
  this.prepareVirtualSettlementTempOrder(tempOrder);
4738
4781
  const isOnlineStorePlatform = this.isOnlineStorePlatform();
4739
4782
  const shouldConfirmPendingVoucherPayments = params?.confirmPendingVoucherPayments !== false;
@@ -456,6 +456,8 @@ export type AddProductBookingInput = Record<string, any>;
456
456
  * 避免步进器修改数量改变该组展示位置。
457
457
  */
458
458
  export interface AddProductToOrderOptions {
459
+ /** Associates a selection with an existing runtime voucher group. */
460
+ productVoucherGroupKey?: string;
459
461
  insertAfterProductUid?: string;
460
462
  /** 跳过同商品/规格匹配,始终新增购物车行。 */
461
463
  disableMerge?: boolean;
@@ -241,7 +241,7 @@ function consolidateDiscountFreeProductLines(tempOrder) {
241
241
  bundle_selling_price: String(bundle?.bundle_selling_price ?? '')
242
242
  }))
243
243
  });
244
- const mergeKey = [discountSplitOriginUid, product.product_id, product.product_variant_id ?? 0, fingerprint, priceSignature].join('#');
244
+ const mergeKey = [discountSplitOriginUid, productUid ? tempOrder._extend?.productsByUid?.[productUid]?.productVoucherGroupKey ?? '' : '', product.product_id, product.product_variant_id ?? 0, fingerprint, priceSignature].join('#');
245
245
  const matchedIndex = productIndexByKey.get(mergeKey);
246
246
 
247
247
  // 当前拆分来源 + 商品结构组合第一次出现时,保留该行作为回并目标。
@@ -0,0 +1,15 @@
1
+ import type { Discount } from '../../Discount/types';
2
+ import type { OrderTempOrder } from '../types';
3
+ /** Runtime-only allocation constraints. Never copied into order/product metadata. */
4
+ export interface ProductVoucherGroup {
5
+ key: string;
6
+ discountIds: Array<number | string>;
7
+ }
8
+ export declare function productVoucherGroupKey(order: OrderTempOrder, product: any): string | undefined;
9
+ export declare function isUsableProductVoucher(discount: Discount): boolean;
10
+ export declare function voucherGroupError(): Error & {
11
+ code: string;
12
+ };
13
+ export declare function allowsProductVoucher(order: OrderTempOrder | null | undefined, product: any, discount: Discount): boolean;
14
+ /** Reductions stay possible when a voucher expires; checkout always requires full coverage. */
15
+ export declare function validateProductVoucherGroups(order: OrderTempOrder, discounts: Discount[], previous?: OrderTempOrder, requireCoverage?: boolean): void;
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.allowsProductVoucher = allowsProductVoucher;
7
+ exports.isUsableProductVoucher = isUsableProductVoucher;
8
+ exports.productVoucherGroupKey = productVoucherGroupKey;
9
+ exports.validateProductVoucherGroups = validateProductVoucherGroups;
10
+ exports.voucherGroupError = voucherGroupError;
11
+ var _availability = require("../../Discount/availability");
12
+ var _dayjs = _interopRequireDefault(require("dayjs"));
13
+ var _utils = require("../../../solution/ShopDiscount/utils");
14
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
15
+ /** Runtime-only allocation constraints. Never copied into order/product metadata. */
16
+
17
+ function productVoucherGroupKey(order, product) {
18
+ const uid = product?.metadata?.unique_identification_number ?? product?.unique_identification_number;
19
+ return order._extend?.productsByUid?.[String(uid)]?.productVoucherGroupKey;
20
+ }
21
+ function isUsableProductVoucher(discount) {
22
+ return (discount.tag || discount.type) === 'good_pass' && !discount.isEditMode && !discount.isDisabled && !(0, _availability.getDiscountUsageBlockReason)(discount) && !(0, _utils.getDiscountTimeUnavailableReason)(discount, (0, _dayjs.default)().format('YYYY-MM-DD HH:mm:ss'));
23
+ }
24
+ function voucherGroupError() {
25
+ return Object.assign(new Error('The selected items no longer match the available vouchers. Please adjust your selection.'), {
26
+ code: 'PRODUCT_VOUCHER_GROUP_INVALID'
27
+ });
28
+ }
29
+ function allowsProductVoucher(order, product, discount) {
30
+ const groups = order?._extend?.productVoucherGroups;
31
+ if (!order || !groups?.length) return true;
32
+ const key = productVoucherGroupKey(order, product);
33
+ if (!key) return !groups.some(group => group.discountIds.some(id => String(id) === String(discount.id)));
34
+ return (discount.tag || discount.type) === 'good_pass' && Boolean(groups.find(group => group.key === key)?.discountIds.some(id => String(id) === String(discount.id)));
35
+ }
36
+
37
+ /** Reductions stay possible when a voucher expires; checkout always requires full coverage. */
38
+ function validateProductVoucherGroups(order, discounts, previous, requireCoverage = false) {
39
+ const groups = order._extend?.productVoucherGroups;
40
+ if (!groups?.length) return;
41
+ const usedIds = new Set();
42
+ const byId = new Map(discounts.filter(isUsableProductVoucher).map(discount => [String(discount.id), discount]));
43
+ const quantity = item => Number(item.num ?? item.product_quantity ?? 0);
44
+ for (const group of groups) {
45
+ const items = order.products.filter(item => productVoucherGroupKey(order, item) === group.key);
46
+ const selected = items.reduce((sum, item) => sum + quantity(item), 0);
47
+ const before = previous?.products.filter(item => productVoucherGroupKey(previous, item) === group.key).reduce((sum, item) => sum + quantity(item), 0) ?? 0;
48
+ if (!requireCoverage && previous && selected <= before) continue;
49
+ const ids = new Set(group.discountIds.map(String).filter(id => byId.has(id)));
50
+ if (!Number.isInteger(selected) || selected < 0 || selected > ids.size) throw voucherGroupError();
51
+ if (!requireCoverage) continue;
52
+ for (const item of items) {
53
+ const applied = (item.discount_list || []).filter(entry => (entry.type || entry.tag) === 'good_pass');
54
+ // Rules splits a good_pass allocation into one physical line per instance.
55
+ const id = String(applied[0]?.discount?.resource_id ?? '');
56
+ const voucher = byId.get(id);
57
+ if (quantity(item) !== 1 || applied.length !== 1 || !ids.has(id) || !voucher?.isAvailable || voucher.config?.isAvailable === false || usedIds.has(id)) throw voucherGroupError();
58
+ usedIds.add(id);
59
+ }
60
+ }
61
+ if (order.products.some(item => !groups.some(group => group.key === productVoucherGroupKey(order, item)))) {
62
+ throw voucherGroupError();
63
+ }
64
+ }
@@ -49,5 +49,5 @@ export declare class Product extends BaseModule implements Module {
49
49
  getCategories(): ProductCategory[];
50
50
  setOtherParams(key: string, value: any): void;
51
51
  getOtherParams(): any;
52
- getProductType(): "duration" | "session" | "normal";
52
+ getProductType(): "normal" | "duration" | "session";
53
53
  }
@@ -9,6 +9,8 @@ export declare class RulesModule extends BaseModule implements Module, RulesModu
9
9
  private store;
10
10
  window: WindowPlugin;
11
11
  private hooks;
12
+ private discountScope?;
13
+ setDiscountScope(scope: (product: any, discount: Discount) => boolean): void;
12
14
  constructor(name?: string, version?: string);
13
15
  initialize(core: PisellCore, options?: ModuleOptions): Promise<void>;
14
16
  setRulesList(rulesList: Rules[]): Promise<void>;
@@ -20,6 +20,10 @@ class RulesModule extends _BaseModule.BaseModule {
20
20
  store;
21
21
  window; // WindowPlugin 实例
22
22
  hooks;
23
+ discountScope;
24
+ setDiscountScope(scope) {
25
+ this.discountScope = scope;
26
+ }
23
27
  constructor(name, version) {
24
28
  super(name, version);
25
29
  this.hooks = {};
@@ -722,6 +726,7 @@ class RulesModule extends _BaseModule.BaseModule {
722
726
 
723
727
  // 辅助函数:检查商品是否对某个折扣卡可用
724
728
  const checkItemApplicableForDiscount = (flatItem, discount) => {
729
+ if (this.discountScope && !this.discountScope(flatItem.originProduct, discount)) return false;
725
730
  if (discount.config?.isAvailable === false) return false;
726
731
  let product;
727
732
  if (flatItem.type === 'main') {
@@ -1093,6 +1098,7 @@ class RulesModule extends _BaseModule.BaseModule {
1093
1098
 
1094
1099
  // 找到适用于此商品的所有优惠券,仅考虑isSelected不为false的优惠券
1095
1100
  const applicableDiscounts = sortedDiscountList.filter(discount => {
1101
+ if (this.discountScope && !this.discountScope(originProduct, discount)) return false;
1096
1102
  if (this.isItemRewardProductDiscount(product)) return false;
1097
1103
  // 🔥 检查策略可用性(针对全额抵扣卡和折扣卡)
1098
1104
  const discountType = discount.tag || discount.type;
@@ -338,7 +338,9 @@ export declare class BaseSalesImpl extends BaseModule implements Module {
338
338
  discountList: Discount[];
339
339
  originalDiscountList?: Discount[];
340
340
  }): Promise<void>;
341
- scanPromotionCode(code: string, customerId?: number): Promise<BaseSalesScanCodeResult>;
341
+ scanPromotionCode(code: string, customerId?: number, options?: {
342
+ productVouchersOnly?: boolean;
343
+ }): Promise<BaseSalesScanCodeResult>;
342
344
  setDiscountSelected(params: {
343
345
  discountId: number;
344
346
  isSelected: boolean;
@@ -519,6 +521,11 @@ export declare class BaseSalesImpl extends BaseModule implements Module {
519
521
  private hasSmartPricingStrategies;
520
522
  shouldRecalculateProductBookingPricesForContextChange(params: BaseSalesPriceRecalculationContextChange): boolean;
521
523
  calculateProductBookingPrices(paramsList: BaseSalesCalculateProductBookingPriceParams[]): Promise<BaseSalesProductBookingPriceResult[]>;
524
+ setProductVoucherGroups(groups: Array<{
525
+ key: string;
526
+ discountIds: Array<number | string>;
527
+ }>): void;
528
+ validateProductVoucherSelection(): void;
522
529
  addProductToOrder(product: Partial<BaseSalesOrderProduct> & BaseSalesOrderProductIdentity, booking?: AddProductBookingInput, options?: AddProductToOrderOptions): Promise<import("../../modules/Order/types").OrderProduct[]>;
523
530
  updateOrderProduct(params: UpdateOrderProductParams): Promise<import("../../modules/Order/types").OrderProduct[]>;
524
531
  updateOrderProducts(paramsList: UpdateOrderProductParams[]): Promise<import("../../modules/Order/types").OrderProduct[]>;
@@ -2412,10 +2412,10 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
2412
2412
  }
2413
2413
  await discountModule.setDiscountList(params.discountList);
2414
2414
  }
2415
- async scanPromotionCode(code, customerId) {
2415
+ async scanPromotionCode(code, customerId, options) {
2416
2416
  try {
2417
2417
  if (!this.store.order) throw new Error('order 模块未初始化');
2418
- const raw = await this.store.order.scanCode(code, customerId);
2418
+ const raw = await this.store.order.scanCode(code, customerId, options);
2419
2419
  if (raw.isAvailable) {
2420
2420
  await this.store.order.recalculateSummary({
2421
2421
  createIfMissing: true
@@ -4112,6 +4112,13 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
4112
4112
  });
4113
4113
  });
4114
4114
  }
4115
+ setProductVoucherGroups(groups) {
4116
+ if (!this.store.order) throw new Error('order 模块未初始化');
4117
+ this.store.order.setProductVoucherGroups(groups);
4118
+ }
4119
+ validateProductVoucherSelection() {
4120
+ this.store.order?.validateProductVoucherSelection();
4121
+ }
4115
4122
  async addProductToOrder(product, booking, options) {
4116
4123
  try {
4117
4124
  if (!this.store.order) throw new Error('order 模块未初始化');
@@ -373,7 +373,7 @@ export declare class BookingByStepImpl extends BaseModule implements Module {
373
373
  };
374
374
  setOtherData(key: string, value: any): void;
375
375
  getOtherData(key: string): any;
376
- getProductTypeById(id: number): Promise<"duration" | "session" | "normal">;
376
+ getProductTypeById(id: number): Promise<"normal" | "duration" | "session">;
377
377
  /**
378
378
  * 提供给 UI 的方法,减轻 UI 层的计算压力,UI 层只需要传递 cartItemId 和 resourceCode 即返回对应的 renderList
379
379
  *
@@ -465,7 +465,19 @@ export declare class BookingTicketImpl extends BaseSalesImpl implements Module {
465
465
  * 加车两阶段主决策(规格弹窗 / 资源编辑 / 直接加车)。
466
466
  * 与 ticketBooking `handleSelectProduct` + `handleBooking4Service` 等价。
467
467
  */
468
- decideAddProduct(item: any, options?: Partial<AddProductDecideContext>): import("./utils/addProductDecision").AddProductDecision;
468
+ decideAddProduct(item: any, options?: Partial<AddProductDecideContext>): {
469
+ action: "reloadCatalog";
470
+ } | {
471
+ action: "add";
472
+ cacheItem: any;
473
+ } | {
474
+ action: "requiresDetail";
475
+ payload: AddProductRequiresDetailPayload;
476
+ } | {
477
+ action: "requiresBookingEdit";
478
+ cacheItem: any;
479
+ payload: import("./utils/addProductDecision").AddProductRequiresBookingEditPayload;
480
+ };
469
481
  /** 规格弹窗 callback 后的第二段决策。 */
470
482
  decideAfterDetail(cacheItem: any, options?: Partial<AddProductDecideContext>): import("./utils/addProductDecision").AddProductDecision;
471
483
  /**
@@ -1699,6 +1699,20 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
1699
1699
  return bridge.openBookingEdit(payload);
1700
1700
  } : undefined
1701
1701
  } : undefined;
1702
+ if (guardedBridge?.scanScope === 'product-voucher') {
1703
+ if (!guardedBridge.applyPromotionCode) return {
1704
+ status: 'failed',
1705
+ reason: 'no_bridge'
1706
+ };
1707
+ const promotion = await guardedBridge.applyPromotionCode(trimmed);
1708
+ return promotion.isAccepted ?? promotion.isAvailable ? {
1709
+ status: 'success',
1710
+ kind: 'promotion'
1711
+ } : {
1712
+ status: 'failed',
1713
+ reason: 'not_found'
1714
+ };
1715
+ }
1702
1716
  const localSearch = v => this.store.products.findProductsByCodeOrBarcode(v);
1703
1717
  const scanCallback = (0, _handleScan.handleGlobalScan)(this.request, localSearch, this.getSubmitOrderSalesChannel());
1704
1718
  const raw = await scanCallback({
@@ -208,6 +208,8 @@ export interface AddAddTimeProductsToBookingInput {
208
208
  }
209
209
  /** 扫码宿主桥接:SalesSdk 在 pubsub 订阅回调里注入,OS 需要 UI 时直接调用 */
210
210
  export interface GlobalScanHostBridge {
211
+ /** Restrict redemption scans before customer/product side effects. */
212
+ scanScope?: 'all' | 'product-voucher';
211
213
  openProductDetail?: (payload: Record<string, any>) => Promise<any | null>;
212
214
  openBookingEdit?: (payload: Record<string, any>) => Promise<any | null>;
213
215
  confirmDetail?: (input: {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "private": false,
3
3
  "name": "@pisell/pisellos",
4
- "version": "0.10.64",
4
+ "version": "0.10.65",
5
5
  "description": "一个可扩展的前端模块化SDK框架,支持插件系统",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",