@pisell/pisellos 0.10.69 → 0.10.70

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.
@@ -208,8 +208,10 @@ export interface AddAddTimeProductsToBookingInput {
208
208
  }
209
209
  /** 扫码宿主桥接:SalesSdk 在 pubsub 订阅回调里注入,OS 需要 UI 时直接调用 */
210
210
  export interface GlobalScanHostBridge {
211
- /** Restrict redemption scans before customer/product side effects. */
212
- scanScope?: 'all' | 'product-voucher';
211
+ /** product-voucher handles voucher codes only; product-voucher-collection also resolves wallet customer vouchers. */
212
+ scanScope?: 'all' | 'product-voucher' | 'product-voucher-collection';
213
+ /** A one-shot entry may outlive its page while requests are in flight. */
214
+ isCurrent?: () => boolean;
213
215
  openProductDetail?: (payload: Record<string, any>) => Promise<any | null>;
214
216
  openBookingEdit?: (payload: Record<string, any>) => Promise<any | null>;
215
217
  confirmDetail?: (input: {
@@ -234,11 +236,13 @@ export interface GlobalScanHostBridge {
234
236
  */
235
237
  applyPromotionCode?: (code: string, customerId?: number, options?: {
236
238
  suppressFailureNotification?: boolean;
239
+ productVouchersOnly?: boolean;
237
240
  }) => Promise<{
238
241
  isAvailable?: boolean;
239
242
  isAccepted?: boolean;
240
243
  type?: string;
241
244
  unavailableReason?: string;
245
+ scannedDiscountList?: Record<string, any>[];
242
246
  }>;
243
247
  /** 当前订单已有其他客户时,由宿主复用客户组件完成确认及切换。 */
244
248
  requestCustomerSwitch?: (customer: ICustomer) => Promise<boolean | null>;
@@ -246,7 +250,7 @@ export interface GlobalScanHostBridge {
246
250
  refresh?: () => void;
247
251
  }
248
252
  /** handleGlobalScanCode 结构化返回 */
249
- export type GlobalScanHandleResult = {
253
+ export type GlobalScanHandleResult = ({
250
254
  status: 'success';
251
255
  kind: 'customer' | 'products' | 'promotion';
252
256
  meta?: {
@@ -259,5 +263,11 @@ export type GlobalScanHandleResult = {
259
263
  status: 'cancelled';
260
264
  } | {
261
265
  status: 'failed';
262
- reason: 'empty_code' | 'not_found' | 'invalid_data' | 'no_bridge';
266
+ reason: 'empty_code' | 'not_found' | 'invalid_data' | 'no_bridge' | 'entry_resolution_failed';
267
+ }) & {
268
+ /** product-voucher-collection returns voucher candidates; the caller decides how to use them. */
269
+ redemption?: {
270
+ source: 'wallet' | 'scan';
271
+ vouchers: Record<string, any>[];
272
+ };
263
273
  };
@@ -289,6 +289,18 @@ async function applyGlobalScan(host, formatted, bridge) {
289
289
  // batch-search 可能返回来源门店客户;先映射到当前门店,确保 Wallet
290
290
  // 支付资产查询使用本店客户上下文。
291
291
  const bindResult = await bindWalletPassCustomer();
292
+ // Voucher collection prioritizes goods coupons resolved from the wristband/order.
293
+ // Only this opt-in flow checks them before selecting a payment wallet asset.
294
+ const collectionPromotion = bridge?.scanScope === 'product-voucher-collection' && bridge.applyPromotionCode ? await bridge.applyPromotionCode(scanCode, getRealCustomerId(activeCustomer), {
295
+ suppressFailureNotification: true,
296
+ productVouchersOnly: true
297
+ }) : undefined;
298
+ if (collectionPromotion?.isAccepted ?? collectionPromotion?.isAvailable) {
299
+ return {
300
+ status: 'success',
301
+ kind: 'promotion'
302
+ };
303
+ }
292
304
  const walletSelection = await trySelectWalletAsset();
293
305
  if (walletSelection) return walletSelection;
294
306
  if (!bridge?.applyPromotionCode) {
@@ -298,9 +310,9 @@ async function applyGlobalScan(host, formatted, bridge) {
298
310
  };
299
311
  }
300
312
  const promotionCustomerId = getRealCustomerId(activeCustomer);
301
- const promotionResult = bindResult.customerBound ? await bridge.applyPromotionCode(scanCode, promotionCustomerId, {
313
+ const promotionResult = collectionPromotion ?? (bindResult.customerBound ? await bridge.applyPromotionCode(scanCode, promotionCustomerId, {
302
314
  suppressFailureNotification: true
303
- }) : await bridge.applyPromotionCode(scanCode, promotionCustomerId);
315
+ }) : await bridge.applyPromotionCode(scanCode, promotionCustomerId));
304
316
  if (promotionResult?.isAccepted ?? promotionResult?.isAvailable) {
305
317
  return {
306
318
  status: 'success',
@@ -0,0 +1,10 @@
1
+ import type { GlobalScanHandleResult, GlobalScanHostBridge } from '../../types';
2
+ import { type GlobalScanApplyHost } from './applyGlobalScan';
3
+ import type { GlobalScanFormattedResult } from './formatGlobalScan';
4
+ export interface ProductVoucherCollectionScanHost extends GlobalScanApplyHost {
5
+ waitForCustomerDiscounts(): Promise<void>;
6
+ loadCustomerDiscounts(): Promise<Record<string, any>[]>;
7
+ }
8
+ /** Collect voucher candidates after customer binding and coupon loading finish.
9
+ * Never expand a wristband's result to other orders of the same customer. */
10
+ export declare function applyProductVoucherCollectionScan(host: ProductVoucherCollectionScanHost, formatted: GlobalScanFormattedResult, bridge: GlobalScanHostBridge): Promise<GlobalScanHandleResult>;
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.applyProductVoucherCollectionScan = applyProductVoucherCollectionScan;
7
+ var _applyGlobalScan = require("./applyGlobalScan");
8
+ /** Collect voucher candidates after customer binding and coupon loading finish.
9
+ * Never expand a wristband's result to other orders of the same customer. */
10
+ async function applyProductVoucherCollectionScan(host, formatted, bridge) {
11
+ const assertCurrent = () => {
12
+ if (bridge.isCurrent?.() === false) {
13
+ throw new DOMException('Scan session ended', 'AbortError');
14
+ }
15
+ };
16
+ let vouchers = [];
17
+ assertCurrent();
18
+ const result = await (0, _applyGlobalScan.applyGlobalScan)({
19
+ ...host,
20
+ setOrderCustomer: customer => {
21
+ assertCurrent();
22
+ host.setOrderCustomer(customer);
23
+ },
24
+ addProductToOrder: (product, booking) => {
25
+ assertCurrent();
26
+ return host.addProductToOrder(product, booking);
27
+ }
28
+ }, formatted, {
29
+ ...bridge,
30
+ applyPromotionCode: bridge.applyPromotionCode && (async (code, customerId, options) => {
31
+ // Customer binding starts prepare/config in the background. Let it finish
32
+ // before adding scanned coupons so its response cannot overwrite them.
33
+ await host.waitForCustomerDiscounts();
34
+ assertCurrent();
35
+ const promotion = await bridge.applyPromotionCode(code, customerId, {
36
+ ...options,
37
+ productVouchersOnly: true,
38
+ suppressFailureNotification: true
39
+ });
40
+ assertCurrent();
41
+ // An empty successful search is different from a swallowed request error.
42
+ if (!Array.isArray(promotion.scannedDiscountList)) {
43
+ throw new Error('Could not resolve scanned vouchers');
44
+ }
45
+ vouchers = promotion.scannedDiscountList;
46
+ return promotion;
47
+ })
48
+ });
49
+ assertCurrent();
50
+ if (formatted.searchType === 'wallet' && result.status === 'success') {
51
+ vouchers = await host.loadCustomerDiscounts();
52
+ assertCurrent();
53
+ return {
54
+ ...result,
55
+ redemption: {
56
+ source: 'wallet',
57
+ vouchers
58
+ }
59
+ };
60
+ }
61
+ if (formatted.searchType === 'walletPass') {
62
+ return {
63
+ ...result,
64
+ redemption: {
65
+ source: 'scan',
66
+ vouchers
67
+ }
68
+ };
69
+ }
70
+ return result;
71
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "private": false,
3
3
  "name": "@pisell/pisellos",
4
- "version": "0.10.69",
4
+ "version": "0.10.70",
5
5
  "description": "一个可扩展的前端模块化SDK框架,支持插件系统",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",