@pisell/pisellos 2.3.37 → 2.3.38

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.
Files changed (90) hide show
  1. package/dist/core/index.d.ts +2 -0
  2. package/dist/core/index.js +7 -0
  3. package/dist/model/strategy/adapter/promotion/adapter.d.ts +4 -0
  4. package/dist/model/strategy/adapter/promotion/adapter.js +23 -0
  5. package/dist/model/strategy/adapter/promotion/evaluator.d.ts +17 -0
  6. package/dist/model/strategy/adapter/promotion/evaluator.js +279 -6
  7. package/dist/model/strategy/adapter/promotion/examples.d.ts +86 -0
  8. package/dist/model/strategy/adapter/promotion/examples.js +99 -0
  9. package/dist/model/strategy/adapter/promotion/index.d.ts +1 -1
  10. package/dist/model/strategy/adapter/promotion/type.d.ts +90 -2
  11. package/dist/model/strategy/adapter/promotion/type.js +45 -0
  12. package/dist/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +5 -3
  13. package/dist/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +6 -3
  14. package/dist/modules/OpenData/index.d.ts +2 -0
  15. package/dist/modules/OpenData/index.js +8 -0
  16. package/dist/modules/Order/index.js +21 -14
  17. package/dist/modules/Order/utils.js +2 -1
  18. package/dist/modules/Payment/index.js +1 -1
  19. package/dist/modules/Payment/types.d.ts +73 -4
  20. package/dist/modules/Payment/types.js +18 -3
  21. package/dist/modules/Payment/walletpass.d.ts +25 -1
  22. package/dist/modules/Payment/walletpass.js +707 -157
  23. package/dist/modules/Rules/index.d.ts +1 -0
  24. package/dist/modules/Rules/index.js +75 -49
  25. package/dist/server/index.js +19 -18
  26. package/dist/server/modules/order/index.d.ts +5 -0
  27. package/dist/server/modules/order/index.js +52 -31
  28. package/dist/solution/BaseSales/index.d.ts +77 -11
  29. package/dist/solution/BaseSales/index.js +1322 -297
  30. package/dist/solution/BaseSales/types.d.ts +52 -0
  31. package/dist/solution/BaseSales/types.js +2 -1
  32. package/dist/solution/BaseSales/utils/cartPromotion.d.ts +3 -1
  33. package/dist/solution/BaseSales/utils/cartPromotion.js +90 -31
  34. package/dist/solution/BookingByStep/index.d.ts +1 -1
  35. package/dist/solution/BookingTicket/index.d.ts +7 -3
  36. package/dist/solution/BookingTicket/index.js +130 -48
  37. package/dist/solution/BookingTicket/utils/addProductDecision.js +6 -0
  38. package/dist/solution/BookingTicket/utils/scan/applyGlobalScan.d.ts +8 -0
  39. package/dist/solution/BookingTicket/utils/scan/applyGlobalScan.js +172 -82
  40. package/dist/solution/BookingTicket/utils/weighingProductSku.d.ts +5 -0
  41. package/dist/solution/BookingTicket/utils/weighingProductSku.js +45 -0
  42. package/dist/solution/ScanOrder/index.js +31 -20
  43. package/dist/solution/VenueBooking/index.js +30 -19
  44. package/dist/types/index.d.ts +1 -0
  45. package/lib/core/index.d.ts +2 -0
  46. package/lib/core/index.js +4 -0
  47. package/lib/model/strategy/adapter/promotion/adapter.d.ts +4 -0
  48. package/lib/model/strategy/adapter/promotion/adapter.js +20 -0
  49. package/lib/model/strategy/adapter/promotion/evaluator.d.ts +17 -0
  50. package/lib/model/strategy/adapter/promotion/evaluator.js +235 -0
  51. package/lib/model/strategy/adapter/promotion/examples.d.ts +86 -0
  52. package/lib/model/strategy/adapter/promotion/examples.js +91 -0
  53. package/lib/model/strategy/adapter/promotion/index.d.ts +1 -1
  54. package/lib/model/strategy/adapter/promotion/index.js +51 -0
  55. package/lib/model/strategy/adapter/promotion/type.d.ts +90 -2
  56. package/lib/model/strategy/adapter/promotion/type.js +2 -0
  57. package/lib/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +2 -1
  58. package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +2 -1
  59. package/lib/modules/OpenData/index.d.ts +2 -0
  60. package/lib/modules/OpenData/index.js +5 -0
  61. package/lib/modules/Order/index.js +10 -4
  62. package/lib/modules/Order/utils.js +6 -1
  63. package/lib/modules/Payment/index.js +1 -1
  64. package/lib/modules/Payment/types.d.ts +73 -4
  65. package/lib/modules/Payment/types.js +4 -3
  66. package/lib/modules/Payment/walletpass.d.ts +25 -1
  67. package/lib/modules/Payment/walletpass.js +470 -21
  68. package/lib/modules/Rules/index.d.ts +1 -0
  69. package/lib/modules/Rules/index.js +30 -7
  70. package/lib/server/index.js +5 -3
  71. package/lib/server/modules/order/index.d.ts +5 -0
  72. package/lib/server/modules/order/index.js +24 -2
  73. package/lib/solution/BaseSales/index.d.ts +77 -11
  74. package/lib/solution/BaseSales/index.js +682 -39
  75. package/lib/solution/BaseSales/types.d.ts +52 -0
  76. package/lib/solution/BaseSales/types.js +2 -1
  77. package/lib/solution/BaseSales/utils/cartPromotion.d.ts +3 -1
  78. package/lib/solution/BaseSales/utils/cartPromotion.js +63 -12
  79. package/lib/solution/BookingByStep/index.d.ts +1 -1
  80. package/lib/solution/BookingTicket/index.d.ts +7 -3
  81. package/lib/solution/BookingTicket/index.js +60 -8
  82. package/lib/solution/BookingTicket/utils/addProductDecision.js +4 -0
  83. package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.d.ts +8 -0
  84. package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.js +36 -4
  85. package/lib/solution/BookingTicket/utils/weighingProductSku.d.ts +5 -0
  86. package/lib/solution/BookingTicket/utils/weighingProductSku.js +63 -0
  87. package/lib/solution/ScanOrder/index.js +8 -0
  88. package/lib/solution/VenueBooking/index.js +8 -0
  89. package/lib/types/index.d.ts +1 -0
  90. package/package.json +2 -2
