@pisell/pisellos 0.10.68 → 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.
Files changed (26) hide show
  1. package/dist/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +5 -4
  2. package/dist/modules/Holder/utils.d.ts +2 -0
  3. package/dist/modules/Holder/utils.js +32 -2
  4. package/dist/modules/Rules/index.js +59 -58
  5. package/dist/server/modules/products/index.js +3 -2
  6. package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +3 -2
  7. package/dist/solution/BookingByStep/index.d.ts +1 -1
  8. package/dist/solution/BookingTicket/index.js +177 -112
  9. package/dist/solution/BookingTicket/types.d.ts +14 -4
  10. package/dist/solution/BookingTicket/utils/scan/applyGlobalScan.js +94 -58
  11. package/dist/solution/BookingTicket/utils/scan/applyProductVoucherCollectionScan.d.ts +10 -0
  12. package/dist/solution/BookingTicket/utils/scan/applyProductVoucherCollectionScan.js +117 -0
  13. package/lib/model/strategy/adapter/promotion/index.js +46 -1
  14. package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +2 -1
  15. package/lib/modules/Holder/utils.d.ts +2 -0
  16. package/lib/modules/Holder/utils.js +27 -1
  17. package/lib/modules/Rules/index.js +2 -1
  18. package/lib/server/modules/products/index.js +2 -1
  19. package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +9 -8
  20. package/lib/solution/BookingByStep/index.d.ts +1 -1
  21. package/lib/solution/BookingTicket/index.js +22 -2
  22. package/lib/solution/BookingTicket/types.d.ts +14 -4
  23. package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.js +14 -2
  24. package/lib/solution/BookingTicket/utils/scan/applyProductVoucherCollectionScan.d.ts +10 -0
  25. package/lib/solution/BookingTicket/utils/scan/applyProductVoucherCollectionScan.js +71 -0
  26. package/package.json +1 -1
@@ -5,8 +5,9 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.transformBaseProductToOrderProduct = transformBaseProductToOrderProduct;
7
7
  var _weighedProduct = require("../../../modules/Order/utils/weighedProduct");
8
+ var _utils = require("../../../modules/Holder/utils");
8
9
  var _serviceTimes = require("../../../modules/BookingContext/utils/serviceTimes");
9
- var _utils = require("../../../modules/Order/utils");
10
+ var _utils2 = require("../../../modules/Order/utils");
10
11
  var _manualProductDiscount = require("../../../modules/Order/utils/manualProductDiscount");
