@pisell/pisellos 2.2.308 → 2.2.310

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 (130) hide show
  1. package/dist/model/strategy/adapter/promotion/index.js +9 -0
  2. package/dist/model/strategy/adapter/walletPass/evaluator.d.ts +2 -0
  3. package/dist/model/strategy/adapter/walletPass/evaluator.js +4 -2
  4. package/dist/modules/BookingContext/index.js +4 -4
  5. package/dist/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +58 -19
  6. package/dist/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +24 -12
  7. package/dist/modules/Customer/index.js +6 -3
  8. package/dist/modules/Customer/types.d.ts +2 -0
  9. package/dist/modules/Discount/availability.d.ts +7 -0
  10. package/dist/modules/Discount/availability.js +49 -0
  11. package/dist/modules/Discount/entitlementPass.d.ts +34 -0
  12. package/dist/modules/Discount/entitlementPass.js +147 -0
  13. package/dist/modules/Discount/index.d.ts +2 -2
  14. package/dist/modules/Discount/index.js +37 -151
  15. package/dist/modules/Discount/types.d.ts +36 -5
  16. package/dist/modules/OpenData/index.d.ts +1 -1
  17. package/dist/modules/OpenData/index.js +21 -12
  18. package/dist/modules/Order/index.d.ts +33 -4
  19. package/dist/modules/Order/index.js +830 -411
  20. package/dist/modules/Order/types.d.ts +17 -0
  21. package/dist/modules/Order/utils/orderCollectionIdentity.js +16 -12
  22. package/dist/modules/Order/utils/weighedProduct.d.ts +12 -0
  23. package/dist/modules/Order/utils/weighedProduct.js +94 -0
  24. package/dist/modules/Order/utils.d.ts +14 -2
  25. package/dist/modules/Order/utils.js +280 -79
  26. package/dist/modules/Payment/index.d.ts +0 -5
  27. package/dist/modules/Payment/index.js +35 -85
  28. package/dist/modules/Payment/types.d.ts +21 -1
  29. package/dist/modules/Payment/types.js +2 -0
  30. package/dist/modules/Payment/walletpass.d.ts +1 -1
  31. package/dist/modules/Payment/walletpass.js +80 -47
  32. package/dist/modules/ProductList/index.js +18 -5
  33. package/dist/modules/Rules/index.d.ts +2 -2
  34. package/dist/modules/Rules/index.js +484 -188
  35. package/dist/modules/Rules/types.d.ts +6 -0
  36. package/dist/server/index.js +8 -5
  37. package/dist/server/modules/order/index.js +6 -0
  38. package/dist/server/modules/order/types.d.ts +12 -0
  39. package/dist/server/modules/payment/index.d.ts +25 -6
  40. package/dist/server/modules/payment/index.js +660 -133
  41. package/dist/server/utils/small-ticket.d.ts +8 -5
  42. package/dist/server/utils/small-ticket.js +1269 -199
  43. package/dist/solution/BaseSales/index.d.ts +11 -2
  44. package/dist/solution/BaseSales/index.js +1463 -1173
  45. package/dist/solution/BaseSales/types.d.ts +7 -3
  46. package/dist/solution/BaseSales/utils/cartPromotion.js +141 -126
  47. package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +40 -25
  48. package/dist/solution/BaseSales/utils.js +3 -2
  49. package/dist/solution/BookingTicket/index.d.ts +11 -2
  50. package/dist/solution/BookingTicket/index.js +663 -542
  51. package/dist/solution/BookingTicket/types.d.ts +5 -1
  52. package/dist/solution/BookingTicket/utils/scan/applyGlobalScan.d.ts +2 -0
  53. package/dist/solution/BookingTicket/utils/scan/applyGlobalScan.js +338 -147
  54. package/dist/solution/BookingTicket/utils/scan/cloudSearch.js +8 -4
  55. package/dist/solution/BookingTicket/utils/scan/formatGlobalScan.d.ts +6 -0
  56. package/dist/solution/BookingTicket/utils/scan/formatGlobalScan.js +19 -6
  57. package/dist/solution/ScanOrder/utils.js +3 -2
  58. package/dist/solution/ShopDiscount/index.d.ts +2 -0
  59. package/dist/solution/ShopDiscount/index.js +99 -42
  60. package/dist/solution/ShopDiscount/utils.d.ts +4 -2
  61. package/dist/solution/ShopDiscount/utils.js +43 -23
  62. package/dist/solution/UnifiedBookingSales/index.d.ts +2 -2
  63. package/dist/solution/UnifiedBookingSales/index.js +5 -5
  64. package/dist/utils/os-private-payload.d.ts +5 -0
  65. package/dist/utils/os-private-payload.js +17 -0
  66. package/lib/model/strategy/adapter/walletPass/evaluator.d.ts +2 -0
  67. package/lib/model/strategy/adapter/walletPass/evaluator.js +2 -0
  68. package/lib/modules/BookingContext/index.js +3 -3
  69. package/lib/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +35 -6
  70. package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +13 -6
  71. package/lib/modules/Customer/index.js +5 -1
  72. package/lib/modules/Customer/types.d.ts +2 -0
  73. package/lib/modules/Discount/availability.d.ts +7 -0
  74. package/lib/modules/Discount/availability.js +43 -0
  75. package/lib/modules/Discount/entitlementPass.d.ts +34 -0
  76. package/lib/modules/Discount/entitlementPass.js +141 -0
  77. package/lib/modules/Discount/index.d.ts +2 -2
  78. package/lib/modules/Discount/index.js +23 -129
  79. package/lib/modules/Discount/types.d.ts +36 -5
  80. package/lib/modules/OpenData/index.d.ts +1 -1
  81. package/lib/modules/OpenData/index.js +11 -2
  82. package/lib/modules/Order/index.d.ts +33 -4
  83. package/lib/modules/Order/index.js +411 -81
  84. package/lib/modules/Order/types.d.ts +17 -0
  85. package/lib/modules/Order/utils/orderCollectionIdentity.js +14 -11
  86. package/lib/modules/Order/utils/weighedProduct.d.ts +12 -0
  87. package/lib/modules/Order/utils/weighedProduct.js +89 -0
  88. package/lib/modules/Order/utils.d.ts +14 -2
  89. package/lib/modules/Order/utils.js +189 -5
  90. package/lib/modules/Payment/index.d.ts +0 -5
  91. package/lib/modules/Payment/index.js +3 -28
  92. package/lib/modules/Payment/types.d.ts +21 -1
  93. package/lib/modules/Payment/types.js +1 -0
  94. package/lib/modules/Payment/walletpass.d.ts +1 -1
  95. package/lib/modules/Payment/walletpass.js +29 -9
  96. package/lib/modules/ProductList/index.js +14 -1
  97. package/lib/modules/Rules/index.d.ts +2 -2
  98. package/lib/modules/Rules/index.js +361 -112
  99. package/lib/modules/Rules/types.d.ts +6 -0
  100. package/lib/server/index.js +8 -5
  101. package/lib/server/modules/order/index.js +6 -0
  102. package/lib/server/modules/order/types.d.ts +12 -0
  103. package/lib/server/modules/payment/index.d.ts +25 -6
  104. package/lib/server/modules/payment/index.js +350 -78
  105. package/lib/server/utils/small-ticket.d.ts +8 -5
  106. package/lib/server/utils/small-ticket.js +1009 -121
  107. package/lib/solution/BaseSales/index.d.ts +11 -2
  108. package/lib/solution/BaseSales/index.js +164 -30
  109. package/lib/solution/BaseSales/types.d.ts +7 -3
  110. package/lib/solution/BaseSales/utils/cartPromotion.js +12 -4
  111. package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +24 -9
  112. package/lib/solution/BaseSales/utils.js +2 -1
  113. package/lib/solution/BookingTicket/index.d.ts +11 -2
  114. package/lib/solution/BookingTicket/index.js +103 -41
  115. package/lib/solution/BookingTicket/types.d.ts +5 -1
  116. package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.d.ts +2 -0
  117. package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.js +174 -42
  118. package/lib/solution/BookingTicket/utils/scan/cloudSearch.js +8 -4
  119. package/lib/solution/BookingTicket/utils/scan/formatGlobalScan.d.ts +6 -0
  120. package/lib/solution/BookingTicket/utils/scan/formatGlobalScan.js +11 -3
  121. package/lib/solution/ScanOrder/utils.js +2 -1
  122. package/lib/solution/ShopDiscount/index.d.ts +2 -0
  123. package/lib/solution/ShopDiscount/index.js +55 -9
  124. package/lib/solution/ShopDiscount/utils.d.ts +4 -2
  125. package/lib/solution/ShopDiscount/utils.js +42 -25
  126. package/lib/solution/UnifiedBookingSales/index.d.ts +2 -2
  127. package/lib/solution/UnifiedBookingSales/index.js +5 -5
  128. package/lib/utils/os-private-payload.d.ts +5 -0
  129. package/lib/utils/os-private-payload.js +22 -0
  130. package/package.json +1 -1
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.transformBaseProductToOrderProduct = transformBaseProductToOrderProduct;
7
+ var _weighedProduct = require("../../../modules/Order/utils/weighedProduct");
7
8
  var _utils = require("../../../modules/Order/utils");
