@pisell/pisellos 2.3.36 → 2.3.38

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 (90) hide show
  1. package/dist/core/index.d.ts +2 -0
  2. package/dist/core/index.js +7 -0
  3. package/dist/model/strategy/adapter/promotion/adapter.d.ts +4 -0
  4. package/dist/model/strategy/adapter/promotion/adapter.js +23 -0
  5. package/dist/model/strategy/adapter/promotion/evaluator.d.ts +17 -0
  6. package/dist/model/strategy/adapter/promotion/evaluator.js +279 -6
  7. package/dist/model/strategy/adapter/promotion/examples.d.ts +86 -0
  8. package/dist/model/strategy/adapter/promotion/examples.js +99 -0
  9. package/dist/model/strategy/adapter/promotion/index.d.ts +1 -1
  10. package/dist/model/strategy/adapter/promotion/type.d.ts +90 -2
  11. package/dist/model/strategy/adapter/promotion/type.js +45 -0
  12. package/dist/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +5 -3
  13. package/dist/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +6 -3
  14. package/dist/modules/OpenData/index.d.ts +2 -0
  15. package/dist/modules/OpenData/index.js +8 -0
  16. package/dist/modules/Order/index.js +21 -14
  17. package/dist/modules/Order/utils.js +2 -1
  18. package/dist/modules/Payment/index.js +1 -1
  19. package/dist/modules/Payment/types.d.ts +73 -4
  20. package/dist/modules/Payment/types.js +18 -3
  21. package/dist/modules/Payment/walletpass.d.ts +25 -1
  22. package/dist/modules/Payment/walletpass.js +707 -157
  23. package/dist/modules/Rules/index.d.ts +1 -0
  24. package/dist/modules/Rules/index.js +75 -49
  25. package/dist/server/index.js +28 -24
  26. package/dist/server/modules/order/index.d.ts +5 -0
  27. package/dist/server/modules/order/index.js +52 -31
  28. package/dist/solution/BaseSales/index.d.ts +77 -11
  29. package/dist/solution/BaseSales/index.js +1476 -434
  30. package/dist/solution/BaseSales/types.d.ts +52 -0
  31. package/dist/solution/BaseSales/types.js +2 -1
  32. package/dist/solution/BaseSales/utils/cartPromotion.d.ts +3 -1
  33. package/dist/solution/BaseSales/utils/cartPromotion.js +90 -31
  34. package/dist/solution/BookingByStep/index.d.ts +1 -1
  35. package/dist/solution/BookingTicket/index.d.ts +7 -3
  36. package/dist/solution/BookingTicket/index.js +130 -48
  37. package/dist/solution/BookingTicket/utils/addProductDecision.js +6 -0
  38. package/dist/solution/BookingTicket/utils/scan/applyGlobalScan.d.ts +8 -0
  39. package/dist/solution/BookingTicket/utils/scan/applyGlobalScan.js +172 -82
  40. package/dist/solution/BookingTicket/utils/weighingProductSku.d.ts +5 -0
  41. package/dist/solution/BookingTicket/utils/weighingProductSku.js +45 -0
  42. package/dist/solution/ScanOrder/index.js +31 -20
  43. package/dist/solution/VenueBooking/index.js +30 -19
  44. package/dist/types/index.d.ts +1 -0
  45. package/lib/core/index.d.ts +2 -0
  46. package/lib/core/index.js +4 -0
  47. package/lib/model/strategy/adapter/promotion/adapter.d.ts +4 -0
  48. package/lib/model/strategy/adapter/promotion/adapter.js +20 -0
  49. package/lib/model/strategy/adapter/promotion/evaluator.d.ts +17 -0
  50. package/lib/model/strategy/adapter/promotion/evaluator.js +235 -0
  51. package/lib/model/strategy/adapter/promotion/examples.d.ts +86 -0
  52. package/lib/model/strategy/adapter/promotion/examples.js +91 -0
  53. package/lib/model/strategy/adapter/promotion/index.d.ts +1 -1
  54. package/lib/model/strategy/adapter/promotion/index.js +2 -0
  55. package/lib/model/strategy/adapter/promotion/type.d.ts +90 -2
  56. package/lib/model/strategy/adapter/promotion/type.js +2 -0
  57. package/lib/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +2 -1
  58. package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +2 -1
  59. package/lib/modules/OpenData/index.d.ts +2 -0
  60. package/lib/modules/OpenData/index.js +5 -0
  61. package/lib/modules/Order/index.js +10 -4
  62. package/lib/modules/Order/utils.js +6 -1
  63. package/lib/modules/Payment/index.js +1 -1
  64. package/lib/modules/Payment/types.d.ts +73 -4
  65. package/lib/modules/Payment/types.js +4 -3
  66. package/lib/modules/Payment/walletpass.d.ts +25 -1
  67. package/lib/modules/Payment/walletpass.js +470 -21
  68. package/lib/modules/Rules/index.d.ts +1 -0
  69. package/lib/modules/Rules/index.js +30 -7
  70. package/lib/server/index.js +11 -8
  71. package/lib/server/modules/order/index.d.ts +5 -0
  72. package/lib/server/modules/order/index.js +24 -2
  73. package/lib/solution/BaseSales/index.d.ts +77 -11
  74. package/lib/solution/BaseSales/index.js +699 -41
  75. package/lib/solution/BaseSales/types.d.ts +52 -0
  76. package/lib/solution/BaseSales/types.js +2 -1
  77. package/lib/solution/BaseSales/utils/cartPromotion.d.ts +3 -1
  78. package/lib/solution/BaseSales/utils/cartPromotion.js +63 -12
  79. package/lib/solution/BookingByStep/index.d.ts +1 -1
  80. package/lib/solution/BookingTicket/index.d.ts +7 -3
  81. package/lib/solution/BookingTicket/index.js +60 -8
  82. package/lib/solution/BookingTicket/utils/addProductDecision.js +4 -0
  83. package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.d.ts +8 -0
  84. package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.js +36 -4
  85. package/lib/solution/BookingTicket/utils/weighingProductSku.d.ts +5 -0
  86. package/lib/solution/BookingTicket/utils/weighingProductSku.js +63 -0
  87. package/lib/solution/ScanOrder/index.js +8 -0
  88. package/lib/solution/VenueBooking/index.js +8 -0
  89. package/lib/types/index.d.ts +1 -0
  90. package/package.json +2 -2
