@pisell/pisellos 0.10.40 → 0.10.41

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 (34) hide show
  1. package/dist/model/strategy/adapter/promotion/index.js +0 -9
  2. package/dist/modules/Customer/index.js +4 -2
  3. package/dist/modules/Customer/types.d.ts +2 -0
  4. package/dist/modules/Payment/types.d.ts +21 -1
  5. package/dist/modules/Payment/types.js +2 -0
  6. package/dist/modules/Payment/walletpass.d.ts +1 -1
  7. package/dist/modules/Payment/walletpass.js +80 -47
  8. package/dist/solution/BaseSales/index.d.ts +1 -0
  9. package/dist/solution/BaseSales/index.js +2 -1
  10. package/dist/solution/BookingByStep/index.d.ts +2 -2
  11. package/dist/solution/BookingTicket/index.d.ts +1 -2
  12. package/dist/solution/BookingTicket/index.js +60 -63
  13. package/dist/solution/BookingTicket/utils/scan/applyGlobalScan.d.ts +2 -0
  14. package/dist/solution/BookingTicket/utils/scan/applyGlobalScan.js +266 -150
  15. package/dist/solution/BookingTicket/utils/scan/cloudSearch.js +5 -3
  16. package/dist/solution/BookingTicket/utils/scan/formatGlobalScan.d.ts +4 -0
  17. package/dist/solution/BookingTicket/utils/scan/formatGlobalScan.js +8 -4
  18. package/lib/modules/Customer/index.js +4 -1
  19. package/lib/modules/Customer/types.d.ts +2 -0
  20. package/lib/modules/Payment/types.d.ts +21 -1
  21. package/lib/modules/Payment/types.js +1 -0
  22. package/lib/modules/Payment/walletpass.d.ts +1 -1
  23. package/lib/modules/Payment/walletpass.js +29 -9
  24. package/lib/solution/BaseSales/index.d.ts +1 -0
  25. package/lib/solution/BaseSales/index.js +2 -1
  26. package/lib/solution/BookingByStep/index.d.ts +2 -2
  27. package/lib/solution/BookingTicket/index.d.ts +1 -2
  28. package/lib/solution/BookingTicket/index.js +36 -33
  29. package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.d.ts +2 -0
  30. package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.js +127 -42
  31. package/lib/solution/BookingTicket/utils/scan/cloudSearch.js +5 -3
  32. package/lib/solution/BookingTicket/utils/scan/formatGlobalScan.d.ts +4 -0
  33. package/lib/solution/BookingTicket/utils/scan/formatGlobalScan.js +7 -3
  34. package/package.json +1 -1
@@ -153,7 +153,10 @@ class CustomerModule extends _BaseModule.BaseModule {
153
153
  ...(search && {
154
154
  search
155
155
  }),
156
- ..._otherParams
156
+ ..._otherParams,
157
+ ...(url === '/customer/select-list' ? {
158
+ with_franchisor_data: 1
159
+ } : {})
157
160
  };