8
9
  var _manualProductDiscount = require("../../../modules/Order/utils/manualProductDiscount");
9
10
  function toNumber(value, fallback = 0) {
@@ -65,11 +66,11 @@ function appendDataVariantIds(base, ...values) {
65
66
  }
66
67
  return Array.isArray(base) || result.length ? result : undefined;
67
68
  }
68
- function normalizeOptionItems(optionItems) {
69
+ function normalizeOptionItems(optionItems, useOriginalPrice = false) {
69
70
  if (!Array.isArray(optionItems)) return [];
70
71
  return optionItems.map(optionItem => {
71
72
  const optionGroupItemId = toNumber(optionItem?.option_group_item_id ?? optionItem?.id);
72
- const price = optionItem?.price ?? optionItem?.add_price ?? 0;
73
+ const price = (useOriginalPrice ? optionItem?.original_price : undefined) ?? optionItem?.price ?? optionItem?.add_price ?? 0;
73
74
  return {
74
75
  ...optionItem,
75
76
  option_group_item_id: optionGroupItemId,
@@ -80,10 +81,12 @@ function normalizeOptionItems(optionItems) {
80
81
  };
81
82
  });
82
83
  }
83
- function normalizeBundleItems(bundleItems, preferPriceField = false) {
84
+ function normalizeBundleItems(bundleItems, preferPriceField = false, preferBundleSellingPrice = false) {
84
85
  if (!Array.isArray(bundleItems)) return [];
85
86
  return bundleItems.map(bundleItem => {
86
- const unitSelling = preferPriceField ? bundleItem.price ?? bundleItem.bundle_selling_price ?? bundleItem.custom_price ?? bundleItem.product_price ?? '0' : bundleItem.bundle_selling_price ?? bundleItem.price ?? bundleItem.custom_price ?? bundleItem.product_price ?? '0';
87
+ // Weighed manual prices distribute the bundle base and nested options separately.
88
+ // Its explicit selling amount already includes those options.
89
+ const unitSelling = preferPriceField && !preferBundleSellingPrice ? bundleItem.price ?? bundleItem.bundle_selling_price ?? bundleItem.custom_price ?? bundleItem.product_price ?? '0' : bundleItem.bundle_selling_price ?? bundleItem.price ?? bundleItem.custom_price ?? bundleItem.product_price ?? '0';
87
90
  const unitStr = toPriceString(unitSelling, '0.00');
88
91
  const num = toSafePositiveInt(bundleItem?.num ?? bundleItem?.quantity, 1);
89
92
  const magnitudeSource = {
@@ -147,19 +150,25 @@ function transformBaseProductToOrderProduct(params) {
147
150
  const productionCode = normalizeProductionCode(Object.prototype.hasOwnProperty.call(payload, 'production_code') ? payload.production_code : productVariantId > 0 ? matchedVariant?.production_code : origin?.production_code ?? sourceProduct?.production_code);
148
151
  const hasPriceOverride = payload.price_override !== undefined && payload.price_override !== null && payload.price_override !== '';
149
152
  // 手动改价后 payload.price 是分摊后的主商品售价,不能再作为券前 source。
150
- // 此时优先从商品/variant 快照读取目录价;售价仍由 line_total - bundle 推导。
153
+ // 称重商品使用改价前净重金额;其它商品从商品/variant 快照读取目录价。
154
+ // 售价仍由 line_total - bundle 推导。
151
155
  const catalogSourcePrice = matchedVariant?.price ?? matchedVariant?.base_price ?? origin?.price ?? origin?.base_price ?? sourceProduct?.price ?? sourceProduct?.selling_price ?? sourceProduct?.base_price;
152
- const sourceProductPrice = toPriceString(hasPriceOverride ? catalogSourcePrice ?? payload.price ?? payload.selling_price : payload.price ?? payload.selling_price ?? catalogSourcePrice, '0.00');
156
+ const weighedSourcePrice = (0, _weighedProduct.resolveWeighedSourcePrice)(payload.product_sku ?? origin.product_sku, payload._extend?.weighing ?? origin._extend?.weighing);
157
+ const sourceProductPrice = toPriceString(weighedSourcePrice ?? (hasPriceOverride ? catalogSourcePrice ?? payload.price ?? payload.selling_price : payload.price ?? payload.selling_price ?? catalogSourcePrice), '0.00');
153
158
  const explicitLineTotal = payload.line_total ?? payload.total ?? payload._extend?.total;
154
159
  const hasExplicitLineTotal = Number.isFinite(Number(explicitLineTotal));
155
160
  const lineCompositeTotal = toPriceString(explicitLineTotal, sourceProductPrice);
156
- const productOptionItem = (0, _utils.normalizeProductSkuOptions)(normalizeOptionItems(payload.product_sku?.option ?? payload.option ?? payload.options ?? payload.product_option_item));
157
- const productBundle = normalizeBundleItems(payload.bundle ?? payload.product_bundle, hasPriceOverride);
161
+ const productOptionItem = (0, _utils.normalizeProductSkuOptions)(normalizeOptionItems(payload.product_sku?.option ?? payload.option ?? payload.options ?? payload.product_option_item, hasPriceOverride || weighedSourcePrice !== undefined));
162
+ const productBundle = normalizeBundleItems(payload.bundle ?? payload.product_bundle, hasPriceOverride, hasPriceOverride && weighedSourcePrice !== undefined);
158
163
  // `payload.unique` 是业务侧商品/票务标识,可能在连续加同一商品时重复;
159
164
  // 行级唯一值只接受显式协议字段,缺省交给 OrderModule 生成。
160
165
  const uniqueIdentificationNumber = payload.unique_identification_number;
161
166
  const resolvedQuantity = toSafePositiveInt(payload.quantity ?? payload.num, 1);
162
- const originTotal = (0, _manualProductDiscount.resolveManualDiscountOriginTotal)({
167
+ const originTotal = weighedSourcePrice !== undefined ? Number((0, _utils.composeLinePrice)({
168
+ mainPrice: Number(weighedSourcePrice) + (0, _utils.sumOptionUnitPrice)(productOptionItem).toNumber(),
169
+ bundle: hasPriceOverride ? (0, _weighedProduct.withOriginalBundleOptionPrices)(productBundle) : productBundle,
170
+ useOriginalBundle: true
171
+ })) : (0, _manualProductDiscount.resolveManualDiscountOriginTotal)({
163
172
  originTotal: payload.origin_total,
164
173
  extendOriginTotal: payload._extend?.origin_total,
165
174
  originExtendOriginTotal: origin?._extend?.origin_total,
@@ -260,6 +269,12 @@ function transformBaseProductToOrderProduct(params) {
260
269
  product_cover: firstNonBlank(matchedVariant?.cover, sourceProduct?.cover),
261
270
  _origin: {
262
271
  ...(sourceProduct || {}),
272
+ ...(weighedSourcePrice !== undefined && hasPriceOverride ? {
273
+ _extend: {
274
+ ...sourceProduct?._extend,
275
+ origin_total: originTotal
276
+ }
277
+ } : {}),
263
278
  callbackData: payload
264
279
  }
265
280
  };
@@ -8,6 +8,7 @@ exports.createEmptySummary = createEmptySummary;
8
8
  exports.getSafeProductNum = getSafeProductNum;
9
9
  exports.isSkuOnlyDeleteIdentity = isSkuOnlyDeleteIdentity;
10
10
  exports.normalizeOrderProduct = normalizeOrderProduct;
11
+ var _weighedProduct = require("../../modules/Order/utils/weighedProduct");
11
12
  var _decimal = _interopRequireDefault(require("decimal.js"));
12
13
  var _utils = require("../../modules/Order/utils");
13
14
  var _manualProductDiscount = require("../../modules/Order/utils/manualProductDiscount");
@@ -237,7 +238,7 @@ function normalizeOrderProduct(product) {
237
238
  });
238
239
  const composedOriginalPrice = (0, _utils.composeLinePrice)({
239
240
  mainPrice: mainOriginalStr,
240
- bundle: normalizedBundle,
241
+ bundle: Number(productSku.open_sold_weight) === 1 && (0, _manualProductDiscount.hasManualProductDiscountFlag)(metadata) ? (0, _weighedProduct.withOriginalBundleOptionPrices)(normalizedBundle) : normalizedBundle,
241
242
  useOriginalBundle: true
242
243
  });
243
244
  const finalOriginalPrice = (0, _utils.resolveManualOverrideLineOriginalPrice)({
@@ -8,6 +8,7 @@ import { BaseSalesImpl } from '../BaseSales';
8
8
  import type { BaseSalesScanCodeResult } from '../BaseSales/types';
9
9
  import type { ISalesDetail } from '../BaseSales/utils/parseSalesResponse';
10
10
  import type { LoadSalesDetailParams } from '../../modules/Order/types';
11
+ import type { GlobalScanSearchType } from './utils/scan/formatGlobalScan';
11
12
  import type { AddAddTimeProductToBookingInput, AddAddTimeProductsToBookingInput, GlobalScanHandleResult, GlobalScanHostBridge } from './types';
12
13
  import { AddProductDecideContext, AddProductRequiresDetailPayload } from './utils/addProductDecision';
13
14
  import { type BuildCacheItemFromOrderLineInput } from '../../modules/BookingContext/utils/buildCacheItemFromOrderLine';
@@ -29,7 +30,9 @@ export declare class BookingTicketImpl extends BaseSalesImpl implements Module {
29
30
  private addTimeProductsCatalog;
30
31
  private orderCustomerDiscountRefreshInFlight;
31
32
  private orderCustomerDiscountRefreshCustomerId;
33
+ private orderCustomerDiscountRefreshTarget;
32
34
  private orderCustomerPromotionRefreshInFlight;
35
+ private readonly customerResolveRequests;
33
36
  protected loadOpenDataInFlight: Promise<OpenDataConfig> | null;
34
37
  protected loadOpenDataInFlightTarget: string | null;
35
38
  protected openDataTarget: string | null;
@@ -45,6 +48,12 @@ export declare class BookingTicketImpl extends BaseSalesImpl implements Module {
45
48
  */
46
49
  protected createSubModule(moduleName: string): Module | null;
47
50
  initialize(core: PisellCore, options?: ModuleOptions): Promise<void>;
51
+ private getInjectedOpenDataConfig;
52
+ /**
53
+ * 在解决方案对外 ready 前准备 Invoice Document;失败只降级模板,不阻断销售。
54
+ * 已由页面取得的 OpenData 直接交给模块接管,没有注入数据时再走 target-scoped 加载。
55
+ */
56
+ private prepareInvoiceTemplateAtSolutionReady;
48
57
  protected getBookingTicketBusinessCode(): string | null;
49
58
  private getIdGeneratorPlugin;
50
59
  private loadOpenDataConfig;
@@ -72,10 +81,8 @@ export declare class BookingTicketImpl extends BaseSalesImpl implements Module {
72
81
  resolveCustomerById(customerId: string | number): Promise<ICustomer | null>;
73
82
  /** 兼容既有优惠码扫码逻辑。 */
74
83
  private resolveCustomerByDiscountCustomerId;
75
- private findCustomerById;
76
84
  private normalizeCustomer;
77
85
  private getRawCustomerId;
78
- private hasUsableCustomerDetails;
79
86
  /**
80
87
  * 更新当前预约的 appointment_status。
81
88
  *
@@ -531,6 +538,8 @@ export declare class BookingTicketImpl extends BaseSalesImpl implements Module {
531
538
  * ```
532
539
  */
533
540
  handleGlobalScanCode(code: string, bridge?: GlobalScanHostBridge): Promise<GlobalScanHandleResult>;
541
+ /** 查询码类型;复用原有识别和缓存,不写订单、不打开选择 UI。 */
542
+ getGlobalScanCodeType(code: string): Promise<GlobalScanSearchType | null>;
534
543
  resolveBestAddTimePlan(addTimeProducts: any[], targetMinutes: number): any;
535
544
  /**
536
545
  * 构造一条绑定到已有 booking 的加时商品行。
@@ -160,7 +160,9 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
160
160
  addTimeProductsCatalog = [];
161
161
  orderCustomerDiscountRefreshInFlight = null;
162
162
  orderCustomerDiscountRefreshCustomerId = null;
163
+ orderCustomerDiscountRefreshTarget = null;
163
164
  orderCustomerPromotionRefreshInFlight = null;
165
+ customerResolveRequests = new Map();
164
166
  loadOpenDataInFlight = null;
165
167
  loadOpenDataInFlightTarget = null;
166
168
  openDataTarget = null;
@@ -193,10 +195,37 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
193
195
  throw new Error('bookingTicket解决方案需要 request 插件支持');
194
196
  }
195
197
  this.platform = this.otherParams?.platform;
198
+ await this.prepareInvoiceTemplateAtSolutionReady(this.getInjectedOpenDataConfig());
196
199
  await this.configureIdGeneratorFromOpenData();
197
200
  }
201
+ getInjectedOpenDataConfig() {
202
+ const openData = this.otherParams?.openData;
203
+ return openData && typeof openData === 'object' && !Array.isArray(openData) ? openData : undefined;
204
+ }
205
+
206
+ /**
207
+ * 在解决方案对外 ready 前准备 Invoice Document;失败只降级模板,不阻断销售。
208
+ * 已由页面取得的 OpenData 直接交给模块接管,没有注入数据时再走 target-scoped 加载。
209
+ */
210
+ async prepareInvoiceTemplateAtSolutionReady(injectedOpenData) {
211
+ const businessCode = this.getBookingTicketBusinessCode();
212
+ if (!businessCode || !this.store.openData) return;
213
+ const channel = this.getSubmitOrderSalesChannel();
214
+ const target = `${businessCode}+${channel}`;
215
+ try {
216
+ if (injectedOpenData && typeof this.store.openData.ensureInvoiceTemplateSettings === 'function') {
217
+ this.openDataTarget = target;
218
+ await this.store.openData.ensureInvoiceTemplateSettings(target, injectedOpenData);
219
+ this.otherParams.openData = injectedOpenData;
220
+ return;
221
+ }
222
+ await this.loadOpenDataConfig();
223
+ } catch (error) {
224
+ console.warn('[BookingTicket] Invoice 模板预加载失败,使用默认配置', error);
225
+ }
226
+ }
198
227
  getBookingTicketBusinessCode() {
199
- const candidates = [this.otherParams?.businessCode, this.otherParams?.business_code, this.getOrderHeader()?.business_code, this.store.order?.getTempOrder()?.business_code, this.getSalesOrder()?.raw?.business_code];
228
+ const candidates = [this.otherParams?.businessCode, this.otherParams?.business_code, this.getOrderHeader()?.business_code, typeof this.store.order?.getTempOrder === 'function' ? this.store.order.getTempOrder()?.business_code : undefined, this.getSalesOrder()?.raw?.business_code];
200
229
  for (const candidate of candidates) {
201
230
  if (candidate === undefined || candidate === null) continue;
202
231
  const normalized = String(candidate).trim();
@@ -415,7 +444,15 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
415
444
 
416
445
  // 缺 Holder 等场景 isAvailable=false,但仍需把 Pass 归属客户带入订单,供 Holder 弹窗使用。
417
446
  if (!hasOrderCustomer && extractedCustomerId && !isUserPlatform) {
418
- await this.hydrateOrderCustomerFromScanDiscounts(scannedList);
447
+ try {
448
+ await this.hydrateOrderCustomerFromScanDiscounts(scannedList);
449
+ } catch (error) {
450
+ // 客户映射失败不能阻断原有 Pass 的可用性、展示或优惠处理。
451
+ console.warn('[BookingTicket] scan discount customer resolve failed', {
452
+ customerId: extractedCustomerId,
453
+ error
454
+ });
455
+ }
419
456
  }
420
457
  if (raw.isAvailable) {
421
458
  await this.store.order.recalculateSummary({
@@ -466,43 +503,39 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
466
503
  return null;
467
504
  }
468
505
  async resolveCustomerById(customerId) {
469
- const localCustomers = this.store.customer.getCustomers();
470
- const localCustomer = this.findCustomerById(localCustomers, customerId);
471
- const normalizedLocalCustomer = localCustomer ? this.normalizeCustomer(localCustomer) : null;
472
- if (normalizedLocalCustomer && this.hasUsableCustomerDetails(normalizedLocalCustomer)) {
473
- return normalizedLocalCustomer;
474
- }
475
- const result = await this.store.customer.queryCustomerList({
476
- ids: [customerId],
477
- skip: 1,
478
- num: 1
479
- });
480
- const remoteCustomer = this.findCustomerById(result?.list || [], customerId) || result?.list?.[0] || null;
481
- if (remoteCustomer) {
482
- return this.normalizeCustomer(remoteCustomer);
483
- }
484
- try {
485
- const detailCustomer = await this.store.customer.queryCustomerDetail(customerId);
486
- return detailCustomer ? this.normalizeCustomer(detailCustomer) : null;
487
- } catch (error) {
488
- console.warn('[BookingTicket] resolveCustomerById detail request failed', {
489
- customerId,
490
- error
491
- });
506
+ const rawCustomerId = String(customerId ?? '').trim();
507
+ const numericCustomerId = Number(rawCustomerId);
508
+ if (!rawCustomerId || !Number.isInteger(numericCustomerId) || numericCustomerId <= 1) {
492
509
  return null;
493
510
  }
511
+ const normalizedCustomerId = String(numericCustomerId);
512
+ const existingRequest = this.customerResolveRequests.get(normalizedCustomerId);
513
+ if (existingRequest) return existingRequest;
514
+ const request = this.request.post(`/customer/resolve/${encodeURIComponent(normalizedCustomerId)}`, {
515
+ with_franchisor_data: 1,
516
+ with: ['latestWalletDetail.wallet', 'contactsInfo', 'formRecord']
517
+ }).then(result => {
518
+ const customer = result?.data;
519
+ const resolvedCustomerId = Number(customer && typeof customer === 'object' && !Array.isArray(customer) ? this.getRawCustomerId(customer) : NaN);
520
+ if (result?.status !== true || result?.code !== 200 || !customer || typeof customer !== 'object' || Array.isArray(customer) || !Number.isInteger(resolvedCustomerId) || resolvedCustomerId <= 1) {
521
+ const error = new Error(result?.message || 'Customer resolve failed');
522
+ error.code = result?.code;
523
+ throw error;
524
+ }
525
+ return this.normalizeCustomer(customer);
526
+ }).finally(() => {
527
+ if (this.customerResolveRequests.get(normalizedCustomerId) === request) {
528
+ this.customerResolveRequests.delete(normalizedCustomerId);
529
+ }
530
+ });
531
+ this.customerResolveRequests.set(normalizedCustomerId, request);
532
+ return request;
494
533
  }
495
534
 
496
535
  /** 兼容既有优惠码扫码逻辑。 */
497
536
  resolveCustomerByDiscountCustomerId(customerId) {
498
537
  return this.resolveCustomerById(customerId);
499
538
  }
500
- findCustomerById(customers = [], customerId) {
501
- return customers.find(customer => {
502
- const rawId = this.getRawCustomerId(customer);
503
- return rawId !== null && String(rawId) === String(customerId);
504
- }) || null;
505
- }
506
539
  normalizeCustomer(customer) {
507
540
  const raw = customer;
508
541
  const rawId = this.getRawCustomerId(customer);
@@ -520,9 +553,6 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
520
553
  const raw = customer;
521
554
  return customer.id ?? raw.customer_id ?? raw.customerId ?? null;
522
555
  }
523
- hasUsableCustomerDetails(customer) {
524
- return !!(customer.name || customer.phone || customer.email || customer.display_name || customer.customer_name);
525
- }
526
556
 
527
557
  /**
528
558
  * 更新当前预约的 appointment_status。
@@ -1094,7 +1124,13 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
1094
1124
  }
1095
1125
  refreshDiscountConfigAfterOrderCustomerChange(customerId) {
1096
1126
  if (!customerId || customerId === 1) return Promise.resolve();
1097
- if (this.orderCustomerDiscountRefreshInFlight && this.orderCustomerDiscountRefreshCustomerId === customerId) {
1127
+ const tempOrder = this.store.order.getTempOrder?.();
1128
+ const orderIdentity = this.getOrderIdentity?.();
1129
+ const orderId = tempOrder?.order_id || orderIdentity?.orderId;
1130
+ const action = !orderId || String(orderId).startsWith('local_order') ? 'create' : 'edit';
1131
+ const orderTarget = orderId ? `order:${String(orderId)}` : `external:${String(tempOrder?.external_sale_number || orderIdentity?.externalSaleNumber || orderIdentity?.localId || 'new')}`;
1132
+ const refreshTarget = `${customerId}:${action}:${orderTarget}`;
1133
+ if (this.orderCustomerDiscountRefreshInFlight && this.orderCustomerDiscountRefreshTarget === refreshTarget) {
1098
1134
  return this.orderCustomerDiscountRefreshInFlight;
1099
1135
  }
1100
1136
  const previousRefresh = this.orderCustomerDiscountRefreshInFlight;
@@ -1106,22 +1142,33 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
1106
1142
  if (!currentCustomer || Number(currentCustomer.customer_id) !== customerId) {
1107
1143
  return;
1108
1144
  }
1109
- const tempOrder = this.store.order.getTempOrder?.();
1110
- const orderId = tempOrder?.order_id || this.getOrderIdentity?.()?.orderId;
1111
- const action = !orderId || String(orderId).startsWith('local_order') ? 'create' : 'edit';
1145
+ const currentTempOrder = this.store.order.getTempOrder?.();
1146
+ const currentOrderIdentity = this.getOrderIdentity?.();
1147
+ const currentOrderId = currentTempOrder?.order_id || currentOrderIdentity?.orderId;
1148
+ const currentAction = !currentOrderId || String(currentOrderId).startsWith('local_order') ? 'create' : 'edit';
1149
+ const currentOrderTarget = currentOrderId ? `order:${String(currentOrderId)}` : `external:${String(currentTempOrder?.external_sale_number || currentOrderIdentity?.externalSaleNumber || currentOrderIdentity?.localId || 'new')}`;
1150
+ if (`${customerId}:${currentAction}:${currentOrderTarget}` !== refreshTarget) {
1151
+ return;
1152
+ }
1153
+
1154
+ // 上一个订单的迟到请求可能在客户切换后重新写入 BaseSales cache key;
1155
+ // 客户变更触发的目标订单刷新必须绕过该缓存。
1112
1156
  await this.loadDiscountConfig({
1113
1157
  customerId,
1114
- action
1158
+ action,
1159
+ force: true
1115
1160
  });
1116
1161
  })();
1117
1162
  this.orderCustomerDiscountRefreshInFlight = refreshTask;
1118
1163
  this.orderCustomerDiscountRefreshCustomerId = customerId;
1164
+ this.orderCustomerDiscountRefreshTarget = refreshTarget;
1119
1165
  refreshTask.catch(error => {
1120
1166
  console.error('Failed to load discount config after customer change:', error);
1121
1167
  }).finally(() => {
1122
1168
  if (this.orderCustomerDiscountRefreshInFlight !== refreshTask) return;
1123
1169
  this.orderCustomerDiscountRefreshInFlight = null;
1124
1170
  this.orderCustomerDiscountRefreshCustomerId = null;
1171
+ this.orderCustomerDiscountRefreshTarget = null;
1125
1172
  });
1126
1173
  return refreshTask;
1127
1174
  }
@@ -1642,6 +1689,7 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
1642
1689
  const result = await (0, _applyGlobalScan.applyGlobalScan)({
1643
1690
  setOrderCustomer: customer => this.setOrderCustomer(customer),
1644
1691
  getOrderCustomerSnapshot: () => this.getOrderCustomerSnapshot(),
1692
+ resolveCustomerById: customerId => this.resolveCustomerById(customerId),
1645
1693
  getBusinessCode: () => this.otherParams?.businessCode,
1646
1694
  buildAddProductCtx: extra => this.buildAddProductCtx(extra),
1647
1695
  decideAddProduct: (item, options) => this.decideAddProduct(item, options),
@@ -1662,6 +1710,18 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
1662
1710
  };
1663
1711
  }
1664
1712
  }
1713
+
1714
+ /** 查询码类型;复用原有识别和缓存,不写订单、不打开选择 UI。 */
1715
+ async getGlobalScanCodeType(code) {
1716
+ const trimmed = typeof code === 'string' ? code.trim() : '';
1717
+ if (!trimmed) return null;
1718
+ const scan = (0, _handleScan.handleGlobalScan)(this.request, value => this.store.products.findProductsByCodeOrBarcode(value), this.getSubmitOrderSalesChannel());
1719
+ const raw = await scan({
1720
+ type: 'nativeScanner',
1721
+ value: trimmed
1722
+ });
1723
+ return (0, _formatGlobalScan.formatGlobalScanResult)(raw, trimmed)?.searchType ?? null;
1724
+ }
1665
1725
  resolveBestAddTimePlan(addTimeProducts, targetMinutes) {
1666
1726
  return (0, _resolveBestAddTimePlan.resolveBestAddTimePlan)(addTimeProducts, targetMinutes);
1667
1727
  }
@@ -1784,10 +1844,12 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
1784
1844
  await super.setOtherParams(params, {
1785
1845
  cover
1786
1846
  });
1847
+ const invoiceContextChanged = params?.businessCode !== undefined || params?.business_code !== undefined || params?.channel !== undefined || params?.platform !== undefined || Object.prototype.hasOwnProperty.call(params, 'openData');
1848
+ if (invoiceContextChanged) {
1849
+ await this.prepareInvoiceTemplateAtSolutionReady(Object.prototype.hasOwnProperty.call(params, 'openData') ? this.getInjectedOpenDataConfig() : undefined);
1850
+ }
1787
1851
  if (params?.businessCode !== undefined || params?.business_code !== undefined) {
1788
1852
  await this.configureIdGeneratorFromOpenData();
1789
- } else if (params?.channel !== undefined) {
1790
- await this.loadOpenDataConfig();
1791
1853
  }
1792
1854
  }
1793
1855
  getDeviceTaskPlugin() {
@@ -230,12 +230,16 @@ export interface GlobalScanHostBridge {
230
230
  /**
231
231
  * walk-in 扫 walletPass:直接核销优惠码(对齐 SaleDetail Voucher.handleApplyCode / cart.scanCode)。
232
232
  */
233
- applyPromotionCode?: (code: string, customerId?: number) => Promise<{
233
+ applyPromotionCode?: (code: string, customerId?: number, options?: {
234
+ suppressFailureNotification?: boolean;
235
+ }) => Promise<{
234
236
  isAvailable?: boolean;
235
237
  isAccepted?: boolean;
236
238
  type?: string;
237
239
  unavailableReason?: string;
238
240
  }>;
241
+ /** 当前订单已有其他客户时,由宿主复用客户组件完成确认及切换。 */
242
+ requestCustomerSwitch?: (customer: ICustomer) => Promise<boolean | null>;
239
243
  onNotify?: (type: 'success' | 'fail' | 'info', messageKey?: string) => void;
240
244
  refresh?: () => void;
241
245
  }
@@ -5,6 +5,7 @@ import { GlobalScanFormattedResult } from './formatGlobalScan';
5
5
  export interface GlobalScanApplyHost {
6
6
  setOrderCustomer(customer: ICustomer | null): void;
7
7
  getOrderCustomerSnapshot(): ICustomer | null;
8
+ resolveCustomerById?(customerId: string | number): Promise<ICustomer | null>;
8
9
  getBusinessCode(): string | undefined;
9
10
  buildAddProductCtx(extra?: Record<string, any>): Record<string, any>;
10
11
  decideAddProduct(item: any, options?: Record<string, any>): AddProductDecision;
@@ -26,6 +27,7 @@ export interface GlobalScanApplyHost {
26
27
  addProductToOrder(product: any, booking?: any): Promise<any>;
27
28
  refreshWalletAssets?(params?: {
28
29
  preserveSelection?: boolean;
30
+ retainSearchResultCodes?: string[];
29
31
  }): Promise<any>;
30
32
  scanWalletAsset?(code: string): Promise<{
31
33
  type: 'walletCode' | 'normalCode';