@pisell/pisellos 1.0.142 → 1.0.143
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/index.d.ts +4 -0
- package/dist/model/strategy/adapter/index.js +4 -1
- package/dist/model/strategy/adapter/itemRule/adapter.d.ts +67 -0
- package/dist/model/strategy/adapter/itemRule/adapter.js +483 -0
- package/dist/model/strategy/adapter/itemRule/evaluator.d.ts +43 -0
- package/dist/model/strategy/adapter/itemRule/evaluator.js +169 -0
- package/dist/model/strategy/adapter/itemRule/examples.d.ts +79 -0
- package/dist/model/strategy/adapter/itemRule/examples.js +382 -0
- package/dist/model/strategy/adapter/itemRule/index.d.ts +5 -0
- package/dist/model/strategy/adapter/itemRule/index.js +5 -0
- package/dist/model/strategy/adapter/itemRule/type.d.ts +225 -0
- package/dist/model/strategy/adapter/itemRule/type.js +101 -0
- package/dist/model/strategy/adapter/promotion/adapter.d.ts +66 -0
- package/dist/model/strategy/adapter/promotion/adapter.js +271 -0
- package/dist/model/strategy/adapter/promotion/evaluator.d.ts +213 -0
- package/dist/model/strategy/adapter/promotion/evaluator.js +1279 -0
- package/dist/model/strategy/adapter/promotion/examples.d.ts +138 -0
- package/dist/model/strategy/adapter/promotion/examples.js +166 -0
- package/dist/model/strategy/adapter/promotion/index.d.ts +4 -0
- package/dist/model/strategy/adapter/promotion/index.js +0 -0
- package/dist/model/strategy/adapter/promotion/type.d.ts +447 -0
- package/dist/model/strategy/adapter/promotion/type.js +209 -0
- package/dist/model/strategy/adapter/walletPass/evaluator.d.ts +2 -0
- package/dist/model/strategy/adapter/walletPass/evaluator.js +67 -10
- package/dist/model/strategy/adapter/walletPass/locales.js +12 -3
- package/dist/model/strategy/adapter/walletPass/type.d.ts +20 -2
- package/dist/model/strategy/adapter/walletPass/utils.d.ts +6 -6
- package/dist/model/strategy/adapter/walletPass/utils.js +143 -103
- package/dist/model/strategy/engine.d.ts +119 -0
- package/dist/model/strategy/engine.js +711 -0
- package/dist/model/strategy/index.d.ts +2 -93
- package/dist/model/strategy/index.js +6 -549
- package/dist/model/strategy/type.d.ts +17 -0
- package/dist/model/strategy/type.js +4 -0
- package/dist/solution/BookingByStep/index.d.ts +1 -1
- package/dist/solution/BookingTicket/index.d.ts +1 -1
- package/lib/model/strategy/adapter/index.d.ts +4 -0
- package/lib/model/strategy/adapter/index.js +12 -0
- package/lib/model/strategy/adapter/itemRule/adapter.d.ts +67 -0
- package/lib/model/strategy/adapter/itemRule/adapter.js +377 -0
- package/lib/model/strategy/adapter/itemRule/evaluator.d.ts +43 -0
- package/lib/model/strategy/adapter/itemRule/evaluator.js +124 -0
- package/lib/model/strategy/adapter/itemRule/examples.d.ts +79 -0
- package/lib/model/strategy/adapter/itemRule/examples.js +316 -0
- package/lib/model/strategy/adapter/itemRule/index.d.ts +5 -0
- package/lib/model/strategy/adapter/itemRule/index.js +60 -0
- package/lib/model/strategy/adapter/itemRule/type.d.ts +225 -0
- package/lib/model/strategy/adapter/itemRule/type.js +46 -0
- package/lib/model/strategy/adapter/promotion/adapter.d.ts +66 -0
- package/lib/model/strategy/adapter/promotion/adapter.js +217 -0
- package/lib/model/strategy/adapter/promotion/evaluator.d.ts +213 -0
- package/lib/model/strategy/adapter/promotion/evaluator.js +893 -0
- package/lib/model/strategy/adapter/promotion/examples.d.ts +138 -0
- package/lib/model/strategy/adapter/promotion/examples.js +192 -0
- package/lib/model/strategy/adapter/promotion/index.d.ts +4 -0
- package/lib/model/strategy/adapter/promotion/index.js +0 -0
- package/lib/model/strategy/adapter/promotion/type.d.ts +447 -0
- package/lib/model/strategy/adapter/promotion/type.js +51 -0
- package/lib/model/strategy/adapter/walletPass/evaluator.d.ts +2 -0
- package/lib/model/strategy/adapter/walletPass/evaluator.js +38 -5
- package/lib/model/strategy/adapter/walletPass/locales.js +12 -3
- package/lib/model/strategy/adapter/walletPass/type.d.ts +20 -2
- package/lib/model/strategy/adapter/walletPass/utils.d.ts +6 -6
- package/lib/model/strategy/adapter/walletPass/utils.js +153 -82
- package/lib/model/strategy/engine.d.ts +119 -0
- package/lib/model/strategy/engine.js +514 -0
- package/lib/model/strategy/index.d.ts +2 -93
- package/lib/model/strategy/index.js +6 -381
- package/lib/model/strategy/type.d.ts +17 -0
- package/lib/solution/BookingByStep/index.d.ts +1 -1
- package/lib/solution/BookingTicket/index.d.ts +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import { Product, Voucher } from './type';
|
|
2
|
+
/** 订单商品数量 */
|
|
3
|
+
export declare const getProductQuantity: (product: any) => any;
|
|
4
|
+
/**
|
|
5
|
+
* 订单商品行唯一键,用于 maxPassesPerItem 按行、按件配额(同 SPU 不同规格视为不同行)。
|
|
6
|
+
*/
|
|
7
|
+
export declare function resolveWalletPassLineKey(product: any, indexInOrder: number): string;
|
|
2
8
|
export declare const getApplicableProductIds: (voucher: Voucher) => number[] | null;
|
|
3
9
|
/**
|
|
4
10
|
* 优惠券处理函数
|
|
@@ -64,12 +70,6 @@ export declare const getBundleItemDiscountDifference: (bundleItem: any) => numbe
|
|
|
64
70
|
* @returns 子商品总价格(不含舍入余数)
|
|
65
71
|
*/
|
|
66
72
|
export declare const getBundleItemPrice: (bundleItem: any, parentQuantity: number, isDeductTaxAndFee: boolean) => number;
|
|
67
|
-
/**
|
|
68
|
-
* 获取商品数量
|
|
69
|
-
* @param product 商品
|
|
70
|
-
* @returns 商品数量
|
|
71
|
-
*/
|
|
72
|
-
export declare const getProductQuantity: (product: any) => any;
|
|
73
73
|
export declare const getBundleItemIsOriginalPrice: (item: any) => boolean;
|
|
74
74
|
export declare const getBundleItemIsMarkupPrice: (item: any) => boolean;
|
|
75
75
|
export declare const getBundleItemIsDiscountPrice: (item: any) => boolean;
|
|
@@ -42,10 +42,72 @@ __export(utils_exports, {
|
|
|
42
42
|
getProductQuantity: () => getProductQuantity,
|
|
43
43
|
getTaxAndFeeRoundingRemainder: () => getTaxAndFeeRoundingRemainder,
|
|
44
44
|
processVouchers: () => processVouchers,
|
|
45
|
-
recalculateVouchers: () => recalculateVouchers
|
|
45
|
+
recalculateVouchers: () => recalculateVouchers,
|
|
46
|
+
resolveWalletPassLineKey: () => resolveWalletPassLineKey
|
|
46
47
|
});
|
|
47
48
|
module.exports = __toCommonJS(utils_exports);
|
|
48
49
|
var import_decimal = __toESM(require("decimal.js"));
|
|
50
|
+
var getProductQuantity = (product) => {
|
|
51
|
+
return product.quantity || product.product_quantity || 1;
|
|
52
|
+
};
|
|
53
|
+
function resolveWalletPassLineKey(product, indexInOrder) {
|
|
54
|
+
const m = (product == null ? void 0 : product.metadata) || {};
|
|
55
|
+
const u1 = m.product_unique;
|
|
56
|
+
if (u1 != null && String(u1).length > 0)
|
|
57
|
+
return String(u1);
|
|
58
|
+
const u2 = m.unique_identification_number;
|
|
59
|
+
if (u2 != null && String(u2).length > 0)
|
|
60
|
+
return String(u2);
|
|
61
|
+
if ((product == null ? void 0 : product.id) != null && String(product.id).length > 0)
|
|
62
|
+
return `order_item:${product.id}`;
|
|
63
|
+
if ((product == null ? void 0 : product.product_unique_string) != null && String(product.product_unique_string).length > 0) {
|
|
64
|
+
return String(product.product_unique_string);
|
|
65
|
+
}
|
|
66
|
+
return `order_line_${indexInOrder}`;
|
|
67
|
+
}
|
|
68
|
+
function getExpandedOrderLineQuantity(p) {
|
|
69
|
+
if ((p == null ? void 0 : p._orderLineQuantity) != null && p._orderLineQuantity > 0)
|
|
70
|
+
return p._orderLineQuantity;
|
|
71
|
+
return getProductQuantity(p);
|
|
72
|
+
}
|
|
73
|
+
function getMaxPassSlotsForExpandedLine(maxPassesPerItem, p) {
|
|
74
|
+
if (maxPassesPerItem <= 0)
|
|
75
|
+
return Infinity;
|
|
76
|
+
return maxPassesPerItem * getExpandedOrderLineQuantity(p);
|
|
77
|
+
}
|
|
78
|
+
function computePassSlotsIncrement(deductAmount, deductQty, maxPassesPerItem, orderLineQuantity, currentUsage) {
|
|
79
|
+
if (maxPassesPerItem <= 0 || !deductAmount.greaterThan(0))
|
|
80
|
+
return 0;
|
|
81
|
+
const cap = maxPassesPerItem * orderLineQuantity;
|
|
82
|
+
const room = Math.max(0, cap - currentUsage);
|
|
83
|
+
if (room <= 0)
|
|
84
|
+
return 0;
|
|
85
|
+
const raw = Math.max(1, Math.ceil(Number(deductQty)) || 1);
|
|
86
|
+
return Math.min(raw, room);
|
|
87
|
+
}
|
|
88
|
+
function applyMaxPassUsageIncrements(usageMap, walletPassProductId, maxPassesPerItem, deductionDetails) {
|
|
89
|
+
deductionDetails.forEach((detail) => {
|
|
90
|
+
var _a;
|
|
91
|
+
const lineKey = detail.lineKey;
|
|
92
|
+
if (!lineKey || maxPassesPerItem <= 0)
|
|
93
|
+
return;
|
|
94
|
+
const orderLineQty = detail.orderLineQuantity != null && detail.orderLineQuantity > 0 ? detail.orderLineQuantity : 1;
|
|
95
|
+
const cur = ((_a = usageMap.get(walletPassProductId)) == null ? void 0 : _a.get(lineKey)) || 0;
|
|
96
|
+
const inc = computePassSlotsIncrement(
|
|
97
|
+
new import_decimal.default(detail.deductAmount),
|
|
98
|
+
detail.deductQuantity,
|
|
99
|
+
maxPassesPerItem,
|
|
100
|
+
orderLineQty,
|
|
101
|
+
cur
|
|
102
|
+
);
|
|
103
|
+
if (inc <= 0)
|
|
104
|
+
return;
|
|
105
|
+
if (!usageMap.has(walletPassProductId))
|
|
106
|
+
usageMap.set(walletPassProductId, /* @__PURE__ */ new Map());
|
|
107
|
+
const inner = usageMap.get(walletPassProductId);
|
|
108
|
+
inner.set(lineKey, cur + inc);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
49
111
|
var getRecommendedAmount = (voucher) => {
|
|
50
112
|
console.log("voucher312", voucher);
|
|
51
113
|
const { config, recommended_usage_amount, recommended_pure_product_usage_amount } = voucher;
|
|
@@ -90,23 +152,19 @@ function processVouchers(applicableVouchers, orderTotalAmount, products) {
|
|
|
90
152
|
console.log(products, "products123");
|
|
91
153
|
const productsCopy = expandProductsWithBundleItems(products, true);
|
|
92
154
|
let remainingOrderAmount = new import_decimal.default(orderTotalAmount);
|
|
93
|
-
const getItemPassUsage = (usageMap, walletPassProductId,
|
|
155
|
+
const getItemPassUsage = (usageMap, walletPassProductId, lineKey) => {
|
|
94
156
|
var _a;
|
|
95
|
-
return ((_a = usageMap.get(walletPassProductId)) == null ? void 0 : _a.get(
|
|
96
|
-
};
|
|
97
|
-
const incrementItemPassUsage = (usageMap, walletPassProductId, orderItemProductId) => {
|
|
98
|
-
if (!usageMap.has(walletPassProductId)) {
|
|
99
|
-
usageMap.set(walletPassProductId, /* @__PURE__ */ new Map());
|
|
100
|
-
}
|
|
101
|
-
const innerMap = usageMap.get(walletPassProductId);
|
|
102
|
-
innerMap.set(orderItemProductId, (innerMap.get(orderItemProductId) || 0) + 1);
|
|
157
|
+
return ((_a = usageMap.get(walletPassProductId)) == null ? void 0 : _a.get(lineKey)) || 0;
|
|
103
158
|
};
|
|
104
159
|
const filterByMaxPassesPerItem = (products2, usageMap, walletPassProductId, maxPassesPerItem) => {
|
|
105
160
|
if (maxPassesPerItem <= 0)
|
|
106
161
|
return products2;
|
|
107
|
-
return products2.filter(
|
|
108
|
-
|
|
109
|
-
|
|
162
|
+
return products2.filter((p) => {
|
|
163
|
+
const lineKey = p._walletPassLineKey;
|
|
164
|
+
if (!lineKey)
|
|
165
|
+
return true;
|
|
166
|
+
return getItemPassUsage(usageMap, walletPassProductId, lineKey) < getMaxPassSlotsForExpandedLine(maxPassesPerItem, p);
|
|
167
|
+
});
|
|
110
168
|
};
|
|
111
169
|
const calculateAvailableMaxAmount = (voucher, productsData, itemPassUsage) => {
|
|
112
170
|
const { config } = voucher;
|
|
@@ -150,14 +208,16 @@ function processVouchers(applicableVouchers, orderTotalAmount, products) {
|
|
|
150
208
|
}
|
|
151
209
|
} else {
|
|
152
210
|
const maxProduct = applicableProducts.reduce(
|
|
153
|
-
(max, p) => p[
|
|
154
|
-
);
|
|
155
|
-
const currentAvailableQty = Math.ceil(maxProduct[amountField].dividedBy(maxProduct[unitPriceField]).toNumber());
|
|
156
|
-
const deductQty = applicableProductLimit > 0 ? Math.min(currentAvailableQty, applicableProductLimit) : currentAvailableQty;
|
|
157
|
-
finalApplicableAmount = import_decimal.default.min(
|
|
158
|
-
maxProduct[unitPriceField].times(deductQty),
|
|
159
|
-
maxProduct[amountField]
|
|
211
|
+
(max, p) => p[unitPriceField].greaterThan(max[unitPriceField]) ? p : max
|
|
160
212
|
);
|
|
213
|
+
if (maxPassesPerItem > 0) {
|
|
214
|
+
finalApplicableAmount = import_decimal.default.min(
|
|
215
|
+
maxProduct[unitPriceField].times(maxPassesPerItem),
|
|
216
|
+
maxProduct[amountField]
|
|
217
|
+
);
|
|
218
|
+
} else {
|
|
219
|
+
finalApplicableAmount = maxProduct[amountField];
|
|
220
|
+
}
|
|
161
221
|
}
|
|
162
222
|
return import_decimal.default.min(baseAmount, finalApplicableAmount, remainingOrderAmount);
|
|
163
223
|
};
|
|
@@ -184,7 +244,12 @@ function processVouchers(applicableVouchers, orderTotalAmount, products) {
|
|
|
184
244
|
if (maxPassesPerItem > 0 && itemPassUsage) {
|
|
185
245
|
const deductTaxAndFee = (config == null ? void 0 : config.deductTaxAndFee) ?? true;
|
|
186
246
|
const amountField = deductTaxAndFee ? "remainingAmountWithTax" : "remainingAmountPure";
|
|
187
|
-
const availableAfterPassLimit = getApplicableProducts(voucher, productsData).filter((p) => p[amountField].greaterThan(0)).filter((p) =>
|
|
247
|
+
const availableAfterPassLimit = getApplicableProducts(voucher, productsData).filter((p) => p[amountField].greaterThan(0)).filter((p) => {
|
|
248
|
+
const lineKey = p._walletPassLineKey;
|
|
249
|
+
if (!lineKey)
|
|
250
|
+
return true;
|
|
251
|
+
return getItemPassUsage(itemPassUsage, product_id, lineKey) < getMaxPassSlotsForExpandedLine(maxPassesPerItem, p);
|
|
252
|
+
});
|
|
188
253
|
if (availableAfterPassLimit.length === 0) {
|
|
189
254
|
return { isAvailable: false, reasonCode: "max_passes_per_item_reached" };
|
|
190
255
|
}
|
|
@@ -267,14 +332,16 @@ function processVouchers(applicableVouchers, orderTotalAmount, products) {
|
|
|
267
332
|
}
|
|
268
333
|
} else {
|
|
269
334
|
const maxProduct = applicableProducts.reduce(
|
|
270
|
-
(max, p) => p[
|
|
271
|
-
);
|
|
272
|
-
const currentAvailableQty = Math.ceil(maxProduct[amountField].dividedBy(maxProduct[unitPriceField]).toNumber());
|
|
273
|
-
const deductQty = applicableProductLimit > 0 ? Math.min(currentAvailableQty, applicableProductLimit) : currentAvailableQty;
|
|
274
|
-
calculatedAvailableMaxAmount = import_decimal.default.min(
|
|
275
|
-
maxProduct[unitPriceField].times(deductQty),
|
|
276
|
-
maxProduct[amountField]
|
|
335
|
+
(max, p) => p[unitPriceField].greaterThan(max[unitPriceField]) ? p : max
|
|
277
336
|
);
|
|
337
|
+
if (maxPassesPerItem > 0) {
|
|
338
|
+
calculatedAvailableMaxAmount = import_decimal.default.min(
|
|
339
|
+
maxProduct[unitPriceField].times(maxPassesPerItem),
|
|
340
|
+
maxProduct[amountField]
|
|
341
|
+
);
|
|
342
|
+
} else {
|
|
343
|
+
calculatedAvailableMaxAmount = maxProduct[amountField];
|
|
344
|
+
}
|
|
278
345
|
}
|
|
279
346
|
const availableMaxAmount = import_decimal.default.min(
|
|
280
347
|
baseAmount,
|
|
@@ -310,6 +377,8 @@ function processVouchers(applicableVouchers, orderTotalAmount, products) {
|
|
|
310
377
|
product_id: product.product_id,
|
|
311
378
|
parent_product_id: product.parent_product_id || null,
|
|
312
379
|
is_bundle_item: product.is_bundle_item || false,
|
|
380
|
+
lineKey: product._walletPassLineKey,
|
|
381
|
+
orderLineQuantity: getExpandedOrderLineQuantity(product),
|
|
313
382
|
deductAmount: actualDeductAmount.toNumber(),
|
|
314
383
|
// 转换为数字
|
|
315
384
|
deductQuantity: actualDeductQty
|
|
@@ -318,14 +387,15 @@ function processVouchers(applicableVouchers, orderTotalAmount, products) {
|
|
|
318
387
|
}
|
|
319
388
|
} else {
|
|
320
389
|
const targetProduct = applicableProducts.reduce(
|
|
321
|
-
(max, p) => p[
|
|
322
|
-
);
|
|
323
|
-
const currentAvailableQty = Math.ceil(targetProduct[amountField].dividedBy(targetProduct[unitPriceField]).toNumber());
|
|
324
|
-
const availableQty = applicableProductLimit > 0 ? Math.min(currentAvailableQty, applicableProductLimit) : currentAvailableQty;
|
|
325
|
-
const maxDeductForProduct = import_decimal.default.min(
|
|
326
|
-
targetProduct[unitPriceField].times(availableQty),
|
|
327
|
-
targetProduct[amountField]
|
|
390
|
+
(max, p) => p[unitPriceField].greaterThan(max[unitPriceField]) ? p : max
|
|
328
391
|
);
|
|
392
|
+
let maxDeductForProduct = targetProduct[amountField];
|
|
393
|
+
if (maxPassesPerItem > 0) {
|
|
394
|
+
maxDeductForProduct = import_decimal.default.min(
|
|
395
|
+
targetProduct[unitPriceField].times(maxPassesPerItem),
|
|
396
|
+
targetProduct[amountField]
|
|
397
|
+
);
|
|
398
|
+
}
|
|
329
399
|
const actualDeductAmount = import_decimal.default.min(deductionLeft, maxDeductForProduct);
|
|
330
400
|
const actualDeductQty = Math.ceil(actualDeductAmount.dividedBy(targetProduct[unitPriceField]).toNumber());
|
|
331
401
|
targetProduct[amountField] = targetProduct[amountField].minus(actualDeductAmount);
|
|
@@ -334,19 +404,17 @@ function processVouchers(applicableVouchers, orderTotalAmount, products) {
|
|
|
334
404
|
product_id: targetProduct.product_id,
|
|
335
405
|
parent_product_id: targetProduct.parent_product_id || null,
|
|
336
406
|
is_bundle_item: targetProduct.is_bundle_item || false,
|
|
407
|
+
lineKey: targetProduct._walletPassLineKey,
|
|
408
|
+
orderLineQuantity: getExpandedOrderLineQuantity(targetProduct),
|
|
337
409
|
deductAmount: actualDeductAmount.toNumber(),
|
|
338
|
-
// 转换为数字
|
|
339
410
|
deductQuantity: actualDeductQty
|
|
340
|
-
// 抵扣涉及的数量(用于记录)
|
|
341
411
|
});
|
|
342
412
|
}
|
|
343
413
|
const totalDeducted = maxDeduction.minus(deductionLeft);
|
|
344
414
|
if (totalDeducted.greaterThan(0)) {
|
|
345
415
|
remainingOrderAmount = remainingOrderAmount.minus(totalDeducted);
|
|
346
416
|
usedVoucherCounts.set(product_id, (usedVoucherCounts.get(product_id) || 0) + 1);
|
|
347
|
-
|
|
348
|
-
incrementItemPassUsage(itemPassUsageMap, product_id, detail.product_id);
|
|
349
|
-
});
|
|
417
|
+
applyMaxPassUsageIncrements(itemPassUsageMap, product_id, maxPassesPerItem, deductionDetails);
|
|
350
418
|
recommendedVouchers.push({
|
|
351
419
|
...voucher,
|
|
352
420
|
actualDeduction: totalDeducted.toNumber(),
|
|
@@ -389,23 +457,19 @@ function recalculateVouchers(allVouchers, selectedVouchers, orderTotalAmount, pr
|
|
|
389
457
|
let remainingOrderAmount = new import_decimal.default(orderTotalAmount);
|
|
390
458
|
const selectedWithDetails = [];
|
|
391
459
|
const itemPassUsageMap = /* @__PURE__ */ new Map();
|
|
392
|
-
const
|
|
460
|
+
const getItemPassUsageRecalc = (walletPassProductId, lineKey) => {
|
|
393
461
|
var _a;
|
|
394
|
-
return ((_a = itemPassUsageMap.get(walletPassProductId)) == null ? void 0 : _a.get(
|
|
462
|
+
return ((_a = itemPassUsageMap.get(walletPassProductId)) == null ? void 0 : _a.get(lineKey)) || 0;
|
|
395
463
|
};
|
|
396
|
-
const
|
|
397
|
-
if (!itemPassUsageMap.has(walletPassProductId)) {
|
|
398
|
-
itemPassUsageMap.set(walletPassProductId, /* @__PURE__ */ new Map());
|
|
399
|
-
}
|
|
400
|
-
const innerMap = itemPassUsageMap.get(walletPassProductId);
|
|
401
|
-
innerMap.set(orderItemProductId, (innerMap.get(orderItemProductId) || 0) + 1);
|
|
402
|
-
};
|
|
403
|
-
const filterByMaxPassesPerItem = (products2, walletPassProductId, maxPassesPerItem) => {
|
|
464
|
+
const filterByMaxPassesPerItemRecalc = (products2, walletPassProductId, maxPassesPerItem) => {
|
|
404
465
|
if (maxPassesPerItem <= 0)
|
|
405
466
|
return products2;
|
|
406
|
-
return products2.filter(
|
|
407
|
-
|
|
408
|
-
|
|
467
|
+
return products2.filter((p) => {
|
|
468
|
+
const lineKey = p._walletPassLineKey;
|
|
469
|
+
if (!lineKey)
|
|
470
|
+
return true;
|
|
471
|
+
return getItemPassUsageRecalc(walletPassProductId, lineKey) < getMaxPassSlotsForExpandedLine(maxPassesPerItem, p);
|
|
472
|
+
});
|
|
409
473
|
};
|
|
410
474
|
selectedVouchers.forEach((selectedVoucher) => {
|
|
411
475
|
const { config, id } = selectedVoucher;
|
|
@@ -416,7 +480,7 @@ function recalculateVouchers(allVouchers, selectedVouchers, orderTotalAmount, pr
|
|
|
416
480
|
selectedVoucher,
|
|
417
481
|
productsForCalc
|
|
418
482
|
).filter((p) => p[amountField].greaterThan(0));
|
|
419
|
-
applicableProducts =
|
|
483
|
+
applicableProducts = filterByMaxPassesPerItemRecalc(applicableProducts, selectedVoucher.product_id, maxPassesPerItem);
|
|
420
484
|
if (applicableProducts.length === 0) {
|
|
421
485
|
selectedWithDetails.push({
|
|
422
486
|
...selectedVoucher,
|
|
@@ -459,6 +523,8 @@ function recalculateVouchers(allVouchers, selectedVouchers, orderTotalAmount, pr
|
|
|
459
523
|
product_id: product.product_id,
|
|
460
524
|
parent_product_id: product.parent_product_id || null,
|
|
461
525
|
is_bundle_item: product.is_bundle_item || false,
|
|
526
|
+
lineKey: product._walletPassLineKey,
|
|
527
|
+
orderLineQuantity: getExpandedOrderLineQuantity(product),
|
|
462
528
|
deductAmount: actualDeductAmount.toNumber(),
|
|
463
529
|
// 转换为数字
|
|
464
530
|
deductQuantity: actualDeductQty
|
|
@@ -467,14 +533,15 @@ function recalculateVouchers(allVouchers, selectedVouchers, orderTotalAmount, pr
|
|
|
467
533
|
}
|
|
468
534
|
} else {
|
|
469
535
|
const targetProduct = applicableProducts.reduce(
|
|
470
|
-
(max, p) => p[
|
|
471
|
-
);
|
|
472
|
-
const currentAvailableQty = Math.ceil(targetProduct[amountField].dividedBy(targetProduct[unitPriceField]).toNumber());
|
|
473
|
-
const availableQty = applicableProductLimit > 0 ? Math.min(currentAvailableQty, applicableProductLimit) : currentAvailableQty;
|
|
474
|
-
const maxDeductForProduct = import_decimal.default.min(
|
|
475
|
-
targetProduct[unitPriceField].times(availableQty),
|
|
476
|
-
targetProduct[amountField]
|
|
536
|
+
(max, p) => p[unitPriceField].greaterThan(max[unitPriceField]) ? p : max
|
|
477
537
|
);
|
|
538
|
+
let maxDeductForProduct = targetProduct[amountField];
|
|
539
|
+
if (maxPassesPerItem > 0) {
|
|
540
|
+
maxDeductForProduct = import_decimal.default.min(
|
|
541
|
+
targetProduct[unitPriceField].times(maxPassesPerItem),
|
|
542
|
+
targetProduct[amountField]
|
|
543
|
+
);
|
|
544
|
+
}
|
|
478
545
|
const actualDeductAmount = import_decimal.default.min(deductionLeft, maxDeductForProduct);
|
|
479
546
|
const actualDeductQty = Math.ceil(actualDeductAmount.dividedBy(targetProduct[unitPriceField]).toNumber());
|
|
480
547
|
targetProduct[amountField] = targetProduct[amountField].minus(actualDeductAmount);
|
|
@@ -483,17 +550,15 @@ function recalculateVouchers(allVouchers, selectedVouchers, orderTotalAmount, pr
|
|
|
483
550
|
product_id: targetProduct.product_id,
|
|
484
551
|
parent_product_id: targetProduct.parent_product_id || null,
|
|
485
552
|
is_bundle_item: targetProduct.is_bundle_item || false,
|
|
553
|
+
lineKey: targetProduct._walletPassLineKey,
|
|
554
|
+
orderLineQuantity: getExpandedOrderLineQuantity(targetProduct),
|
|
486
555
|
deductAmount: actualDeductAmount.toNumber(),
|
|
487
|
-
// 转换为数字
|
|
488
556
|
deductQuantity: actualDeductQty
|
|
489
|
-
// 抵扣涉及的数量(用于记录)
|
|
490
557
|
});
|
|
491
558
|
}
|
|
492
559
|
const totalDeducted = maxDeduction.minus(deductionLeft);
|
|
493
560
|
remainingOrderAmount = remainingOrderAmount.minus(totalDeducted);
|
|
494
|
-
|
|
495
|
-
incrementItemPassUsage(selectedVoucher.product_id, detail.product_id);
|
|
496
|
-
});
|
|
561
|
+
applyMaxPassUsageIncrements(itemPassUsageMap, selectedVoucher.product_id, maxPassesPerItem, deductionDetails);
|
|
497
562
|
selectedWithDetails.push({
|
|
498
563
|
...selectedVoucher,
|
|
499
564
|
actualDeduction: totalDeducted.toNumber(),
|
|
@@ -543,7 +608,7 @@ function recalculateVouchers(allVouchers, selectedVouchers, orderTotalAmount, pr
|
|
|
543
608
|
voucher,
|
|
544
609
|
productsForCalc
|
|
545
610
|
).filter((p) => p[amountField].greaterThan(0));
|
|
546
|
-
applicableProducts =
|
|
611
|
+
applicableProducts = filterByMaxPassesPerItemRecalc(applicableProducts, product_id, maxPassesPerItem);
|
|
547
612
|
if (applicableProducts.length === 0) {
|
|
548
613
|
isAvailable = false;
|
|
549
614
|
reasonCode = "not_meet_the_required_conditions";
|
|
@@ -576,14 +641,16 @@ function recalculateVouchers(allVouchers, selectedVouchers, orderTotalAmount, pr
|
|
|
576
641
|
}
|
|
577
642
|
} else {
|
|
578
643
|
const maxProduct = applicableProducts.reduce(
|
|
579
|
-
(max, p) => p[
|
|
580
|
-
);
|
|
581
|
-
const currentAvailableQty = Math.ceil(maxProduct[amountField].dividedBy(maxProduct[unitPriceField]).toNumber());
|
|
582
|
-
const deductQty = applicableProductLimit > 0 ? Math.min(currentAvailableQty, applicableProductLimit) : currentAvailableQty;
|
|
583
|
-
calculatedMaxAmount = import_decimal.default.min(
|
|
584
|
-
maxProduct[unitPriceField].times(deductQty),
|
|
585
|
-
maxProduct[amountField]
|
|
644
|
+
(max, p) => p[unitPriceField].greaterThan(max[unitPriceField]) ? p : max
|
|
586
645
|
);
|
|
646
|
+
if (maxPassesPerItem > 0) {
|
|
647
|
+
calculatedMaxAmount = import_decimal.default.min(
|
|
648
|
+
maxProduct[unitPriceField].times(maxPassesPerItem),
|
|
649
|
+
maxProduct[amountField]
|
|
650
|
+
);
|
|
651
|
+
} else {
|
|
652
|
+
calculatedMaxAmount = maxProduct[amountField];
|
|
653
|
+
}
|
|
587
654
|
}
|
|
588
655
|
calculatedMaxAmount = import_decimal.default.min(
|
|
589
656
|
baseAmount,
|
|
@@ -627,7 +694,7 @@ var getProductDiscountDifference = (product) => {
|
|
|
627
694
|
return new import_decimal.default(((_a = product == null ? void 0 : product.metadata) == null ? void 0 : _a.product_discount_difference) || 0).toNumber();
|
|
628
695
|
};
|
|
629
696
|
var getMainProductPrice = (product, isDeductTaxAndFee) => {
|
|
630
|
-
var _a, _b, _c;
|
|
697
|
+
var _a, _b, _c, _d, _e;
|
|
631
698
|
let mainProductPrice = new import_decimal.default((product == null ? void 0 : product.main_product_selling_price) || ((_a = product.metadata) == null ? void 0 : _a.main_product_selling_price) || 0);
|
|
632
699
|
for (let bundleItem of (product == null ? void 0 : product.product_bundle) || []) {
|
|
633
700
|
if (getBundleItemIsMarkupOrDiscountPrice(bundleItem)) {
|
|
@@ -635,11 +702,11 @@ var getMainProductPrice = (product, isDeductTaxAndFee) => {
|
|
|
635
702
|
mainProductPrice = mainProductPrice.add(bundleItemPrice.times(bundleItem.num));
|
|
636
703
|
}
|
|
637
704
|
}
|
|
638
|
-
let taxFee = new import_decimal.default((product == null ? void 0 : product.tax_fee) || ((_b = product == null ? void 0 : product.metadata) == null ? void 0 : _b.main_product_attached_bundle_tax_fee) || 0);
|
|
705
|
+
let taxFee = new import_decimal.default((product == null ? void 0 : product.tax_fee) || ((_b = product == null ? void 0 : product.metadata) == null ? void 0 : _b.main_product_attached_bundle_tax_fee) || 0).add(((_c = product == null ? void 0 : product.metadata) == null ? void 0 : _c.tax_fee_rounding_remainder) || 0);
|
|
639
706
|
if (product.is_price_include_tax === 1) {
|
|
640
707
|
taxFee = new import_decimal.default(0);
|
|
641
708
|
}
|
|
642
|
-
const surchargeFee = new import_decimal.default(((
|
|
709
|
+
const surchargeFee = new import_decimal.default(((_d = product == null ? void 0 : product.metadata) == null ? void 0 : _d.main_product_attached_bundle_surcharge_fee) || 0).add(((_e = product == null ? void 0 : product.metadata) == null ? void 0 : _e.surcharge_rounding_remainder) || 0);
|
|
643
710
|
const taxAndFeeTotal = taxFee.add(surchargeFee);
|
|
644
711
|
if (isDeductTaxAndFee) {
|
|
645
712
|
mainProductPrice = mainProductPrice.add(taxAndFeeTotal);
|
|
@@ -678,8 +745,9 @@ var getBundleItemPrice = (bundleItem, parentQuantity, isDeductTaxAndFee) => {
|
|
|
678
745
|
};
|
|
679
746
|
var expandProductsWithBundleItems = (products, deductTaxAndFee) => {
|
|
680
747
|
const expandedProducts = [];
|
|
681
|
-
products.forEach((product) => {
|
|
748
|
+
products.forEach((product, indexInOrder) => {
|
|
682
749
|
const productQuantity = getProductQuantity(product);
|
|
750
|
+
const parentLineKey = resolveWalletPassLineKey(product, indexInOrder);
|
|
683
751
|
const unitPriceWithTax = getMainProductPrice(product, true);
|
|
684
752
|
const unitPricePure = getMainProductPrice(product, false);
|
|
685
753
|
const roundingRemainderWithTax = getTaxAndFeeRoundingRemainder(product, true);
|
|
@@ -688,6 +756,8 @@ var expandProductsWithBundleItems = (products, deductTaxAndFee) => {
|
|
|
688
756
|
...product,
|
|
689
757
|
is_bundle_item: false,
|
|
690
758
|
parent_product_id: null,
|
|
759
|
+
_walletPassLineKey: parentLineKey,
|
|
760
|
+
_orderLineQuantity: productQuantity,
|
|
691
761
|
// 单价(用于按 quantity 计算抵扣)
|
|
692
762
|
unitPriceWithTax: new import_decimal.default(unitPriceWithTax),
|
|
693
763
|
unitPricePure: new import_decimal.default(unitPricePure),
|
|
@@ -702,6 +772,7 @@ var expandProductsWithBundleItems = (products, deductTaxAndFee) => {
|
|
|
702
772
|
product.product_bundle.forEach((bundleItem) => {
|
|
703
773
|
if (getBundleItemIsOriginalPrice(bundleItem)) {
|
|
704
774
|
const bundleQuantity = bundleItem.num * productQuantity;
|
|
775
|
+
const bundleLineKey = `${parentLineKey}#bundle:${bundleItem.bundle_id}:${bundleItem.bundle_product_id}`;
|
|
705
776
|
const bundleUnitPriceWithTax = new import_decimal.default(getBundleItemPrice(bundleItem, 1, true)).dividedBy(bundleItem.num);
|
|
706
777
|
const bundleUnitPricePure = new import_decimal.default(getBundleItemPrice(bundleItem, 1, false)).dividedBy(bundleItem.num);
|
|
707
778
|
const bundleRoundingRemainder = getBundleItemTaxAndFeeRoundingRemainder(bundleItem, true);
|
|
@@ -714,6 +785,8 @@ var expandProductsWithBundleItems = (products, deductTaxAndFee) => {
|
|
|
714
785
|
parent_product_id: product.product_id,
|
|
715
786
|
quantity: bundleQuantity,
|
|
716
787
|
// 子商品数量 * 主商品数量
|
|
788
|
+
_walletPassLineKey: bundleLineKey,
|
|
789
|
+
_orderLineQuantity: bundleQuantity,
|
|
717
790
|
// 单价(用于按 quantity 计算抵扣)
|
|
718
791
|
unitPriceWithTax: bundleUnitPriceWithTax,
|
|
719
792
|
unitPricePure: bundleUnitPricePure,
|
|
@@ -730,9 +803,6 @@ var expandProductsWithBundleItems = (products, deductTaxAndFee) => {
|
|
|
730
803
|
});
|
|
731
804
|
return expandedProducts;
|
|
732
805
|
};
|
|
733
|
-
var getProductQuantity = (product) => {
|
|
734
|
-
return product.quantity || product.product_quantity || 1;
|
|
735
|
-
};
|
|
736
806
|
var getBundleItemIsOriginalPrice = (item) => {
|
|
737
807
|
return (item == null ? void 0 : item.price_type) === "markup" && (item == null ? void 0 : item.price_type_ext) === "product_price";
|
|
738
808
|
};
|
|
@@ -760,5 +830,6 @@ var getBundleItemIsMarkupOrDiscountPrice = (item) => {
|
|
|
760
830
|
getProductQuantity,
|
|
761
831
|
getTaxAndFeeRoundingRemainder,
|
|
762
832
|
processVouchers,
|
|
763
|
-
recalculateVouchers
|
|
833
|
+
recalculateVouchers,
|
|
834
|
+
resolveWalletPassLineKey
|
|
764
835
|
});
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { StrategyConfig, RuntimeContext, EvaluationResult, EngineOptions, OperatorHandler } from './type';
|
|
2
|
+
/**
|
|
3
|
+
* 策略模型 - 核心实现
|
|
4
|
+
*
|
|
5
|
+
* 完全业务无关的通用策略引擎
|
|
6
|
+
* 支持条件评估、动作匹配、结果返回
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* 策略引擎
|
|
10
|
+
*
|
|
11
|
+
* 核心职责:
|
|
12
|
+
* 1. 递归评估条件组
|
|
13
|
+
* 2. 收集满足条件的 actionIds
|
|
14
|
+
* 3. 根据 actionIds 获取 ActionEffect 对象
|
|
15
|
+
* 4. 按 priority 排序返回
|
|
16
|
+
*/
|
|
17
|
+
export declare class StrategyEngine {
|
|
18
|
+
private options;
|
|
19
|
+
private operatorHandlers;
|
|
20
|
+
constructor(options?: EngineOptions);
|
|
21
|
+
/**
|
|
22
|
+
* 评估策略
|
|
23
|
+
*/
|
|
24
|
+
evaluate(config: StrategyConfig, context: RuntimeContext): EvaluationResult;
|
|
25
|
+
/**
|
|
26
|
+
* 递归评估条件组
|
|
27
|
+
*
|
|
28
|
+
* @param group 条件组
|
|
29
|
+
* @param context 运行时上下文
|
|
30
|
+
* @returns 评估结果对象,包含条件是否满足、收集到的 actionIds、以及失败的原子规则信息
|
|
31
|
+
*/
|
|
32
|
+
private evaluateConditionGroup;
|
|
33
|
+
/**
|
|
34
|
+
* 评估条件组的逻辑运算(带失败规则收集)
|
|
35
|
+
*
|
|
36
|
+
* 失败规则收集策略:
|
|
37
|
+
* - `and`:返回第一个失败的原子规则(嵌套 group 递归取其首个失败原子规则)
|
|
38
|
+
* - `or`:所有分支失败时,聚合每个分支的失败规则
|
|
39
|
+
* - `not`:被取反规则实际通过时,记录该规则
|
|
40
|
+
*/
|
|
41
|
+
private evaluateGroupLogicDetailed;
|
|
42
|
+
/**
|
|
43
|
+
* 评估单个规则(带失败规则收集)
|
|
44
|
+
*/
|
|
45
|
+
private evaluateRuleDetailed;
|
|
46
|
+
/**
|
|
47
|
+
* 评估原子规则(不收集失败信息,仅返回布尔结果)
|
|
48
|
+
*/
|
|
49
|
+
private evaluateAtomicRule;
|
|
50
|
+
/**
|
|
51
|
+
* 构建失败规则信息
|
|
52
|
+
*/
|
|
53
|
+
private buildFailedRuleInfo;
|
|
54
|
+
/**
|
|
55
|
+
* 评估代码模式条件
|
|
56
|
+
*/
|
|
57
|
+
private evaluateCodeCondition;
|
|
58
|
+
/**
|
|
59
|
+
* 评估运算符模式条件
|
|
60
|
+
*/
|
|
61
|
+
private evaluateOperatorCondition;
|
|
62
|
+
/**
|
|
63
|
+
* 获取字段值
|
|
64
|
+
*/
|
|
65
|
+
private getFieldValue;
|
|
66
|
+
/**
|
|
67
|
+
* 评估内置运算符
|
|
68
|
+
*/
|
|
69
|
+
private evaluateBuiltInOperator;
|
|
70
|
+
/**
|
|
71
|
+
* 根据 actionIds 获取 ActionEffect 对象
|
|
72
|
+
*/
|
|
73
|
+
private getActionsByIds;
|
|
74
|
+
/**
|
|
75
|
+
* 按 priority 排序(降序)
|
|
76
|
+
*/
|
|
77
|
+
private sortActionsByPriority;
|
|
78
|
+
/**
|
|
79
|
+
* 判断是否为条件组
|
|
80
|
+
*/
|
|
81
|
+
private isConditionGroup;
|
|
82
|
+
/**
|
|
83
|
+
* 验证配置
|
|
84
|
+
*/
|
|
85
|
+
private validateConfig;
|
|
86
|
+
/**
|
|
87
|
+
* 初始化内置运算符处理器
|
|
88
|
+
*/
|
|
89
|
+
private initializeBuiltInOperators;
|
|
90
|
+
/**
|
|
91
|
+
* 评估商品匹配运算符
|
|
92
|
+
*
|
|
93
|
+
* 匹配规则:
|
|
94
|
+
* - 首先匹配 product_id
|
|
95
|
+
* - 如果配置的 product_variant_id = 0,只需要 product_id 匹配(表示任意变体)
|
|
96
|
+
* - 如果配置的 product_variant_id != 0,还需要 product_variant_id 精确匹配
|
|
97
|
+
*
|
|
98
|
+
* @param fieldValue 实际商品 { product_id: number, product_variant_id: number }
|
|
99
|
+
* @param configValue 配置的商品列表 Array<{ product_id: number, product_variant_id: number }>
|
|
100
|
+
* @returns 是否匹配
|
|
101
|
+
*/
|
|
102
|
+
private evaluateProductMatch;
|
|
103
|
+
/**
|
|
104
|
+
* 注册自定义运算符
|
|
105
|
+
*/
|
|
106
|
+
registerOperator(operator: string, handler: OperatorHandler): void;
|
|
107
|
+
/**
|
|
108
|
+
* 获取调试信息
|
|
109
|
+
*/
|
|
110
|
+
getDebugInfo(): Record<string, any>;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* 创建策略引擎实例
|
|
114
|
+
*/
|
|
115
|
+
export declare function createStrategyEngine(options?: EngineOptions): StrategyEngine;
|
|
116
|
+
/**
|
|
117
|
+
* 快速评估(使用默认引擎配置)
|
|
118
|
+
*/
|
|
119
|
+
export declare function evaluate(config: StrategyConfig, context: RuntimeContext): EvaluationResult;
|