@@ -3,6 +3,7 @@ import { OrderModule, ProductList, QuotationModule, SalesSummaryModule, ScanOrde
3
3
  import type { ScanOrderLogInput as BaseSalesLogInput, ScanOrderLoggerProviderConfig as BaseSalesLoggerProviderConfig, ScanOrderLoggerProviderType as BaseSalesLoggerProviderType } from '../../modules/ScanOrderLogger/types';
4
4
  import type { QuantityCheckResult, QuantityLimitResult } from '../../model/strategy/adapter/itemRule';
5
5
  import type { QuotationCustomerScopeInfo } from '../../modules/Quotation/types';
6
+ import type { PaymentMethod } from '../../modules/Payment/types';
6
7
  /**
7
8
  * BaseSales 流程 hook 后缀。
8
9
  * 完整事件名应由当前模块名动态拼接,避免多个实例共享固定事件 key。
@@ -16,11 +17,62 @@ export declare const BaseSalesHookNames: {
16
17
  readonly onPaymentSyncStart: "onPaymentSyncStart";
17
18
  readonly onPaymentSyncEnd: "onPaymentSyncEnd";
18
19
  readonly onDepositPaymentSyncEnd: "onDepositPaymentSyncEnd";
20
+ readonly onWalletAssetsStateChanged: "onWalletAssetsStateChanged";
19
21
  };
20
22
  export type BaseSalesHookName = typeof BaseSalesHookNames[keyof typeof BaseSalesHookNames];
21
23
  export declare function createBaseSalesHook(moduleName: string, hookName: BaseSalesHookName): string;
22
24
  export type BaseSalesStatus = 'idle' | 'initializing' | 'ready' | 'error';
23
25
  export type BaseSalesPaymentStatus = 'paid' | 'partially_paid' | 'unpaid' | 'payment_processing' | string;
26
+ export interface BaseSalesCashPaymentConfig {
27
+ available: boolean;
28
+ paymentMethod?: PaymentMethod;
29
+ amountDue: number;
30
+ recommendedAmounts: number[];
31
+ }
32
+ export interface BaseSalesPayCashParams {
33
+ /** 实际计入订单的现金支付金额(部分支付时小于待付金额)。 */
34
+ amount: number;
35
+ /** 顾客实际交付现金;可大于 amount。 */
36
+ actualPaidAmount?: number;
37
+ changeAmount?: number;
38
+ roundingAmount?: number;
39
+ }
40
+ export interface BaseSalesPayCashResult {
41
+ payment: Record<string, any>;
42
+ payments: Record<string, any>[];
43
+ syncResult: Record<string, any>;
44
+ isOrderFullyPaid: boolean;
45
+ }
46
+ export interface BaseSalesCommitPaymentMethodParams {
47
+ /** 支付方式 id;与 paymentMethodCode 至少传一个。 */
48
+ paymentMethodId?: number | string;
49
+ /** 支付方式 code,大小写不敏感。 */
50
+ paymentMethodCode?: string;
51
+ /** 本次计入订单的支付金额。 */
52
+ amount: number;
53
+ /** 支付设备返回的原始金额;缺省时与 amount 相同。 */
54
+ originAmount?: number;
55
+ /** 支付设备返回的实际手续费配置。 */
56
+ serviceCharge?: {
57
+ amount?: string | number;
58
+ percentage?: string | number;
59
+ } | null;
60
+ /** 支付设备返回的唯一流水号等扩展信息。 */
61
+ metadata?: Record<string, any>;
62
+ /** 少数支付方式需要透传的额外 payment 字段。 */
63
+ paymentData?: Record<string, any>;
64
+ }
65
+ export interface BaseSalesCommitPaymentMethodResult {
66
+ payment: Record<string, any>;
67
+ payments: Record<string, any>[];
68
+ syncResult: Record<string, any>;
69
+ isOrderFullyPaid: boolean;
70
+ }
71
+ export interface BaseSalesConfirmWalletPaymentResult<T = Record<string, any>> {
72
+ submitResult: T;
73
+ payments: Record<string, any>[];
74
+ isOrderFullyPaid: boolean;
75
+ }
24
76
  export interface BaseSalesEntryContext {
25
77
  biz: string;
26
78
  mode: 'scan' | 'append';
@@ -31,7 +31,8 @@ var BaseSalesHookNames = {
31
31
  onCartValidationChanged: "onCartValidationChanged",
32
32
  onPaymentSyncStart: "onPaymentSyncStart",
33
33
  onPaymentSyncEnd: "onPaymentSyncEnd",
34
- onDepositPaymentSyncEnd: "onDepositPaymentSyncEnd"
34
+ onDepositPaymentSyncEnd: "onDepositPaymentSyncEnd",
35
+ onWalletAssetsStateChanged: "onWalletAssetsStateChanged"
35
36
  };
36
37
  function createBaseSalesHook(moduleName, hookName) {
37
38
  return `${moduleName}:${hookName}`;
@@ -78,6 +78,7 @@ export interface CartPromotionEvaluator {
78
78
  }>;
79
79
  hasApplicableStrategy: boolean;
80
80
  }>;
81
+ getStrategyConfigs?: () => unknown[];
81
82
  }
