@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
@@ -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,81 @@ async function applyGlobalScan(host, formatted, bridge) {
93
104
  const {
94
105
  searchType,
95
106
  data,
96
- scanCode
107
+ scanCode,
108
+ customerId: formattedCustomerId,
109
+ isCrossShopWallet,
110
+ isWalletPassUnavailableAtStore
97
111
  } = formatted;
98
112
  if (searchType === 'wallet') {
99
- if (!data) {
113
+ const currentCustomer = host.getOrderCustomerSnapshot();
114
+ const currentCustomerId = getRealCustomerId(currentCustomer);
115
+ let customer = data;
116
+ if (isCrossShopWallet) {
117
+ const sourceCustomerId = getRealCustomerId({
118
+ id: formattedCustomerId
119
+ });
120
+ if (sourceCustomerId === undefined || !host.resolveCustomerById) {
121
+ bridge?.onNotify?.('fail', 'pisell2.text.scan-global-failed');
122
+ return {
123
+ status: 'failed',
124
+ reason: 'invalid_data'
125
+ };
126
+ }
127
+ try {
128
+ customer = await host.resolveCustomerById(sourceCustomerId);
129
+ } catch (error) {
130
+ bridge?.onNotify?.('fail', getErrorMessage(error) || 'pisell2.text.scan-global-failed');
131
+ return {
132
+ status: 'failed',
133
+ reason: 'invalid_data'
134
+ };
135
+ }
136
+ }
137
+ const customerId = getRealCustomerId(customer) ?? (!isCrossShopWallet ? getRealCustomerId({
138
+ id: formattedCustomerId
139
+ }) : undefined);
140
+ if (!customer || customerId === undefined) {
100
141
  bridge?.onNotify?.('fail', 'pisell2.text.scan-global-failed');
101
142
  return {
102
143
  status: 'failed',
103
144
  reason: 'invalid_data'
104
145
  };
105
146
  }
106
- host.setOrderCustomer(data);
147
+ const orderCustomer = customer.id === undefined || customer.id === null ? {
148
+ ...customer,
149
+ id: customerId
150
+ } : customer;
151
+ if (currentCustomerId === customerId) {
152
+ return {
153
+ status: 'success',
154
+ kind: 'customer'
155
+ };
156
+ }
157
+ if (currentCustomerId !== undefined) {
158
+ // 未接入新桥接的宿主保持原行为:已有客户时不直接覆盖。
159
+ if (!bridge?.requestCustomerSwitch) {
160
+ return {
161
+ status: 'success',
162
+ kind: 'customer'
163
+ };
164
+ }
165
+ try {
166
+ const switched = await bridge.requestCustomerSwitch(orderCustomer);
167
+ return switched ? {
168
+ status: 'success',
169
+ kind: 'customer'
170
+ } : {
171
+ status: 'cancelled'
172
+ };
173
+ } catch (error) {
174
+ bridge.onNotify?.('fail', getErrorMessage(error) || 'pisell2.text.scan-global-failed');
175
+ return {
176
+ status: 'failed',
177
+ reason: 'invalid_data'
178
+ };
179
+ }
180
+ }
181
+ host.setOrderCustomer(orderCustomer);
107
182
  bridge?.onNotify?.('success', 'pisell2.text.add-client-success');
108
183
  return {
109
184
  status: 'success',
@@ -111,8 +186,70 @@ async function applyGlobalScan(host, formatted, bridge) {
111
186
  };
112
187
  }
113
188
  if (searchType === 'walletPass') {
189
+ if (isWalletPassUnavailableAtStore) {
190
+ bridge?.onNotify?.('fail', 'pisell2.text.wallet-pass-not-available-at-store');
191
+ return {
192
+ status: 'failed',
193
+ reason: 'invalid_data'
194
+ };
195
+ }
114
196
  const snapshot = host.getOrderCustomerSnapshot();
115
- const scannedCustomerIsWalkIn = (0, _cartView.isWalkInCustomer)(data);
197
+ let activeCustomer = snapshot;
198
+ const bindWalletPassCustomer = async asset => {
199
+ if (getRealCustomerId(activeCustomer) !== undefined) {
200
+ return {
201
+ resolveFailed: false,
202
+ customerBound: false
203
+ };
204
+ }
205
+ const assetCustomerId = getWalletAssetCustomerId(asset) ?? getRealCustomerId({
206
+ id: formattedCustomerId
207
+ });
208
+ let customer = null;
209
+ if (assetCustomerId !== undefined) {
210
+ if (!host.resolveCustomerById) {
211
+ bridge?.onNotify?.('fail', 'pisell2.text.scan-global-failed');
212
+ return {
213
+ resolveFailed: true,
214
+ customerBound: false
215
+ };
216
+ }
217
+ try {
218
+ customer = await host.resolveCustomerById(assetCustomerId);
219
+ } catch (error) {
220
+ console.warn('[GlobalScan] Wallet asset customer lookup failed', error);
221
+ bridge?.onNotify?.('fail', getErrorMessage(error) || 'pisell2.text.scan-global-failed');
222
+ return {
223
+ resolveFailed: true,
224
+ customerBound: false
225
+ };
226
+ }
227
+ }
228
+ const customerId = getRealCustomerId(customer);
229
+ if (!customer || customerId === undefined) {
230
+ if (assetCustomerId !== undefined) {
231
+ bridge?.onNotify?.('fail', 'pisell2.text.scan-global-failed');
232
+ }
233
+ return {
234
+ resolveFailed: assetCustomerId !== undefined,
235
+ customerBound: false
236
+ };
237
+ }
238
+ const orderCustomer = customer.id === undefined || customer.id === null ? {
239
+ ...customer,
240
+ id: customerId
241
+ } : customer;
242
+ host.setOrderCustomer(orderCustomer);
243
+ activeCustomer = orderCustomer;
244
+ await host.refreshWalletAssets?.({
245
+ preserveSelection: false,
246
+ retainSearchResultCodes: [scanCode]
247
+ });
248
+ return {
249
+ resolveFailed: false,
250
+ customerBound: true
251
+ };
252
+ };
116
253
  const trySelectWalletAsset = async () => {
117
254
  if (!host.scanWalletAsset) return null;
118
255
  const walletResult = await host.scanWalletAsset(scanCode).catch(error => {
@@ -123,61 +260,56 @@ async function applyGlobalScan(host, formatted, bridge) {
123
260
  if (walletResult.type !== 'normalCode' || !walletResult.asset) {
124
261
  return null;
125
262
  }
263
+ const bindResult = await bindWalletPassCustomer(walletResult.asset);
126
264
  if (walletResult.selected) {
127
- bridge?.onNotify?.('success');
265
+ if (!bindResult.resolveFailed) {
266
+ bridge?.onNotify?.('success');
267
+ }
128
268
  return {
129
269
  status: 'success',
130
270
  kind: 'promotion'
131
271
  };
132
272
  }
133
273
  // 已识别为 Wallet 支付资产但当前不可用:保留禁用展示,不回退优惠码。
134
- bridge?.onNotify?.('fail', 'pisell2.text.goodpass.code-invalid-cart');
274
+ if (!bindResult.resolveFailed) {
275
+ bridge?.onNotify?.('fail', 'pisell2.text.goodpass.code-invalid-cart');
276
+ }
135
277
  return {
136
278
  status: 'failed',
137
279
  reason: 'invalid_data'
138
280
  };
139
281
  };
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
- }
282
+
283
+ // batch-search 可能返回来源门店客户;先映射到当前门店,确保 Wallet
284
+ // 支付资产查询使用本店客户上下文。
285
+ const bindResult = await bindWalletPassCustomer();
286
+ const walletSelection = await trySelectWalletAsset();
287
+ if (walletSelection) return walletSelection;
288
+ if (!bridge?.applyPromotionCode) {
157
289
  return {
158
290
  status: 'failed',
159
- reason: 'invalid_data'
291
+ reason: 'no_bridge'
160
292
  };
161
293
  }
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
- }
294
+ const promotionCustomerId = getRealCustomerId(activeCustomer);
295
+ const promotionResult = bindResult.customerBound ? await bridge.applyPromotionCode(scanCode, promotionCustomerId, {
296
+ suppressFailureNotification: true
297
+ }) : await bridge.applyPromotionCode(scanCode, promotionCustomerId);
298
+ if (promotionResult?.isAccepted ?? promotionResult?.isAvailable) {
299
+ return {
300
+ status: 'success',
301
+ kind: 'promotion'
302
+ };
303
+ }
304
+ if (bindResult.customerBound) {
305
+ return {
306
+ status: 'success',
307
+ kind: 'customer'
308
+ };
176
309
  }
177
- bridge?.onNotify?.('success', 'pisell2.text.add-client-success');
178
310
  return {
179
- status: 'success',
180
- kind: 'customer'
311
+ status: 'failed',
312
+ reason: 'invalid_data'
181
313
  };
182
314
  }
183
315
  if (searchType === 'product' || searchType === 'local_product') {
@@ -14,7 +14,8 @@ const searchWalletPass = async (request, code) => {
14
14
  // 翻译识别码名称 0:不翻译 1:翻译
15
15
  relation_product: 1,
16
16
  // 组装关联商品: 0不处理 1处理
17
- with: ['customer'],
17
+ with_franchisor_data: 1,
18
+ with: ['customer', 'franchiseeWalletPass:shop_id,source_shop_id,product_id,use'],
18
19
  tags: ['point_card',
19
20
  // 积分卡
20
21
  'gift_card',
@@ -25,6 +26,8 @@ const searchWalletPass = async (request, code) => {
25
26
  // 折扣卡
26
27
  'good_pass',
27
28
  // 商品券
29
+ 'entitlement_pass',
30
+ // 月卡 / 年卡 / 多次使用卡
28
31
  'session_ticket',
29
32
  // 场次票
30
33
  'appointment_ticket' // 预约类门票
@@ -51,17 +54,18 @@ exports.searchWalletPass = searchWalletPass;
51
54
  const searchWallet = async (request, code) => {
52
55
  const params = {
53
56
  code,
54
- with_customer: 1
57
+ with_customer: 1,
58
+ with_franchisor_data: 1
55
59
  };
56
60
  try {
57
61
  const res = await request?.post('/wallet/detail/search', params);
58
- if (res?.code == 200 && !!res?.data) {
62
+ if (res?.status === true && res?.code === 200 && !!res?.data) {
59
63
  return {
60
64
  searchType: 'wallet',
61
65
  response: res
62
66
  };
63
67
  }
64
- throw new Error('没有找到对应的识别码');
68
+ throw new Error(res?.message || '没有找到对应的识别码');
65
69
  } catch (error) {
66
70
  throw error;
67
71
  }
@@ -6,6 +6,12 @@ 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;
13
+ /** 总店 Wallet Pass 未共享给当前子店。 */
14
+ isWalletPassUnavailableAtStore?: boolean;
9
15
  }
10
16
  interface RawScanSearchResult {
11
17
  searchType?: GlobalScanSearchType;
@@ -36,14 +36,22 @@ function formatGlobalScanResult(raw, scanCode) {
36
36
  return {
37
37
  searchType,
38
38
  data: resultData?.customer,
39
- scanCode
39
+ scanCode,
40
+ customerId: resultData?.customer_id,
41
+ isCrossShopWallet: resultData?.is_cross_shop_wallet === true || Number(resultData?.is_cross_shop_wallet) === 1
40
42
  };
41
43
  }
42
44
  if (searchType === 'walletPass') {
45
+ const firstResult = resultData?.[0];
46
+ const isWalletPassUnavailableAtStore = firstResult?.is_franchisor_pass === 1 && firstResult?.franchisee_wallet_pass?.use !== 'enabled';
43
47
  return {
44
48
  searchType,
45
- data: resultData?.[0]?.customer,
46
- scanCode
49
+ data: firstResult?.customer,
50
+ scanCode,
51
+ customerId: firstResult?.customer_id ?? firstResult?.holder?.customer_id ?? firstResult?.customer?.id ?? firstResult?.customer?.customer_id,
52
+ ...(isWalletPassUnavailableAtStore ? {
53
+ isWalletPassUnavailableAtStore: true
54
+ } : {})
47
55
  };
48
56
  }
49
57
  if (searchType === 'product' || searchType === 'local_product') {
@@ -34,6 +34,7 @@ exports.toNonNegativeInt = toNonNegativeInt;
34
34
  exports.toNonNegativeNumber = toNonNegativeNumber;
35
35
  exports.toPositiveString = toPositiveString;
36
36
  exports.toPriceString = toPriceString;
37
+ var _weighedProduct = require("../../modules/Order/utils/weighedProduct");
37
38
  var _dayjs = _interopRequireDefault(require("dayjs"));
38
39
  var _decimal = _interopRequireDefault(require("decimal.js"));
39
40
  var _utils = require("../../modules/Order/utils");
@@ -612,7 +613,7 @@ function normalizeOrderProduct(product) {
612
613
  });
613
614
  const composedOriginalPrice = (0, _utils.composeLinePrice)({
614
615
  mainPrice: mainOriginalStr,
615
- bundle: normalizedBundle,
616
+ bundle: Number(productSku.open_sold_weight) === 1 && (0, _manualProductDiscount.hasManualProductDiscountFlag)(metadata) ? (0, _weighedProduct.withOriginalBundleOptionPrices)(normalizedBundle) : normalizedBundle,
616
617
  useOriginalBundle: true
617
618
  });
618
619
  const finalOriginalPrice = (0, _utils.resolveManualOverrideLineOriginalPrice)({
@@ -43,6 +43,7 @@ export declare class ShopDiscountImpl extends BaseModule implements Module {
43
43
  batchSearchByProductIds(productIds: number[]): Promise<Discount[]>;
44
44
  scanCode(code: string, customerId?: number): Promise<{
45
45
  isAvailable: boolean;
46
+ isAccepted?: boolean;
46
47
  productList: Record<string, any>[];
47
48
  discountList: Discount[];
48
49
  type: "server" | "clientCalc";
@@ -62,6 +63,7 @@ export declare class ShopDiscountImpl extends BaseModule implements Module {
62
63
  action?: 'create' | 'edit';
63
64
  with_good_pass?: 0 | 1;
64
65
  with_discount_card?: 0 | 1;
66
+ with_entitlement_pass?: 0 | 1;
65
67
  }): Promise<void>;
66
68
  }
67
69
  export default ShopDiscountImpl;
@@ -7,8 +7,10 @@ exports.default = exports.ShopDiscountImpl = void 0;
7
7
  var _BaseModule = require("../../modules/BaseModule");
8
8
  var _Discount = require("../../modules/Discount");
9
9
  var _Rules = require("../../modules/Rules");
10
+ var _entitlementPass = require("../../modules/Discount/entitlementPass");
10
11
  var _utils = require("./utils");
11
12
  var _types = require("../../modules/Rules/types");
13
+ var _availability = require("../../modules/Discount/availability");
12
14
  var _decimal = _interopRequireDefault(require("decimal.js"));
13
15
  var _lodashEs = require("lodash-es");
14
16
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
@@ -125,7 +127,8 @@ class ShopDiscountImpl extends _BaseModule.BaseModule {
125
127
  customerId: customer.id,
126
128
  action: 'create',
127
129
  with_good_pass: 1,
128
- with_discount_card: 1
130
+ with_discount_card: 1,
131
+ with_entitlement_pass: 1
129
132
  });
130
133
  });
131
134
  }
@@ -347,6 +350,7 @@ class ShopDiscountImpl extends _BaseModule.BaseModule {
347
350
  const {
348
351
  productList: newProductList,
349
352
  discountList: newDiscountList,
353
+ evaluatedDiscountList,
350
354
  isAvailable,
351
355
  unavailableReason
352
356
  } = rulesModule.isDiscountListAvailable({
@@ -361,6 +365,7 @@ class ShopDiscountImpl extends _BaseModule.BaseModule {
361
365
  productList: this.store.productList || [],
362
366
  discountList: this.getDiscountList()
363
367
  };
368
+ let isAccepted = isAvailable;
364
369
  if (isAvailable) {
365
370
  this.setDiscountList(newDiscountList || []);
366
371
  this.store.originalDiscountList = newDiscountList || [];
@@ -368,12 +373,34 @@ class ShopDiscountImpl extends _BaseModule.BaseModule {
368
373
  if (this.isWalkIn() && resultDiscountList.length && this.options.otherParams?.platform === 'shop' && !((resultDiscountList[0].customer_id || 1) === 1)) {
369
374
  await this.getCustomerWallet(resultDiscountList[0].customer_id);
370
375
  }
376
+ } else {
377
+ const scannedIds = new Set(withScanList.map(discount => String(discount.id)));
378
+ const retainedScannedDiscounts = (evaluatedDiscountList || withScanList).filter(discount => scannedIds.has(String(discount.id)) && !(0, _availability.getDiscountLifecycleReason)(discount));
379
+ if (retainedScannedDiscounts.length) {
380
+ const retainedDiscounts = new Map(this.getDiscountList().map(discount => [String(discount.id), discount]));
381
+ retainedScannedDiscounts.forEach(discount => {
382
+ retainedDiscounts.set(String(discount.id), {
383
+ ...discount,
384
+ isScan: true,
385
+ isAvailable: false,
386
+ isSelected: false,
387
+ reasonCode: discount.reasonCode || 'product_not_applicable',
388
+ appliedProductDetails: [],
389
+ savedAmount: 0
390
+ });
391
+ });
392
+ this.setDiscountList([...retainedDiscounts.values()]);
393
+ this.store.originalDiscountList = this.getDiscountList();
394
+ await this.store.discount?.setOriginalDiscountList(this.getDiscountList());
395
+ isAccepted = true;
396
+ }
371
397
  }
372
398
  return {
373
399
  type: "clientCalc",
374
400
  isAvailable: isAvailable || false,
401
+ isAccepted: Boolean(isAccepted),
375
402
  productList: newProductList || this.store.productList || [],
376
- discountList: newDiscountList || this.getDiscountList(),
403
+ discountList: this.getDiscountList(),
377
404
  unavailableReason
378
405
  };
379
406
  } catch (error) {
@@ -416,7 +443,7 @@ class ShopDiscountImpl extends _BaseModule.BaseModule {
416
443
  if (item.booking_id) {
417
444
  const product = this.hooks?.getProduct(item);
418
445
  (item.discount_list || []).forEach(discount => {
419
- if (discount.id && ['good_pass', 'discount_card'].includes(discount.type)) {
446
+ if (discount.id && ['entitlement_pass', 'good_pass', 'discount_card'].includes(discount.type)) {
420
447
  const index = editModeDiscountList.findIndex(n => n.id === (discount.discount?.resource_id || discount.id));
421
448
  if (index !== -1) {
422
449
  editModeDiscountList[index] = {
@@ -522,6 +549,8 @@ class ShopDiscountImpl extends _BaseModule.BaseModule {
522
549
  with_good_pass: 1,
523
550
  with_discount_card: 1,
524
551
  with_wallet_pass_holder: 1,
552
+ with_entitlement_pass: 1,
553
+ filter_order_wallet_pass_usage: 0,
525
554
  request_timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
526
555
  ...(orderId ? {
527
556
  order_id: orderId
@@ -530,17 +559,34 @@ class ShopDiscountImpl extends _BaseModule.BaseModule {
530
559
  const goodPassList = prepareConfigResult?.discountList || [];
531
560
  const scanDiscount = this.getDiscountList()?.filter(item => item.isScan);
532
561
 
533
- // goodPassList 里可能有 scanDiscount 重合的部分,goodPassList 需要剔除
534
- const scanDiscountIds = scanDiscount.map(n => n.id);
535
- const newGoodPassList = goodPassList?.filter(n => !scanDiscountIds.includes(n.id));
536
- const newDiscountList = [...scanDiscount, ...(newGoodPassList || [])];
562
+ // 扫码 Entitlement Pass 与客户列表重复时保留扫码选择状态,但刷新实时余额。
563
+ // 其他券继续沿用原来的“保留扫码结果、剔除列表重复项”行为。
564
+ const scanDiscountById = new Map(scanDiscount.map(discount => [String(discount.id), discount]));
565
+ const refreshedScanEntitlementIds = new Set();
566
+ const newGoodPassList = goodPassList.flatMap(discount => {
567
+ const key = String(discount.id);
568
+ const scannedDiscount = scanDiscountById.get(key);
569
+ if (!scannedDiscount) return [discount];
570
+ if ((0, _entitlementPass.isEntitlementPass)(scannedDiscount) && (0, _entitlementPass.isEntitlementPass)(discount)) {
571
+ refreshedScanEntitlementIds.add(key);
572
+ return [{
573
+ ...discount,
574
+ isScan: true,
575
+ isSelected: scannedDiscount.isSelected ?? discount.isSelected,
576
+ isManualSelect: scannedDiscount.isManualSelect ?? discount.isManualSelect
577
+ }];
578
+ }
579
+ return [];
580
+ });
581
+ const retainedScanDiscounts = scanDiscount.filter(discount => !refreshedScanEntitlementIds.has(String(discount.id)));
582
+ const newDiscountList = [...retainedScanDiscounts, ...newGoodPassList];
537
583
 
538
584
  // 存储原始优惠券列表
539
585
  this.store.originalDiscountList = newDiscountList;
540
586
  await this.store.discount?.setOriginalDiscountList(newDiscountList);
541
587
 
542
- // 根据当前预约时间过滤优惠券列表
543
- const filteredDiscountList = this.filterDiscountListByBookingTime(newDiscountList, this.store.currentBookingTime);
588
+ // 日程限制交给 Rules 计算可用状态,展示列表保留当前不可用的卡券。
589
+ const filteredDiscountList = newDiscountList;
544
590
  this.store.filteredDiscountList = filteredDiscountList;
545
591
  this.setDiscountList(filteredDiscountList || []);
546
592
  if (this.store.productList?.length) {
@@ -59,12 +59,14 @@ export interface ScheduleItem {
59
59
  }
60
60
  export declare const getDateIsInSchedule: (dateTime: string, scheduleList: ScheduleItem[]) => boolean;
61
61
  /**
62
- * 根据预约时间过滤优惠券列表
62
+ * 根据商品使用时间过滤优惠券列表(预约开始时间或普通商品的当前时间)
63
63
  * @param discountList 优惠券列表
64
- * @param bookingTime 预约时间 (格式: YYYY-MM-DD HH:mm:ss)
64
+ * @param bookingTime 使用时间 (格式: YYYY-MM-DD HH:mm:ss),未确定时不提前过滤
65
65
  * @returns 过滤后的优惠券列表
66
66
  */
67
67
  export declare const filterDiscountListByBookingTime: (discountList: Discount[], bookingTime: string | null) => Discount[];
68
+ /** 与抵扣计算共用时间判断,区分未开始、有效期不匹配和日程限制。 */
69
+ export declare const getDiscountTimeUnavailableReason: (discount: Discount, useTime: string) => 'not_started' | 'time_limit' | 'outside_schedule' | undefined;
68
70
  /**
69
71
  * 判断是否是订单级别的固定金额折扣卡
70
72
  * @param discount 折扣
@@ -3,10 +3,15 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.uniqueById = exports.isOrderLevelFixedAmountDiscount = exports.isNormalProductByDurationSchedule = exports.isAllNormalProduct = exports.getDiscountListAmountTotal = exports.getDiscountListAmount = exports.getDiscountAmount = exports.getDateIsInSchedule = exports.filterDiscountListByBookingTime = exports.calculateOrderLevelDiscountAllocation = void 0;
6
+ exports.uniqueById = exports.isOrderLevelFixedAmountDiscount = exports.isNormalProductByDurationSchedule = exports.isAllNormalProduct = exports.getDiscountTimeUnavailableReason = exports.getDiscountListAmountTotal = exports.getDiscountListAmount = exports.getDiscountAmount = exports.getDateIsInSchedule = exports.filterDiscountListByBookingTime = exports.calculateOrderLevelDiscountAllocation = void 0;
7
7
  var _decimal = _interopRequireDefault(require("decimal.js"));
8
8
  var _dayjs = _interopRequireDefault(require("dayjs"));
9
+ var _isSameOrAfter = _interopRequireDefault(require("dayjs/plugin/isSameOrAfter"));
10
+ var _isSameOrBefore = _interopRequireDefault(require("dayjs/plugin/isSameOrBefore"));
11
+ var _entitlementPass = require("../../modules/Discount/entitlementPass");
9
12
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
13
+ _dayjs.default.extend(_isSameOrAfter.default);
14
+ _dayjs.default.extend(_isSameOrBefore.default);
10
15
  const uniqueById = (arr, key = 'id') => {
11
16
  const seen = new Set();
12
17
  return arr.filter(item => !seen.has(item[key]) && seen.add(item[key]));
@@ -35,8 +40,8 @@ const isAllNormalProduct = items => {
35
40
  */
36
41
  exports.isAllNormalProduct = isAllNormalProduct;
37
42
  const getDiscountAmount = (discount, total, price) => {
38
- // 商品券
39
- if (discount.tag === 'good_pass') {
43
+ // 商品券和 Entitlement Pass 都是全额抵扣。
44
+ if ((0, _entitlementPass.isFullCoveragePass)(discount)) {
40
45
  return new _decimal.default(price).minus(new _decimal.default(price || 0)).toNumber();
41
46
  }
42
47
 
@@ -376,41 +381,53 @@ const isInWeeklyRepeatForDate = (targetDate, startDate, repeatRule) => {
376
381
  };
377
382
 
378
383
  /**
379
- * 根据预约时间过滤优惠券列表
384
+ * 根据商品使用时间过滤优惠券列表(预约开始时间或普通商品的当前时间)
380
385
  * @param discountList 优惠券列表
381
- * @param bookingTime 预约时间 (格式: YYYY-MM-DD HH:mm:ss)
386
+ * @param bookingTime 使用时间 (格式: YYYY-MM-DD HH:mm:ss),未确定时不提前过滤
382
387
  * @returns 过滤后的优惠券列表
383
388
  */
384
389
  const filterDiscountListByBookingTime = (discountList, bookingTime) => {
385
- if (!bookingTime) {
386
- return discountList;
387
- }
388
- return discountList.filter(discount => {
390
+ if (!bookingTime) return discountList;
391
+ return discountList.filter(discount => !getDiscountTimeUnavailableReason(discount, bookingTime));
392
+ };
393
+
394
+ /** 与抵扣计算共用时间判断,区分未开始、有效期不匹配和日程限制。 */
395
+ exports.filterDiscountListByBookingTime = filterDiscountListByBookingTime;
396
+ const getDiscountTimeUnavailableReason = (discount, useTime) => {
397
+ const useTimeDayjs = (0, _dayjs.default)(useTime);
398
+ if (!useTimeDayjs.isValid()) return 'time_limit';
399
+ try {
389
400
  if (discount?.metadata?.validity_type === "custom_schedule_validity") {
390
- return getDateIsInSchedule(bookingTime, discount.custom_schedule_snapshot?.data || []);
401
+ return getDateIsInSchedule(useTime, discount.custom_schedule_snapshot?.data || []) ? undefined : 'outside_schedule';
391
402
  }
392
- try {
393
- const isHasStart = !!discount.start_date && !!discount.start_time;
394
- const startDate = (0, _dayjs.default)(`${discount.start_date} ${discount.start_time}`);
395
- const bookingTimeDayjs = (0, _dayjs.default)(bookingTime);
396
-
397
- // 判断预约时间是否在开始时间之后
398
- const isAfterStart = isHasStart ? bookingTimeDayjs.isSameOrAfter(startDate) : true;
399
-
400
- // 判断预约时间是否在过期时间之前(expire_time 为空时为长期有效)
401
- const isBeforeExpire = discount.expire_time ? bookingTimeDayjs.isSameOrBefore((0, _dayjs.default)(discount.expire_time)) : true;
402
- return isAfterStart && isBeforeExpire;
403
- } catch (error) {
404
- return true;
403
+ const isHasStart = !!discount.start_date && !!discount.start_time;
404
+ const startDate = (0, _dayjs.default)(`${discount.start_date} ${discount.start_time}`);
405
+
406
+ // 开始时刻包含在有效期内。
407
+ if (isHasStart) {
408
+ if (!startDate.isValid()) return 'time_limit';
409
+ if (useTimeDayjs.isBefore(startDate)) return 'not_started';
405
410
  }
406
- });
411
+
412
+ // 到期时刻不再可用;expire_time 为空时为长期有效。
413
+ const expireTime = discount.expire_time ? (0, _dayjs.default)(discount.expire_time) : null;
414
+ const isBeforeExpire = expireTime ? expireTime.isValid() && useTimeDayjs.isBefore(expireTime) : true;
415
+ if (!isBeforeExpire) return 'time_limit';
416
+ const validity = discount.extension_data?.find(field => field.field_key === 'valid_times')?.value;
417
+ if (validity?.restrict_use_by_schedule === true && !getDateIsInSchedule(useTime, discount.custom_schedule_snapshot?.data || [])) {
418
+ return 'outside_schedule';
419
+ }
420
+ return undefined;
421
+ } catch (error) {
422
+ return 'time_limit';
423
+ }
407
424
  };
408
425
  /**
409
426
  * 判断是否是订单级别的固定金额折扣卡
410
427
  * @param discount 折扣
411
428
  * @returns 是否是订单级别的固定金额折扣卡
412
429
  */
413
- exports.filterDiscountListByBookingTime = filterDiscountListByBookingTime;
430
+ exports.getDiscountTimeUnavailableReason = getDiscountTimeUnavailableReason;
414
431
  const isOrderLevelFixedAmountDiscount = discount => {
415
432
  return discount.tag === 'product_discount_card' && discount?.metadata?.discount_card_type === 'fixed_amount' && discount?.metadata?.discount_calculation_mode === 'order_level';
416
433
  };
@@ -127,8 +127,8 @@ export declare class UnifiedBookingSalesImpl extends BookingTicket {
127
127
  */
128
128
  clearSessionStorageOrderSnapshot(): void;
129
129
  /**
130
- * H5/Kiosk 不能把已收款订单留在本地等待重试,必须直接等待云端 checkout。
131
- * 其它平台保持 WebPOS/POS 的本地待同步任务;显式 true 仍可强制使用本地任务。
130
+ * PC/H5/Kiosk 结账默认直接等待云端 checkout;POS/WebPOS 仍保持本地待同步。
131
+ * 显式传 checkoutSyncTaskEnabled: true 时仍可回退到本地同步模式。
132
132
  */
133
133
  protected shouldUseCheckoutSyncTask(): boolean;
134
134
  /** UnifiedBookingSales 的真实结账默认请求收据数据,供云端确认后再本地打印。 */