@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
|
@@ -115,7 +115,7 @@ export declare class BookingTicketImpl extends BaseModule implements Module {
|
|
|
115
115
|
* 获取当前的客户搜索条件
|
|
116
116
|
* @returns 当前搜索条件
|
|
117
117
|
*/
|
|
118
|
-
getCurrentCustomerSearchParams(): Omit<import("../../modules").ShopGetCustomerListParams, "
|
|
118
|
+
getCurrentCustomerSearchParams(): Omit<import("../../modules").ShopGetCustomerListParams, "num" | "skip">;
|
|
119
119
|
/**
|
|
120
120
|
* 获取客户列表状态(包含滚动加载相关状态)
|
|
121
121
|
* @returns 客户状态
|
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
export * from './type';
|
|
2
2
|
export { default as WalletPassAdapter } from './walletPass';
|
|
3
3
|
export { WalletPassEvaluator } from './walletPass/evaluator';
|
|
4
|
+
export { PromotionEvaluator } from './promotion/evaluator';
|
|
5
|
+
export { PromotionAdapter } from './promotion/adapter';
|
|
6
|
+
export { ItemRuleEvaluator } from './itemRule/evaluator';
|
|
7
|
+
export { ItemRuleAdapter } from './itemRule/adapter';
|
|
@@ -30,6 +30,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/model/strategy/adapter/index.ts
|
|
31
31
|
var adapter_exports = {};
|
|
32
32
|
__export(adapter_exports, {
|
|
33
|
+
ItemRuleAdapter: () => import_adapter2.ItemRuleAdapter,
|
|
34
|
+
ItemRuleEvaluator: () => import_evaluator3.ItemRuleEvaluator,
|
|
35
|
+
PromotionAdapter: () => import_adapter.PromotionAdapter,
|
|
36
|
+
PromotionEvaluator: () => import_evaluator2.PromotionEvaluator,
|
|
33
37
|
WalletPassAdapter: () => import_walletPass.default,
|
|
34
38
|
WalletPassEvaluator: () => import_evaluator.WalletPassEvaluator
|
|
35
39
|
});
|
|
@@ -37,8 +41,16 @@ module.exports = __toCommonJS(adapter_exports);
|
|
|
37
41
|
__reExport(adapter_exports, require("./type"), module.exports);
|
|
38
42
|
var import_walletPass = __toESM(require("./walletPass"));
|
|
39
43
|
var import_evaluator = require("./walletPass/evaluator");
|
|
44
|
+
var import_evaluator2 = require("./promotion/evaluator");
|
|
45
|
+
var import_adapter = require("./promotion/adapter");
|
|
46
|
+
var import_evaluator3 = require("./itemRule/evaluator");
|
|
47
|
+
var import_adapter2 = require("./itemRule/adapter");
|
|
40
48
|
// Annotate the CommonJS export names for ESM import in node:
|
|
41
49
|
0 && (module.exports = {
|
|
50
|
+
ItemRuleAdapter,
|
|
51
|
+
ItemRuleEvaluator,
|
|
52
|
+
PromotionAdapter,
|
|
53
|
+
PromotionEvaluator,
|
|
42
54
|
WalletPassAdapter,
|
|
43
55
|
WalletPassEvaluator,
|
|
44
56
|
...require("./type")
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { EvaluationResult, RuntimeContext, ActionEffect } from '../../type';
|
|
2
|
+
import type { BusinessAdapter } from '../type';
|
|
3
|
+
import { type ItemRuleBusinessData, type QuantityCheckResult, type PrefillItemResult, type QuantityLimitResult } from './type';
|
|
4
|
+
/**
|
|
5
|
+
* ItemRule 适配器
|
|
6
|
+
*
|
|
7
|
+
* 将堂食扫码点餐的商品规则业务数据转换为策略引擎格式,
|
|
8
|
+
* 并将引擎结果转换为数量校验和预填购物车的业务结果。
|
|
9
|
+
*/
|
|
10
|
+
export declare class ItemRuleAdapter implements BusinessAdapter {
|
|
11
|
+
name: string;
|
|
12
|
+
version: string;
|
|
13
|
+
prepareContext(businessData: ItemRuleBusinessData): RuntimeContext;
|
|
14
|
+
transformResult(result: EvaluationResult, businessData?: ItemRuleBusinessData): {
|
|
15
|
+
quantityChecks: QuantityCheckResult[];
|
|
16
|
+
prefillItems: PrefillItemResult[];
|
|
17
|
+
};
|
|
18
|
+
formatConfig(result: EvaluationResult, businessData?: ItemRuleBusinessData): {
|
|
19
|
+
result: EvaluationResult;
|
|
20
|
+
businessData?: ItemRuleBusinessData;
|
|
21
|
+
};
|
|
22
|
+
private processQuantityCheck;
|
|
23
|
+
/**
|
|
24
|
+
* 根据 scope 判断当前提交是否需要评估此规则
|
|
25
|
+
*/
|
|
26
|
+
private shouldEvaluateForScope;
|
|
27
|
+
/**
|
|
28
|
+
* 计算目标商品在购物车中的实际数量
|
|
29
|
+
*
|
|
30
|
+
* cumulative scope 时还会加上历史订单数量。
|
|
31
|
+
*/
|
|
32
|
+
private calculateActualQuantity;
|
|
33
|
+
/**
|
|
34
|
+
* 逐条评估数量规则
|
|
35
|
+
*/
|
|
36
|
+
private evaluateQuantityRules;
|
|
37
|
+
/**
|
|
38
|
+
* 根据 source 解析出数量规则的实际数值
|
|
39
|
+
*/
|
|
40
|
+
private resolveQuantityValue;
|
|
41
|
+
/**
|
|
42
|
+
* 格式化校验提示语,替换 {min} {max} 占位符
|
|
43
|
+
*/
|
|
44
|
+
private formatValidationMessage;
|
|
45
|
+
/**
|
|
46
|
+
* 从单个 QUANTITY_CHECK action 中提取数量限制配置。
|
|
47
|
+
*
|
|
48
|
+
* 根据 scope + submissionIndex 判断是否适用,
|
|
49
|
+
* 解析动态数量来源(pax)后返回 min/max/exact/mustInclude。
|
|
50
|
+
* 不适用或无配置时返回 null。
|
|
51
|
+
*/
|
|
52
|
+
extractQuantityLimits(action: ActionEffect, businessData: ItemRuleBusinessData): QuantityLimitResult | null;
|
|
53
|
+
/**
|
|
54
|
+
* 计算当前提交剩余可选的最大数量。
|
|
55
|
+
*
|
|
56
|
+
* 仅在 scope='cumulative' 且存在 requiredMax 时生效:
|
|
57
|
+
* 用 requiredMax 减去 historicalItems 中目标商品的累计数量,保底 0。
|
|
58
|
+
* 其余场景返回 undefined,保持向后兼容。
|
|
59
|
+
*/
|
|
60
|
+
private calculateRemainingMax;
|
|
61
|
+
private processPrefillCart;
|
|
62
|
+
/**
|
|
63
|
+
* 根据 quantityFrom 计算预填数量
|
|
64
|
+
*/
|
|
65
|
+
private resolvePrefillQuantity;
|
|
66
|
+
}
|
|
67
|
+
export default ItemRuleAdapter;
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
|
|
19
|
+
// src/model/strategy/adapter/itemRule/adapter.ts
|
|
20
|
+
var adapter_exports = {};
|
|
21
|
+
__export(adapter_exports, {
|
|
22
|
+
ItemRuleAdapter: () => ItemRuleAdapter,
|
|
23
|
+
default: () => adapter_default
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(adapter_exports);
|
|
26
|
+
var import_type = require("./type");
|
|
27
|
+
var ItemRuleAdapter = class {
|
|
28
|
+
constructor() {
|
|
29
|
+
this.name = "ItemRuleAdapter";
|
|
30
|
+
this.version = "1.0.0";
|
|
31
|
+
}
|
|
32
|
+
prepareContext(businessData) {
|
|
33
|
+
const { pax, cartItems, serviceType, submissionIndex, custom } = businessData;
|
|
34
|
+
return {
|
|
35
|
+
entities: {
|
|
36
|
+
cartItems,
|
|
37
|
+
historicalItems: businessData.historicalItems || [],
|
|
38
|
+
pax
|
|
39
|
+
},
|
|
40
|
+
attributes: {
|
|
41
|
+
serviceType: serviceType || "",
|
|
42
|
+
totalPax: pax.total,
|
|
43
|
+
adultPax: pax.adult,
|
|
44
|
+
childPax: pax.child,
|
|
45
|
+
submissionIndex: submissionIndex ?? 0,
|
|
46
|
+
cartItemCount: cartItems.length,
|
|
47
|
+
cartTotalQuantity: cartItems.reduce((sum, item) => sum + item.quantity, 0),
|
|
48
|
+
...custom
|
|
49
|
+
},
|
|
50
|
+
metadata: {
|
|
51
|
+
timestamp: Date.now()
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
transformResult(result, businessData) {
|
|
56
|
+
const quantityChecks = [];
|
|
57
|
+
const prefillItems = [];
|
|
58
|
+
if (!result.applicable || !businessData) {
|
|
59
|
+
return { quantityChecks, prefillItems };
|
|
60
|
+
}
|
|
61
|
+
for (const action of result.matchedActions) {
|
|
62
|
+
if (action.type === import_type.ITEM_RULE_ACTION_TYPES.QUANTITY_CHECK) {
|
|
63
|
+
const checkResult = this.processQuantityCheck(action, businessData);
|
|
64
|
+
if (checkResult) {
|
|
65
|
+
quantityChecks.push(checkResult);
|
|
66
|
+
}
|
|
67
|
+
} else if (action.type === import_type.ITEM_RULE_ACTION_TYPES.PREFILL_CART) {
|
|
68
|
+
const items = this.processPrefillCart(action, businessData);
|
|
69
|
+
prefillItems.push(...items);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return { quantityChecks, prefillItems };
|
|
73
|
+
}
|
|
74
|
+
formatConfig(result, businessData) {
|
|
75
|
+
return { result, businessData };
|
|
76
|
+
}
|
|
77
|
+
// ============================================
|
|
78
|
+
// Quantity Check 处理
|
|
79
|
+
// ============================================
|
|
80
|
+
processQuantityCheck(action, businessData) {
|
|
81
|
+
const config = action.config;
|
|
82
|
+
if (!config)
|
|
83
|
+
return null;
|
|
84
|
+
const { scope, quantityRules, targetType, targets, validationMessage } = config;
|
|
85
|
+
if (!this.shouldEvaluateForScope(scope, businessData.submissionIndex ?? 0)) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
const actualQuantity = this.calculateActualQuantity(
|
|
89
|
+
targets,
|
|
90
|
+
scope,
|
|
91
|
+
businessData
|
|
92
|
+
);
|
|
93
|
+
const { passed, failedRules, requiredMin, requiredMax, requiredExact, mustInclude } = this.evaluateQuantityRules(quantityRules, actualQuantity, businessData);
|
|
94
|
+
const message = this.formatValidationMessage(
|
|
95
|
+
validationMessage,
|
|
96
|
+
requiredMin,
|
|
97
|
+
requiredMax,
|
|
98
|
+
requiredExact
|
|
99
|
+
);
|
|
100
|
+
return {
|
|
101
|
+
ruleId: action.id,
|
|
102
|
+
passed,
|
|
103
|
+
targetType,
|
|
104
|
+
targets,
|
|
105
|
+
actualQuantity,
|
|
106
|
+
requiredMin,
|
|
107
|
+
requiredMax,
|
|
108
|
+
requiredExact,
|
|
109
|
+
mustInclude,
|
|
110
|
+
validationMessage: message,
|
|
111
|
+
scope,
|
|
112
|
+
failedRules: passed ? void 0 : failedRules
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* 根据 scope 判断当前提交是否需要评估此规则
|
|
117
|
+
*/
|
|
118
|
+
shouldEvaluateForScope(scope, submissionIndex) {
|
|
119
|
+
switch (scope) {
|
|
120
|
+
case "each_submission":
|
|
121
|
+
return true;
|
|
122
|
+
case "first_submission_only":
|
|
123
|
+
return submissionIndex === 0;
|
|
124
|
+
case "subsequent_only":
|
|
125
|
+
return submissionIndex > 0;
|
|
126
|
+
case "cumulative":
|
|
127
|
+
return true;
|
|
128
|
+
default:
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* 计算目标商品在购物车中的实际数量
|
|
134
|
+
*
|
|
135
|
+
* cumulative scope 时还会加上历史订单数量。
|
|
136
|
+
*/
|
|
137
|
+
calculateActualQuantity(targets, scope, businessData) {
|
|
138
|
+
const targetIds = new Set(targets.map((t) => t.product_id));
|
|
139
|
+
let quantity = 0;
|
|
140
|
+
const countFromItems = (items) => {
|
|
141
|
+
for (const item of items) {
|
|
142
|
+
if (targetIds.has(item.product_id)) {
|
|
143
|
+
quantity += item.quantity;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
if (scope === "cumulative") {
|
|
148
|
+
if (businessData.historicalItems) {
|
|
149
|
+
countFromItems(businessData.historicalItems);
|
|
150
|
+
}
|
|
151
|
+
countFromItems(businessData.cartItems);
|
|
152
|
+
} else {
|
|
153
|
+
countFromItems(businessData.cartItems);
|
|
154
|
+
}
|
|
155
|
+
return quantity;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* 逐条评估数量规则
|
|
159
|
+
*/
|
|
160
|
+
evaluateQuantityRules(rules, actualQuantity, businessData) {
|
|
161
|
+
const failedRules = [];
|
|
162
|
+
let requiredMin;
|
|
163
|
+
let requiredMax;
|
|
164
|
+
let requiredExact;
|
|
165
|
+
let mustInclude;
|
|
166
|
+
for (const rule of rules) {
|
|
167
|
+
const resolvedValue = this.resolveQuantityValue(rule, businessData);
|
|
168
|
+
switch (rule.comparison) {
|
|
169
|
+
case "minimum": {
|
|
170
|
+
if (requiredMin === void 0 || resolvedValue > requiredMin) {
|
|
171
|
+
requiredMin = resolvedValue;
|
|
172
|
+
}
|
|
173
|
+
if (actualQuantity < resolvedValue) {
|
|
174
|
+
failedRules.push(rule);
|
|
175
|
+
}
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
case "maximum": {
|
|
179
|
+
if (requiredMax === void 0 || resolvedValue < requiredMax) {
|
|
180
|
+
requiredMax = resolvedValue;
|
|
181
|
+
}
|
|
182
|
+
if (actualQuantity > resolvedValue) {
|
|
183
|
+
failedRules.push(rule);
|
|
184
|
+
}
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
case "exact": {
|
|
188
|
+
requiredExact = resolvedValue;
|
|
189
|
+
if (actualQuantity !== resolvedValue) {
|
|
190
|
+
failedRules.push(rule);
|
|
191
|
+
}
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
case "must_include": {
|
|
195
|
+
mustInclude = true;
|
|
196
|
+
if (actualQuantity < 1) {
|
|
197
|
+
failedRules.push(rule);
|
|
198
|
+
}
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
passed: failedRules.length === 0,
|
|
205
|
+
failedRules,
|
|
206
|
+
requiredMin,
|
|
207
|
+
requiredMax,
|
|
208
|
+
requiredExact,
|
|
209
|
+
mustInclude
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* 根据 source 解析出数量规则的实际数值
|
|
214
|
+
*/
|
|
215
|
+
resolveQuantityValue(rule, businessData) {
|
|
216
|
+
switch (rule.source) {
|
|
217
|
+
case "total_pax":
|
|
218
|
+
return businessData.pax.total;
|
|
219
|
+
case "adult_pax":
|
|
220
|
+
return businessData.pax.adult;
|
|
221
|
+
case "fixed":
|
|
222
|
+
return rule.value ?? 0;
|
|
223
|
+
default:
|
|
224
|
+
return rule.value ?? 0;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* 格式化校验提示语,替换 {min} {max} 占位符
|
|
229
|
+
*/
|
|
230
|
+
formatValidationMessage(template, min, max, exact, locale) {
|
|
231
|
+
if (!template)
|
|
232
|
+
return void 0;
|
|
233
|
+
let text = typeof template === "string" ? template : template[locale || "en"] || Object.values(template)[0] || "";
|
|
234
|
+
text = text.replace(/\{min\}/g, String(min ?? ""));
|
|
235
|
+
text = text.replace(/\{max\}/g, String(max ?? ""));
|
|
236
|
+
text = text.replace(/\{exact\}/g, String(exact ?? ""));
|
|
237
|
+
return text;
|
|
238
|
+
}
|
|
239
|
+
// ============================================
|
|
240
|
+
// Quantity Limit 提取(仅读取配置,不做校验)
|
|
241
|
+
// ============================================
|
|
242
|
+
/**
|
|
243
|
+
* 从单个 QUANTITY_CHECK action 中提取数量限制配置。
|
|
244
|
+
*
|
|
245
|
+
* 根据 scope + submissionIndex 判断是否适用,
|
|
246
|
+
* 解析动态数量来源(pax)后返回 min/max/exact/mustInclude。
|
|
247
|
+
* 不适用或无配置时返回 null。
|
|
248
|
+
*/
|
|
249
|
+
extractQuantityLimits(action, businessData) {
|
|
250
|
+
const config = action.config;
|
|
251
|
+
if (!config)
|
|
252
|
+
return null;
|
|
253
|
+
const { scope, quantityRules, targetType, targets, validationMessage } = config;
|
|
254
|
+
if (!this.shouldEvaluateForScope(scope, businessData.submissionIndex ?? 0)) {
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
let requiredMin;
|
|
258
|
+
let requiredMax;
|
|
259
|
+
let requiredExact;
|
|
260
|
+
let mustInclude;
|
|
261
|
+
for (const rule of quantityRules) {
|
|
262
|
+
const resolvedValue = this.resolveQuantityValue(rule, businessData);
|
|
263
|
+
switch (rule.comparison) {
|
|
264
|
+
case "minimum": {
|
|
265
|
+
if (requiredMin === void 0 || resolvedValue > requiredMin) {
|
|
266
|
+
requiredMin = resolvedValue;
|
|
267
|
+
}
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
case "maximum": {
|
|
271
|
+
if (requiredMax === void 0 || resolvedValue < requiredMax) {
|
|
272
|
+
requiredMax = resolvedValue;
|
|
273
|
+
}
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
case "exact": {
|
|
277
|
+
requiredExact = resolvedValue;
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
case "must_include": {
|
|
281
|
+
mustInclude = true;
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
const remainingMax = this.calculateRemainingMax(
|
|
287
|
+
scope,
|
|
288
|
+
requiredMax,
|
|
289
|
+
targets,
|
|
290
|
+
businessData
|
|
291
|
+
);
|
|
292
|
+
const formattedValidationMessage = this.formatValidationMessage(
|
|
293
|
+
validationMessage,
|
|
294
|
+
requiredMin,
|
|
295
|
+
requiredMax,
|
|
296
|
+
requiredExact
|
|
297
|
+
);
|
|
298
|
+
return {
|
|
299
|
+
ruleId: action.id,
|
|
300
|
+
targetType,
|
|
301
|
+
targets,
|
|
302
|
+
requiredMin,
|
|
303
|
+
requiredMax,
|
|
304
|
+
remainingMax,
|
|
305
|
+
requiredExact,
|
|
306
|
+
mustInclude,
|
|
307
|
+
scope,
|
|
308
|
+
validationMessage: formattedValidationMessage,
|
|
309
|
+
rawValidationMessage: validationMessage
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* 计算当前提交剩余可选的最大数量。
|
|
314
|
+
*
|
|
315
|
+
* 仅在 scope='cumulative' 且存在 requiredMax 时生效:
|
|
316
|
+
* 用 requiredMax 减去 historicalItems 中目标商品的累计数量,保底 0。
|
|
317
|
+
* 其余场景返回 undefined,保持向后兼容。
|
|
318
|
+
*/
|
|
319
|
+
calculateRemainingMax(scope, requiredMax, targets, businessData) {
|
|
320
|
+
if (scope !== "cumulative" || typeof requiredMax !== "number") {
|
|
321
|
+
return void 0;
|
|
322
|
+
}
|
|
323
|
+
const historicalItems = businessData.historicalItems;
|
|
324
|
+
if (!(historicalItems == null ? void 0 : historicalItems.length)) {
|
|
325
|
+
return Math.max(0, requiredMax);
|
|
326
|
+
}
|
|
327
|
+
const targetIds = new Set(targets.map((t) => t.product_id));
|
|
328
|
+
let historicalQty = 0;
|
|
329
|
+
for (const item of historicalItems) {
|
|
330
|
+
if (targetIds.has(item.product_id)) {
|
|
331
|
+
historicalQty += item.quantity;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return Math.max(0, requiredMax - historicalQty);
|
|
335
|
+
}
|
|
336
|
+
// ============================================
|
|
337
|
+
// Prefill Cart 处理
|
|
338
|
+
// ============================================
|
|
339
|
+
processPrefillCart(action, businessData) {
|
|
340
|
+
const config = action.config;
|
|
341
|
+
if (!(config == null ? void 0 : config.products))
|
|
342
|
+
return [];
|
|
343
|
+
const results = [];
|
|
344
|
+
for (const prefillProduct of config.products) {
|
|
345
|
+
const quantity = this.resolvePrefillQuantity(prefillProduct.quantityFrom, prefillProduct.quantity, businessData);
|
|
346
|
+
if (quantity <= 0)
|
|
347
|
+
continue;
|
|
348
|
+
results.push({
|
|
349
|
+
ruleId: action.id,
|
|
350
|
+
product_id: prefillProduct.product_id,
|
|
351
|
+
product_variant_id: prefillProduct.product_variant_id,
|
|
352
|
+
quantity
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
return results;
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* 根据 quantityFrom 计算预填数量
|
|
359
|
+
*/
|
|
360
|
+
resolvePrefillQuantity(quantityFrom, fixedValue, businessData) {
|
|
361
|
+
switch (quantityFrom) {
|
|
362
|
+
case "total_pax":
|
|
363
|
+
return businessData.pax.total;
|
|
364
|
+
case "adult_pax":
|
|
365
|
+
return businessData.pax.adult;
|
|
366
|
+
case "fixed":
|
|
367
|
+
return fixedValue ?? 0;
|
|
368
|
+
default:
|
|
369
|
+
return fixedValue ?? 0;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
var adapter_default = ItemRuleAdapter;
|
|
374
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
375
|
+
0 && (module.exports = {
|
|
376
|
+
ItemRuleAdapter
|
|
377
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { StrategyConfig } from '../../type';
|
|
2
|
+
import { type ItemRuleBusinessData, type ItemRuleEvaluationResult, type QuantityCheckResult, type QuantityLimitResult, type PrefillItemResult } from './type';
|
|
3
|
+
/**
|
|
4
|
+
* ItemRule 评估器
|
|
5
|
+
*
|
|
6
|
+
* 组合 StrategyEngine + ItemRuleAdapter,提供面向业务的高层 API。
|
|
7
|
+
* 支持批量评估多条 item_rule 策略配置,聚合数量校验和预填购物车结果。
|
|
8
|
+
*/
|
|
9
|
+
export declare class ItemRuleEvaluator {
|
|
10
|
+
private engine;
|
|
11
|
+
private adapter;
|
|
12
|
+
private strategyConfigs;
|
|
13
|
+
constructor();
|
|
14
|
+
/**
|
|
15
|
+
* 设置策略配置列表
|
|
16
|
+
* 自动过滤 metadata.type === 'item_rule' 的配置
|
|
17
|
+
*/
|
|
18
|
+
setStrategyConfigs(configs: StrategyConfig[]): void;
|
|
19
|
+
getStrategyConfigs(): StrategyConfig[];
|
|
20
|
+
/**
|
|
21
|
+
* 完整评估:数量校验 + 预填购物车
|
|
22
|
+
*/
|
|
23
|
+
evaluate(businessData: ItemRuleBusinessData): ItemRuleEvaluationResult;
|
|
24
|
+
/**
|
|
25
|
+
* 获取当前配置中各商品的数量限制
|
|
26
|
+
*
|
|
27
|
+
* 不做购物车校验,仅从策略配置中提取 QUANTITY_CHECK 规则的
|
|
28
|
+
* min / max / exact / mustInclude 限制值。
|
|
29
|
+
* 会评估条件是否满足、根据 scope + submissionIndex 过滤。
|
|
30
|
+
*/
|
|
31
|
+
getQuantityLimits(businessData: ItemRuleBusinessData): QuantityLimitResult[];
|
|
32
|
+
/**
|
|
33
|
+
* 便捷方法:仅做数量校验,返回是否全部通过及失败详情
|
|
34
|
+
*/
|
|
35
|
+
validateCart(businessData: ItemRuleBusinessData): {
|
|
36
|
+
passed: boolean;
|
|
37
|
+
failures: QuantityCheckResult[];
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* 便捷方法:仅获取需要预填到购物车的商品
|
|
41
|
+
*/
|
|
42
|
+
getPrefillItems(businessData: ItemRuleBusinessData): PrefillItemResult[];
|
|
43
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
|
|
19
|
+
// src/model/strategy/adapter/itemRule/evaluator.ts
|
|
20
|
+
var evaluator_exports = {};
|
|
21
|
+
__export(evaluator_exports, {
|
|
22
|
+
ItemRuleEvaluator: () => ItemRuleEvaluator
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(evaluator_exports);
|
|
25
|
+
var import_engine = require("../../engine");
|
|
26
|
+
var import_adapter = require("./adapter");
|
|
27
|
+
var import_type = require("./type");
|
|
28
|
+
var ItemRuleEvaluator = class {
|
|
29
|
+
constructor() {
|
|
30
|
+
this.strategyConfigs = [];
|
|
31
|
+
this.engine = new import_engine.StrategyEngine({
|
|
32
|
+
debug: false,
|
|
33
|
+
enableTrace: false
|
|
34
|
+
});
|
|
35
|
+
this.adapter = new import_adapter.ItemRuleAdapter();
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* 设置策略配置列表
|
|
39
|
+
* 自动过滤 metadata.type === 'item_rule' 的配置
|
|
40
|
+
*/
|
|
41
|
+
setStrategyConfigs(configs) {
|
|
42
|
+
this.strategyConfigs = configs.filter(
|
|
43
|
+
(c) => c.metadata.type === "item_rule"
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
getStrategyConfigs() {
|
|
47
|
+
return this.strategyConfigs;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* 完整评估:数量校验 + 预填购物车
|
|
51
|
+
*/
|
|
52
|
+
evaluate(businessData) {
|
|
53
|
+
const configs = businessData.strategyConfigs ? businessData.strategyConfigs.filter((c) => c.metadata.type === "item_rule") : this.strategyConfigs;
|
|
54
|
+
const allQuantityChecks = [];
|
|
55
|
+
const allPrefillItems = [];
|
|
56
|
+
const rawResults = [];
|
|
57
|
+
const context = this.adapter.prepareContext(businessData);
|
|
58
|
+
for (const config of configs) {
|
|
59
|
+
const evalResult = this.engine.evaluate(config, context);
|
|
60
|
+
rawResults.push(evalResult);
|
|
61
|
+
const { quantityChecks, prefillItems } = this.adapter.transformResult(
|
|
62
|
+
evalResult,
|
|
63
|
+
businessData
|
|
64
|
+
);
|
|
65
|
+
allQuantityChecks.push(...quantityChecks);
|
|
66
|
+
allPrefillItems.push(...prefillItems);
|
|
67
|
+
}
|
|
68
|
+
const failedChecks = allQuantityChecks.filter((c) => !c.passed);
|
|
69
|
+
return {
|
|
70
|
+
quantityChecks: allQuantityChecks,
|
|
71
|
+
prefillItems: allPrefillItems,
|
|
72
|
+
allPassed: failedChecks.length === 0,
|
|
73
|
+
failedChecks,
|
|
74
|
+
rawResults
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* 获取当前配置中各商品的数量限制
|
|
79
|
+
*
|
|
80
|
+
* 不做购物车校验,仅从策略配置中提取 QUANTITY_CHECK 规则的
|
|
81
|
+
* min / max / exact / mustInclude 限制值。
|
|
82
|
+
* 会评估条件是否满足、根据 scope + submissionIndex 过滤。
|
|
83
|
+
*/
|
|
84
|
+
getQuantityLimits(businessData) {
|
|
85
|
+
const configs = businessData.strategyConfigs ? businessData.strategyConfigs.filter((c) => c.metadata.type === "item_rule") : this.strategyConfigs;
|
|
86
|
+
const results = [];
|
|
87
|
+
const context = this.adapter.prepareContext(businessData);
|
|
88
|
+
for (const config of configs) {
|
|
89
|
+
const evalResult = this.engine.evaluate(config, context);
|
|
90
|
+
if (!evalResult.applicable)
|
|
91
|
+
continue;
|
|
92
|
+
for (const action of evalResult.matchedActions) {
|
|
93
|
+
if (action.type !== import_type.ITEM_RULE_ACTION_TYPES.QUANTITY_CHECK)
|
|
94
|
+
continue;
|
|
95
|
+
const limit = this.adapter.extractQuantityLimits(action, businessData);
|
|
96
|
+
if (limit) {
|
|
97
|
+
results.push(limit);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return results;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* 便捷方法:仅做数量校验,返回是否全部通过及失败详情
|
|
105
|
+
*/
|
|
106
|
+
validateCart(businessData) {
|
|
107
|
+
const result = this.evaluate(businessData);
|
|
108
|
+
return {
|
|
109
|
+
passed: result.allPassed,
|
|
110
|
+
failures: result.failedChecks
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* 便捷方法:仅获取需要预填到购物车的商品
|
|
115
|
+
*/
|
|
116
|
+
getPrefillItems(businessData) {
|
|
117
|
+
const result = this.evaluate(businessData);
|
|
118
|
+
return result.prefillItems;
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
122
|
+
0 && (module.exports = {
|
|
123
|
+
ItemRuleEvaluator
|
|
124
|
+
});
|