82
83
  /** 单个赠品减量项 */
83
84
  export interface GiftReduceItem {
@@ -267,7 +268,8 @@ export declare function appendPromotionTags<T extends Record<string, any>>(produ
267
268
  * 处理购物车的促销计算与赠品差异化。
268
269
  *
269
270
  * 流程:
270
- * 1. 分离 main / gift / edit-product 三类(带 `booking_id` 的编辑态商品不参与促销)
271
+ * 1. 分离 main / gift / edit-product 三类(普通促销下带 `booking_id` 的编辑态商品不参与;
272
+ * 配置 ITEM_REWARD 时,编辑态商品也参与商品奖励重算)
271
273
  * 2. 转 PromotionProduct 喂评估器 → 拿到 `evaluateCartWithPricing` 与 `getProductsApplicableStrategies`
272
274
  * 3. 把 PricedProduct 映射回 OrderProduct 形态:
273
275
  * - 参与促销的,写 `metadata._promotion` + 调整 `selling_price` / `main_product_selling_price`
@@ -77,19 +77,43 @@ function getOrderProductOriginalPriceForPromotion(product) {
77
77
  }
78
78
  return getOrderProductBasePrice(product);
79
79
  }
80
- function updatePromotionDiscountList(item, promotionDiscount) {
80
+ var X_ITEMS_FOR_Y_PRICE = "X_ITEMS_FOR_Y_PRICE";
81
+ var ITEM_REWARD = "ITEM_REWARD";
82
+ function isItemRewardPromotionDiscountItem(item) {
83
+ var _a, _b;
84
+ return (item == null ? void 0 : item.type) === "product" && ((_a = item == null ? void 0 : item.metadata) == null ? void 0 : _a.source) === "promotion" && ((_b = item == null ? void 0 : item.metadata) == null ? void 0 : _b.actionType) === ITEM_REWARD;
85
+ }
86
+ function isManagedPromotionDiscountItem(item) {
87
+ if ((item == null ? void 0 : item.type) === "promotion")
88
+ return true;
89
+ return isItemRewardPromotionDiscountItem(item);
90
+ }
91
+ function wasInPromotionBeforeEvaluation(item) {
92
+ var _a, _b;
93
+ if (((_b = (_a = item == null ? void 0 : item.metadata) == null ? void 0 : _a._promotion) == null ? void 0 : _b.inPromotion) === true)
94
+ return true;
95
+ return Array.isArray(item == null ? void 0 : item.discount_list) && item.discount_list.some(isItemRewardPromotionDiscountItem);
96
+ }
97
+ function updatePromotionDiscountList(item, promotionDiscount, options) {
81
98
  let discountList = Array.isArray(item.discount_list) ? [...item.discount_list] : [];
82
- discountList = discountList.filter((d) => (d == null ? void 0 : d.type) !== "promotion");
99
+ discountList = discountList.filter((d) => !isManagedPromotionDiscountItem(d));
83
100
  if (promotionDiscount) {
101
+ const discountType = (options == null ? void 0 : options.discountType) || "promotion";
84
102
  discountList.push({
85
- type: "promotion",
103
+ type: discountType,
86
104
  amount: promotionDiscount.amount,
87
105
  discount: {
88
106
  original_amount: promotionDiscount.original_amount,
89
107
  fixed_amount: promotionDiscount.fixed_amount,
90
108
  title: promotionDiscount.title,
91
109
  resource_id: ""
92
- }
110
+ },
111
+ ...discountType === "product" ? {
112
+ metadata: {
113
+ source: "promotion",
114
+ actionType: (options == null ? void 0 : options.actionType) || ITEM_REWARD
115
+ }
116
+ } : {}
93
117
  });
94
118
  }
95
119
  if (discountList.length > 0) {
@@ -98,7 +122,15 @@ function updatePromotionDiscountList(item, promotionDiscount) {
98
122
  delete item.discount_list;
99
123
  }
100
124
  }
101
- var X_ITEMS_FOR_Y_PRICE = "X_ITEMS_FOR_Y_PRICE";
125
+ function evaluatorHasActionType(evaluator, actionType) {
126
+ var _a;
127
+ const configs = (_a = evaluator == null ? void 0 : evaluator.getStrategyConfigs) == null ? void 0 : _a.call(evaluator);
128
+ if (!Array.isArray(configs))
129
+ return false;
130
+ return configs.some(
131
+ (config) => ((config == null ? void 0 : config.actions) || []).some((action) => (action == null ? void 0 : action.type) === actionType)
132
+ );
133
+ }
102
134
  function applyPromoUnitPriceToLine(item, input) {
103
135
  const metadata = item.metadata || (item.metadata = {});
104
136
  const optionSum = (0, import_utils.sumOptionUnitPrice)((0, import_utils.getProductSkuOptions)(item)).toNumber();
@@ -125,6 +157,9 @@ function applyPromoUnitPriceToLine(item, input) {
125
157
  amount: discountAmount,
126
158
  original_amount: catalogSource,
127
159
  fixed_amount: discountAmount
160
+ }, {
161
+ actionType: input.actionType,
162
+ discountType: input.actionType === ITEM_REWARD ? "product" : "promotion"
128
163
  });
129
164
  } else {
130
165
  updatePromotionDiscountList(item, null);
@@ -396,14 +431,13 @@ function buildConsolidateKeyForPromotion(item) {
396
431
  ].join("#");
397
432
  }
398
433
  function consolidateMainProductsForPromotion(entries) {
399
- var _a, _b;
400
434
  if (!Array.isArray(entries) || entries.length <= 1) {
401
435
  return [...entries || []];
402
436
  }
403
437
  const groups = /* @__PURE__ */ new Map();
404
438
  for (const { item, originalListIndex } of entries) {
405
439
  const key = buildConsolidateKeyForPromotion(item);
406
- const wasInPromotion = ((_b = (_a = item == null ? void 0 : item.metadata) == null ? void 0 : _a._promotion) == null ? void 0 : _b.inPromotion) === true;
440
+ const wasInPromotion = wasInPromotionBeforeEvaluation(item);
407
441
  const existing = groups.get(key);
408
442
  if (!existing) {
409
443
  const mergedItem = (0, import_lodash_es.cloneDeep)(item);
@@ -469,6 +503,9 @@ function splitXItemsPromoLinesByGroup(lines, strategyActionMap) {
469
503
  function generateNumericId() {
470
504
  return Math.floor(Math.random() * 1e9) + Date.now();
471
505
  }
506
+ function isBookingEditProductLine(product) {
507
+ return (product == null ? void 0 : product.booking_id) !== void 0 && (product == null ? void 0 : product.booking_id) !== null && product.booking_id !== 0 && product.booking_id !== "0";
508
+ }
472
509
  function convertToPromotionProduct(product, index) {
473
510
  const metadata = (product == null ? void 0 : product.metadata) || {};
474
511
  const numericId = generateNumericId();
@@ -481,6 +518,7 @@ function convertToPromotionProduct(product, index) {
481
518
  price: getOrderProductOriginalPriceForPromotion(product),
482
519
  quantity: Number((product == null ? void 0 : product.num) ?? 1) || 1,
483
520
  bundle: getProductBundleForEvaluator(product),
521
+ _promotionOnlyForItemReward: isBookingEditProductLine(product),
484
522
  _originalItem: originalItem,
485
523
  _originalId: getOrderProductUid(product),
486
524
  _originalIndex: index
@@ -582,6 +620,9 @@ function resolveStrategyRequiredQuantity(matched) {
582
620
  if (matched.actionType === "BUY_X_GET_Y_FREE") {
583
621
  return Number(detail.buyQuantity) || 1;
584
622
  }
623
+ if (matched.actionType === "ITEM_REWARD") {
624
+ return Number(detail.triggerQuantity) || 1;
625
+ }
585
626
  return 1;
586
627
  }
587
628
  function resolveStrategyEligibleProducts(matched) {
@@ -692,6 +733,10 @@ function processCartPromotion(list, evaluator, options) {
692
733
  };
693
734
  if (!list || list.length === 0 || !evaluator)
694
735
  return empty;
736
+ const shouldIncludeEditProductsForItemReward = evaluatorHasActionType(
737
+ evaluator,
738
+ ITEM_REWARD
739
+ );
695
740
  const mainProductsWithIndex = [];
696
741
  const existingGiftsWithIndex = [];
697
742
  const editProductsWithIndex = [];
@@ -702,8 +747,12 @@ function processCartPromotion(list, evaluator, options) {
702
747
  editProductsWithIndex.push({ item, originalListIndex: i });
703
748
  } else if (giftInfo) {
704
749
  existingGiftsWithIndex.push({ item, originalListIndex: i });
705
- } else if ((item == null ? void 0 : item.booking_id) !== void 0 && (item == null ? void 0 : item.booking_id) !== null && item.booking_id !== 0 && item.booking_id !== "0") {
706
- editProductsWithIndex.push({ item, originalListIndex: i });
750
+ } else if (isBookingEditProductLine(item)) {
751
+ if (shouldIncludeEditProductsForItemReward) {
752
+ mainProductsWithIndex.push({ item, originalListIndex: i });
753
+ } else {
754
+ editProductsWithIndex.push({ item, originalListIndex: i });
755
+ }
707
756
  } else {
708
757
  mainProductsWithIndex.push({ item, originalListIndex: i });
709
758
  }
@@ -746,7 +795,8 @@ function processCartPromotion(list, evaluator, options) {
746
795
  name: ((_a2 = item == null ? void 0 : item.metadata) == null ? void 0 : _a2.product_name) || (item == null ? void 0 : item.title) || "",
747
796
  price: getOrderProductBasePrice(item),
748
797
  quantity: Number((item == null ? void 0 : item.num) ?? 1) || 1,
749
- bundle: getProductBundleForEvaluator(item)
798
+ bundle: getProductBundleForEvaluator(item),
799
+ _promotionOnlyForItemReward: isBookingEditProductLine(item)
750
800
  };
751
801
  });
752
802
  for (let i = 0; i < consolidatedMainProductsWithIndex.length; i++) {
@@ -815,7 +865,7 @@ function processCartPromotion(list, evaluator, options) {
815
865
  const metadata = newItem.metadata || {};
816
866
  const sourceCartNum = (0, import_utils2.getSafeProductNum)(originalItem.num);
817
867
  newItem._sourceCartNum = sourceCartNum;
818
- const wasInPromotion = ((_a = metadata._promotion) == null ? void 0 : _a.inPromotion) === true || originalItem._wasInPromotionBeforeConsolidate === true;
868
+ const wasInPromotion = wasInPromotionBeforeEvaluation(originalItem) || originalItem._wasInPromotionBeforeConsolidate === true;
819
869
  delete metadata._promotion;
820
870
  if (Array.isArray(newItem.product_bundle)) {
821
871
  for (const b of newItem.product_bundle) {
@@ -832,7 +882,8 @@ function processCartPromotion(list, evaluator, options) {
832
882
  applyPromoUnitPriceToLine(newItem, {
833
883
  promoUnitPrice,
834
884
  originalBasePrice,
835
- strategyTitle: (_b = priced.strategyMetadata) == null ? void 0 : _b.name
885
+ strategyTitle: (_a = priced.strategyMetadata) == null ? void 0 : _a.name,
886
+ actionType: priced.strategyId ? (_b = strategyActionMap.get(priced.strategyId)) == null ? void 0 : _b.actionType : void 0
836
887
  });
837
888
  } else {
838
889
  const optionSum = (0, import_utils.sumOptionUnitPrice)((0, import_utils.getProductSkuOptions)(newItem)).toNumber();
@@ -311,7 +311,7 @@ export declare class BookingByStepImpl extends BaseModule implements Module {
311
311
  date: string;
312
312
  status: string;
313
313
  week: string;
314
- weekNum: 0 | 2 | 1 | 6 | 4 | 5 | 3;
314
+ weekNum: 0 | 1 | 2 | 3 | 4 | 5 | 6;
315
315
  }[]>;
316
316
  submitTimeSlot(timeSlots: TimeSliceItem): void;
317
317
  private getScheduleDataByIds;
@@ -46,6 +46,8 @@ export declare class BookingTicketImpl extends BaseSalesImpl implements Module {
46
46
  private getIdGeneratorPlugin;
47
47
  private loadOpenDataConfig;
48
48
  getOpenData(): Promise<OpenDataConfig | null>;
49
+ /** 清除店铺级 OpenData;下一次 getOpenData 时会重新拉取。 */
50
+ clearOpenDataCache(): void;
49
51
  private getShortNumberOrDeviceId;
50
52
  private configureIdGeneratorFromOpenData;
51
53
  private normalizePositiveInteger;
@@ -189,10 +191,12 @@ export declare class BookingTicketImpl extends BaseSalesImpl implements Module {
189
191
  */
190
192
  getCustomer(): ICustomer | null;
191
193
  /**
192
- * 通过 ids 获取商品列表(不加载到模块中)
193
- * @returns 商品列表
194
+ * 通过 ids 获取商品列表;默认只读取内存,显式开启 loadMissing 时按当前订单上下文补查缺失商品。
195
+ * 补查直接写入 ProductList,不更新 BookingTicket.productCatalog,避免当前商品列表被查询子集覆盖。
194
196
  */
195
- getProductByIds(ids: number[]): Promise<ProductData[] | undefined>;
197
+ getProductByIds(ids: number[], options?: {
198
+ loadMissing?: boolean;
199
+ }): Promise<ProductData[]>;
196
200
  /**
197
201
  * 获取日程时间段点
198
202
  * @param params 参数
@@ -219,8 +219,14 @@ var BookingTicketImpl = class extends import_BaseSales.BaseSalesImpl {
219
219
  }
220
220
  }
221
221
  async getOpenData() {
222
+ return this.loadOpenDataConfig();
223
+ }
224
+ /** 清除店铺级 OpenData;下一次 getOpenData 时会重新拉取。 */
225
+ clearOpenDataCache() {
222
226
  var _a;
223
- return (_a = this.store.openData) == null ? void 0 : _a.getOpenData();
227
+ this.loadOpenDataConfigInFlight = null;
228
+ this.otherParams.openData = null;
229
+ (_a = this.store.openData) == null ? void 0 : _a.clearCache();
224
230
  }
225
231
  async getShortNumberOrDeviceId() {
226
232
  const getAsyncIotDeviceInfo = this.getAppData("async_iot_device_info");
@@ -233,6 +239,7 @@ var BookingTicketImpl = class extends import_BaseSales.BaseSalesImpl {
233
239
  return String(this.getAppData("device_id") || 0).slice(-2);
234
240
  }
235
241
  async configureIdGeneratorFromOpenData() {
242
+ this.setPaymentNumberDevicePrefix(null);
236
243
  let openDataConfig = null;
237
244
  try {
238
245
  openDataConfig = await this.loadOpenDataConfig();
@@ -264,6 +271,7 @@ var BookingTicketImpl = class extends import_BaseSales.BaseSalesImpl {
264
271
  const resetReceiptSequenceDaily = typeof (openDataConfig == null ? void 0 : openDataConfig["sale.short_number_daily_reset"]) === "boolean" ? openDataConfig == null ? void 0 : openDataConfig["sale.short_number_daily_reset"] : false;
265
272
  const operatingDayBoundary = this.getAppData("operating_day_boundary");
266
273
  const deviceId = await this.getShortNumberOrDeviceId();
274
+ this.setPaymentNumberDevicePrefix(deviceId);
267
275
  const businessCode = this.getBookingTicketBusinessCode();
268
276
  if (!businessCode) {
269
277
  console.warn("[BookingTicket] businessCode 缺失,跳过 idGenerator 配置");
@@ -714,11 +722,53 @@ var BookingTicketImpl = class extends import_BaseSales.BaseSalesImpl {
714
722
  return this.getOrderCustomerSnapshot();
715
723
  }
716
724
  /**
717
- * 通过 ids 获取商品列表(不加载到模块中)
718
- * @returns 商品列表
725
+ * 通过 ids 获取商品列表;默认只读取内存,显式开启 loadMissing 时按当前订单上下文补查缺失商品。
726
+ * 补查直接写入 ProductList,不更新 BookingTicket.productCatalog,避免当前商品列表被查询子集覆盖。
719
727
  */
720
- async getProductByIds(ids) {
721
- return this.store.products.getProductByIds(ids);
728
+ async getProductByIds(ids, options) {
729
+ var _a, _b;
730
+ const normalizedIds = Array.from(
731
+ new Set(
732
+ (ids || []).map((id) => Number(id)).filter((id) => Number.isFinite(id))
733
+ )
734
+ );
735
+ const localProducts = await this.store.products.getProductByIds(normalizedIds) || [];
736
+ if (!(options == null ? void 0 : options.loadMissing)) {
737
+ return localProducts;
738
+ }
739
+ const localProductIds = new Set(
740
+ localProducts.map((product) => Number(product.id))
741
+ );
742
+ const missingIds = normalizedIds.filter(
743
+ (id) => !localProductIds.has(id)
744
+ );
745
+ if (!missingIds.length) {
746
+ return localProducts;
747
+ }
748
+ const rawBookingDate = this.getBookingDate();
749
+ const bookingDate = rawBookingDate && (0, import_dayjs.default)(rawBookingDate).isValid() ? (0, import_dayjs.default)(rawBookingDate) : null;
750
+ const customerId = Number(
751
+ ((_a = this.getOrderCustomerSnapshot()) == null ? void 0 : _a.id) ?? ((_b = this.getOrderCustomer()) == null ? void 0 : _b.customer_id)
752
+ );
753
+ const loadedProducts = await this.store.products.loadProducts({
754
+ product_ids: missingIds,
755
+ with_count: ["bundleGroup", "optionGroup"],
756
+ with_schedule: 1,
757
+ cacheId: this.cacheId,
758
+ ...bookingDate ? {
759
+ schedule_date: bookingDate.format("YYYY-MM-DD"),
760
+ schedule_datetime: bookingDate.format("YYYY-MM-DD HH:mm:ss")
761
+ } : {},
762
+ ...Number.isFinite(customerId) && customerId > 0 ? { customer_id: customerId } : {}
763
+ });
764
+ const productById = /* @__PURE__ */ new Map();
765
+ [...localProducts, ...loadedProducts || []].forEach((product) => {
766
+ const productId = Number(product.id);
767
+ if (Number.isFinite(productId)) {
768
+ productById.set(productId, product);
769
+ }
770
+ });
771
+ return normalizedIds.map((id) => productById.get(id)).filter((product) => Boolean(product));
722
772
  }
723
773
  /**
724
774
  * 获取日程时间段点
@@ -1390,7 +1440,9 @@ var BookingTicketImpl = class extends import_BaseSales.BaseSalesImpl {
1390
1440
  decideAddProduct: (item, options) => this.decideAddProduct(item, options),
1391
1441
  decideAfterDetail: (cacheItem, options) => this.decideAfterDetail(cacheItem, options),
1392
1442
  transformDetailToProductAndBooking: (input) => this.transformDetailToProductAndBooking(input),
1393
- addProductToOrder: (product, booking) => this.addProductToOrder(product, booking)
1443
+ addProductToOrder: (product, booking) => this.addProductToOrder(product, booking),
1444
+ refreshWalletAssets: (params) => this.refreshWalletAssets(params),
1445
+ scanWalletAsset: (scanCode) => this.scanWalletAsset(scanCode)
1394
1446
  },
1395
1447
  formatted,
1396
1448
  bridge
@@ -1527,8 +1579,8 @@ var BookingTicketImpl = class extends import_BaseSales.BaseSalesImpl {
1527
1579
  return result;
1528
1580
  }
1529
1581
  async setOtherParams(params, { cover = false } = {}) {
1530
- super.setOtherParams(params, { cover });
1531
- if (params == null ? void 0 : params.businessCode) {
1582
+ await super.setOtherParams(params, { cover });
1583
+ if ((params == null ? void 0 : params.businessCode) !== void 0 || (params == null ? void 0 : params.business_code) !== void 0) {
1532
1584
  await this.configureIdGeneratorFromOpenData();
1533
1585
  }
1534
1586
  }
@@ -44,6 +44,7 @@ module.exports = __toCommonJS(addProductDecision_exports);
44
44
  var import_dayjs = __toESM(require("dayjs"));
45
45
  var import_BookingContext = require("../../../modules/BookingContext");
46
46
  var import_transformBaseProductToOrderProduct = require("../../BaseSales/utils/transformBaseProductToOrderProduct");
47
+ var import_weighingProductSku = require("./weighingProductSku");
47
48
  function needsSpecDetailModal(item) {
48
49
  if (item == null ? void 0 : item.open_sold_weight)
49
50
  return true;
@@ -281,11 +282,14 @@ function buildBasePayloadFromCacheItem(cacheItem, ctx) {
281
282
  const quantity = (ctx == null ? void 0 : ctx.quantity) != null && ctx.quantity > 0 ? ctx.quantity : ((_b = cacheItem == null ? void 0 : cacheItem._extend) == null ? void 0 : _b.quantity) ?? 1;
282
283
  const resolvedPid = (cacheItem == null ? void 0 : cacheItem.product_id) ?? (cacheItem == null ? void 0 : cacheItem.id);
283
284
  const extend = (cacheItem == null ? void 0 : cacheItem._extend) || {};
285
+ const productSku = (0, import_weighingProductSku.buildProductSkuFromCacheItem)(cacheItem);
284
286
  return {
285
287
  id: (cacheItem == null ? void 0 : cacheItem.id) ?? (cacheItem == null ? void 0 : cacheItem.product_id),
286
288
  product_id: resolvedPid,
287
289
  variant_id: (other == null ? void 0 : other.product_variant_id) ?? (other == null ? void 0 : other.variant_id) ?? 0,
288
290
  quantity,
291
+ // 规格弹窗本次选择是 option 权威源,避免目录/旧订单里的空 option 覆盖它。
292
+ product_sku: productSku ? { ...productSku, option: (other == null ? void 0 : other.option) || [] } : void 0,
289
293
  option: (other == null ? void 0 : other.option) || [],
290
294
  bundle: (other == null ? void 0 : other.bundle) || ((_c = extend == null ? void 0 : extend.skuValue) == null ? void 0 : _c.bundle) || ((_d = extend == null ? void 0 : extend._skuValue) == null ? void 0 : _d.bundle) || [],
291
295
  unique: other == null ? void 0 : other.unique,
@@ -24,6 +24,14 @@ export interface GlobalScanApplyHost {
24
24
  cacheItem: any;
25
25
  };
26
26
  addProductToOrder(product: any, booking?: any): Promise<any>;
27
+ refreshWalletAssets?(params?: {
28
+ preserveSelection?: boolean;
29
+ }): Promise<any>;
30
+ scanWalletAsset?(code: string): Promise<{
31
+ type: 'walletCode' | 'normalCode';
32
+ asset?: any;
33
+ selected: boolean;
34
+ }>;
27
35
  }
28
36
  /**
29
37
  * 应用全局扫码格式化结果:设客户 / 加车 / 开单 / 弹窗链。
@@ -101,13 +101,32 @@ async function applyGlobalScan(host, formatted, bridge) {
101
101
  return { status: "success", kind: "customer" };
102
102
  }
103
103
  if (searchType === "walletPass") {
104
- if (!data) {
105
- (_c = bridge == null ? void 0 : bridge.onNotify) == null ? void 0 : _c.call(bridge, "fail", "pisell2.text.scan-global-failed");
106
- return { status: "failed", reason: "invalid_data" };
107
- }
108
104
  const snapshot = host.getOrderCustomerSnapshot();
109
105
  const scannedCustomerIsWalkIn = (0, import_cartView.isWalkInCustomer)(data);
106
+ const trySelectWalletAsset = async () => {
107
+ var _a2, _b2;
108
+ if (!host.scanWalletAsset)
109
+ return null;
110
+ const walletResult = await host.scanWalletAsset(scanCode).catch((error) => {
111
+ console.warn("[GlobalScan] Wallet asset lookup failed", error);
112
+ return null;
113
+ });
114
+ if (!walletResult)
115
+ return null;
116
+ if (walletResult.type !== "normalCode" || !walletResult.asset) {
117
+ return null;
118
+ }
119
+ if (walletResult.selected) {
120
+ (_a2 = bridge == null ? void 0 : bridge.onNotify) == null ? void 0 : _a2.call(bridge, "success");
121
+ return { status: "success", kind: "promotion" };
122
+ }
123
+ (_b2 = bridge == null ? void 0 : bridge.onNotify) == null ? void 0 : _b2.call(bridge, "fail", "pisell2.text.goodpass.code-invalid-cart");
124
+ return { status: "failed", reason: "invalid_data" };
125
+ };
110
126
  if ((0, import_cartView.isWalkInCustomer)(snapshot) || scannedCustomerIsWalkIn) {
127
+ const walletSelection2 = await trySelectWalletAsset();
128
+ if (walletSelection2)
129
+ return walletSelection2;
111
130
  if (!(bridge == null ? void 0 : bridge.applyPromotionCode)) {
112
131
  return { status: "failed", reason: "no_bridge" };
113
132
  }
@@ -118,6 +137,19 @@ async function applyGlobalScan(host, formatted, bridge) {
118
137
  return { status: "failed", reason: "invalid_data" };
119
138
  }
120
139
  host.setOrderCustomer(data);
140
+ await ((_c = host.refreshWalletAssets) == null ? void 0 : _c.call(host, { preserveSelection: false }));
141
+ const walletSelection = await trySelectWalletAsset();
142
+ if (walletSelection)
143
+ return walletSelection;
144
+ if (bridge == null ? void 0 : bridge.applyPromotionCode) {
145
+ const promotionResult = await bridge.applyPromotionCode(
146
+ scanCode,
147
+ Number((data == null ? void 0 : data.id) ?? (data == null ? void 0 : data.customer_id)) || void 0
148
+ );
149
+ if (promotionResult == null ? void 0 : promotionResult.isAvailable) {
150
+ return { status: "success", kind: "promotion" };
151
+ }
152
+ }
121
153
  (_d = bridge == null ? void 0 : bridge.onNotify) == null ? void 0 : _d.call(bridge, "success", "pisell2.text.add-client-success");
122
154
  return { status: "success", kind: "customer" };
123
155
  }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * 将规格弹窗的称重结果写入订单行权威字段 product_sku。
3
+ * 商品目录已有的 SKU 字段会保留,本次称重结果只覆盖运行态称重字段。
4
+ */
5
+ export declare function buildProductSkuFromCacheItem(cacheItem: Record<string, any> | null | undefined): Record<string, any> | undefined;
@@ -0,0 +1,63 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+
19
+ // src/solution/BookingTicket/utils/weighingProductSku.ts
20
+ var weighingProductSku_exports = {};
21
+ __export(weighingProductSku_exports, {
22
+ buildProductSkuFromCacheItem: () => buildProductSkuFromCacheItem
23
+ });
24
+ module.exports = __toCommonJS(weighingProductSku_exports);
25
+ var WEIGHT_UNIT_MAP = {
26
+ KILOGRAMS: "kg",
27
+ GRAMS: "g",
28
+ POUNDS: "lb",
29
+ OUNCES: "oz"
30
+ };
31
+ function isRecord(value) {
32
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
33
+ }
34
+ function buildProductSkuFromCacheItem(cacheItem) {
35
+ var _a, _b, _c, _d;
36
+ const currentSku = isRecord(cacheItem == null ? void 0 : cacheItem.product_sku) ? cacheItem.product_sku : void 0;
37
+ const weighing = isRecord((_a = cacheItem == null ? void 0 : cacheItem._extend) == null ? void 0 : _a.weighing) ? (_b = cacheItem == null ? void 0 : cacheItem._extend) == null ? void 0 : _b.weighing : void 0;
38
+ const hasRuntimeWeighingSku = Number((currentSku == null ? void 0 : currentSku.open_sold_weight) || 0) === 1 && ((currentSku == null ? void 0 : currentSku.weight) !== void 0 || (currentSku == null ? void 0 : currentSku.net_weight) !== void 0 || (currentSku == null ? void 0 : currentSku.weight_source) !== void 0);
39
+ const isWeighingSku = Number((currentSku == null ? void 0 : currentSku.open_sold_weight) || 0) === 1;
40
+ if (!isWeighingSku && !weighing)
41
+ return void 0;
42
+ if (hasRuntimeWeighingSku)
43
+ return { ...currentSku };
44
+ if (!weighing)
45
+ return currentSku ? { ...currentSku } : void 0;
46
+ return {
47
+ ...currentSku || {},
48
+ open_sold_weight: 1,
49
+ unit_data_snapshot: weighing.scalePriceProduct,
50
+ unit: WEIGHT_UNIT_MAP[String(weighing.unit)] || weighing.unit,
51
+ unit_weight: weighing.unit_weight ?? "1.000",
52
+ unit_price: weighing.unit_price,
53
+ weight_source: weighing.mode,
54
+ tare_name: ((_c = weighing.tare) == null ? void 0 : _c.label) ?? "",
55
+ tare_weight: ((_d = weighing.tare) == null ? void 0 : _d.value) ?? 0,
56
+ weight: weighing.weight ?? 0,
57
+ net_weight: weighing.net_weight ?? 0
58
+ };
59
+ }
60
+ // Annotate the CommonJS export names for ESM import in node:
61
+ 0 && (module.exports = {
62
+ buildProductSkuFromCacheItem
63
+ });
@@ -48,6 +48,10 @@ var import_utils2 = require("../../modules/Order/utils");
48
48
  var import_dayjs = __toESM(require("dayjs"));
49
49
  var import_itemRule = require("../../model/strategy/adapter/itemRule");
50
50
  __reExport(ScanOrder_exports, require("./types"), module.exports);
51
+ function isItemRewardProductDiscount(discount) {
52
+ var _a, _b;
53
+ return (discount == null ? void 0 : discount.type) === "product" && ((_a = discount == null ? void 0 : discount.metadata) == null ? void 0 : _a.source) === "promotion" && ((_b = discount == null ? void 0 : discount.metadata) == null ? void 0 : _b.actionType) === "ITEM_REWARD";
54
+ }
51
55
  var _ScanOrderImpl = class extends import_BaseModule.BaseModule {
52
56
  constructor(name, version) {
53
57
  super(name, version);
@@ -742,6 +746,10 @@ var _ScanOrderImpl = class extends import_BaseModule.BaseModule {
742
746
  for (const product of tempOrder.products) {
743
747
  if ((_c = product._origin) == null ? void 0 : _c.isManualDiscount)
744
748
  continue;
749
+ if ((product.discount_list || []).some(isItemRewardProductDiscount)) {
750
+ product.discount_list = (product.discount_list || []).filter(isItemRewardProductDiscount);
751
+ continue;
752
+ }
745
753
  product.discount_list = (product.discount_list || []).filter((pd) => {
746
754
  var _a2;
747
755
  const rid = ((_a2 = pd.discount) == null ? void 0 : _a2.resource_id) ?? pd.id;
@@ -65,6 +65,10 @@ var OPEN_DATA_SECTION_CODES = [
65
65
  "workflow",
66
66
  "checkout"
67
67
  ];
68
+ function isItemRewardProductDiscount(discount) {
69
+ var _a, _b;
70
+ return (discount == null ? void 0 : discount.type) === "product" && ((_a = discount == null ? void 0 : discount.metadata) == null ? void 0 : _a.source) === "promotion" && ((_b = discount == null ? void 0 : discount.metadata) == null ? void 0 : _b.actionType) === "ITEM_REWARD";
71
+ }
68
72
  function cloneCustomDepositData(customDepositData) {
69
73
  if (!customDepositData || typeof customDepositData !== "object")
70
74
  return void 0;
@@ -1339,6 +1343,10 @@ var _VenueBookingImpl = class extends import_BaseModule.BaseModule {
1339
1343
  for (const product of tempOrder.products) {
1340
1344
  if ((_c = product._origin) == null ? void 0 : _c.isManualDiscount)
1341
1345
  continue;
1346
+ if ((product.discount_list || []).some(isItemRewardProductDiscount)) {
1347
+ product.discount_list = (product.discount_list || []).filter(isItemRewardProductDiscount);
1348
+ continue;
1349
+ }
1342
1350
  product.discount_list = (product.discount_list || []).filter((pd) => {
1343
1351
  var _a2;
1344
1352
  const rid = ((_a2 = pd.discount) == null ? void 0 : _a2.resource_id) ?? pd.id;
@@ -47,6 +47,7 @@ export interface PisellCore {
47
47
  getModule: <T extends Module>(name: string) => T | null;
48
48
  getModuleExports: <T = any>(name: string) => T | null;
49
49
  hasModule: (name: string) => boolean;
50
+ getModuleNames: () => string[];
50
51
  effects: {
51
52
  on: (event: string, callback: (payload: any) => void) => () => void;
52
53
  once: (event: string, callback: (payload: any) => void) => () => void;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "private": false,
3
3
  "name": "@pisell/pisellos",
4
- "version": "2.3.36",
4
+ "version": "2.3.38",
5
5
  "description": "一个可扩展的前端模块化SDK框架,支持插件系统",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -18,7 +18,7 @@
18
18
  "format": "prettier --write \"src/**/*.{ts,tsx,js,jsx,json,md}\"",
19
19
  "changeset": "changeset",
20
20
  "version": "changeset version",
21
- "release": "changeset publish --registry=https://registry.npmjs.com/ && npm run sync && git push",
21
+ "release": "changeset publish && npm run sync && git push",
22
22
  "example": "npx parcel-bundler examples/index.html",
23
23
  "docs": "typedoc --out docs src/index.ts",
24
24
  "sync": "node sync.js",