@pisell/pisellos 0.10.63 → 0.10.65
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/model/strategy/adapter/promotion/index.js +0 -9
- package/dist/modules/Order/index.d.ts +6 -1
- package/dist/modules/Order/index.js +564 -475
- package/dist/modules/Order/types.d.ts +2 -0
- package/dist/modules/Order/utils/discountProductLineIdentity.js +2 -2
- package/dist/modules/Order/utils/productVoucherGroups.d.ts +15 -0
- package/dist/modules/Order/utils/productVoucherGroups.js +115 -0
- package/dist/modules/Rules/index.d.ts +2 -0
- package/dist/modules/Rules/index.js +29 -21
- package/dist/solution/BaseSales/index.d.ts +8 -1
- package/dist/solution/BaseSales/index.js +53 -41
- package/dist/solution/BookingTicket/index.js +36 -11
- package/dist/solution/BookingTicket/types.d.ts +2 -0
- package/lib/modules/Order/index.d.ts +6 -1
- package/lib/modules/Order/index.js +49 -6
- package/lib/modules/Order/types.d.ts +2 -0
- package/lib/modules/Order/utils/discountProductLineIdentity.js +1 -1
- package/lib/modules/Order/utils/productVoucherGroups.d.ts +15 -0
- package/lib/modules/Order/utils/productVoucherGroups.js +64 -0
- package/lib/modules/Rules/index.d.ts +2 -0
- package/lib/modules/Rules/index.js +6 -0
- package/lib/solution/BaseSales/index.d.ts +8 -1
- package/lib/solution/BaseSales/index.js +9 -2
- package/lib/solution/BookingTicket/index.js +14 -0
- package/lib/solution/BookingTicket/types.d.ts +2 -0
- package/package.json +1 -1
|
@@ -241,7 +241,7 @@ function consolidateDiscountFreeProductLines(tempOrder) {
|
|
|
241
241
|
bundle_selling_price: String(bundle?.bundle_selling_price ?? '')
|
|
242
242
|
}))
|
|
243
243
|
});
|
|
244
|
-
const mergeKey = [discountSplitOriginUid, product.product_id, product.product_variant_id ?? 0, fingerprint, priceSignature].join('#');
|
|
244
|
+
const mergeKey = [discountSplitOriginUid, productUid ? tempOrder._extend?.productsByUid?.[productUid]?.productVoucherGroupKey ?? '' : '', product.product_id, product.product_variant_id ?? 0, fingerprint, priceSignature].join('#');
|
|
245
245
|
const matchedIndex = productIndexByKey.get(mergeKey);
|
|
246
246
|
|
|
247
247
|
// 当前拆分来源 + 商品结构组合第一次出现时,保留该行作为回并目标。
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Discount } from '../../Discount/types';
|
|
2
|
+
import type { OrderTempOrder } from '../types';
|
|
3
|
+
/** Runtime-only allocation constraints. Never copied into order/product metadata. */
|
|
4
|
+
export interface ProductVoucherGroup {
|
|
5
|
+
key: string;
|
|
6
|
+
discountIds: Array<number | string>;
|
|
7
|
+
}
|
|
8
|
+
export declare function productVoucherGroupKey(order: OrderTempOrder, product: any): string | undefined;
|
|
9
|
+
export declare function isUsableProductVoucher(discount: Discount): boolean;
|
|
10
|
+
export declare function voucherGroupError(): Error & {
|
|
11
|
+
code: string;
|
|
12
|
+
};
|
|
13
|
+
export declare function allowsProductVoucher(order: OrderTempOrder | null | undefined, product: any, discount: Discount): boolean;
|
|
14
|
+
/** Reductions stay possible when a voucher expires; checkout always requires full coverage. */
|
|
15
|
+
export declare function validateProductVoucherGroups(order: OrderTempOrder, discounts: Discount[], previous?: OrderTempOrder, requireCoverage?: boolean): void;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.allowsProductVoucher = allowsProductVoucher;
|
|
7
|
+
exports.isUsableProductVoucher = isUsableProductVoucher;
|
|
8
|
+
exports.productVoucherGroupKey = productVoucherGroupKey;
|
|
9
|
+
exports.validateProductVoucherGroups = validateProductVoucherGroups;
|
|
10
|
+
exports.voucherGroupError = voucherGroupError;
|
|
11
|
+
var _availability = require("../../Discount/availability");
|
|
12
|
+
var _dayjs = _interopRequireDefault(require("dayjs"));
|
|
13
|
+
var _utils = require("../../../solution/ShopDiscount/utils");
|
|
14
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
15
|
+
/** Runtime-only allocation constraints. Never copied into order/product metadata. */
|
|
16
|
+
|
|
17
|
+
function productVoucherGroupKey(order, product) {
|
|
18
|
+
const uid = product?.metadata?.unique_identification_number ?? product?.unique_identification_number;
|
|
19
|
+
return order._extend?.productsByUid?.[String(uid)]?.productVoucherGroupKey;
|
|
20
|
+
}
|
|
21
|
+
function isUsableProductVoucher(discount) {
|
|
22
|
+
return (discount.tag || discount.type) === 'good_pass' && !discount.isEditMode && !discount.isDisabled && !(0, _availability.getDiscountUsageBlockReason)(discount) && !(0, _utils.getDiscountTimeUnavailableReason)(discount, (0, _dayjs.default)().format('YYYY-MM-DD HH:mm:ss'));
|
|
23
|
+
}
|
|
24
|
+
function voucherGroupError() {
|
|
25
|
+
return Object.assign(new Error('The selected items no longer match the available vouchers. Please adjust your selection.'), {
|
|
26
|
+
code: 'PRODUCT_VOUCHER_GROUP_INVALID'
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function allowsProductVoucher(order, product, discount) {
|
|
30
|
+
const groups = order?._extend?.productVoucherGroups;
|
|
31
|
+
if (!order || !groups?.length) return true;
|
|
32
|
+
const key = productVoucherGroupKey(order, product);
|
|
33
|
+
if (!key) return !groups.some(group => group.discountIds.some(id => String(id) === String(discount.id)));
|
|
34
|
+
return (discount.tag || discount.type) === 'good_pass' && Boolean(groups.find(group => group.key === key)?.discountIds.some(id => String(id) === String(discount.id)));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Reductions stay possible when a voucher expires; checkout always requires full coverage. */
|
|
38
|
+
function validateProductVoucherGroups(order, discounts, previous, requireCoverage = false) {
|
|
39
|
+
const groups = order._extend?.productVoucherGroups;
|
|
40
|
+
if (!groups?.length) return;
|
|
41
|
+
const usedIds = new Set();
|
|
42
|
+
const byId = new Map(discounts.filter(isUsableProductVoucher).map(discount => [String(discount.id), discount]));
|
|
43
|
+
const quantity = item => Number(item.num ?? item.product_quantity ?? 0);
|
|
44
|
+
for (const group of groups) {
|
|
45
|
+
const items = order.products.filter(item => productVoucherGroupKey(order, item) === group.key);
|
|
46
|
+
const selected = items.reduce((sum, item) => sum + quantity(item), 0);
|
|
47
|
+
const before = previous?.products.filter(item => productVoucherGroupKey(previous, item) === group.key).reduce((sum, item) => sum + quantity(item), 0) ?? 0;
|
|
48
|
+
if (!requireCoverage && previous && selected <= before) continue;
|
|
49
|
+
const ids = new Set(group.discountIds.map(String).filter(id => byId.has(id)));
|
|
50
|
+
if (!Number.isInteger(selected) || selected < 0 || selected > ids.size) throw voucherGroupError();
|
|
51
|
+
if (!requireCoverage) continue;
|
|
52
|
+
for (const item of items) {
|
|
53
|
+
const applied = (item.discount_list || []).filter(entry => (entry.type || entry.tag) === 'good_pass');
|
|
54
|
+
// Rules splits a good_pass allocation into one physical line per instance.
|
|
55
|
+
const id = String(applied[0]?.discount?.resource_id ?? '');
|
|
56
|
+
const voucher = byId.get(id);
|
|
57
|
+
if (quantity(item) !== 1 || applied.length !== 1 || !ids.has(id) || !voucher?.isAvailable || voucher.config?.isAvailable === false || usedIds.has(id)) throw voucherGroupError();
|
|
58
|
+
usedIds.add(id);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (order.products.some(item => !groups.some(group => group.key === productVoucherGroupKey(order, item)))) {
|
|
62
|
+
throw voucherGroupError();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -9,6 +9,8 @@ export declare class RulesModule extends BaseModule implements Module, RulesModu
|
|
|
9
9
|
private store;
|
|
10
10
|
window: WindowPlugin;
|
|
11
11
|
private hooks;
|
|
12
|
+
private discountScope?;
|
|
13
|
+
setDiscountScope(scope: (product: any, discount: Discount) => boolean): void;
|
|
12
14
|
constructor(name?: string, version?: string);
|
|
13
15
|
initialize(core: PisellCore, options?: ModuleOptions): Promise<void>;
|
|
14
16
|
setRulesList(rulesList: Rules[]): Promise<void>;
|
|
@@ -20,6 +20,10 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
20
20
|
store;
|
|
21
21
|
window; // WindowPlugin 实例
|
|
22
22
|
hooks;
|
|
23
|
+
discountScope;
|
|
24
|
+
setDiscountScope(scope) {
|
|
25
|
+
this.discountScope = scope;
|
|
26
|
+
}
|
|
23
27
|
constructor(name, version) {
|
|
24
28
|
super(name, version);
|
|
25
29
|
this.hooks = {};
|
|
@@ -722,6 +726,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
722
726
|
|
|
723
727
|
// 辅助函数:检查商品是否对某个折扣卡可用
|
|
724
728
|
const checkItemApplicableForDiscount = (flatItem, discount) => {
|
|
729
|
+
if (this.discountScope && !this.discountScope(flatItem.originProduct, discount)) return false;
|
|
725
730
|
if (discount.config?.isAvailable === false) return false;
|
|
726
731
|
let product;
|
|
727
732
|
if (flatItem.type === 'main') {
|
|
@@ -1093,6 +1098,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1093
1098
|
|
|
1094
1099
|
// 找到适用于此商品的所有优惠券,仅考虑isSelected不为false的优惠券
|
|
1095
1100
|
const applicableDiscounts = sortedDiscountList.filter(discount => {
|
|
1101
|
+
if (this.discountScope && !this.discountScope(originProduct, discount)) return false;
|
|
1096
1102
|
if (this.isItemRewardProductDiscount(product)) return false;
|
|
1097
1103
|
// 🔥 检查策略可用性(针对全额抵扣卡和折扣卡)
|
|
1098
1104
|
const discountType = discount.tag || discount.type;
|
|
@@ -338,7 +338,9 @@ export declare class BaseSalesImpl extends BaseModule implements Module {
|
|
|
338
338
|
discountList: Discount[];
|
|
339
339
|
originalDiscountList?: Discount[];
|
|
340
340
|
}): Promise<void>;
|
|
341
|
-
scanPromotionCode(code: string, customerId?: number
|
|
341
|
+
scanPromotionCode(code: string, customerId?: number, options?: {
|
|
342
|
+
productVouchersOnly?: boolean;
|
|
343
|
+
}): Promise<BaseSalesScanCodeResult>;
|
|
342
344
|
setDiscountSelected(params: {
|
|
343
345
|
discountId: number;
|
|
344
346
|
isSelected: boolean;
|
|
@@ -519,6 +521,11 @@ export declare class BaseSalesImpl extends BaseModule implements Module {
|
|
|
519
521
|
private hasSmartPricingStrategies;
|
|
520
522
|
shouldRecalculateProductBookingPricesForContextChange(params: BaseSalesPriceRecalculationContextChange): boolean;
|
|
521
523
|
calculateProductBookingPrices(paramsList: BaseSalesCalculateProductBookingPriceParams[]): Promise<BaseSalesProductBookingPriceResult[]>;
|
|
524
|
+
setProductVoucherGroups(groups: Array<{
|
|
525
|
+
key: string;
|
|
526
|
+
discountIds: Array<number | string>;
|
|
527
|
+
}>): void;
|
|
528
|
+
validateProductVoucherSelection(): void;
|
|
522
529
|
addProductToOrder(product: Partial<BaseSalesOrderProduct> & BaseSalesOrderProductIdentity, booking?: AddProductBookingInput, options?: AddProductToOrderOptions): Promise<import("../../modules/Order/types").OrderProduct[]>;
|
|
523
530
|
updateOrderProduct(params: UpdateOrderProductParams): Promise<import("../../modules/Order/types").OrderProduct[]>;
|
|
524
531
|
updateOrderProducts(paramsList: UpdateOrderProductParams[]): Promise<import("../../modules/Order/types").OrderProduct[]>;
|
|
@@ -2412,10 +2412,10 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
|
|
|
2412
2412
|
}
|
|
2413
2413
|
await discountModule.setDiscountList(params.discountList);
|
|
2414
2414
|
}
|
|
2415
|
-
async scanPromotionCode(code, customerId) {
|
|
2415
|
+
async scanPromotionCode(code, customerId, options) {
|
|
2416
2416
|
try {
|
|
2417
2417
|
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
2418
|
-
const raw = await this.store.order.scanCode(code, customerId);
|
|
2418
|
+
const raw = await this.store.order.scanCode(code, customerId, options);
|
|
2419
2419
|
if (raw.isAvailable) {
|
|
2420
2420
|
await this.store.order.recalculateSummary({
|
|
2421
2421
|
createIfMissing: true
|
|
@@ -4112,6 +4112,13 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
|
|
|
4112
4112
|
});
|
|
4113
4113
|
});
|
|
4114
4114
|
}
|
|
4115
|
+
setProductVoucherGroups(groups) {
|
|
4116
|
+
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
4117
|
+
this.store.order.setProductVoucherGroups(groups);
|
|
4118
|
+
}
|
|
4119
|
+
validateProductVoucherSelection() {
|
|
4120
|
+
this.store.order?.validateProductVoucherSelection();
|
|
4121
|
+
}
|
|
4115
4122
|
async addProductToOrder(product, booking, options) {
|
|
4116
4123
|
try {
|
|
4117
4124
|
if (!this.store.order) throw new Error('order 模块未初始化');
|
|
@@ -1699,6 +1699,20 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
|
|
|
1699
1699
|
return bridge.openBookingEdit(payload);
|
|
1700
1700
|
} : undefined
|
|
1701
1701
|
} : undefined;
|
|
1702
|
+
if (guardedBridge?.scanScope === 'product-voucher') {
|
|
1703
|
+
if (!guardedBridge.applyPromotionCode) return {
|
|
1704
|
+
status: 'failed',
|
|
1705
|
+
reason: 'no_bridge'
|
|
1706
|
+
};
|
|
1707
|
+
const promotion = await guardedBridge.applyPromotionCode(trimmed);
|
|
1708
|
+
return promotion.isAccepted ?? promotion.isAvailable ? {
|
|
1709
|
+
status: 'success',
|
|
1710
|
+
kind: 'promotion'
|
|
1711
|
+
} : {
|
|
1712
|
+
status: 'failed',
|
|
1713
|
+
reason: 'not_found'
|
|
1714
|
+
};
|
|
1715
|
+
}
|
|
1702
1716
|
const localSearch = v => this.store.products.findProductsByCodeOrBarcode(v);
|
|
1703
1717
|
const scanCallback = (0, _handleScan.handleGlobalScan)(this.request, localSearch, this.getSubmitOrderSalesChannel());
|
|
1704
1718
|
const raw = await scanCallback({
|
|
@@ -208,6 +208,8 @@ 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
213
|
openProductDetail?: (payload: Record<string, any>) => Promise<any | null>;
|
|
212
214
|
openBookingEdit?: (payload: Record<string, any>) => Promise<any | null>;
|
|
213
215
|
confirmDetail?: (input: {
|