@pisell/pisellos 0.10.41 → 0.10.43
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.
- package/dist/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +58 -19
- package/dist/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +24 -12
- package/dist/modules/Discount/entitlementPass.d.ts +34 -0
- package/dist/modules/Discount/entitlementPass.js +147 -0
- package/dist/modules/Discount/index.d.ts +2 -0
- package/dist/modules/Discount/index.js +39 -13
- package/dist/modules/Discount/types.d.ts +26 -5
- package/dist/modules/Order/index.d.ts +5 -0
- package/dist/modules/Order/index.js +283 -118
- package/dist/modules/Order/utils/orderCollectionIdentity.js +16 -12
- package/dist/modules/Order/utils.js +17 -1
- package/dist/modules/ResourcePlanner/planner.d.ts +2 -2
- package/dist/modules/ResourcePlanner/planner.js +163 -46
- package/dist/modules/ResourcePlanner/types.d.ts +17 -0
- package/dist/modules/Rules/index.js +312 -135
- package/dist/solution/BaseSales/index.d.ts +2 -0
- package/dist/solution/BaseSales/index.js +9 -1
- package/dist/solution/BaseSales/utils/cartPromotion.js +141 -126
- package/dist/solution/BookingTicket/types.d.ts +5 -1
- package/dist/solution/BookingTicket/utils/scan/applyGlobalScan.js +171 -96
- package/dist/solution/BookingTicket/utils/scan/cloudSearch.js +3 -1
- package/dist/solution/BookingTicket/utils/scan/formatGlobalScan.d.ts +2 -0
- package/dist/solution/BookingTicket/utils/scan/formatGlobalScan.js +12 -3
- package/dist/solution/ShopDiscount/index.d.ts +1 -0
- package/dist/solution/ShopDiscount/index.js +41 -21
- package/dist/solution/ShopDiscount/utils.js +3 -2
- package/dist/solution/UnifiedBookingSales/index.js +94 -21
- package/lib/model/strategy/adapter/promotion/index.js +1 -46
- package/lib/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +35 -6
- package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +13 -6
- package/lib/modules/Discount/entitlementPass.d.ts +34 -0
- package/lib/modules/Discount/entitlementPass.js +141 -0
- package/lib/modules/Discount/index.d.ts +2 -0
- package/lib/modules/Discount/index.js +25 -9
- package/lib/modules/Discount/types.d.ts +26 -5
- package/lib/modules/Order/index.d.ts +5 -0
- package/lib/modules/Order/index.js +172 -28
- package/lib/modules/Order/utils/orderCollectionIdentity.js +14 -11
- package/lib/modules/Order/utils.js +19 -1
- package/lib/modules/ResourcePlanner/planner.d.ts +2 -2
- package/lib/modules/ResourcePlanner/planner.js +137 -35
- package/lib/modules/ResourcePlanner/types.d.ts +17 -0
- package/lib/modules/Rules/index.js +232 -97
- package/lib/solution/BaseSales/index.d.ts +2 -0
- package/lib/solution/BaseSales/index.js +7 -1
- package/lib/solution/BaseSales/utils/cartPromotion.js +12 -4
- package/lib/solution/BookingTicket/types.d.ts +5 -1
- package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.js +65 -18
- package/lib/solution/BookingTicket/utils/scan/cloudSearch.js +3 -1
- package/lib/solution/BookingTicket/utils/scan/formatGlobalScan.d.ts +2 -0
- package/lib/solution/BookingTicket/utils/scan/formatGlobalScan.js +5 -1
- package/lib/solution/ShopDiscount/index.d.ts +1 -0
- package/lib/solution/ShopDiscount/index.js +26 -6
- package/lib/solution/ShopDiscount/utils.js +3 -2
- package/lib/solution/UnifiedBookingSales/index.js +86 -22
- package/package.json +1 -1
|
@@ -236,6 +236,8 @@ export declare class BaseSalesImpl extends BaseModule implements Module {
|
|
|
236
236
|
updateTempOrderNote(note: string, sync: true): Promise<string>;
|
|
237
237
|
updateTempOrderNote(note: string, sync: boolean): string | Promise<string>;
|
|
238
238
|
updateRemoteOrderNote(orderId: number | string, note: string): void;
|
|
239
|
+
/** 存储用户自定义取餐标识到当前 tempOrder.buzzer。 */
|
|
240
|
+
setPickupRef(buzzer: string): string;
|
|
239
241
|
updateTempOrderShopDiscount(amount: string | number): string;
|
|
240
242
|
updateTempOrderDepositAmount(amount: string | number): OrderSummary;
|
|
241
243
|
updateTempOrderContactsInfo(contactsInfo: Record<string, any> | null): Record<string, any> | null;
|
|
@@ -1531,6 +1531,12 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
|
|
|
1531
1531
|
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
1532
1532
|
this.store.order.syncTempOrderNoteToRemote(orderId, note);
|
|
1533
1533
|
}
|
|
1534
|
+
|
|
1535
|
+
/** 存储用户自定义取餐标识到当前 tempOrder.buzzer。 */
|
|
1536
|
+
setPickupRef(buzzer) {
|
|
1537
|
+
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
1538
|
+
return this.store.order.updateTempOrderBuzzer(buzzer);
|
|
1539
|
+
}
|
|
1534
1540
|
updateTempOrderShopDiscount(amount) {
|
|
1535
1541
|
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
1536
1542
|
return this.store.order.updateTempOrderShopDiscount(amount);
|
|
@@ -1680,7 +1686,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
|
|
|
1680
1686
|
if (!this.isPersistedOrderProduct(item)) return;
|
|
1681
1687
|
(item.discount_list || []).forEach(discount => {
|
|
1682
1688
|
const discountId = discount.discount?.resource_id || discount.id;
|
|
1683
|
-
if (!discountId || !['good_pass', 'discount_card'].includes(discount.type)) return;
|
|
1689
|
+
if (!discountId || !['entitlement_pass', 'good_pass', 'discount_card'].includes(discount.type)) return;
|
|
1684
1690
|
const quantity = item.quantity || item.num || item.product_quantity || 1;
|
|
1685
1691
|
const savedAmount = new _decimal.default(discount.amount || 0).times(quantity).plus(discount?.metadata?.product_discount_difference || 0);
|
|
1686
1692
|
const index = historyDiscountList.findIndex(n => n.id === discountId);
|
|
@@ -17,6 +17,7 @@ exports.resolveStrategyEligibleProducts = resolveStrategyEligibleProducts;
|
|
|
17
17
|
exports.resolveStrategyRequiredQuantity = resolveStrategyRequiredQuantity;
|
|
18
18
|
var _decimal = _interopRequireDefault(require("decimal.js"));
|
|
19
19
|
var _lodashEs = require("lodash-es");
|
|
20
|
+
var _entitlementPass = require("../../../modules/Discount/entitlementPass");
|
|
20
21
|
var _utils = require("../../../modules/Order/utils");
|
|
21
22
|
var _utils2 = require("../../ScanOrder/utils");
|
|
22
23
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
@@ -395,11 +396,16 @@ function getProductLineNoteSignature(product) {
|
|
|
395
396
|
const uid = getOrderProductUid(product);
|
|
396
397
|
return uid ? `note:${uid}` : `note:${normalizedNote}`;
|
|
397
398
|
}
|
|
398
|
-
function
|
|
399
|
+
function getFullCoveragePassDiscountType(product) {
|
|
399
400
|
const productBundle = Array.isArray(product?.product_bundle) ? product.product_bundle : [];
|
|
400
401
|
const extendedBundle = Array.isArray(product?._extend?.other?.bundle) ? product._extend.other.bundle : [];
|
|
401
402
|
const discountLists = [product?.discount_list, ...productBundle.map(item => item?.discount_list), ...extendedBundle.map(item => item?.discount_list)];
|
|
402
|
-
|
|
403
|
+
for (const discountList of discountLists) {
|
|
404
|
+
if (!Array.isArray(discountList)) continue;
|
|
405
|
+
const pass = discountList.find(item => (0, _entitlementPass.isFullCoveragePass)(item));
|
|
406
|
+
if (pass) return String(pass.type || pass.tag);
|
|
407
|
+
}
|
|
408
|
+
return null;
|
|
403
409
|
}
|
|
404
410
|
|
|
405
411
|
/**
|
|
@@ -412,8 +418,10 @@ function getPromotionLineMergeBoundary(product) {
|
|
|
412
418
|
if (product?.product_id === undefined || product?.product_id === null) {
|
|
413
419
|
return `custom:${getOrderProductUid(product)}`;
|
|
414
420
|
}
|
|
415
|
-
|
|
416
|
-
|
|
421
|
+
const fullCoveragePassType = getFullCoveragePassDiscountType(product);
|
|
422
|
+
if (fullCoveragePassType) {
|
|
423
|
+
const boundary = fullCoveragePassType === 'good_pass' ? 'good-pass' : 'entitlement-pass';
|
|
424
|
+
return `${boundary}:${getOrderProductUid(product)}`;
|
|
417
425
|
}
|
|
418
426
|
if (Number(product?.product_sku?.open_sold_weight || 0) === 1) {
|
|
419
427
|
return `weighing:${getOrderProductUid(product)}`;
|
|
@@ -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
|
|
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
|
}
|
|
@@ -106,16 +106,12 @@ async function applyGlobalScan(host, formatted, bridge) {
|
|
|
106
106
|
data,
|
|
107
107
|
scanCode,
|
|
108
108
|
customerId: formattedCustomerId,
|
|
109
|
-
isCrossShopWallet
|
|
109
|
+
isCrossShopWallet,
|
|
110
|
+
isWalletPassUnavailableAtStore
|
|
110
111
|
} = formatted;
|
|
111
112
|
if (searchType === 'wallet') {
|
|
112
113
|
const currentCustomer = host.getOrderCustomerSnapshot();
|
|
113
|
-
|
|
114
|
-
return {
|
|
115
|
-
status: 'success',
|
|
116
|
-
kind: 'customer'
|
|
117
|
-
};
|
|
118
|
-
}
|
|
114
|
+
const currentCustomerId = getRealCustomerId(currentCustomer);
|
|
119
115
|
let customer = data;
|
|
120
116
|
if (isCrossShopWallet) {
|
|
121
117
|
const sourceCustomerId = getRealCustomerId({
|
|
@@ -152,6 +148,36 @@ async function applyGlobalScan(host, formatted, bridge) {
|
|
|
152
148
|
...customer,
|
|
153
149
|
id: customerId
|
|
154
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
|
+
}
|
|
155
181
|
host.setOrderCustomer(orderCustomer);
|
|
156
182
|
bridge?.onNotify?.('success', 'pisell2.text.add-client-success');
|
|
157
183
|
return {
|
|
@@ -160,13 +186,20 @@ async function applyGlobalScan(host, formatted, bridge) {
|
|
|
160
186
|
};
|
|
161
187
|
}
|
|
162
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
|
+
}
|
|
163
196
|
const snapshot = host.getOrderCustomerSnapshot();
|
|
164
|
-
const currentCustomerIsWalkIn = getRealCustomerId(snapshot) === undefined;
|
|
165
197
|
let activeCustomer = snapshot;
|
|
166
198
|
const bindWalletPassCustomer = async asset => {
|
|
167
|
-
if (
|
|
199
|
+
if (getRealCustomerId(activeCustomer) !== undefined) {
|
|
168
200
|
return {
|
|
169
|
-
resolveFailed: false
|
|
201
|
+
resolveFailed: false,
|
|
202
|
+
customerBound: false
|
|
170
203
|
};
|
|
171
204
|
}
|
|
172
205
|
const assetCustomerId = getWalletAssetCustomerId(asset) ?? getRealCustomerId({
|
|
@@ -177,7 +210,8 @@ async function applyGlobalScan(host, formatted, bridge) {
|
|
|
177
210
|
if (!host.resolveCustomerById) {
|
|
178
211
|
bridge?.onNotify?.('fail', 'pisell2.text.scan-global-failed');
|
|
179
212
|
return {
|
|
180
|
-
resolveFailed: true
|
|
213
|
+
resolveFailed: true,
|
|
214
|
+
customerBound: false
|
|
181
215
|
};
|
|
182
216
|
}
|
|
183
217
|
try {
|
|
@@ -186,7 +220,8 @@ async function applyGlobalScan(host, formatted, bridge) {
|
|
|
186
220
|
console.warn('[GlobalScan] Wallet asset customer lookup failed', error);
|
|
187
221
|
bridge?.onNotify?.('fail', getErrorMessage(error) || 'pisell2.text.scan-global-failed');
|
|
188
222
|
return {
|
|
189
|
-
resolveFailed: true
|
|
223
|
+
resolveFailed: true,
|
|
224
|
+
customerBound: false
|
|
190
225
|
};
|
|
191
226
|
}
|
|
192
227
|
}
|
|
@@ -196,7 +231,8 @@ async function applyGlobalScan(host, formatted, bridge) {
|
|
|
196
231
|
bridge?.onNotify?.('fail', 'pisell2.text.scan-global-failed');
|
|
197
232
|
}
|
|
198
233
|
return {
|
|
199
|
-
resolveFailed: assetCustomerId !== undefined
|
|
234
|
+
resolveFailed: assetCustomerId !== undefined,
|
|
235
|
+
customerBound: false
|
|
200
236
|
};
|
|
201
237
|
}
|
|
202
238
|
const orderCustomer = customer.id === undefined || customer.id === null ? {
|
|
@@ -210,7 +246,8 @@ async function applyGlobalScan(host, formatted, bridge) {
|
|
|
210
246
|
retainSearchResultCodes: [scanCode]
|
|
211
247
|
});
|
|
212
248
|
return {
|
|
213
|
-
resolveFailed: false
|
|
249
|
+
resolveFailed: false,
|
|
250
|
+
customerBound: true
|
|
214
251
|
};
|
|
215
252
|
};
|
|
216
253
|
const trySelectWalletAsset = async () => {
|
|
@@ -242,24 +279,34 @@ async function applyGlobalScan(host, formatted, bridge) {
|
|
|
242
279
|
reason: 'invalid_data'
|
|
243
280
|
};
|
|
244
281
|
};
|
|
282
|
+
|
|
283
|
+
// batch-search 可能返回来源门店客户;先映射到当前门店,确保 Wallet
|
|
284
|
+
// 支付资产查询使用本店客户上下文。
|
|
285
|
+
const bindResult = await bindWalletPassCustomer();
|
|
245
286
|
const walletSelection = await trySelectWalletAsset();
|
|
246
287
|
if (walletSelection) return walletSelection;
|
|
247
|
-
|
|
248
|
-
// 非支付类 Wallet Pass 仍可能由 batch-search 返回所属客户。
|
|
249
|
-
await bindWalletPassCustomer();
|
|
250
288
|
if (!bridge?.applyPromotionCode) {
|
|
251
289
|
return {
|
|
252
290
|
status: 'failed',
|
|
253
291
|
reason: 'no_bridge'
|
|
254
292
|
};
|
|
255
293
|
}
|
|
256
|
-
const
|
|
294
|
+
const promotionCustomerId = getRealCustomerId(activeCustomer);
|
|
295
|
+
const promotionResult = bindResult.customerBound ? await bridge.applyPromotionCode(scanCode, promotionCustomerId, {
|
|
296
|
+
suppressFailureNotification: true
|
|
297
|
+
}) : await bridge.applyPromotionCode(scanCode, promotionCustomerId);
|
|
257
298
|
if (promotionResult?.isAccepted ?? promotionResult?.isAvailable) {
|
|
258
299
|
return {
|
|
259
300
|
status: 'success',
|
|
260
301
|
kind: 'promotion'
|
|
261
302
|
};
|
|
262
303
|
}
|
|
304
|
+
if (bindResult.customerBound) {
|
|
305
|
+
return {
|
|
306
|
+
status: 'success',
|
|
307
|
+
kind: 'customer'
|
|
308
|
+
};
|
|
309
|
+
}
|
|
263
310
|
return {
|
|
264
311
|
status: 'failed',
|
|
265
312
|
reason: 'invalid_data'
|
|
@@ -15,7 +15,7 @@ const searchWalletPass = async (request, code) => {
|
|
|
15
15
|
relation_product: 1,
|
|
16
16
|
// 组装关联商品: 0不处理 1处理
|
|
17
17
|
with_franchisor_data: 1,
|
|
18
|
-
with: ['customer'],
|
|
18
|
+
with: ['customer', 'franchiseeWalletPass:shop_id,source_shop_id,product_id,use'],
|
|
19
19
|
tags: ['point_card',
|
|
20
20
|
// 积分卡
|
|
21
21
|
'gift_card',
|
|
@@ -26,6 +26,8 @@ const searchWalletPass = async (request, code) => {
|
|
|
26
26
|
// 折扣卡
|
|
27
27
|
'good_pass',
|
|
28
28
|
// 商品券
|
|
29
|
+
'entitlement_pass',
|
|
30
|
+
// 月卡 / 年卡 / 多次使用卡
|
|
29
31
|
'session_ticket',
|
|
30
32
|
// 场次票
|
|
31
33
|
'appointment_ticket' // 预约类门票
|
|
@@ -10,6 +10,8 @@ export interface GlobalScanFormattedResult {
|
|
|
10
10
|
customerId?: string | number;
|
|
11
11
|
/** 仅 Wallet 返回;true 时禁止直接使用响应内的来源客户。 */
|
|
12
12
|
isCrossShopWallet?: boolean;
|
|
13
|
+
/** 总店 Wallet Pass 未共享给当前子店。 */
|
|
14
|
+
isWalletPassUnavailableAtStore?: boolean;
|
|
13
15
|
}
|
|
14
16
|
interface RawScanSearchResult {
|
|
15
17
|
searchType?: GlobalScanSearchType;
|
|
@@ -43,11 +43,15 @@ function formatGlobalScanResult(raw, scanCode) {
|
|
|
43
43
|
}
|
|
44
44
|
if (searchType === 'walletPass') {
|
|
45
45
|
const firstResult = resultData?.[0];
|
|
46
|
+
const isWalletPassUnavailableAtStore = firstResult?.is_franchisor_pass === 1 && firstResult?.franchisee_wallet_pass?.use !== 'enabled';
|
|
46
47
|
return {
|
|
47
48
|
searchType,
|
|
48
49
|
data: firstResult?.customer,
|
|
49
50
|
scanCode,
|
|
50
|
-
customerId: firstResult?.customer_id ?? firstResult?.holder?.customer_id ?? firstResult?.customer?.id ?? firstResult?.customer?.customer_id
|
|
51
|
+
customerId: firstResult?.customer_id ?? firstResult?.holder?.customer_id ?? firstResult?.customer?.id ?? firstResult?.customer?.customer_id,
|
|
52
|
+
...(isWalletPassUnavailableAtStore ? {
|
|
53
|
+
isWalletPassUnavailableAtStore: true
|
|
54
|
+
} : {})
|
|
51
55
|
};
|
|
52
56
|
}
|
|
53
57
|
if (searchType === 'product' || searchType === 'local_product') {
|
|
@@ -62,6 +62,7 @@ export declare class ShopDiscountImpl extends BaseModule implements Module {
|
|
|
62
62
|
action?: 'create' | 'edit';
|
|
63
63
|
with_good_pass?: 0 | 1;
|
|
64
64
|
with_discount_card?: 0 | 1;
|
|
65
|
+
with_entitlement_pass?: 0 | 1;
|
|
65
66
|
}): Promise<void>;
|
|
66
67
|
}
|
|
67
68
|
export default ShopDiscountImpl;
|
|
@@ -7,6 +7,7 @@ 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");
|
|
12
13
|
var _decimal = _interopRequireDefault(require("decimal.js"));
|
|
@@ -125,7 +126,8 @@ class ShopDiscountImpl extends _BaseModule.BaseModule {
|
|
|
125
126
|
customerId: customer.id,
|
|
126
127
|
action: 'create',
|
|
127
128
|
with_good_pass: 1,
|
|
128
|
-
with_discount_card: 1
|
|
129
|
+
with_discount_card: 1,
|
|
130
|
+
with_entitlement_pass: 1
|
|
129
131
|
});
|
|
130
132
|
});
|
|
131
133
|
}
|
|
@@ -416,7 +418,7 @@ class ShopDiscountImpl extends _BaseModule.BaseModule {
|
|
|
416
418
|
if (item.booking_id) {
|
|
417
419
|
const product = this.hooks?.getProduct(item);
|
|
418
420
|
(item.discount_list || []).forEach(discount => {
|
|
419
|
-
if (discount.id && ['good_pass', 'discount_card'].includes(discount.type)) {
|
|
421
|
+
if (discount.id && ['entitlement_pass', 'good_pass', 'discount_card'].includes(discount.type)) {
|
|
420
422
|
const index = editModeDiscountList.findIndex(n => n.id === (discount.discount?.resource_id || discount.id));
|
|
421
423
|
if (index !== -1) {
|
|
422
424
|
editModeDiscountList[index] = {
|
|
@@ -522,6 +524,7 @@ class ShopDiscountImpl extends _BaseModule.BaseModule {
|
|
|
522
524
|
with_good_pass: 1,
|
|
523
525
|
with_discount_card: 1,
|
|
524
526
|
with_wallet_pass_holder: 1,
|
|
527
|
+
with_entitlement_pass: 1,
|
|
525
528
|
request_timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
526
529
|
...(orderId ? {
|
|
527
530
|
order_id: orderId
|
|
@@ -530,10 +533,27 @@ class ShopDiscountImpl extends _BaseModule.BaseModule {
|
|
|
530
533
|
const goodPassList = prepareConfigResult?.discountList || [];
|
|
531
534
|
const scanDiscount = this.getDiscountList()?.filter(item => item.isScan);
|
|
532
535
|
|
|
533
|
-
//
|
|
534
|
-
|
|
535
|
-
const
|
|
536
|
-
const
|
|
536
|
+
// 扫码 Entitlement Pass 与客户列表重复时保留扫码选择状态,但刷新实时余额。
|
|
537
|
+
// 其他券继续沿用原来的“保留扫码结果、剔除列表重复项”行为。
|
|
538
|
+
const scanDiscountById = new Map(scanDiscount.map(discount => [String(discount.id), discount]));
|
|
539
|
+
const refreshedScanEntitlementIds = new Set();
|
|
540
|
+
const newGoodPassList = goodPassList.flatMap(discount => {
|
|
541
|
+
const key = String(discount.id);
|
|
542
|
+
const scannedDiscount = scanDiscountById.get(key);
|
|
543
|
+
if (!scannedDiscount) return [discount];
|
|
544
|
+
if ((0, _entitlementPass.isEntitlementPass)(scannedDiscount) && (0, _entitlementPass.isEntitlementPass)(discount)) {
|
|
545
|
+
refreshedScanEntitlementIds.add(key);
|
|
546
|
+
return [{
|
|
547
|
+
...discount,
|
|
548
|
+
isScan: true,
|
|
549
|
+
isSelected: scannedDiscount.isSelected ?? discount.isSelected,
|
|
550
|
+
isManualSelect: scannedDiscount.isManualSelect ?? discount.isManualSelect
|
|
551
|
+
}];
|
|
552
|
+
}
|
|
553
|
+
return [];
|
|
554
|
+
});
|
|
555
|
+
const retainedScanDiscounts = scanDiscount.filter(discount => !refreshedScanEntitlementIds.has(String(discount.id)));
|
|
556
|
+
const newDiscountList = [...retainedScanDiscounts, ...newGoodPassList];
|
|
537
557
|
|
|
538
558
|
// 存储原始优惠券列表
|
|
539
559
|
this.store.originalDiscountList = newDiscountList;
|
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.uniqueById = exports.isOrderLevelFixedAmountDiscount = exports.isNormalProductByDurationSchedule = exports.isAllNormalProduct = 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 _entitlementPass = require("../../modules/Discount/entitlementPass");
|
|
9
10
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
10
11
|
const uniqueById = (arr, key = 'id') => {
|
|
11
12
|
const seen = new Set();
|
|
@@ -35,8 +36,8 @@ const isAllNormalProduct = items => {
|
|
|
35
36
|
*/
|
|
36
37
|
exports.isAllNormalProduct = isAllNormalProduct;
|
|
37
38
|
const getDiscountAmount = (discount, total, price) => {
|
|
38
|
-
//
|
|
39
|
-
if (
|
|
39
|
+
// 商品券和 Entitlement Pass 都是全额抵扣。
|
|
40
|
+
if ((0, _entitlementPass.isFullCoveragePass)(discount)) {
|
|
40
41
|
return new _decimal.default(price).minus(new _decimal.default(price || 0)).toNumber();
|
|
41
42
|
}
|
|
42
43
|
|
|
@@ -142,7 +142,7 @@ function buildPlannerDiscountResult(params) {
|
|
|
142
142
|
const productDiscounts = [...(Array.isArray(product.discount_list) ? product.discount_list : []), ...(Array.isArray(product.product_bundle) ? product.product_bundle.flatMap(bundle => bundle?.discount_list || []) : [])];
|
|
143
143
|
productDiscounts.forEach(discount => {
|
|
144
144
|
const type = discount.tag || discount.type;
|
|
145
|
-
if (!['good_pass', 'discount_card', 'product_discount_card'].includes(type)) return;
|
|
145
|
+
if (!['entitlement_pass', 'good_pass', 'discount_card', 'product_discount_card'].includes(type)) return;
|
|
146
146
|
const discountId = resolvePlannerDiscountId(discount);
|
|
147
147
|
applications.push({
|
|
148
148
|
discountId,
|
|
@@ -184,7 +184,7 @@ function isLocalOrderId(orderId) {
|
|
|
184
184
|
}
|
|
185
185
|
function isCustomerBoundDiscount(discount) {
|
|
186
186
|
const type = discount?.type ?? discount?.tag;
|
|
187
|
-
return type === 'good_pass' || type === 'discount_card' || type === 'product_discount_card';
|
|
187
|
+
return type === 'entitlement_pass' || type === 'good_pass' || type === 'discount_card' || type === 'product_discount_card';
|
|
188
188
|
}
|
|
189
189
|
function filterCustomerBoundDiscounts(discountList) {
|
|
190
190
|
if (!Array.isArray(discountList)) return [];
|
|
@@ -639,6 +639,22 @@ function normalizeResource(raw, fixedOffsetMinutes = (0, _localClock.captureRunt
|
|
|
639
639
|
const id = raw.resourceId ?? raw.resource_id ?? raw.id;
|
|
640
640
|
if (id === undefined || id === null || id === '') return null;
|
|
641
641
|
const rawWindows = raw.windows || raw.times || [];
|
|
642
|
+
const rawExcludedWindows = raw.excludedWindows || raw.excluded_windows || [];
|
|
643
|
+
const normalizeWindows = windows => (Array.isArray(windows) ? windows : []).flatMap(window => {
|
|
644
|
+
const range = normalizeAvailabilityLocalDateTimeRange({
|
|
645
|
+
startAt: window.startAt || window.start_at || window.start,
|
|
646
|
+
endAt: window.endAt || window.end_at || window.end,
|
|
647
|
+
fixedOffsetMinutes,
|
|
648
|
+
rollOverEnd: true
|
|
649
|
+
});
|
|
650
|
+
if (!range) return [];
|
|
651
|
+
return [{
|
|
652
|
+
startAt: range.startAt,
|
|
653
|
+
endAt: range.endAt,
|
|
654
|
+
source: window.source,
|
|
655
|
+
raw: window
|
|
656
|
+
}];
|
|
657
|
+
});
|
|
642
658
|
return {
|
|
643
659
|
id,
|
|
644
660
|
formId: raw.formId ?? raw.form_id ?? raw.resource_form_id,
|
|
@@ -646,21 +662,8 @@ function normalizeResource(raw, fixedOffsetMinutes = (0, _localClock.captureRunt
|
|
|
646
662
|
code: raw.code,
|
|
647
663
|
resourceType: normalizeResourceType(raw.type || raw.resourceType),
|
|
648
664
|
capacity: Math.max(1, Number(raw.capacity || raw.max_capacity || 1) || 1),
|
|
649
|
-
windows: (
|
|
650
|
-
|
|
651
|
-
startAt: window.startAt || window.start_at || window.start,
|
|
652
|
-
endAt: window.endAt || window.end_at || window.end,
|
|
653
|
-
fixedOffsetMinutes,
|
|
654
|
-
rollOverEnd: true
|
|
655
|
-
});
|
|
656
|
-
if (!range) return [];
|
|
657
|
-
return [{
|
|
658
|
-
startAt: range.startAt,
|
|
659
|
-
endAt: range.endAt,
|
|
660
|
-
source: window.source,
|
|
661
|
-
raw: window
|
|
662
|
-
}];
|
|
663
|
-
}),
|
|
665
|
+
windows: normalizeWindows(rawWindows),
|
|
666
|
+
excludedWindows: normalizeWindows(rawExcludedWindows),
|
|
664
667
|
raw
|
|
665
668
|
};
|
|
666
669
|
}
|
|
@@ -679,6 +682,17 @@ function dateRangeDays(dateRange) {
|
|
|
679
682
|
}
|
|
680
683
|
return dates;
|
|
681
684
|
}
|
|
685
|
+
function getExcludedScheduleDateOverrideTimePoints(date, schedule) {
|
|
686
|
+
if (!schedule.repeat_rule?.excluded_date?.length) return [];
|
|
687
|
+
if ((0, _getDateIsInSchedule.getScheduleStartEndTimePoints)(date, [schedule]).length > 0) return [];
|
|
688
|
+
return (0, _getDateIsInSchedule.getScheduleStartEndTimePoints)(date, [{
|
|
689
|
+
...schedule,
|
|
690
|
+
repeat_rule: {
|
|
691
|
+
...schedule.repeat_rule,
|
|
692
|
+
excluded_date: []
|
|
693
|
+
}
|
|
694
|
+
}]);
|
|
695
|
+
}
|
|
682
696
|
function normalizeResourceOccupancy(rawEvent, resourceId, source = 'remote', fixedOffsetMinutes = (0, _localClock.captureRuntimeOffsetMinutes)()) {
|
|
683
697
|
const range = normalizeAvailabilityLocalDateTimeRange({
|
|
684
698
|
startAt: rawEvent.start_at ?? rawEvent.startAt ?? rawEvent.start_time ?? rawEvent.startTime,
|
|
@@ -771,7 +785,26 @@ function buildAvailabilityResourcesFromV2(params) {
|
|
|
771
785
|
}
|
|
772
786
|
}];
|
|
773
787
|
})));
|
|
788
|
+
const excludedWindows = dates.flatMap(date => schedules.flatMap(schedule => getExcludedScheduleDateOverrideTimePoints(date, schedule).flatMap(slot => {
|
|
789
|
+
const range = normalizeAvailabilityLocalDateTimeRange({
|
|
790
|
+
startAt: slot.start_at,
|
|
791
|
+
endAt: slot.end_at,
|
|
792
|
+
fixedOffsetMinutes,
|
|
793
|
+
rollOverEnd: true
|
|
794
|
+
});
|
|
795
|
+
if (!range) return [];
|
|
796
|
+
return [{
|
|
797
|
+
startAt: range.startAt,
|
|
798
|
+
endAt: range.endAt,
|
|
799
|
+
source: `resource_schedule_excluded:${schedule.id}`,
|
|
800
|
+
raw: {
|
|
801
|
+
scheduleId: schedule.id,
|
|
802
|
+
excludedDate: date
|
|
803
|
+
}
|
|
804
|
+
}];
|
|
805
|
+
})));
|
|
774
806
|
resource.windows = materializedWindows.length > 0 ? materializedWindows : explicitWindows;
|
|
807
|
+
resource.excludedWindows = [...(resource.excludedWindows || []), ...excludedWindows];
|
|
775
808
|
if (resource.windows.length === 0 && missingScheduleIds.length > 0) {
|
|
776
809
|
resource.windows = buildResourceWindowFallbackTimes({
|
|
777
810
|
raw,
|
|
@@ -1012,7 +1045,7 @@ function buildBookingFromAvailabilitySelection(params) {
|
|
|
1012
1045
|
}
|
|
1013
1046
|
};
|
|
1014
1047
|
}
|
|
1015
|
-
const POS_OVERRIDABLE_AVAILABILITY_CONFLICT_CODES = new Set(['past', 'booking_cutoff', 'resource_full', 'resource_unavailable', 'resource_capacity_exceeded',
|
|
1048
|
+
const POS_OVERRIDABLE_AVAILABILITY_CONFLICT_CODES = new Set(['past', 'booking_cutoff', 'schedule_excluded_date', 'resource_full', 'resource_unavailable', 'resource_capacity_exceeded',
|
|
1016
1049
|
// A required group whose selected resource is outside its window is surfaced
|
|
1017
1050
|
// as missing_resource at group level. Structural validation below still
|
|
1018
1051
|
// rejects a genuinely missing selection.
|
|
@@ -2147,11 +2180,31 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
2147
2180
|
source: `operating_schedule:${descriptor.scheduleIds.map(String).join(',')}`
|
|
2148
2181
|
}] : [];
|
|
2149
2182
|
}));
|
|
2183
|
+
const excludedWindows = dateRangeDays(params.dateRange).flatMap(date => schedules.flatMap(schedule => getExcludedScheduleDateOverrideTimePoints(date, schedule).flatMap(slot => {
|
|
2184
|
+
const range = normalizeAvailabilityLocalDateTimeRange({
|
|
2185
|
+
startAt: slot.start_at,
|
|
2186
|
+
endAt: slot.end_at,
|
|
2187
|
+
fixedOffsetMinutes: this.getAvailabilityRuntimeOffsetMinutes(),
|
|
2188
|
+
rollOverEnd: true
|
|
2189
|
+
});
|
|
2190
|
+
return range ? [{
|
|
2191
|
+
startAt: range.startAt,
|
|
2192
|
+
endAt: range.endAt,
|
|
2193
|
+
source: `operating_schedule_excluded:${schedule.id}`,
|
|
2194
|
+
raw: {
|
|
2195
|
+
scheduleId: schedule.id,
|
|
2196
|
+
excludedDate: date
|
|
2197
|
+
}
|
|
2198
|
+
}] : [];
|
|
2199
|
+
})));
|
|
2150
2200
|
return {
|
|
2151
2201
|
source: descriptor.source,
|
|
2152
2202
|
scheduleIds: [...descriptor.scheduleIds],
|
|
2153
2203
|
missingScheduleIds,
|
|
2154
|
-
windows
|
|
2204
|
+
windows,
|
|
2205
|
+
...(excludedWindows.length > 0 ? {
|
|
2206
|
+
excludedWindows
|
|
2207
|
+
} : {})
|
|
2155
2208
|
};
|
|
2156
2209
|
}
|
|
2157
2210
|
async loadAvailabilityProducts(params) {
|
|
@@ -2849,7 +2902,9 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
2849
2902
|
} : {})
|
|
2850
2903
|
}, {
|
|
2851
2904
|
evaluatedAt: this.getAvailabilityRuntimeDateTime(),
|
|
2852
|
-
|
|
2905
|
+
useExactDurationCandidateMinute: isPosAvailabilityRequest,
|
|
2906
|
+
allowSessionCustomRanges: isPosAvailabilityRequest,
|
|
2907
|
+
allowExcludedScheduleDateOverride: isPosAvailabilityRequest
|
|
2853
2908
|
}).map(group => ({
|
|
2854
2909
|
...group,
|
|
2855
2910
|
ranges: group.ranges.map(candidate => this.decorateAvailabilityCandidate(context.contextId, contextVersion, candidate))
|
|
@@ -2864,7 +2919,11 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
2864
2919
|
refreshed
|
|
2865
2920
|
} = await this.ensureAvailabilityContextProjection(contextId, 'query_booking_availability');
|
|
2866
2921
|
this.assertContextualQueryFreshness(context, request);
|
|
2867
|
-
const
|
|
2922
|
+
const isPosAvailabilityRequest = String(this.otherParams?.platform ?? '').trim().toLowerCase() === 'pos';
|
|
2923
|
+
const result = (0, _ResourcePlanner.queryAvailabilityProjection)(projectionEntry.projection, request, {
|
|
2924
|
+
useExactDurationCandidateMinute: isPosAvailabilityRequest,
|
|
2925
|
+
allowExcludedScheduleDateOverride: isPosAvailabilityRequest
|
|
2926
|
+
});
|
|
2868
2927
|
const contextVersion = context.activeVersion;
|
|
2869
2928
|
const contextualResult = result.target === 'time' ? {
|
|
2870
2929
|
...result,
|
|
@@ -3121,7 +3180,9 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
3121
3180
|
} : {})
|
|
3122
3181
|
} : {})
|
|
3123
3182
|
}, {
|
|
3124
|
-
|
|
3183
|
+
useExactDurationCandidateMinute: allowPosAvailabilityOverride,
|
|
3184
|
+
allowSessionCustomRanges: allowPosAvailabilityOverride,
|
|
3185
|
+
allowExcludedScheduleDateOverride: allowPosAvailabilityOverride
|
|
3125
3186
|
}).flatMap(group => group.ranges);
|
|
3126
3187
|
const refreshedCandidateBase = refreshedRanges.find(candidate => candidate.startAt === params.candidate.startAt && candidate.endAt === params.candidate.endAt && candidate.bookingStartAt === params.candidate.bookingStartAt && candidate.bookingEndAt === params.candidate.bookingEndAt);
|
|
3127
3188
|
if (!refreshedCandidateBase) {
|
|
@@ -3142,6 +3203,9 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
|
|
|
3142
3203
|
requirementSelections: params.requirementSelections,
|
|
3143
3204
|
partySize,
|
|
3144
3205
|
bookingCount
|
|
3206
|
+
}, {
|
|
3207
|
+
useExactDurationCandidateMinute: allowPosAvailabilityOverride,
|
|
3208
|
+
allowExcludedScheduleDateOverride: allowPosAvailabilityOverride
|
|
3145
3209
|
});
|
|
3146
3210
|
if (allowPosAvailabilityOverride && !validation.ok && validation.conflicts.every(conflict => POS_OVERRIDABLE_AVAILABILITY_CONFLICT_CODES.has(conflict.code))) {
|
|
3147
3211
|
const forced = buildPosForcedAvailabilityAssignments({
|