@pisell/pisellos 2.3.30 → 2.3.31
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/dataVariant/adapter.d.ts +50 -0
- package/dist/model/strategy/adapter/dataVariant/adapter.js +167 -0
- package/dist/model/strategy/adapter/dataVariant/evaluator.d.ts +89 -0
- package/dist/model/strategy/adapter/dataVariant/evaluator.js +780 -0
- package/dist/model/strategy/adapter/dataVariant/examples.d.ts +39 -0
- package/dist/model/strategy/adapter/dataVariant/examples.js +277 -0
- package/dist/model/strategy/adapter/dataVariant/index.d.ts +4 -0
- package/dist/model/strategy/adapter/dataVariant/index.js +4 -0
- package/dist/model/strategy/adapter/dataVariant/type.d.ts +148 -0
- package/dist/model/strategy/adapter/dataVariant/type.js +54 -0
- package/dist/model/strategy/adapter/index.d.ts +4 -0
- package/dist/model/strategy/adapter/index.js +5 -1
- package/dist/model/strategy/adapter/promotion/index.js +0 -9
- package/dist/modules/Discount/index.d.ts +5 -2
- package/dist/modules/Discount/index.js +30 -7
- package/dist/modules/Discount/types.d.ts +4 -0
- package/dist/modules/Order/index.d.ts +2 -1
- package/dist/modules/Order/index.js +25 -11
- package/dist/modules/Order/types.d.ts +2 -0
- package/dist/modules/Product/index.d.ts +1 -1
- package/dist/modules/Product/types.d.ts +22 -0
- package/dist/modules/ProductList/index.d.ts +1 -1
- package/dist/modules/ProductList/index.js +4 -2
- package/dist/modules/ProductList/types.d.ts +2 -0
- package/dist/server/index.d.ts +50 -0
- package/dist/server/index.js +1095 -849
- package/dist/server/modules/products/index.d.ts +26 -7
- package/dist/server/modules/products/index.js +167 -76
- package/dist/server/modules/products/types.d.ts +14 -0
- package/dist/solution/BaseSales/index.d.ts +2 -0
- package/dist/solution/BaseSales/index.js +23 -13
- package/dist/solution/BookingByStep/index.d.ts +1 -1
- package/dist/solution/BookingTicket/types.d.ts +2 -0
- package/dist/solution/ShopDiscount/index.js +15 -14
- package/lib/model/strategy/adapter/dataVariant/adapter.d.ts +50 -0
- package/lib/model/strategy/adapter/dataVariant/adapter.js +149 -0
- package/lib/model/strategy/adapter/dataVariant/evaluator.d.ts +89 -0
- package/lib/model/strategy/adapter/dataVariant/evaluator.js +583 -0
- package/lib/model/strategy/adapter/dataVariant/examples.d.ts +39 -0
- package/lib/model/strategy/adapter/dataVariant/examples.js +293 -0
- package/lib/model/strategy/adapter/dataVariant/index.d.ts +4 -0
- package/lib/model/strategy/adapter/dataVariant/index.js +38 -0
- package/lib/model/strategy/adapter/dataVariant/type.d.ts +148 -0
- package/lib/model/strategy/adapter/dataVariant/type.js +31 -0
- package/lib/model/strategy/adapter/index.d.ts +4 -0
- package/lib/model/strategy/adapter/index.js +13 -2
- package/lib/model/strategy/adapter/promotion/index.js +0 -49
- package/lib/modules/Discount/index.d.ts +5 -2
- package/lib/modules/Discount/index.js +23 -3
- package/lib/modules/Discount/types.d.ts +4 -0
- package/lib/modules/Order/index.d.ts +2 -1
- package/lib/modules/Order/index.js +13 -1
- package/lib/modules/Order/types.d.ts +2 -0
- package/lib/modules/Product/index.d.ts +1 -1
- package/lib/modules/Product/types.d.ts +22 -0
- package/lib/modules/ProductList/index.d.ts +1 -1
- package/lib/modules/ProductList/index.js +4 -2
- package/lib/modules/ProductList/types.d.ts +2 -0
- package/lib/server/index.d.ts +50 -0
- package/lib/server/index.js +206 -29
- package/lib/server/modules/products/index.d.ts +26 -7
- package/lib/server/modules/products/index.js +114 -35
- package/lib/server/modules/products/types.d.ts +14 -0
- package/lib/solution/BaseSales/index.d.ts +2 -0
- package/lib/solution/BaseSales/index.js +12 -3
- package/lib/solution/BookingByStep/index.d.ts +1 -1
- package/lib/solution/BookingTicket/types.d.ts +2 -0
- package/lib/solution/ShopDiscount/index.js +2 -1
- package/package.json +1 -1
|
@@ -0,0 +1,583 @@
|
|
|
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/dataVariant/evaluator.ts
|
|
20
|
+
var evaluator_exports = {};
|
|
21
|
+
__export(evaluator_exports, {
|
|
22
|
+
DataVariantEvaluator: () => DataVariantEvaluator,
|
|
23
|
+
default: () => evaluator_default
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(evaluator_exports);
|
|
26
|
+
var import_engine = require("../../engine");
|
|
27
|
+
var import_getDateIsInSchedule = require("../../../../modules/Schedule/getDateIsInSchedule");
|
|
28
|
+
var import_adapter = require("./adapter");
|
|
29
|
+
var DataVariantEvaluator = class {
|
|
30
|
+
constructor() {
|
|
31
|
+
this.strategyConfigs = [];
|
|
32
|
+
this.adapter = new import_adapter.DataVariantAdapter();
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 设置策略配置列表,仅保留 metadata.type === 'data_variant' 的策略。
|
|
36
|
+
*/
|
|
37
|
+
setStrategyConfigs(configs) {
|
|
38
|
+
this.strategyConfigs = this.normalizeStrategyConfigs(configs);
|
|
39
|
+
}
|
|
40
|
+
getStrategyConfigs() {
|
|
41
|
+
return this.strategyConfigs;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* 批量解析商品 Data Variant。
|
|
45
|
+
*
|
|
46
|
+
* 先执行全局预计算,再只针对商品自身 data_variants 引用到的规则做商品级判断。
|
|
47
|
+
*/
|
|
48
|
+
resolveProducts(businessData) {
|
|
49
|
+
const strategyConfigs = businessData.strategyConfigs ? this.normalizeStrategyConfigs(businessData.strategyConfigs) : this.strategyConfigs;
|
|
50
|
+
const menuScopeMap = this.buildMenuScopeMap(businessData.menuList || []);
|
|
51
|
+
const scheduleTimePoints = this.buildScheduleTimePoints(
|
|
52
|
+
strategyConfigs,
|
|
53
|
+
businessData.scheduleList || []
|
|
54
|
+
);
|
|
55
|
+
const { globalEffectiveRuleMap, rawResults } = this.buildGlobalEffectiveRuleMap(
|
|
56
|
+
strategyConfigs,
|
|
57
|
+
businessData,
|
|
58
|
+
menuScopeMap
|
|
59
|
+
);
|
|
60
|
+
const products = businessData.products.map((product) => {
|
|
61
|
+
const resolvedProduct = this.resolveProduct(
|
|
62
|
+
product,
|
|
63
|
+
businessData,
|
|
64
|
+
globalEffectiveRuleMap,
|
|
65
|
+
menuScopeMap
|
|
66
|
+
);
|
|
67
|
+
return this.resolveProductBundleItems(
|
|
68
|
+
resolvedProduct,
|
|
69
|
+
businessData,
|
|
70
|
+
globalEffectiveRuleMap,
|
|
71
|
+
menuScopeMap
|
|
72
|
+
);
|
|
73
|
+
});
|
|
74
|
+
return {
|
|
75
|
+
products,
|
|
76
|
+
globalEffectiveRuleMap,
|
|
77
|
+
menuScopeMap,
|
|
78
|
+
scheduleTimePoints,
|
|
79
|
+
rawResults
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* 根据 operator/field 判断条件是否依赖当前商品。
|
|
84
|
+
*/
|
|
85
|
+
getConditionScope(rule) {
|
|
86
|
+
if (rule.operator === "schedule_match")
|
|
87
|
+
return "global";
|
|
88
|
+
if (rule.operator === "menu_match")
|
|
89
|
+
return "product";
|
|
90
|
+
if (rule.operator === "product_match")
|
|
91
|
+
return "product";
|
|
92
|
+
if (rule.field === "product" || rule.field === "productIdAndVariantId") {
|
|
93
|
+
return "product";
|
|
94
|
+
}
|
|
95
|
+
return "global";
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* 将餐牌 partyroom_package 预构建为 Map/Set 索引。
|
|
99
|
+
*/
|
|
100
|
+
buildMenuScopeMap(menuList) {
|
|
101
|
+
const map = /* @__PURE__ */ new Map();
|
|
102
|
+
for (const menu of menuList) {
|
|
103
|
+
if ((menu == null ? void 0 : menu.status) && menu.status !== "valid")
|
|
104
|
+
continue;
|
|
105
|
+
const menuId = this.toNumber(menu == null ? void 0 : menu.form_record_id);
|
|
106
|
+
if (!menuId)
|
|
107
|
+
continue;
|
|
108
|
+
const scope = {
|
|
109
|
+
isAllProducts: false,
|
|
110
|
+
collectionIds: /* @__PURE__ */ new Set(),
|
|
111
|
+
productKeys: /* @__PURE__ */ new Set(),
|
|
112
|
+
productAllVariantIds: /* @__PURE__ */ new Set()
|
|
113
|
+
};
|
|
114
|
+
const packageConfig = menu.partyroom_package;
|
|
115
|
+
const products = Array.isArray(packageConfig == null ? void 0 : packageConfig.products) ? packageConfig.products : [];
|
|
116
|
+
if ((packageConfig == null ? void 0 : packageConfig.type) === "product_all") {
|
|
117
|
+
scope.isAllProducts = true;
|
|
118
|
+
} else if ((packageConfig == null ? void 0 : packageConfig.type) === "product_collection") {
|
|
119
|
+
for (const item of products) {
|
|
120
|
+
const collectionId = this.toNumber(item.product_collection_id);
|
|
121
|
+
if (collectionId) {
|
|
122
|
+
scope.collectionIds.add(collectionId);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
} else if ((packageConfig == null ? void 0 : packageConfig.type) === "products") {
|
|
126
|
+
for (const item of products) {
|
|
127
|
+
const productId = this.toNumber(item.product_id);
|
|
128
|
+
const variantId = this.toNumber(item.product_variant_id);
|
|
129
|
+
if (!productId)
|
|
130
|
+
continue;
|
|
131
|
+
if (variantId === 0) {
|
|
132
|
+
scope.productAllVariantIds.add(productId);
|
|
133
|
+
} else {
|
|
134
|
+
scope.productKeys.add(this.buildProductKey(productId, variantId));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
map.set(menuId, scope);
|
|
139
|
+
}
|
|
140
|
+
return map;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* 从当前策略中所有 schedule_match.value 绑定的 schedule_id 提取时间点。
|
|
144
|
+
*
|
|
145
|
+
* 这里不依赖餐牌,只读取策略配置和调用方传入的 scheduleList。
|
|
146
|
+
*/
|
|
147
|
+
buildScheduleTimePoints(strategyConfigs, scheduleList) {
|
|
148
|
+
const scheduleIds = this.collectScheduleIdsFromStrategies(strategyConfigs);
|
|
149
|
+
if (!scheduleIds.length || !scheduleList.length)
|
|
150
|
+
return [];
|
|
151
|
+
const scheduleIdSet = new Set(scheduleIds);
|
|
152
|
+
const matchedSchedules = scheduleList.filter(
|
|
153
|
+
(schedule) => scheduleIdSet.has(Number(schedule.id))
|
|
154
|
+
);
|
|
155
|
+
return this.extractTimePointsFromSchedules(matchedSchedules);
|
|
156
|
+
}
|
|
157
|
+
buildGlobalEffectiveRuleMap(strategyConfigs, businessData, menuScopeMap) {
|
|
158
|
+
const globalEffectiveRuleMap = /* @__PURE__ */ new Map();
|
|
159
|
+
const rawResults = [];
|
|
160
|
+
const engine = this.createEngine(businessData, menuScopeMap);
|
|
161
|
+
const context = this.adapter.prepareGlobalContext(businessData);
|
|
162
|
+
for (const config of strategyConfigs) {
|
|
163
|
+
if (!this.isStrategyActive(config))
|
|
164
|
+
continue;
|
|
165
|
+
const { globalConfig, productRules } = this.splitStrategyConfig(config);
|
|
166
|
+
const result = engine.evaluate(globalConfig, context);
|
|
167
|
+
rawResults.push(result);
|
|
168
|
+
if (!result.applicable)
|
|
169
|
+
continue;
|
|
170
|
+
globalEffectiveRuleMap.set(String(config.metadata.id), {
|
|
171
|
+
ruleId: String(config.metadata.id),
|
|
172
|
+
priority: this.getStrategyPriority(config),
|
|
173
|
+
strategyConfig: config,
|
|
174
|
+
productRules,
|
|
175
|
+
globalResult: result
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
return { globalEffectiveRuleMap, rawResults };
|
|
179
|
+
}
|
|
180
|
+
resolveProduct(product, businessData, globalEffectiveRuleMap, menuScopeMap) {
|
|
181
|
+
const dataVariants = this.getProductDataVariants(product);
|
|
182
|
+
if (!dataVariants.length)
|
|
183
|
+
return product;
|
|
184
|
+
const matchedVariants = [];
|
|
185
|
+
for (const variant of dataVariants) {
|
|
186
|
+
const rule = globalEffectiveRuleMap.get(
|
|
187
|
+
String(variant.data_variant_rule_id)
|
|
188
|
+
);
|
|
189
|
+
if (!rule)
|
|
190
|
+
continue;
|
|
191
|
+
if (!this.evaluateProductRules(rule, product, businessData, menuScopeMap)) {
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
matchedVariants.push({
|
|
195
|
+
ruleId: rule.ruleId,
|
|
196
|
+
priority: rule.priority,
|
|
197
|
+
data: variant.data || {},
|
|
198
|
+
variant
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
if (!matchedVariants.length)
|
|
202
|
+
return product;
|
|
203
|
+
return this.mergeMatchedVariants(product, matchedVariants);
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* 解析套餐子商品上的 Data Variant 覆盖。
|
|
207
|
+
*
|
|
208
|
+
* bundle_item 以 bundle_product_id / bundle_variant_id 作为商品身份参与 product_match 等条件判断。
|
|
209
|
+
*/
|
|
210
|
+
resolveProductBundleItems(product, businessData, globalEffectiveRuleMap, menuScopeMap) {
|
|
211
|
+
const bundleGroups = product.bundle_group;
|
|
212
|
+
if (!Array.isArray(bundleGroups) || bundleGroups.length === 0) {
|
|
213
|
+
return product;
|
|
214
|
+
}
|
|
215
|
+
const resolvedGroups = bundleGroups.map((group) => {
|
|
216
|
+
const bundleItems = group == null ? void 0 : group.bundle_item;
|
|
217
|
+
if (!Array.isArray(bundleItems) || bundleItems.length === 0) {
|
|
218
|
+
return group;
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
...group,
|
|
222
|
+
bundle_item: bundleItems.map(
|
|
223
|
+
(item) => this.resolveBundleItem(
|
|
224
|
+
item,
|
|
225
|
+
businessData,
|
|
226
|
+
globalEffectiveRuleMap,
|
|
227
|
+
menuScopeMap
|
|
228
|
+
)
|
|
229
|
+
)
|
|
230
|
+
};
|
|
231
|
+
});
|
|
232
|
+
return {
|
|
233
|
+
...product,
|
|
234
|
+
bundle_group: resolvedGroups
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* 将 bundle_item 转为 DataVariantProduct 并评估 data_variants。
|
|
239
|
+
*/
|
|
240
|
+
resolveBundleItem(item, businessData, globalEffectiveRuleMap, menuScopeMap) {
|
|
241
|
+
const bundleProduct = this.toBundleItemDataVariantProduct(item);
|
|
242
|
+
if (!this.getProductDataVariants(bundleProduct).length) {
|
|
243
|
+
return item;
|
|
244
|
+
}
|
|
245
|
+
const resolvedItem = this.resolveProduct(
|
|
246
|
+
bundleProduct,
|
|
247
|
+
businessData,
|
|
248
|
+
globalEffectiveRuleMap,
|
|
249
|
+
menuScopeMap
|
|
250
|
+
);
|
|
251
|
+
return this.mergeBundleItemResolvedData(item, resolvedItem);
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* bundle_item 的商品身份映射:bundle_product_id + bundle_variant_id。
|
|
255
|
+
*/
|
|
256
|
+
toBundleItemDataVariantProduct(item) {
|
|
257
|
+
const productId = item.bundle_product_id ?? item.product_id;
|
|
258
|
+
const variantId = item.bundle_variant_id ?? item.variant_id ?? item.product_variant_id ?? 0;
|
|
259
|
+
return {
|
|
260
|
+
id: productId,
|
|
261
|
+
product_id: productId,
|
|
262
|
+
product_variant_id: variantId,
|
|
263
|
+
variant_id: variantId,
|
|
264
|
+
collection: item.collection,
|
|
265
|
+
data_variants: item.data_variants
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* 仅回写命中的字段,避免污染 bundle_item 的结构字段。
|
|
270
|
+
*/
|
|
271
|
+
mergeBundleItemResolvedData(originalItem, resolvedItem) {
|
|
272
|
+
var _a;
|
|
273
|
+
const appliedInfo = resolvedItem._dataVariant;
|
|
274
|
+
if (!((_a = appliedInfo == null ? void 0 : appliedInfo.appliedFields) == null ? void 0 : _a.length)) {
|
|
275
|
+
return originalItem;
|
|
276
|
+
}
|
|
277
|
+
const result = { ...originalItem };
|
|
278
|
+
for (const field of appliedInfo.appliedFields) {
|
|
279
|
+
if (resolvedItem[field] !== void 0) {
|
|
280
|
+
result[field] = resolvedItem[field];
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
result._dataVariant = appliedInfo;
|
|
284
|
+
return result;
|
|
285
|
+
}
|
|
286
|
+
evaluateProductRules(rule, product, businessData, menuScopeMap) {
|
|
287
|
+
if (!rule.productRules.length)
|
|
288
|
+
return true;
|
|
289
|
+
const engine = this.createEngine(businessData, menuScopeMap);
|
|
290
|
+
const context = this.adapter.prepareProductContext(businessData, product);
|
|
291
|
+
const productOnlyConfig = {
|
|
292
|
+
...rule.strategyConfig,
|
|
293
|
+
conditions: {
|
|
294
|
+
operator: "and",
|
|
295
|
+
rules: rule.productRules,
|
|
296
|
+
actionIds: rule.strategyConfig.conditions.actionIds || []
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
return engine.evaluate(productOnlyConfig, context).applicable;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* 拆分策略条件。
|
|
303
|
+
*
|
|
304
|
+
* 当前 MVP 支持顶层 and 原子条件拆分;复杂条件保守地放到商品级完整判断。
|
|
305
|
+
*/
|
|
306
|
+
splitStrategyConfig(config) {
|
|
307
|
+
const conditions = config.conditions;
|
|
308
|
+
const globalRules = [];
|
|
309
|
+
const productRules = [];
|
|
310
|
+
if (conditions.operator !== "and") {
|
|
311
|
+
return {
|
|
312
|
+
globalConfig: {
|
|
313
|
+
...config,
|
|
314
|
+
conditions: this.createAlwaysTrueConditions(conditions.actionIds)
|
|
315
|
+
},
|
|
316
|
+
productRules: this.flattenConditionRules(conditions)
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
for (const rule of conditions.rules) {
|
|
320
|
+
if (this.isConditionGroup(rule)) {
|
|
321
|
+
productRules.push(...this.flattenConditionRules(rule));
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
if (this.getConditionScope(rule) === "product") {
|
|
325
|
+
productRules.push(rule);
|
|
326
|
+
} else {
|
|
327
|
+
globalRules.push(rule);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return {
|
|
331
|
+
globalConfig: {
|
|
332
|
+
...config,
|
|
333
|
+
conditions: {
|
|
334
|
+
operator: "and",
|
|
335
|
+
rules: globalRules,
|
|
336
|
+
actionIds: conditions.actionIds || []
|
|
337
|
+
}
|
|
338
|
+
},
|
|
339
|
+
productRules
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
createEngine(businessData, menuScopeMap) {
|
|
343
|
+
const operatorHandlers = {
|
|
344
|
+
schedule_match: (fieldValue, compareValue) => this.evaluateScheduleMatch(
|
|
345
|
+
fieldValue,
|
|
346
|
+
compareValue,
|
|
347
|
+
businessData.scheduleList || []
|
|
348
|
+
),
|
|
349
|
+
menu_match: (fieldValue, compareValue) => this.evaluateMenuMatch(fieldValue, compareValue, menuScopeMap),
|
|
350
|
+
array_intersects: (fieldValue, compareValue) => this.evaluateArrayIntersects(fieldValue, compareValue)
|
|
351
|
+
};
|
|
352
|
+
return new import_engine.StrategyEngine({
|
|
353
|
+
debug: false,
|
|
354
|
+
enableTrace: false,
|
|
355
|
+
operatorHandlers
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
evaluateScheduleMatch(fieldValue, compareValue, scheduleList) {
|
|
359
|
+
const scheduleDateTime = String(fieldValue || "");
|
|
360
|
+
const scheduleIds = this.normalizeNumberArray(compareValue);
|
|
361
|
+
if (!scheduleIds.length)
|
|
362
|
+
return true;
|
|
363
|
+
if (!scheduleDateTime || !(scheduleList == null ? void 0 : scheduleList.length))
|
|
364
|
+
return false;
|
|
365
|
+
const matchedSchedules = scheduleList.filter(
|
|
366
|
+
(schedule) => scheduleIds.includes(Number(schedule.id))
|
|
367
|
+
);
|
|
368
|
+
return (0, import_getDateIsInSchedule.getDateIsInSchedule)(scheduleDateTime, matchedSchedules);
|
|
369
|
+
}
|
|
370
|
+
evaluateMenuMatch(fieldValue, compareValue, menuScopeMap) {
|
|
371
|
+
const menuIds = this.normalizeNumberArray(compareValue);
|
|
372
|
+
if (!menuIds.length)
|
|
373
|
+
return true;
|
|
374
|
+
for (const menuId of menuIds) {
|
|
375
|
+
const scope = menuScopeMap.get(menuId);
|
|
376
|
+
if (!scope)
|
|
377
|
+
continue;
|
|
378
|
+
if (this.isProductInMenuScope(fieldValue, scope))
|
|
379
|
+
return true;
|
|
380
|
+
}
|
|
381
|
+
return false;
|
|
382
|
+
}
|
|
383
|
+
evaluateArrayIntersects(fieldValue, compareValue) {
|
|
384
|
+
const fieldValues = this.normalizeComparableArray(fieldValue);
|
|
385
|
+
const compareValues = this.normalizeComparableArray(compareValue);
|
|
386
|
+
if (!fieldValues.length || !compareValues.length)
|
|
387
|
+
return false;
|
|
388
|
+
const compareSet = new Set(compareValues);
|
|
389
|
+
return fieldValues.some((item) => compareSet.has(item));
|
|
390
|
+
}
|
|
391
|
+
collectScheduleIdsFromStrategies(strategyConfigs) {
|
|
392
|
+
const scheduleIds = /* @__PURE__ */ new Set();
|
|
393
|
+
for (const config of strategyConfigs) {
|
|
394
|
+
if (!this.isStrategyActive(config))
|
|
395
|
+
continue;
|
|
396
|
+
const rules = this.flattenConditionRules(config.conditions);
|
|
397
|
+
for (const rule of rules) {
|
|
398
|
+
if (rule.operator !== "schedule_match")
|
|
399
|
+
continue;
|
|
400
|
+
for (const scheduleId of this.normalizeNumberArray(rule.value)) {
|
|
401
|
+
scheduleIds.add(scheduleId);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return Array.from(scheduleIds);
|
|
406
|
+
}
|
|
407
|
+
extractTimePointsFromSchedules(schedules) {
|
|
408
|
+
const timePoints = /* @__PURE__ */ new Set();
|
|
409
|
+
for (const schedule of schedules) {
|
|
410
|
+
switch (schedule.type) {
|
|
411
|
+
case "standard": {
|
|
412
|
+
this.addDateTimePoint(timePoints, schedule.start_time);
|
|
413
|
+
this.addDateTimePoint(timePoints, schedule.end_time);
|
|
414
|
+
break;
|
|
415
|
+
}
|
|
416
|
+
case "time-slots": {
|
|
417
|
+
const slots = Array.isArray(schedule.time_slot) ? schedule.time_slot : [];
|
|
418
|
+
for (const slot of slots) {
|
|
419
|
+
this.addRawTimePoint(timePoints, slot.start_time);
|
|
420
|
+
this.addRawTimePoint(timePoints, slot.end_time);
|
|
421
|
+
}
|
|
422
|
+
break;
|
|
423
|
+
}
|
|
424
|
+
case "designation": {
|
|
425
|
+
const designations = Array.isArray(schedule.designation) ? schedule.designation : schedule.designation ? [schedule.designation] : [];
|
|
426
|
+
for (const designation of designations) {
|
|
427
|
+
this.addRawTimePoint(timePoints, designation.start_time);
|
|
428
|
+
this.addRawTimePoint(timePoints, designation.end_time);
|
|
429
|
+
}
|
|
430
|
+
break;
|
|
431
|
+
}
|
|
432
|
+
default:
|
|
433
|
+
break;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
return Array.from(timePoints).sort(
|
|
437
|
+
(a, b) => this.timeToMinutes(a) - this.timeToMinutes(b)
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
addDateTimePoint(points, value) {
|
|
441
|
+
const time = this.extractTimeFromDateTime(value);
|
|
442
|
+
if (time)
|
|
443
|
+
points.add(time);
|
|
444
|
+
}
|
|
445
|
+
addRawTimePoint(points, value) {
|
|
446
|
+
const time = this.normalizeTime(value);
|
|
447
|
+
if (time)
|
|
448
|
+
points.add(time);
|
|
449
|
+
}
|
|
450
|
+
extractTimeFromDateTime(value) {
|
|
451
|
+
if (!value)
|
|
452
|
+
return null;
|
|
453
|
+
const match = value.match(/(\d{1,2}):(\d{2})/);
|
|
454
|
+
if (!match)
|
|
455
|
+
return null;
|
|
456
|
+
return `${match[1].padStart(2, "0")}:${match[2]}`;
|
|
457
|
+
}
|
|
458
|
+
normalizeTime(value) {
|
|
459
|
+
if (!value)
|
|
460
|
+
return null;
|
|
461
|
+
const match = value.match(/^(\d{1,2}):(\d{2})/);
|
|
462
|
+
if (!match)
|
|
463
|
+
return null;
|
|
464
|
+
return `${match[1].padStart(2, "0")}:${match[2]}`;
|
|
465
|
+
}
|
|
466
|
+
timeToMinutes(value) {
|
|
467
|
+
const [hour, minute] = value.split(":").map((item) => Number(item));
|
|
468
|
+
if (!Number.isFinite(hour) || !Number.isFinite(minute))
|
|
469
|
+
return 0;
|
|
470
|
+
return hour * 60 + minute;
|
|
471
|
+
}
|
|
472
|
+
isProductInMenuScope(product, scope) {
|
|
473
|
+
if (scope.isAllProducts)
|
|
474
|
+
return true;
|
|
475
|
+
if (!product || typeof product !== "object")
|
|
476
|
+
return false;
|
|
477
|
+
const productId = this.toNumber(product.id ?? product.product_id);
|
|
478
|
+
const variantId = this.toNumber(product.product_variant_id);
|
|
479
|
+
const productKey = this.buildProductKey(productId, variantId);
|
|
480
|
+
const collectionIds = Array.isArray(product.collection_ids) ? product.collection_ids.map((id) => this.toNumber(id)) : [];
|
|
481
|
+
return collectionIds.some((id) => scope.collectionIds.has(id)) || scope.productKeys.has(productKey) || scope.productAllVariantIds.has(productId);
|
|
482
|
+
}
|
|
483
|
+
mergeMatchedVariants(product, matchedVariants) {
|
|
484
|
+
const original = { ...product };
|
|
485
|
+
const sortedVariants = [...matchedVariants].sort((a, b) => {
|
|
486
|
+
if (a.priority !== b.priority)
|
|
487
|
+
return a.priority - b.priority;
|
|
488
|
+
return Number(a.ruleId) - Number(b.ruleId);
|
|
489
|
+
});
|
|
490
|
+
const result = { ...product };
|
|
491
|
+
const appliedRuleIds = [];
|
|
492
|
+
const appliedFields = /* @__PURE__ */ new Set();
|
|
493
|
+
for (const matched of sortedVariants) {
|
|
494
|
+
for (const [key, value] of Object.entries(matched.data)) {
|
|
495
|
+
if (value === void 0)
|
|
496
|
+
continue;
|
|
497
|
+
result[key] = value;
|
|
498
|
+
appliedFields.add(key);
|
|
499
|
+
}
|
|
500
|
+
appliedRuleIds.push(matched.ruleId);
|
|
501
|
+
}
|
|
502
|
+
result._dataVariant = {
|
|
503
|
+
appliedRuleIds,
|
|
504
|
+
appliedFields: Array.from(appliedFields),
|
|
505
|
+
original
|
|
506
|
+
};
|
|
507
|
+
return result;
|
|
508
|
+
}
|
|
509
|
+
getProductDataVariants(product) {
|
|
510
|
+
return Array.isArray(product.data_variants) ? product.data_variants : [];
|
|
511
|
+
}
|
|
512
|
+
normalizeStrategyConfigs(configs) {
|
|
513
|
+
return configs.filter(
|
|
514
|
+
(config) => {
|
|
515
|
+
var _a;
|
|
516
|
+
return ((_a = config == null ? void 0 : config.metadata) == null ? void 0 : _a.type) === "data_variant";
|
|
517
|
+
}
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
isStrategyActive(config) {
|
|
521
|
+
var _a;
|
|
522
|
+
const status = (_a = config.metadata.custom) == null ? void 0 : _a.status;
|
|
523
|
+
return !status || status === "active";
|
|
524
|
+
}
|
|
525
|
+
getStrategyPriority(config) {
|
|
526
|
+
var _a;
|
|
527
|
+
const priority = Number((_a = config.metadata.custom) == null ? void 0 : _a.priority);
|
|
528
|
+
if (Number.isFinite(priority))
|
|
529
|
+
return priority;
|
|
530
|
+
return Math.max(
|
|
531
|
+
0,
|
|
532
|
+
...config.actions.map((action) => Number(action.priority || 0))
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
createAlwaysTrueConditions(actionIds) {
|
|
536
|
+
return {
|
|
537
|
+
operator: "and",
|
|
538
|
+
rules: [],
|
|
539
|
+
actionIds
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
flattenConditionRules(group) {
|
|
543
|
+
const rules = [];
|
|
544
|
+
for (const rule of group.rules) {
|
|
545
|
+
if (this.isConditionGroup(rule)) {
|
|
546
|
+
rules.push(...this.flattenConditionRules(rule));
|
|
547
|
+
} else {
|
|
548
|
+
rules.push(rule);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
return rules;
|
|
552
|
+
}
|
|
553
|
+
isConditionGroup(rule) {
|
|
554
|
+
return "rules" in rule && Array.isArray(rule.rules);
|
|
555
|
+
}
|
|
556
|
+
normalizeNumberArray(value) {
|
|
557
|
+
if (!Array.isArray(value))
|
|
558
|
+
return [];
|
|
559
|
+
return value.map((item) => this.toNumber(item)).filter((item) => item > 0);
|
|
560
|
+
}
|
|
561
|
+
normalizeComparableArray(value) {
|
|
562
|
+
const list = Array.isArray(value) ? value : value == null || value === "" ? [] : [value];
|
|
563
|
+
const normalized = list.map((item) => {
|
|
564
|
+
if (item == null || item === "")
|
|
565
|
+
return "";
|
|
566
|
+
const parsed = Number(item);
|
|
567
|
+
return Number.isFinite(parsed) ? String(parsed) : String(item).trim();
|
|
568
|
+
}).filter(Boolean);
|
|
569
|
+
return Array.from(new Set(normalized));
|
|
570
|
+
}
|
|
571
|
+
buildProductKey(productId, variantId) {
|
|
572
|
+
return `${productId}:${variantId}`;
|
|
573
|
+
}
|
|
574
|
+
toNumber(value) {
|
|
575
|
+
const parsed = Number(value);
|
|
576
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
577
|
+
}
|
|
578
|
+
};
|
|
579
|
+
var evaluator_default = DataVariantEvaluator;
|
|
580
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
581
|
+
0 && (module.exports = {
|
|
582
|
+
DataVariantEvaluator
|
|
583
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { StrategyConfig } from '../../type';
|
|
2
|
+
import type { ScheduleItem } from '../../../../modules/Schedule/types';
|
|
3
|
+
import type { DataVariantItem, DataVariantMenu, DataVariantProduct, DataVariantRuleMetadata } from './type';
|
|
4
|
+
/**
|
|
5
|
+
* 构造示例用 data_variant 记录,字段结构与后端 /product/query 返回一致。
|
|
6
|
+
*/
|
|
7
|
+
export declare function createExampleDataVariant(ruleId: number | string, data: Record<string, any>, overrides?: Partial<Omit<DataVariantItem, 'data_variant_rule_id' | 'data'>>): DataVariantItem;
|
|
8
|
+
/**
|
|
9
|
+
* 晚场餐牌价:命中日程 + 餐牌商品范围后覆盖 price / stock。
|
|
10
|
+
*/
|
|
11
|
+
export declare const EVENING_MENU_PRICE_STRATEGY: StrategyConfig<DataVariantRuleMetadata>;
|
|
12
|
+
/**
|
|
13
|
+
* VIP 商品价:演示 product_match 与更高优先级字段覆盖。
|
|
14
|
+
*/
|
|
15
|
+
export declare const VIP_PRODUCT_PRICE_STRATEGY: StrategyConfig<DataVariantRuleMetadata>;
|
|
16
|
+
/**
|
|
17
|
+
* 客户/渠道/业务范围价:演示 customer_id、channel、business_code 的 in 条件。
|
|
18
|
+
*/
|
|
19
|
+
export declare const CUSTOMER_SCOPE_PRICE_STRATEGY: StrategyConfig<DataVariantRuleMetadata>;
|
|
20
|
+
/**
|
|
21
|
+
* 示例日程:2026-06-26 17:00 ~ 20:00 生效。
|
|
22
|
+
*/
|
|
23
|
+
export declare const DATA_VARIANT_EXAMPLE_SCHEDULES: ScheduleItem[];
|
|
24
|
+
/**
|
|
25
|
+
* 示例餐牌:product_collection = 128 的商品均属于餐牌。
|
|
26
|
+
*/
|
|
27
|
+
export declare const DATA_VARIANT_EXAMPLE_MENUS: DataVariantMenu[];
|
|
28
|
+
/**
|
|
29
|
+
* 示例商品:同时带两条 Data Variant 覆盖数据。
|
|
30
|
+
*/
|
|
31
|
+
export declare const DATA_VARIANT_EXAMPLE_PRODUCTS: DataVariantProduct[];
|
|
32
|
+
/**
|
|
33
|
+
* Data Variant 评估器示例调用。
|
|
34
|
+
*/
|
|
35
|
+
export declare function runDataVariantDemo(): import("./type").ResolvedDataVariantResult;
|
|
36
|
+
/**
|
|
37
|
+
* 客户/渠道/业务范围条件示例调用。
|
|
38
|
+
*/
|
|
39
|
+
export declare function runCustomerScopeDataVariantDemo(): import("./type").ResolvedDataVariantResult;
|