11
12
  function toNumber(value, fallback = 0) {
12
13
  const parsedValue = Number(value);
@@ -109,7 +110,7 @@ function normalizeBundleItems(bundleItems, preferPriceField = false, preferBundl
109
110
  discount_list: preferPriceField ? [] : bundleItem.discount_list
110
111
  };
111
112
  // bundle_selling_price 落「正净额」:markdown = |price| − Σoptions;markup/原价维持原值。
112
- normalizedItem.bundle_selling_price = (0, _utils.getBundleSellingMagnitude)(magnitudeSource).toFixed(2);
113
+ normalizedItem.bundle_selling_price = (0, _utils2.getBundleSellingMagnitude)(magnitudeSource).toFixed(2);
113
114
  if ((normalizedItem.price_type ?? normalizedItem.custom_price_type) === 'markdown' && (normalizedItem.price_type_ext === '' || normalizedItem.price_type_ext === undefined || normalizedItem.price_type_ext === null)) {
114
115
  normalizedItem.custom_price = normalizedItem.custom_price ?? normalizedItem.price;
115
116
  normalizedItem.price = normalizedItem.bundle_selling_price;
@@ -159,14 +160,14 @@ function transformBaseProductToOrderProduct(params) {
159
160
  const explicitLineTotal = payload.line_total ?? payload.total ?? payload._extend?.total;
160
161
  const hasExplicitLineTotal = Number.isFinite(Number(explicitLineTotal));
161
162
  const lineCompositeTotal = toPriceString(explicitLineTotal, sourceProductPrice);
162
- const productOptionItem = (0, _utils.normalizeProductSkuOptions)(normalizeOptionItems(payload.product_sku?.option ?? payload.option ?? payload.options ?? payload.product_option_item, hasPriceOverride || weighedSourcePrice !== undefined));
163
+ const productOptionItem = (0, _utils2.normalizeProductSkuOptions)(normalizeOptionItems(payload.product_sku?.option ?? payload.option ?? payload.options ?? payload.product_option_item, hasPriceOverride || weighedSourcePrice !== undefined));
163
164
  const productBundle = normalizeBundleItems(payload.bundle ?? payload.product_bundle, hasPriceOverride, hasPriceOverride && weighedSourcePrice !== undefined);
164
165
  // `payload.unique` 是业务侧商品/票务标识,可能在连续加同一商品时重复;
165
166
  // 行级唯一值只接受显式协议字段,缺省交给 OrderModule 生成。
166
167
  const uniqueIdentificationNumber = payload.unique_identification_number;
167
168
  const resolvedQuantity = toSafePositiveInt(payload.quantity ?? payload.num, 1);
168
- const originTotal = weighedSourcePrice !== undefined ? Number((0, _utils.composeLinePrice)({
169
- mainPrice: Number(weighedSourcePrice) + (0, _utils.sumOptionUnitPrice)(productOptionItem).toNumber(),
169
+ const originTotal = weighedSourcePrice !== undefined ? Number((0, _utils2.composeLinePrice)({
170
+ mainPrice: Number(weighedSourcePrice) + (0, _utils2.sumOptionUnitPrice)(productOptionItem).toNumber(),
170
171
  bundle: hasPriceOverride ? (0, _weighedProduct.withOriginalBundleOptionPrices)(productBundle) : productBundle,
171
172
  useOriginalBundle: true
172
173
  })) : (0, _manualProductDiscount.resolveManualDiscountOriginTotal)({
@@ -198,9 +199,9 @@ function transformBaseProductToOrderProduct(params) {
198
199
  })) : payload.discount_list || [];
199
200
 
200
201
  // v2:source 不含 option;main_product_* 含 option(与 normalizeOrderProduct 一致)
201
- const optionSum = (0, _utils.sumOptionUnitPrice)(productOptionItem).toNumber();
202
+ const optionSum = (0, _utils2.sumOptionUnitPrice)(productOptionItem).toNumber();
202
203
  const mainProductOriginalPrice = toPriceString(Number(sourceProductPrice) + optionSum);
203
- const bundleOnlyComposite = (0, _utils.composeLinePrice)({
204
+ const bundleOnlyComposite = (0, _utils2.composeLinePrice)({
204
205
  mainPrice: 0,
205
206
  bundle: productBundle
206
207
  });
@@ -221,7 +222,7 @@ function transformBaseProductToOrderProduct(params) {
221
222
  main_product_selling_price: mainProductSellingPrice,
222
223
  main_product_original_price: mainProductOriginalPrice,
223
224
  price_schema_version: 2,
224
- holder_config: sourceProduct?.holder_config,
225
+ holder_config: (0, _utils.resolveProductHolderConfig)(sourceProduct) ?? undefined,
225
226
  ...(dataVariantIds !== undefined ? {
226
227
  data_variant_ids: dataVariantIds
227
228
  } : {}),
@@ -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 | 6 | 5 | 1 | 2 | 3 | 4;
329
+ weekNum: 0 | 2 | 1 | 6 | 4 | 5 | 3;
330
330
  }[]>;
331
331
  submitTimeSlot(timeSlots: TimeSliceItem): void;
332
332
  private getScheduleDataByIds;
@@ -30,6 +30,7 @@ var _handleScan = require("./utils/scan/handleScan");
30
30
  var _scanCache = _interopRequireDefault(require("./utils/scan/scanCache"));
31
31
  var _formatGlobalScan = require("./utils/scan/formatGlobalScan");
32
32
  var _applyGlobalScan = require("./utils/scan/applyGlobalScan");
33
+ var _applyProductVoucherCollectionScan = require("./utils/scan/applyProductVoucherCollectionScan");
33
34
  var _cartView = require("./utils/cartView");
34
35
  var _addProductDecision = require("./utils/addProductDecision");
35
36
  var _buildCacheItemFromOrderLine = require("../../modules/BookingContext/utils/buildCacheItemFromOrderLine");
@@ -1847,7 +1848,7 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
1847
1848
  reason: 'not_found'
1848
1849
  };
1849
1850
  }
1850
- const result = await (0, _applyGlobalScan.applyGlobalScan)({
1851
+ const host = {
1851
1852
  setOrderCustomer: customer => {
1852
1853
  beforeScanCommit();
1853
1854
  this.setOrderCustomer(customer);
@@ -1868,10 +1869,29 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
1868
1869
  beforeScanCommit();
1869
1870
  return this.scanWalletAsset(scanCode);
1870
1871
  }
1871
- }, formatted, guardedBridge);
1872
+ };
1873
+ const result = guardedBridge?.scanScope === 'product-voucher-collection' ? await (0, _applyProductVoucherCollectionScan.applyProductVoucherCollectionScan)({
1874
+ ...host,
1875
+ waitForCustomerDiscounts: () => this.orderCustomerDiscountRefreshInFlight || Promise.resolve(),
1876
+ loadCustomerDiscounts: async () => {
1877
+ const customerId = Number(this.getOrderCustomer()?.customer_id || 0);
1878
+ if (customerId <= 1) return [];
1879
+ await this.refreshDiscountConfigAfterOrderCustomerChange(customerId);
1880
+ return this.getDiscountList();
1881
+ }
1882
+ }, formatted, guardedBridge) : await (0, _applyGlobalScan.applyGlobalScan)(host, formatted, guardedBridge);
1872
1883
  bridge?.refresh?.();
1873
1884
  return result;
1874
1885
  } catch (error) {
1886
+ if (bridge?.scanScope === 'product-voucher-collection') {
1887
+ if (bridge.isCurrent?.() === false) return {
1888
+ status: 'cancelled'
1889
+ };
1890
+ return {
1891
+ status: 'failed',
1892
+ reason: 'entry_resolution_failed'
1893
+ };
1894
+ }
1875
1895
  console.error('[BookingTicket] handleGlobalScanCode failed', error);
1876
1896
  bridge?.onNotify?.('fail', 'pisell2.text.scan-global-failed');
1877
1897
  return {
@@ -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.68",
4
+ "version": "0.10.70",
5
5
  "description": "一个可扩展的前端模块化SDK框架,支持插件系统",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",