@@ -28,6 +28,8 @@ declare class PisellOSCore implements PisellCore {
28
28
  getModule<T extends Module>(name: string): T | null;
29
29
  getModuleExports<T = any>(name: string): T | null;
30
30
  hasModule(name: string): boolean;
31
+ /** 返回当前已注册模块名,供宿主在切换店铺等生命周期中定向清理运行态。 */
32
+ getModuleNames(): string[];
31
33
  destroy(): Promise<void>;
32
34
  setContext(ctx: Partial<BusinessContext>): void;
33
35
  /**
@@ -300,6 +300,13 @@ var PisellOSCore = /*#__PURE__*/function () {
300
300
  return this.modules.has(name);
301
301
  }
302
302
 
303
+ /** 返回当前已注册模块名,供宿主在切换店铺等生命周期中定向清理运行态。 */
304
+ }, {
305
+ key: "getModuleNames",
306
+ value: function getModuleNames() {
307
+ return Array.from(this.modules.keys());
308
+ }
309
+
303
310
  // 销毁方法
304
311
  }, {
305
312
  key: "destroy",
@@ -48,6 +48,10 @@ export declare class PromotionAdapter implements BusinessAdapter {
48
48
  * 解析 买X送Y Action
49
49
  */
50
50
  private parseBuyXGetYFreeAction;
51
+ /**
52
+ * 解析 商品奖励 Action
53
+ */
54
+ private parseItemRewardAction;
51
55
  /**
52
56
  * 获取适用的商品列表
53
57
  *
@@ -162,6 +162,8 @@ export var PromotionAdapter = /*#__PURE__*/function () {
162
162
  return this.parseXItemsForYPriceAction(action);
163
163
  case PROMOTION_ACTION_TYPES.BUY_X_GET_Y_FREE:
164
164
  return this.parseBuyXGetYFreeAction(action);
165
+ case PROMOTION_ACTION_TYPES.ITEM_REWARD:
166
+ return this.parseItemRewardAction(action);
165
167
  default:
166
168
  return undefined;
167
169
  }
@@ -204,6 +206,27 @@ export var PromotionAdapter = /*#__PURE__*/function () {
204
206
  };
205
207
  }
206
208
 
209
+ /**
210
+ * 解析 商品奖励 Action
211
+ */
212
+ }, {
213
+ key: "parseItemRewardAction",
214
+ value: function parseItemRewardAction(action) {
215
+ var value = action.value || {};
216
+ var config = action.config || {};
217
+ return {
218
+ type: PROMOTION_ACTION_TYPES.ITEM_REWARD,
219
+ rewardMethod: value.rewardMethod || 'free',
220
+ rewardValue: Number(value.rewardValue || 0),
221
+ quantityRule: value.quantityRule || 'per_condition_quantity',
222
+ triggerQuantity: Number(value.triggerQuantity || 1),
223
+ rewardQuantity: Number(value.rewardQuantity || 1),
224
+ maxRewardQuantity: value.maxRewardQuantity === undefined || value.maxRewardQuantity === null ? undefined : Number(value.maxRewardQuantity),
225
+ rewardTargets: Array.isArray(config.rewardTargets) ? config.rewardTargets : [],
226
+ targetSelection: config.targetSelection || 'consumer_best'
227
+ };
228
+ }
229
+
207
230
  /**
208
231
  * 获取适用的商品列表
209
232
  *
@@ -169,6 +169,21 @@ export declare class PromotionEvaluator {
169
169
  * 支持 bundle 子商品的数量计算(主商品数量 × 子商品数量)
170
170
  */
171
171
  private processBuyXGetYFree;
172
+ /**
173
+ * 处理 商品奖励 促销
174
+ *
175
+ * 1. 使用 group.products 统计触发条件商品数量
176
+ * 2. 按 quantityRule 计算最多奖励的目标商品数量
177
+ * 3. 在全购物车商品中筛选 rewardTargets
178
+ * 4. 按消费者最优(可节省金额从高到低)选择目标单位
179
+ * 5. 对目标商品拆分并写入 finalPrice,触发商品本身不改价
180
+ */
181
+ private processItemReward;
182
+ private normalizePositiveInteger;
183
+ private getAvailablePromotionQuantity;
184
+ private buildItemRewardTargetUnits;
185
+ private resolveItemRewardFinalPrice;
186
+ private resolveItemRewardLimit;
172
187
  /**
173
188
  * 获取未参与任何促销的商品
174
189
  */
@@ -194,6 +209,8 @@ export declare class PromotionEvaluator {
194
209
  * 检查商品是否在策略的适用范围内
195
210
  */
196
211
  private isProductInStrategy;
212
+ private shouldSkipStrategyForProduct;
213
+ private hasActionType;
197
214
  /**
198
215
  * 查找商品匹配规则
199
216
  */
@@ -178,6 +178,10 @@ export var PromotionEvaluator = /*#__PURE__*/function () {
178
178
  try {
179
179
  for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
180
180
  var config = _step2.value;
181
+ if (this.shouldSkipStrategyForProduct(product, config)) {
182
+ continue;
183
+ }
184
+
181
185
  // 1. 先用 getProductMatchInfo 检查商品是否在策略的适用范围内(包含 bundle 子商品检查)
182
186
  var matchInfo = this.getProductMatchInfo(product, config);
183
187
  if (!matchInfo.isMatch) {
@@ -400,6 +404,12 @@ export var PromotionEvaluator = /*#__PURE__*/function () {
400
404
  if (_result.giftInfo) {
401
405
  gifts.push(_result.giftInfo);
402
406
  }
407
+ } else if (actionType === PROMOTION_ACTION_TYPES.ITEM_REWARD) {
408
+ // 商品奖励处理:条件商品触发,目标商品行享优惠,不自动新增商品
409
+ var _result2 = this.processItemReward(group, processedQuantityMap, products, matchedBundleIndexMap);
410
+ pricedProducts.push.apply(pricedProducts, _toConsumableArray(_result2.products));
411
+ totalOriginalAmount = totalOriginalAmount.plus(_result2.originalAmount);
412
+ totalFinalAmount = totalFinalAmount.plus(_result2.finalAmount);
403
413
  }
404
414
  }
405
415
 
@@ -530,6 +540,9 @@ export var PromotionEvaluator = /*#__PURE__*/function () {
530
540
  try {
531
541
  for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) {
532
542
  var config = _step8.value;
543
+ if (_this3.shouldSkipStrategyForProduct(product, config)) {
544
+ continue;
545
+ }
533
546
  var contextProduct = product;
534
547
  if (!matchVariant) {
535
548
  // 只匹配 product_id,忽略 product_variant_id
@@ -627,6 +640,9 @@ export var PromotionEvaluator = /*#__PURE__*/function () {
627
640
  } else if (actionType === 'X_ITEMS_FOR_Y_PRICE') {
628
641
  var _detail = actionDetail;
629
642
  requiredQuantity = _detail.x || 0;
643
+ } else if (actionType === 'ITEM_REWARD') {
644
+ var _detail2 = actionDetail;
645
+ requiredQuantity = _detail2.triggerQuantity || 1;
630
646
  }
631
647
 
632
648
  // 获取可参与商品列表
@@ -1104,6 +1120,248 @@ export var PromotionEvaluator = /*#__PURE__*/function () {
1104
1120
  };
1105
1121
  }
1106
1122
 
1123
+ /**
1124
+ * 处理 商品奖励 促销
1125
+ *
1126
+ * 1. 使用 group.products 统计触发条件商品数量
1127
+ * 2. 按 quantityRule 计算最多奖励的目标商品数量
1128
+ * 3. 在全购物车商品中筛选 rewardTargets
1129
+ * 4. 按消费者最优(可节省金额从高到低)选择目标单位
1130
+ * 5. 对目标商品拆分并写入 finalPrice,触发商品本身不改价
1131
+ */
1132
+ }, {
1133
+ key: "processItemReward",
1134
+ value: function processItemReward(group, processedQuantityMap, allProducts, matchedBundleIndexMap) {
1135
+ var _this4 = this;
1136
+ var strategyId = group.strategyId,
1137
+ strategyMetadata = group.strategyMetadata,
1138
+ actionDetail = group.actionDetail,
1139
+ triggerProducts = group.products;
1140
+ var detail = actionDetail;
1141
+ var triggerQuantity = this.normalizePositiveInteger(detail.triggerQuantity, 1);
1142
+ var rewardQuantity = this.normalizePositiveInteger(detail.rewardQuantity, 1);
1143
+ var conditionQuantity = this.getAvailablePromotionQuantity(triggerProducts, processedQuantityMap, matchedBundleIndexMap);
1144
+ if (conditionQuantity < triggerQuantity) {
1145
+ return {
1146
+ products: [],
1147
+ originalAmount: 0,
1148
+ finalAmount: 0
1149
+ };
1150
+ }
1151
+ var targetProducts = allProducts.filter(function (product) {
1152
+ return _this4.isProductMatch(product, detail.rewardTargets || []);
1153
+ });
1154
+ var targetUnitCandidates = this.buildItemRewardTargetUnits(targetProducts, processedQuantityMap, detail);
1155
+ if (targetUnitCandidates.length === 0) {
1156
+ return {
1157
+ products: [],
1158
+ originalAmount: 0,
1159
+ finalAmount: 0
1160
+ };
1161
+ }
1162
+ var rewardLimit = this.resolveItemRewardLimit(detail, conditionQuantity, triggerQuantity, rewardQuantity, targetUnitCandidates.length);
1163
+ if (rewardLimit <= 0) {
1164
+ return {
1165
+ products: [],
1166
+ originalAmount: 0,
1167
+ finalAmount: 0
1168
+ };
1169
+ }
1170
+ var selectedUnits = _toConsumableArray(targetUnitCandidates).sort(function (a, b) {
1171
+ var discountDiff = b.discountAmount.minus(a.discountAmount).toNumber();
1172
+ if (discountDiff !== 0) return discountDiff;
1173
+ return b.originalPrice.minus(a.originalPrice).toNumber();
1174
+ }).slice(0, rewardLimit);
1175
+ var selectedByProductId = new Map();
1176
+ var _iterator18 = _createForOfIteratorHelper(selectedUnits),
1177
+ _step18;
1178
+ try {
1179
+ for (_iterator18.s(); !(_step18 = _iterator18.n()).done;) {
1180
+ var unit = _step18.value;
1181
+ var existing = selectedByProductId.get(unit.product.id);
1182
+ if (existing) {
1183
+ existing.quantity += 1;
1184
+ } else {
1185
+ selectedByProductId.set(unit.product.id, {
1186
+ quantity: 1,
1187
+ finalPrice: unit.finalPrice,
1188
+ originalPrice: unit.originalPrice
1189
+ });
1190
+ }
1191
+ }
1192
+ } catch (err) {
1193
+ _iterator18.e(err);
1194
+ } finally {
1195
+ _iterator18.f();
1196
+ }
1197
+ var result = [];
1198
+ var originalAmount = new Decimal(0);
1199
+ var finalAmount = new Decimal(0);
1200
+ var _iterator19 = _createForOfIteratorHelper(targetProducts),
1201
+ _step19;
1202
+ try {
1203
+ for (_iterator19.s(); !(_step19 = _iterator19.n()).done;) {
1204
+ var product = _step19.value;
1205
+ var key = this.getProductKey(product);
1206
+ var processedQty = processedQuantityMap.get(key) || 0;
1207
+ var availableQty = product.quantity - processedQty;
1208
+ if (availableQty <= 0) continue;
1209
+ var selected = selectedByProductId.get(product.id);
1210
+ var selectedQty = (selected === null || selected === void 0 ? void 0 : selected.quantity) || 0;
1211
+ var remainingQty = availableQty - selectedQty;
1212
+ if (selectedQty <= 0) continue;
1213
+ if (selectedQty > 0) {
1214
+ result.push(_objectSpread(_objectSpread({}, product), {}, {
1215
+ id: selectedQty === availableQty ? product.id : this.generateRandomId(),
1216
+ originalId: product.id,
1217
+ quantity: selectedQty,
1218
+ finalPrice: this.formatPrice(selected.finalPrice),
1219
+ strategyId: strategyId,
1220
+ strategyMetadata: strategyMetadata,
1221
+ inPromotion: true,
1222
+ isSplit: selectedQty !== product.quantity
1223
+ }));
1224
+ originalAmount = originalAmount.plus(selected.originalPrice.mul(selectedQty));
1225
+ finalAmount = finalAmount.plus(selected.finalPrice.mul(selectedQty));
1226
+ }
1227
+ if (remainingQty > 0) {
1228
+ result.push(_objectSpread(_objectSpread({}, product), {}, {
1229
+ quantity: remainingQty,
1230
+ finalPrice: this.formatPrice(product.price),
1231
+ strategyId: undefined,
1232
+ strategyMetadata: undefined,
1233
+ inPromotion: false,
1234
+ isSplit: selectedQty > 0
1235
+ }));
1236
+ var remainingAmount = new Decimal(product.price).mul(remainingQty);
1237
+ originalAmount = originalAmount.plus(remainingAmount);
1238
+ finalAmount = finalAmount.plus(remainingAmount);
1239
+ }
1240
+ processedQuantityMap.set(key, processedQty + availableQty);
1241
+ }
1242
+ } catch (err) {
1243
+ _iterator19.e(err);
1244
+ } finally {
1245
+ _iterator19.f();
1246
+ }
1247
+ return {
1248
+ products: result,
1249
+ originalAmount: originalAmount.toNumber(),
1250
+ finalAmount: finalAmount.toNumber()
1251
+ };
1252
+ }
1253
+ }, {
1254
+ key: "normalizePositiveInteger",
1255
+ value: function normalizePositiveInteger(value, fallback) {
1256
+ var n = Math.floor(Number(value));
1257
+ return Number.isFinite(n) && n > 0 ? n : fallback;
1258
+ }
1259
+ }, {
1260
+ key: "getAvailablePromotionQuantity",
1261
+ value: function getAvailablePromotionQuantity(products, processedQuantityMap, matchedBundleIndexMap) {
1262
+ var total = 0;
1263
+ var _iterator20 = _createForOfIteratorHelper(products),
1264
+ _step20;
1265
+ try {
1266
+ for (_iterator20.s(); !(_step20 = _iterator20.n()).done;) {
1267
+ var product = _step20.value;
1268
+ var key = this.getProductKey(product);
1269
+ var processedQty = processedQuantityMap.get(key) || 0;
1270
+ var availableQty = product.quantity - processedQty;
1271
+ if (availableQty <= 0) continue;
1272
+ var matchedBundleIndex = matchedBundleIndexMap === null || matchedBundleIndexMap === void 0 ? void 0 : matchedBundleIndexMap.get(product.id);
1273
+ if (matchedBundleIndex !== undefined && product.bundle) {
1274
+ var bundleItem = product.bundle[matchedBundleIndex];
1275
+ total += availableQty * ((bundleItem === null || bundleItem === void 0 ? void 0 : bundleItem.quantity) || 1);
1276
+ } else {
1277
+ total += availableQty;
1278
+ }
1279
+ }
1280
+ } catch (err) {
1281
+ _iterator20.e(err);
1282
+ } finally {
1283
+ _iterator20.f();
1284
+ }
1285
+ return total;
1286
+ }
1287
+ }, {
1288
+ key: "buildItemRewardTargetUnits",
1289
+ value: function buildItemRewardTargetUnits(products, processedQuantityMap, detail) {
1290
+ var units = [];
1291
+ var _iterator21 = _createForOfIteratorHelper(products),
1292
+ _step21;
1293
+ try {
1294
+ for (_iterator21.s(); !(_step21 = _iterator21.n()).done;) {
1295
+ var product = _step21.value;
1296
+ var key = this.getProductKey(product);
1297
+ var processedQty = processedQuantityMap.get(key) || 0;
1298
+ var availableQty = product.quantity - processedQty;
1299
+ if (availableQty <= 0) continue;
1300
+ var originalPrice = new Decimal(product.price || 0);
1301
+ var finalPrice = this.resolveItemRewardFinalPrice(originalPrice, detail);
1302
+ var discountAmount = originalPrice.minus(finalPrice);
1303
+ if (discountAmount.lte(0)) continue;
1304
+ for (var i = 0; i < availableQty; i++) {
1305
+ units.push({
1306
+ product: product,
1307
+ originalPrice: originalPrice,
1308
+ finalPrice: finalPrice,
1309
+ discountAmount: discountAmount
1310
+ });
1311
+ }
1312
+ }
1313
+ } catch (err) {
1314
+ _iterator21.e(err);
1315
+ } finally {
1316
+ _iterator21.f();
1317
+ }
1318
+ return units;
1319
+ }
1320
+ }, {
1321
+ key: "resolveItemRewardFinalPrice",
1322
+ value: function resolveItemRewardFinalPrice(originalPrice, detail) {
1323
+ var rewardValue = new Decimal(Number(detail.rewardValue) || 0);
1324
+ var finalPrice;
1325
+ switch (detail.rewardMethod) {
1326
+ case 'percent_off':
1327
+ {
1328
+ var percent = Decimal.min(100, Decimal.max(0, rewardValue));
1329
+ finalPrice = originalPrice.mul(new Decimal(100).minus(percent)).div(100);
1330
+ break;
1331
+ }
1332
+ case 'amount_off':
1333
+ finalPrice = originalPrice.minus(Decimal.max(0, rewardValue));
1334
+ break;
1335
+ case 'fixed_price':
1336
+ finalPrice = Decimal.max(0, rewardValue);
1337
+ break;
1338
+ case 'free':
1339
+ default:
1340
+ finalPrice = new Decimal(0);
1341
+ break;
1342
+ }
1343
+ return Decimal.max(0, Decimal.min(originalPrice, finalPrice)).toDecimalPlaces(2, Decimal.ROUND_HALF_UP);
1344
+ }
1345
+ }, {
1346
+ key: "resolveItemRewardLimit",
1347
+ value: function resolveItemRewardLimit(detail, conditionQuantity, triggerQuantity, rewardQuantity, targetUnitCount) {
1348
+ var _detail$maxRewardQuan;
1349
+ var limit = 0;
1350
+ switch (detail.quantityRule) {
1351
+ case 'all_targets':
1352
+ limit = targetUnitCount;
1353
+ break;
1354
+ case 'max_per_order':
1355
+ limit = this.normalizePositiveInteger((_detail$maxRewardQuan = detail.maxRewardQuantity) !== null && _detail$maxRewardQuan !== void 0 ? _detail$maxRewardQuan : detail.rewardQuantity, rewardQuantity);
1356
+ break;
1357
+ case 'per_condition_quantity':
1358
+ default:
1359
+ limit = Math.floor(conditionQuantity / triggerQuantity) * rewardQuantity;
1360
+ break;
1361
+ }
1362
+ return Math.min(Math.max(0, limit), targetUnitCount);
1363
+ }
1364
+
1107
1365
  /**
1108
1366
  * 获取未参与任何促销的商品
1109
1367
  */
@@ -1111,11 +1369,11 @@ export var PromotionEvaluator = /*#__PURE__*/function () {
1111
1369
  key: "getRemainingProducts",
1112
1370
  value: function getRemainingProducts(allProducts, processedQuantityMap) {
1113
1371
  var result = [];
1114
- var _iterator18 = _createForOfIteratorHelper(allProducts),
1115
- _step18;
1372
+ var _iterator22 = _createForOfIteratorHelper(allProducts),
1373
+ _step22;
1116
1374
  try {
1117
- for (_iterator18.s(); !(_step18 = _iterator18.n()).done;) {
1118
- var product = _step18.value;
1375
+ for (_iterator22.s(); !(_step22 = _iterator22.n()).done;) {
1376
+ var product = _step22.value;
1119
1377
  var key = this.getProductKey(product);
1120
1378
  var processedQty = processedQuantityMap.get(key) || 0;
1121
1379
  var remainingQty = product.quantity - processedQty;
@@ -1130,9 +1388,9 @@ export var PromotionEvaluator = /*#__PURE__*/function () {
1130
1388
  }
1131
1389
  }
1132
1390
  } catch (err) {
1133
- _iterator18.e(err);
1391
+ _iterator22.e(err);
1134
1392
  } finally {
1135
- _iterator18.f();
1393
+ _iterator22.f();
1136
1394
  }
1137
1395
  return result;
1138
1396
  }
@@ -1210,8 +1468,23 @@ export var PromotionEvaluator = /*#__PURE__*/function () {
1210
1468
  }, {
1211
1469
  key: "isProductInStrategy",
1212
1470
  value: function isProductInStrategy(product, config) {
1471
+ if (this.shouldSkipStrategyForProduct(product, config)) {
1472
+ return false;
1473
+ }
1213
1474
  return this.getProductMatchInfo(product, config).isMatch;
1214
1475
  }
1476
+ }, {
1477
+ key: "shouldSkipStrategyForProduct",
1478
+ value: function shouldSkipStrategyForProduct(product, config) {
1479
+ return Boolean(product._promotionOnlyForItemReward) && !this.hasActionType(config, PROMOTION_ACTION_TYPES.ITEM_REWARD);
1480
+ }
1481
+ }, {
1482
+ key: "hasActionType",
1483
+ value: function hasActionType(config, actionType) {
1484
+ return (config.actions || []).some(function (action) {
1485
+ return (action === null || action === void 0 ? void 0 : action.type) === actionType;
1486
+ });
1487
+ }
1215
1488
 
1216
1489
  /**
1217
1490
  * 查找商品匹配规则
@@ -136,3 +136,89 @@ export declare const BUY_X_GET_Y_FREE_STRATEGY: {
136
136
  };
137
137
  }[];
138
138
  };
139
+ /**
140
+ * ITEM_REWARD 策略配置示例:儿童票触发婴儿票免费
141
+ *
142
+ * 业务规则:
143
+ * - 条件商品:儿童票(conditions.rules.productIdAndVariantId)
144
+ * - 奖励商品:购物车/订单内已经存在的婴儿票(actions[0].config.rewardTargets)
145
+ * - 本 action 不自动新增婴儿票;每次购物车变更后由统一促销链路重算
146
+ *
147
+ * 字段说明:
148
+ * - type: "ITEM_REWARD" 表示商品奖励 action
149
+ * - target: "product" 表示本期只奖励商品行
150
+ * - config.rewardTargets: 奖励对象数组,支持 { product_id, product_variant_id }
151
+ * - config.targetSelection: "consumer_best" 表示目标商品超额时选择节省金额最高的商品
152
+ * - value.rewardMethod:
153
+ * - "free": 目标商品最终价为 0
154
+ * - "percent_off": 百分比折扣,rewardValue 为 0-100
155
+ * - "amount_off": 每个目标商品固定减免 rewardValue
156
+ * - "fixed_price": 目标商品最终单价为 rewardValue
157
+ * - value.quantityRule:
158
+ * - "per_condition_quantity": 每满足 triggerQuantity 个条件商品,奖励 rewardQuantity 个目标商品
159
+ * - "all_targets": 满足条件后奖励全部目标商品
160
+ * - "max_per_order": 满足条件后每单最多奖励 maxRewardQuantity 个目标商品
161
+ */
162
+ export declare const ITEM_REWARD_STRATEGY: {
163
+ metadata: {
164
+ id: string;
165
+ name: {
166
+ en: string;
167
+ 'zh-CN': string;
168
+ 'zh-HK': string;
169
+ };
170
+ type: string;
171
+ custom: {
172
+ display: {
173
+ product_card: {
174
+ text: {
175
+ en: string;
176
+ 'zh-CN': string;
177
+ 'zh-HK': string;
178
+ };
179
+ type: string;
180
+ };
181
+ };
182
+ };
183
+ };
184
+ conditions: {
185
+ operator: string;
186
+ rules: ({
187
+ type: string;
188
+ field: string;
189
+ value: string;
190
+ operator: string;
191
+ } | {
192
+ type: string;
193
+ field: string;
194
+ value: {
195
+ product_id: number;
196
+ product_variant_id: number;
197
+ }[];
198
+ operator: string;
199
+ })[];
200
+ actionIds: string[];
201
+ };
202
+ actions: {
203
+ id: string;
204
+ type: string;
205
+ value: {
206
+ rewardMethod: string;
207
+ rewardValue: number;
208
+ quantityRule: string;
209
+ triggerQuantity: number;
210
+ rewardQuantity: number;
211
+ maxRewardQuantity: number;
212
+ };
213
+ valueType: string;
214
+ target: string;
215
+ priority: number;
216
+ config: {
217
+ rewardTargets: {
218
+ product_id: number;
219
+ product_variant_id: number;
220
+ }[];
221
+ targetSelection: string;
222
+ };
223
+ }[];
224
+ };
@@ -163,4 +163,103 @@ export var BUY_X_GET_Y_FREE_STRATEGY = {
163
163
  }]
164
164
  }
165
165
  }]
166
+ };
167
+
168
+ /**
169
+ * ITEM_REWARD 策略配置示例:儿童票触发婴儿票免费
170
+ *
171
+ * 业务规则:
172
+ * - 条件商品:儿童票(conditions.rules.productIdAndVariantId)
173
+ * - 奖励商品:购物车/订单内已经存在的婴儿票(actions[0].config.rewardTargets)
174
+ * - 本 action 不自动新增婴儿票;每次购物车变更后由统一促销链路重算
175
+ *
176
+ * 字段说明:
177
+ * - type: "ITEM_REWARD" 表示商品奖励 action
178
+ * - target: "product" 表示本期只奖励商品行
179
+ * - config.rewardTargets: 奖励对象数组,支持 { product_id, product_variant_id }
180
+ * - config.targetSelection: "consumer_best" 表示目标商品超额时选择节省金额最高的商品
181
+ * - value.rewardMethod:
182
+ * - "free": 目标商品最终价为 0
183
+ * - "percent_off": 百分比折扣,rewardValue 为 0-100
184
+ * - "amount_off": 每个目标商品固定减免 rewardValue
185
+ * - "fixed_price": 目标商品最终单价为 rewardValue
186
+ * - value.quantityRule:
187
+ * - "per_condition_quantity": 每满足 triggerQuantity 个条件商品,奖励 rewardQuantity 个目标商品
188
+ * - "all_targets": 满足条件后奖励全部目标商品
189
+ * - "max_per_order": 满足条件后每单最多奖励 maxRewardQuantity 个目标商品
190
+ */
191
+ export var ITEM_REWARD_STRATEGY = {
192
+ metadata: {
193
+ id: 'STRATEGY_003',
194
+ name: {
195
+ en: 'Infant ticket free with child ticket',
196
+ 'zh-CN': '儿童票触发婴儿票免费',
197
+ 'zh-HK': '兒童票觸發嬰兒票免費'
198
+ },
199
+ type: 'promotion',
200
+ custom: {
201
+ display: {
202
+ product_card: {
203
+ text: {
204
+ en: 'Infant ticket free',
205
+ 'zh-CN': '婴儿票免费',
206
+ 'zh-HK': '嬰兒票免費'
207
+ },
208
+ type: 'tag'
209
+ }
210
+ }
211
+ }
212
+ },
213
+ conditions: {
214
+ operator: 'and',
215
+ rules: [{
216
+ type: 'operator',
217
+ field: 'currentDateTime',
218
+ value: '2023-01-01 00:00:00',
219
+ operator: '>='
220
+ }, {
221
+ type: 'operator',
222
+ field: 'currentDateTime',
223
+ value: '2030-01-01 00:00:00',
224
+ operator: '<='
225
+ }, {
226
+ type: 'operator',
227
+ field: 'productIdAndVariantId',
228
+ value: [{
229
+ product_id: 1001,
230
+ // 条件商品:儿童票
231
+ product_variant_id: 0 // 0 = 匹配任意变体
232
+ }],
233
+ operator: 'product_match'
234
+ }],
235
+ actionIds: ['1']
236
+ },
237
+ actions: [{
238
+ id: '1',
239
+ type: 'ITEM_REWARD',
240
+ value: {
241
+ rewardMethod: 'free',
242
+ // free | percent_off | amount_off | fixed_price
243
+ rewardValue: 0,
244
+ // free 可省略;其他 rewardMethod 使用
245
+ quantityRule: 'per_condition_quantity',
246
+ // per_condition_quantity | all_targets | max_per_order
247
+ triggerQuantity: 1,
248
+ // N:每满足 N 个条件商品
249
+ rewardQuantity: 1,
250
+ // Y:奖励 Y 个目标商品
251
+ maxRewardQuantity: 1 // max_per_order 使用;本例保留为配置示意
252
+ },
253
+ valueType: 'object',
254
+ target: 'product',
255
+ priority: 1,
256
+ config: {
257
+ rewardTargets: [{
258
+ product_id: 2001,
259
+ // 奖励对象:婴儿票
260
+ product_variant_id: 0 // 0 = 匹配任意变体
261
+ }],
262
+ targetSelection: 'consumer_best' // consumer_best = 按可节省金额从高到低选择
263
+ }
264
+ }]
166
265
  };
@@ -1,4 +1,4 @@
1
1
  export { PromotionEvaluator } from './evaluator';
2
2
  export { PromotionAdapter } from './adapter';
3
3
  export { default } from './adapter';
4
- export { X_ITEMS_FOR_Y_PRICE_STRATEGY, BUY_X_GET_Y_FREE_STRATEGY, } from './examples';
4
+ export { X_ITEMS_FOR_Y_PRICE_STRATEGY, BUY_X_GET_Y_FREE_STRATEGY, ITEM_REWARD_STRATEGY, } from './examples';