158
161
  const res = await this.request.get(url, queryParams, {
159
162
  cache: {
@@ -50,6 +50,8 @@ export interface ShopGetCustomerListParams {
50
50
  search_wallet_flag?: 0 | 1;
51
51
  /** wallet pass搜索标识 */
52
52
  search_wallet_pass_flag?: 0 | 1;
53
+ /** 是否查询连锁加盟组合店铺数据 */
54
+ with_franchisor_data?: boolean | 0 | 1;
53
55
  /** 其他筛选参数 */
54
56
  [key: string]: any;
55
57
  }
@@ -412,6 +412,8 @@ export interface WalletAssetsBehaviorOptions {
412
412
  export interface RefreshWalletAssetsOptions {
413
413
  /** 商品/金额变化默认保留并重验选择;客户变化由实现层强制清空。 */
414
414
  preserveSelection?: boolean;
415
+ /** 客户变化时,仅保留与本次扫码匹配的搜索结果;其他搜索缓存仍会清空。 */
416
+ retainSearchResultCodes?: string[];
415
417
  }
416
418
  export interface SelectWalletAssetOptions {
417
419
  selected: boolean;
@@ -419,6 +421,8 @@ export interface SelectWalletAssetOptions {
419
421
  }
420
422
  export interface ScanWalletAssetResult {
421
423
  type: SearchIdentificationCodeResult['type'];
424
+ /** walletCode 时保留完整 Wallet Detail,供客户解析与客户选择器使用。 */
425
+ data?: WalletDetailSearchData;
422
426
  asset?: any;
423
427
  selected: boolean;
424
428
  state: WalletAssetsState;
@@ -864,6 +868,8 @@ export interface SearchIdentificationCodeParams extends WalletDeductionRecommend
864
868
  * 搜索识别码响应数据
865
869
  */
866
870
  export interface SearchIdentificationCodeResponse {
871
+ /** 业务是否成功 */
872
+ status?: boolean;
867
873
  /** 响应状态码 */
868
874
  code?: number;
869
875
  /** 响应消息 */
@@ -871,12 +877,26 @@ export interface SearchIdentificationCodeResponse {
871
877
  /** 响应数据 */
872
878
  data?: SearchIdentificationCodeItem[];
873
879
  }
880
+ /** Wallet code 精确搜索返回的 Wallet Detail。 */
881
+ export interface WalletDetailSearchData {
882
+ id?: string | number;
883
+ customer_id?: string | number;
884
+ customer?: Record<string, any> | null;
885
+ is_cross_shop_wallet?: boolean | 0 | 1;
886
+ [key: string]: any;
887
+ }
888
+ export interface WalletDetailSearchResponse {
889
+ status?: boolean;
890
+ code?: number;
891
+ message?: string;
892
+ data?: WalletDetailSearchData | null;
893
+ }
874
894
  /**
875
895
  * 搜索识别码结果类型
876
896
  */
877
897
  export type SearchIdentificationCodeResult = {
878
898
  type: 'walletCode';
879
- data: SearchIdentificationCodeItem[];
899
+ data: WalletDetailSearchData;
880
900
  } | {
881
901
  type: 'normalCode';
882
902
  data: SearchIdentificationCodeItem[];
@@ -221,6 +221,7 @@ let PaymentHooks = exports.PaymentHooks = /*#__PURE__*/function (PaymentHooks) {
221
221
  /**
222
222
  * 搜索识别码响应数据
223
223
  */
224
+ /** Wallet code 精确搜索返回的 Wallet Detail。 */
224
225
  /**
225
226
  * 搜索识别码结果类型
226
227
  */
@@ -96,7 +96,7 @@ export declare class WalletPassPaymentImpl implements WalletPassPayment {
96
96
  * 搜索识别码信息
97
97
  * 通过识别码搜索相关的钱包通行证信息
98
98
  * 基于 WalletDeductionRecommendParams 参数结构
99
- * 特殊逻辑:当识别码长度为9位且前3位为"000"时,调用 /wallet/detail/search 接口
99
+ * 特殊逻辑:WL 或指定 200 格式的 Wallet Code 调用 /wallet/detail/search 接口。
100
100
  */
101
101
  searchIdentificationCodeAsync(params: SearchIdentificationCodeParams, config?: {
102
102
  noCache?: boolean;
@@ -705,7 +705,7 @@ class WalletPassPaymentImpl {
705
705
  * 搜索识别码信息
706
706
  * 通过识别码搜索相关的钱包通行证信息
707
707
  * 基于 WalletDeductionRecommendParams 参数结构
708
- * 特殊逻辑:当识别码长度为9位且前3位为"000"时,调用 /wallet/detail/search 接口
708
+ * 特殊逻辑:WL 或指定 200 格式的 Wallet Code 调用 /wallet/detail/search 接口。
709
709
  */
710
710
  async searchIdentificationCodeAsync(params, config = {}) {
711
711
  try {
@@ -725,6 +725,7 @@ class WalletPassPaymentImpl {
725
725
  const walletDetailParams = {
726
726
  code: code,
727
727
  with_customer: 1,
728
+ with_franchisor_data: 1,
728
729
  with: ['wallet']
729
730
  };
730
731
  this.paymentModule.logInfo('[WalletPass] 搜索钱包识别码', {
@@ -733,15 +734,18 @@ class WalletPassPaymentImpl {
733
734
  with: walletDetailParams.with
734
735
  });
735
736
  const response = await this.paymentModule.request.post('/wallet/detail/search', walletDetailParams);
736
- const searchResults = response?.data || [];
737
+ const walletDetail = response?.data;
738
+ if (response?.status !== true || response?.code !== 200 || !walletDetail || typeof walletDetail !== 'object' || Array.isArray(walletDetail)) {
739
+ throw new Error(response?.message || 'Wallet detail search failed');
740
+ }
737
741
  this.paymentModule.logInfo('[WalletPass] 钱包识别码搜索完成', {
738
742
  code: code,
739
- results_count: searchResults.length,
743
+ results_count: 1,
740
744
  type: 'walletCode'
741
745
  });
742
746
  return {
743
747
  type: 'walletCode',
744
- data: searchResults
748
+ data: walletDetail
745
749
  };
746
750
  }
747
751
 
@@ -830,9 +834,16 @@ class WalletPassPaymentImpl {
830
834
  const previousCustomerId = this.walletAssetsState.customerId;
831
835
  const customerChanged = String(previousCustomerId ?? '') !== String(nextCustomerId ?? '');
832
836
  const preserveSelection = options.preserveSelection !== false && !customerChanged;
837
+ const retainedSearchCodeKeys = new Set((options.retainSearchResultCodes || []).map(normalizeWalletAssetCode).filter(Boolean));
838
+ const retainedSearchResults = customerChanged && retainedSearchCodeKeys.size > 0 ? this.searchResults.filter(item => isWalletPaymentAsset(item) && getWalletAssetCodeKeys(item).some(code => retainedSearchCodeKeys.has(code))) : [];
833
839
  if (customerChanged) {
834
840
  this.clearSearchResults();
841
+ if (retainedSearchResults.length > 0) {
842
+ this.searchResults = mergeWalletAssets(retainedSearchResults);
843
+ this.queuePendingAutoSelectForAssets(retainedSearchResults);
844
+ }
835
845
  }
846
+ const preserveSearchContext = preserveSelection || retainedSearchResults.length > 0;
836
847
  const committedKeys = new Set(this.walletAssetsState.committedIds.map(id => String(id)));
837
848
  const committedItems = this.walletAssetsState.items.filter(item => committedKeys.has(getWalletAssetKey(item)));
838
849
  const selectedIds = preserveSelection ? [...this.walletAssetsState.selectedIds] : [];
@@ -901,8 +912,8 @@ class WalletPassPaymentImpl {
901
912
  error: null
902
913
  });
903
914
  try {
904
- const searchedWalletAssetCodes = preserveSelection ? this.getSearchedWalletAssetCodes() : [];
905
- const useAggregateRecalculate = preserveSelection && (selectedIds.length > 0 || searchedWalletAssetCodes.length > 0);
915
+ const searchedWalletAssetCodes = preserveSearchContext ? this.getSearchedWalletAssetCodes() : [];
916
+ const useAggregateRecalculate = preserveSearchContext && (selectedIds.length > 0 || searchedWalletAssetCodes.length > 0);
906
917
  const aggregateResult = useAggregateRecalculate ? await this.aggregateRecalculateWalletAssetsAsync(businessData, selectedIds) : null;
907
918
  const result = aggregateResult || (await this.initializeWalletDataFromBusinessAsync(businessData));
908
919
  if (requestSequence !== this.walletAssetsRequestSequence) {
@@ -920,7 +931,7 @@ class WalletPassPaymentImpl {
920
931
  this.searchResults = this.searchResults.filter(item => !missingSelectedKeys.has(getWalletAssetKey(item)));
921
932
  }
922
933
  }
923
- const searchedWalletAssets = preserveSelection ? this.syncSearchedWalletAssets([...(result.transformList || []), ...(result.noApplicableVoucher || [])]) : [];
934
+ const searchedWalletAssets = preserveSearchContext ? this.syncSearchedWalletAssets([...(result.transformList || []), ...(result.noApplicableVoucher || [])]) : [];
924
935
  if (requestSequence !== this.walletAssetsRequestSequence) {
925
936
  return this.getWalletAssetsState();
926
937
  }
@@ -1122,14 +1133,23 @@ class WalletPassPaymentImpl {
1122
1133
  code: normalizedCode,
1123
1134
  prepare_payments: this.formatWalletPassList2PreparePayments(this.walletAssetsState.selectedItems)
1124
1135
  });
1136
+ if (result.type === 'walletCode') {
1137
+ return {
1138
+ type: 'walletCode',
1139
+ data: result.data,
1140
+ asset: result.data,
1141
+ selected: false,
1142
+ state: this.getWalletAssetsState()
1143
+ };
1144
+ }
1125
1145
  const paymentAssets = this.formatSearchedWalletPaymentAssets(result.data.filter(isWalletPaymentAsset));
1126
1146
  if (paymentAssets.length > 0) {
1127
1147
  this.searchResults = mergeWalletAssets(this.searchResults, paymentAssets);
1128
1148
  }
1129
1149
  const asset = paymentAssets.find(item => String(item?.code || '') === normalizedCode) || paymentAssets[0];
1130
- if (!asset || result.type === 'walletCode') {
1150
+ if (!asset) {
1131
1151
  return {
1132
- type: result.type,
1152
+ type: 'normalCode',
1133
1153
  asset,
1134
1154
  selected: false,
1135
1155
  state: this.getWalletAssetsState()
@@ -397,6 +397,7 @@ export declare class BaseSalesImpl extends BaseModule implements Module {
397
397
  refreshWalletAssets(params?: {
398
398
  order_wait_pay_amount?: number;
399
399
  preserveSelection?: boolean;
400
+ retainSearchResultCodes?: string[];
400
401
  }): Promise<WalletAssetsState>;
401
402
  /** 选择或取消单张钱包资产,并立即同步 pending wallet payments。 */
402
403
  selectWalletAsset(params: {
@@ -3013,7 +3013,8 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
3013
3013
  await this.publishWalletAssetsState(clearedState);
3014
3014
  }
3015
3015
  const refreshTask = wallet.refreshWalletAssetsFromBusinessAsync(businessData, {
3016
- preserveSelection: params?.preserveSelection !== false && !customerChanged
3016
+ preserveSelection: params?.preserveSelection !== false && !customerChanged,
3017
+ retainSearchResultCodes: params?.retainSearchResultCodes
3017
3018
  });
3018
3019
  await this.publishWalletAssetsState(wallet.getWalletAssetsState());
3019
3020
  let state = await refreshTask;
@@ -326,7 +326,7 @@ export declare class BookingByStepImpl extends BaseModule implements Module {
326
326
  date: string;
327
327
  status: string;
328
328
  week: string;
329
- weekNum: 0 | 1 | 2 | 5 | 6 | 3 | 4;
329
+ weekNum: 0 | 2 | 1 | 3 | 4 | 5 | 6;
330
330
  }[]>;
331
331
  submitTimeSlot(timeSlots: TimeSliceItem): void;
332
332
  private getScheduleDataByIds;
@@ -345,7 +345,7 @@ export declare class BookingByStepImpl extends BaseModule implements Module {
345
345
  count: number;
346
346
  left: number;
347
347
  summaryCount: number;
348
- status: "sold_out" | "lots_of_space" | "filling_up_fast";
348
+ status: "lots_of_space" | "filling_up_fast" | "sold_out";
349
349
  }[];
350
350
  /**
351
351
  * 找到多个资源的公共可用时间段
@@ -33,6 +33,7 @@ export declare class BookingTicketImpl extends BaseSalesImpl implements Module {
33
33
  protected loadOpenDataInFlight: Promise<OpenDataConfig> | null;
34
34
  protected loadOpenDataInFlightTarget: string | null;
35
35
  protected openDataTarget: string | null;
36
+ private readonly customerResolveRequests;
36
37
  /**
37
38
  * 在 BaseSales 默认模块清单(products/order/salesSummary/schedule)基础上追加 customer + bookingContext。
38
39
  * bookingContext 是 SalesSdk 加车下沉所需的「预约 UI 上下文」(bookingConfig / resourcesOrigin / date)。
@@ -78,10 +79,8 @@ export declare class BookingTicketImpl extends BaseSalesImpl implements Module {
78
79
  resolveCustomerById(customerId: string | number): Promise<ICustomer | null>;
79
80
  /** 兼容既有优惠码扫码逻辑。 */
80
81
  private resolveCustomerByDiscountCustomerId;
81
- private findCustomerById;
82
82
  private normalizeCustomer;
83
83
  private getRawCustomerId;
84
- private hasUsableCustomerDetails;
85
84
  /**
86
85
  * 更新当前预约的 appointment_status。
87
86
  *
@@ -164,6 +164,7 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
164
164
  loadOpenDataInFlight = null;
165
165
  loadOpenDataInFlightTarget = null;
166
166
  openDataTarget = null;
167
+ customerResolveRequests = new Map();
167
168
 
168
169
  /**
169
170
  * 在 BaseSales 默认模块清单(products/order/salesSummary/schedule)基础上追加 customer + bookingContext。
@@ -442,7 +443,15 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
442
443
 
443
444
  // 缺 Holder 等场景 isAvailable=false,但仍需把 Pass 归属客户带入订单,供 Holder 弹窗使用。
444
445
  if (!hasOrderCustomer && extractedCustomerId && !isUserPlatform) {
445
- await this.hydrateOrderCustomerFromScanDiscounts(scannedList);
446
+ try {
447
+ await this.hydrateOrderCustomerFromScanDiscounts(scannedList);
448
+ } catch (error) {
449
+ // 客户映射失败不能阻断原有 Pass 的可用性、展示或优惠处理。
450
+ console.warn('[BookingTicket] scan discount customer resolve failed', {
451
+ customerId: extractedCustomerId,
452
+ error
453
+ });
454
+ }
446
455
  }
447
456
  if (raw.isAvailable) {
448
457
  await this.store.order.recalculateSummary({
@@ -493,43 +502,39 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
493
502
  return null;
494
503
  }
495
504
  async resolveCustomerById(customerId) {
496
- const localCustomers = this.store.customer.getCustomers();
497
- const localCustomer = this.findCustomerById(localCustomers, customerId);
498
- const normalizedLocalCustomer = localCustomer ? this.normalizeCustomer(localCustomer) : null;
499
- if (normalizedLocalCustomer && this.hasUsableCustomerDetails(normalizedLocalCustomer)) {
500
- return normalizedLocalCustomer;
501
- }
502
- const result = await this.store.customer.queryCustomerList({
503
- ids: [customerId],
504
- skip: 1,
505
- num: 1
506
- });
507
- const remoteCustomer = this.findCustomerById(result?.list || [], customerId) || result?.list?.[0] || null;
508
- if (remoteCustomer) {
509
- return this.normalizeCustomer(remoteCustomer);
510
- }
511
- try {
512
- const detailCustomer = await this.store.customer.queryCustomerDetail(customerId);
513
- return detailCustomer ? this.normalizeCustomer(detailCustomer) : null;
514
- } catch (error) {
515
- console.warn('[BookingTicket] resolveCustomerById detail request failed', {
516
- customerId,
517
- error
518
- });
505
+ const rawCustomerId = String(customerId ?? '').trim();
506
+ const numericCustomerId = Number(rawCustomerId);
507
+ if (!rawCustomerId || !Number.isInteger(numericCustomerId) || numericCustomerId <= 1) {
519
508
  return null;
520
509
  }
510
+ const normalizedCustomerId = String(numericCustomerId);
511
+ const existingRequest = this.customerResolveRequests.get(normalizedCustomerId);
512
+ if (existingRequest) return existingRequest;
513
+ const request = this.request.post(`/customer/resolve/${encodeURIComponent(normalizedCustomerId)}`, {
514
+ with_franchisor_data: 1,
515
+ with: ['latestWalletDetail.wallet', 'contactsInfo', 'formRecord']
516
+ }).then(result => {
517
+ const customer = result?.data;
518
+ const resolvedCustomerId = Number(customer && typeof customer === 'object' && !Array.isArray(customer) ? this.getRawCustomerId(customer) : NaN);
519
+ if (result?.status !== true || result?.code !== 200 || !customer || typeof customer !== 'object' || Array.isArray(customer) || !Number.isInteger(resolvedCustomerId) || resolvedCustomerId <= 1) {
520
+ const error = new Error(result?.message || 'Customer resolve failed');
521
+ error.code = result?.code;
522
+ throw error;
523
+ }
524
+ return this.normalizeCustomer(customer);
525
+ }).finally(() => {
526
+ if (this.customerResolveRequests.get(normalizedCustomerId) === request) {
527
+ this.customerResolveRequests.delete(normalizedCustomerId);
528
+ }
529
+ });
530
+ this.customerResolveRequests.set(normalizedCustomerId, request);
531
+ return request;
521
532
  }
522
533
 
523
534
  /** 兼容既有优惠码扫码逻辑。 */
524
535
  resolveCustomerByDiscountCustomerId(customerId) {
525
536
  return this.resolveCustomerById(customerId);
526
537
  }
527
- findCustomerById(customers = [], customerId) {
528
- return customers.find(customer => {
529
- const rawId = this.getRawCustomerId(customer);
530
- return rawId !== null && String(rawId) === String(customerId);
531
- }) || null;
532
- }
533
538
  normalizeCustomer(customer) {
534
539
  const raw = customer;
535
540
  const rawId = this.getRawCustomerId(customer);
@@ -547,9 +552,6 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
547
552
  const raw = customer;
548
553
  return customer.id ?? raw.customer_id ?? raw.customerId ?? null;
549
554
  }
550
- hasUsableCustomerDetails(customer) {
551
- return !!(customer.name || customer.phone || customer.email || customer.display_name || customer.customer_name);
552
- }
553
555
 
554
556
  /**
555
557
  * 更新当前预约的 appointment_status。
@@ -1669,6 +1671,7 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
1669
1671
  const result = await (0, _applyGlobalScan.applyGlobalScan)({
1670
1672
  setOrderCustomer: customer => this.setOrderCustomer(customer),
1671
1673
  getOrderCustomerSnapshot: () => this.getOrderCustomerSnapshot(),
1674
+ resolveCustomerById: customerId => this.resolveCustomerById(customerId),
1672
1675
  getBusinessCode: () => this.otherParams?.businessCode,
1673
1676
  buildAddProductCtx: extra => this.buildAddProductCtx(extra),
1674
1677
  decideAddProduct: (item, options) => this.decideAddProduct(item, options),
@@ -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';
@@ -5,10 +5,21 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.applyGlobalScan = applyGlobalScan;
7
7
  var _addProductDecision = require("../addProductDecision");
8
- var _cartView = require("../cartView");
9
8
  var _scanProductValues = require("./scanProductValues");
10
9
  /** applyGlobalScan 所需的 BookingTicket 运行时能力 */
11
10
 
11
+ function getRealCustomerId(customer) {
12
+ const customerId = Number(customer?.id ?? customer?.customer_id);
13
+ return Number.isInteger(customerId) && customerId > 1 ? customerId : undefined;
14
+ }
15
+ function getWalletAssetCustomerId(asset) {
16
+ const customerId = Number(asset?.customer_id ?? asset?.holder?.customer_id);
17
+ return Number.isInteger(customerId) && customerId > 1 ? customerId : undefined;
18
+ }
19
+ function getErrorMessage(error) {
20
+ return error instanceof Error ? error.message : String(error || '');
21
+ }
22
+
12
23
  /**
13
24
  * 处理单条扫码商品的加车决策(含弹窗链)。
14
25
  */
@@ -93,17 +104,55 @@ async function applyGlobalScan(host, formatted, bridge) {
93
104
  const {
94
105
  searchType,
95
106
  data,
96
- scanCode
107
+ scanCode,
108
+ customerId: formattedCustomerId,
109
+ isCrossShopWallet
97
110
  } = formatted;
98
111
  if (searchType === 'wallet') {
99
- if (!data) {
112
+ const currentCustomer = host.getOrderCustomerSnapshot();
113
+ if (getRealCustomerId(currentCustomer) !== undefined) {
114
+ return {
115
+ status: 'success',
116
+ kind: 'customer'
117
+ };
118
+ }
119
+ let customer = data;
120
+ if (isCrossShopWallet) {
121
+ const sourceCustomerId = getRealCustomerId({
122
+ id: formattedCustomerId
123
+ });
124
+ if (sourceCustomerId === undefined || !host.resolveCustomerById) {
125
+ bridge?.onNotify?.('fail', 'pisell2.text.scan-global-failed');
126
+ return {
127
+ status: 'failed',
128
+ reason: 'invalid_data'
129
+ };
130
+ }
131
+ try {
132
+ customer = await host.resolveCustomerById(sourceCustomerId);
133
+ } catch (error) {
134
+ bridge?.onNotify?.('fail', getErrorMessage(error) || 'pisell2.text.scan-global-failed');
135
+ return {
136
+ status: 'failed',
137
+ reason: 'invalid_data'
138
+ };
139
+ }
140
+ }
141
+ const customerId = getRealCustomerId(customer) ?? (!isCrossShopWallet ? getRealCustomerId({
142
+ id: formattedCustomerId
143
+ }) : undefined);
144
+ if (!customer || customerId === undefined) {
100
145
  bridge?.onNotify?.('fail', 'pisell2.text.scan-global-failed');
101
146
  return {
102
147
  status: 'failed',
103
148
  reason: 'invalid_data'
104
149
  };
105
150
  }
106
- host.setOrderCustomer(data);
151
+ const orderCustomer = customer.id === undefined || customer.id === null ? {
152
+ ...customer,
153
+ id: customerId
154
+ } : customer;
155
+ host.setOrderCustomer(orderCustomer);
107
156
  bridge?.onNotify?.('success', 'pisell2.text.add-client-success');
108
157
  return {
109
158
  status: 'success',
@@ -112,7 +161,58 @@ async function applyGlobalScan(host, formatted, bridge) {
112
161
  }
113
162
  if (searchType === 'walletPass') {
114
163
  const snapshot = host.getOrderCustomerSnapshot();
115
- const scannedCustomerIsWalkIn = (0, _cartView.isWalkInCustomer)(data);
164
+ const currentCustomerIsWalkIn = getRealCustomerId(snapshot) === undefined;
165
+ let activeCustomer = snapshot;
166
+ const bindWalletPassCustomer = async asset => {
167
+ if (!currentCustomerIsWalkIn) {
168
+ return {
169
+ resolveFailed: false
170
+ };
171
+ }
172
+ const assetCustomerId = getWalletAssetCustomerId(asset) ?? getRealCustomerId({
173
+ id: formattedCustomerId
174
+ });
175
+ let customer = null;
176
+ if (assetCustomerId !== undefined) {
177
+ if (!host.resolveCustomerById) {
178
+ bridge?.onNotify?.('fail', 'pisell2.text.scan-global-failed');
179
+ return {
180
+ resolveFailed: true
181
+ };
182
+ }
183
+ try {
184
+ customer = await host.resolveCustomerById(assetCustomerId);
185
+ } catch (error) {
186
+ console.warn('[GlobalScan] Wallet asset customer lookup failed', error);
187
+ bridge?.onNotify?.('fail', getErrorMessage(error) || 'pisell2.text.scan-global-failed');
188
+ return {
189
+ resolveFailed: true
190
+ };
191
+ }
192
+ }
193
+ const customerId = getRealCustomerId(customer);
194
+ if (!customer || customerId === undefined) {
195
+ if (assetCustomerId !== undefined) {
196
+ bridge?.onNotify?.('fail', 'pisell2.text.scan-global-failed');
197
+ }
198
+ return {
199
+ resolveFailed: assetCustomerId !== undefined
200
+ };
201
+ }
202
+ const orderCustomer = customer.id === undefined || customer.id === null ? {
203
+ ...customer,
204
+ id: customerId
205
+ } : customer;
206
+ host.setOrderCustomer(orderCustomer);
207
+ activeCustomer = orderCustomer;
208
+ await host.refreshWalletAssets?.({
209
+ preserveSelection: false,
210
+ retainSearchResultCodes: [scanCode]
211
+ });
212
+ return {
213
+ resolveFailed: false
214
+ };
215
+ };
116
216
  const trySelectWalletAsset = async () => {
117
217
  if (!host.scanWalletAsset) return null;
118
218
  const walletResult = await host.scanWalletAsset(scanCode).catch(error => {
@@ -123,61 +223,46 @@ async function applyGlobalScan(host, formatted, bridge) {
123
223
  if (walletResult.type !== 'normalCode' || !walletResult.asset) {
124
224
  return null;
125
225
  }
226
+ const bindResult = await bindWalletPassCustomer(walletResult.asset);
126
227
  if (walletResult.selected) {
127
- bridge?.onNotify?.('success');
228
+ if (!bindResult.resolveFailed) {
229
+ bridge?.onNotify?.('success');
230
+ }
128
231
  return {
129
232
  status: 'success',
130
233
  kind: 'promotion'
131
234
  };
132
235
  }
133
236
  // 已识别为 Wallet 支付资产但当前不可用:保留禁用展示,不回退优惠码。
134
- bridge?.onNotify?.('fail', 'pisell2.text.goodpass.code-invalid-cart');
237
+ if (!bindResult.resolveFailed) {
238
+ bridge?.onNotify?.('fail', 'pisell2.text.goodpass.code-invalid-cart');
239
+ }
135
240
  return {
136
241
  status: 'failed',
137
242
  reason: 'invalid_data'
138
243
  };
139
244
  };
140
- if ((0, _cartView.isWalkInCustomer)(snapshot) || scannedCustomerIsWalkIn) {
141
- // 当前购物车或 walletPass 绑定客户为 walk-in 时不绑客户,避免覆盖已有下单客户。
142
- const walletSelection = await trySelectWalletAsset();
143
- if (walletSelection) return walletSelection;
144
- if (!bridge?.applyPromotionCode) {
145
- return {
146
- status: 'failed',
147
- reason: 'no_bridge'
148
- };
149
- }
150
- const result = await bridge.applyPromotionCode(scanCode);
151
- if (result?.isAccepted ?? result?.isAvailable) {
152
- return {
153
- status: 'success',
154
- kind: 'promotion'
155
- };
156
- }
245
+ const walletSelection = await trySelectWalletAsset();
246
+ if (walletSelection) return walletSelection;
247
+
248
+ // 非支付类 Wallet Pass 仍可能由 batch-search 返回所属客户。
249
+ await bindWalletPassCustomer();
250
+ if (!bridge?.applyPromotionCode) {
157
251
  return {
158
252
  status: 'failed',
159
- reason: 'invalid_data'
253
+ reason: 'no_bridge'
160
254
  };
161
255
  }
162
- host.setOrderCustomer(data);
163
- await host.refreshWalletAssets?.({
164
- preserveSelection: false
165
- });
166
- const walletSelection = await trySelectWalletAsset();
167
- if (walletSelection) return walletSelection;
168
- if (bridge?.applyPromotionCode) {
169
- const promotionResult = await bridge.applyPromotionCode(scanCode, Number(data?.id ?? data?.customer_id) || undefined);
170
- if (promotionResult?.isAccepted ?? promotionResult?.isAvailable) {
171
- return {
172
- status: 'success',
173
- kind: 'promotion'
174
- };
175
- }
256
+ const promotionResult = await bridge.applyPromotionCode(scanCode, getRealCustomerId(activeCustomer));
257
+ if (promotionResult?.isAccepted ?? promotionResult?.isAvailable) {
258
+ return {
259
+ status: 'success',
260
+ kind: 'promotion'
261
+ };
176
262
  }
177
- bridge?.onNotify?.('success', 'pisell2.text.add-client-success');
178
263
  return {
179
- status: 'success',
180
- kind: 'customer'
264
+ status: 'failed',
265
+ reason: 'invalid_data'
181
266
  };
182
267
  }
183
268
  if (searchType === 'product' || searchType === 'local_product') {
@@ -14,6 +14,7 @@ const searchWalletPass = async (request, code) => {
14
14
  // 翻译识别码名称 0:不翻译 1:翻译
15
15
  relation_product: 1,
16
16
  // 组装关联商品: 0不处理 1处理
17
+ with_franchisor_data: 1,
17
18
  with: ['customer'],
18
19
  tags: ['point_card',
19
20
  // 积分卡
@@ -51,17 +52,18 @@ exports.searchWalletPass = searchWalletPass;
51
52
  const searchWallet = async (request, code) => {
52
53
  const params = {
53
54
  code,
54
- with_customer: 1
55
+ with_customer: 1,
56
+ with_franchisor_data: 1
55
57
  };
56
58
  try {
57
59
  const res = await request?.post('/wallet/detail/search', params);
58
- if (res?.code == 200 && !!res?.data) {
60
+ if (res?.status === true && res?.code === 200 && !!res?.data) {
59
61
  return {
60
62
  searchType: 'wallet',
61
63
  response: res
62
64
  };
63
65
  }
64
- throw new Error('没有找到对应的识别码');
66
+ throw new Error(res?.message || '没有找到对应的识别码');
65
67
  } catch (error) {
66
68
  throw error;
67
69
  }
@@ -6,6 +6,10 @@ export interface GlobalScanFormattedResult {
6
6
  searchType: GlobalScanSearchType;
7
7
  data: any;
8
8
  scanCode: string;
9
+ /** Wallet / Wallet Pass 来源客户 ID;跨店时必须先解析为当前店客户。 */
10
+ customerId?: string | number;
11
+ /** 仅 Wallet 返回;true 时禁止直接使用响应内的来源客户。 */
12
+ isCrossShopWallet?: boolean;
9
13
  }
10
14
  interface RawScanSearchResult {
11
15
  searchType?: GlobalScanSearchType;