@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
@@ -47,6 +47,7 @@ export interface PisellCore {
47
47
  getModule: <T extends Module>(name: string) => T | null;
48
48
  getModuleExports: <T = any>(name: string) => T | null;
49
49
  hasModule: (name: string) => boolean;
50
+ getModuleNames: () => string[];
50
51
  effects: {
51
52
  on: (event: string, callback: (payload: any) => void) => () => void;
52
53
  once: (event: string, callback: (payload: any) => void) => () => void;
@@ -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
  /**
package/lib/core/index.js CHANGED
@@ -210,6 +210,10 @@ var PisellOSCore = class {
210
210
  hasModule(name) {
211
211
  return this.modules.has(name);
212
212
  }
213
+ /** 返回当前已注册模块名,供宿主在切换店铺等生命周期中定向清理运行态。 */
214
+ getModuleNames() {
215
+ return Array.from(this.modules.keys());
216
+ }
213
217
  // 销毁方法
214
218
  async destroy() {
215
219
  for (const [name, module2] of this.modules.entries()) {
@@ -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
  *
@@ -134,6 +134,8 @@ var PromotionAdapter = class {
134
134
  return this.parseXItemsForYPriceAction(action);
135
135
  case import_type.PROMOTION_ACTION_TYPES.BUY_X_GET_Y_FREE:
136
136
  return this.parseBuyXGetYFreeAction(action);
137
+ case import_type.PROMOTION_ACTION_TYPES.ITEM_REWARD:
138
+ return this.parseItemRewardAction(action);
137
139
  default:
138
140
  return void 0;
139
141
  }
@@ -167,6 +169,24 @@ var PromotionAdapter = class {
167
169
  giftProducts: config.giftProducts || []
168
170
  };
169
171
  }
172
+ /**
173
+ * 解析 商品奖励 Action
174
+ */
175
+ parseItemRewardAction(action) {
176
+ const value = action.value || {};
177
+ const config = action.config || {};
178
+ return {
179
+ type: import_type.PROMOTION_ACTION_TYPES.ITEM_REWARD,
180
+ rewardMethod: value.rewardMethod || "free",
181
+ rewardValue: Number(value.rewardValue || 0),
182
+ quantityRule: value.quantityRule || "per_condition_quantity",
183
+ triggerQuantity: Number(value.triggerQuantity || 1),
184
+ rewardQuantity: Number(value.rewardQuantity || 1),
185
+ maxRewardQuantity: value.maxRewardQuantity === void 0 || value.maxRewardQuantity === null ? void 0 : Number(value.maxRewardQuantity),
186
+ rewardTargets: Array.isArray(config.rewardTargets) ? config.rewardTargets : [],
187
+ targetSelection: config.targetSelection || "consumer_best"
188
+ };
189
+ }
170
190
  /**
171
191
  * 获取适用的商品列表
172
192
  *
@@ -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
  */
@@ -133,6 +133,9 @@ var PromotionEvaluator = class {
133
133
  for (const product of products) {
134
134
  const applicablePromotions = [];
135
135
  for (const config of configs) {
136
+ if (this.shouldSkipStrategyForProduct(product, config)) {
137
+ continue;
138
+ }
136
139
  const matchInfo = this.getProductMatchInfo(product, config);
137
140
  if (!matchInfo.isMatch) {
138
141
  continue;
@@ -296,6 +299,16 @@ var PromotionEvaluator = class {
296
299
  if (result.giftInfo) {
297
300
  gifts.push(result.giftInfo);
298
301
  }
302
+ } else if (actionType === import_type.PROMOTION_ACTION_TYPES.ITEM_REWARD) {
303
+ const result = this.processItemReward(
304
+ group,
305
+ processedQuantityMap,
306
+ products,
307
+ matchedBundleIndexMap
308
+ );
309
+ pricedProducts.push(...result.products);
310
+ totalOriginalAmount = totalOriginalAmount.plus(result.originalAmount);
311
+ totalFinalAmount = totalFinalAmount.plus(result.finalAmount);
299
312
  }
300
313
  }
301
314
  const remainingProducts = this.getRemainingProducts(
@@ -379,6 +392,9 @@ var PromotionEvaluator = class {
379
392
  for (const product of products) {
380
393
  const applicableStrategies = [];
381
394
  for (const config of configs) {
395
+ if (this.shouldSkipStrategyForProduct(product, config)) {
396
+ continue;
397
+ }
382
398
  let contextProduct = product;
383
399
  if (!matchVariant) {
384
400
  const productMatchRule = this.findProductMatchRule(config);
@@ -455,6 +471,9 @@ var PromotionEvaluator = class {
455
471
  } else if (actionType === "X_ITEMS_FOR_Y_PRICE") {
456
472
  const detail = actionDetail;
457
473
  requiredQuantity = detail.x || 0;
474
+ } else if (actionType === "ITEM_REWARD") {
475
+ const detail = actionDetail;
476
+ requiredQuantity = detail.triggerQuantity || 1;
458
477
  }
459
478
  const productMatchRule = this.findProductMatchRule(config);
460
479
  const eligibleProducts = (productMatchRule == null ? void 0 : productMatchRule.value) || [];
@@ -768,6 +787,213 @@ var PromotionEvaluator = class {
768
787
  giftInfo
769
788
  };
770
789
  }
790
+ /**
791
+ * 处理 商品奖励 促销
792
+ *
793
+ * 1. 使用 group.products 统计触发条件商品数量
794
+ * 2. 按 quantityRule 计算最多奖励的目标商品数量
795
+ * 3. 在全购物车商品中筛选 rewardTargets
796
+ * 4. 按消费者最优(可节省金额从高到低)选择目标单位
797
+ * 5. 对目标商品拆分并写入 finalPrice,触发商品本身不改价
798
+ */
799
+ processItemReward(group, processedQuantityMap, allProducts, matchedBundleIndexMap) {
800
+ const {
801
+ strategyId,
802
+ strategyMetadata,
803
+ actionDetail,
804
+ products: triggerProducts
805
+ } = group;
806
+ const detail = actionDetail;
807
+ const triggerQuantity = this.normalizePositiveInteger(detail.triggerQuantity, 1);
808
+ const rewardQuantity = this.normalizePositiveInteger(detail.rewardQuantity, 1);
809
+ const conditionQuantity = this.getAvailablePromotionQuantity(
810
+ triggerProducts,
811
+ processedQuantityMap,
812
+ matchedBundleIndexMap
813
+ );
814
+ if (conditionQuantity < triggerQuantity) {
815
+ return { products: [], originalAmount: 0, finalAmount: 0 };
816
+ }
817
+ const targetProducts = allProducts.filter(
818
+ (product) => this.isProductMatch(product, detail.rewardTargets || [])
819
+ );
820
+ const targetUnitCandidates = this.buildItemRewardTargetUnits(
821
+ targetProducts,
822
+ processedQuantityMap,
823
+ detail
824
+ );
825
+ if (targetUnitCandidates.length === 0) {
826
+ return { products: [], originalAmount: 0, finalAmount: 0 };
827
+ }
828
+ const rewardLimit = this.resolveItemRewardLimit(
829
+ detail,
830
+ conditionQuantity,
831
+ triggerQuantity,
832
+ rewardQuantity,
833
+ targetUnitCandidates.length
834
+ );
835
+ if (rewardLimit <= 0) {
836
+ return { products: [], originalAmount: 0, finalAmount: 0 };
837
+ }
838
+ const selectedUnits = [...targetUnitCandidates].sort((a, b) => {
839
+ const discountDiff = b.discountAmount.minus(a.discountAmount).toNumber();
840
+ if (discountDiff !== 0)
841
+ return discountDiff;
842
+ return b.originalPrice.minus(a.originalPrice).toNumber();
843
+ }).slice(0, rewardLimit);
844
+ const selectedByProductId = /* @__PURE__ */ new Map();
845
+ for (const unit of selectedUnits) {
846
+ const existing = selectedByProductId.get(unit.product.id);
847
+ if (existing) {
848
+ existing.quantity += 1;
849
+ } else {
850
+ selectedByProductId.set(unit.product.id, {
851
+ quantity: 1,
852
+ finalPrice: unit.finalPrice,
853
+ originalPrice: unit.originalPrice
854
+ });
855
+ }
856
+ }
857
+ const result = [];
858
+ let originalAmount = new import_decimal.default(0);
859
+ let finalAmount = new import_decimal.default(0);
860
+ for (const product of targetProducts) {
861
+ const key = this.getProductKey(product);
862
+ const processedQty = processedQuantityMap.get(key) || 0;
863
+ const availableQty = product.quantity - processedQty;
864
+ if (availableQty <= 0)
865
+ continue;
866
+ const selected = selectedByProductId.get(product.id);
867
+ const selectedQty = (selected == null ? void 0 : selected.quantity) || 0;
868
+ const remainingQty = availableQty - selectedQty;
869
+ if (selectedQty <= 0)
870
+ continue;
871
+ if (selectedQty > 0) {
872
+ result.push({
873
+ ...product,
874
+ id: selectedQty === availableQty ? product.id : this.generateRandomId(),
875
+ originalId: product.id,
876
+ quantity: selectedQty,
877
+ finalPrice: this.formatPrice(selected.finalPrice),
878
+ strategyId,
879
+ strategyMetadata,
880
+ inPromotion: true,
881
+ isSplit: selectedQty !== product.quantity
882
+ });
883
+ originalAmount = originalAmount.plus(
884
+ selected.originalPrice.mul(selectedQty)
885
+ );
886
+ finalAmount = finalAmount.plus(selected.finalPrice.mul(selectedQty));
887
+ }
888
+ if (remainingQty > 0) {
889
+ result.push({
890
+ ...product,
891
+ quantity: remainingQty,
892
+ finalPrice: this.formatPrice(product.price),
893
+ strategyId: void 0,
894
+ strategyMetadata: void 0,
895
+ inPromotion: false,
896
+ isSplit: selectedQty > 0
897
+ });
898
+ const remainingAmount = new import_decimal.default(product.price).mul(remainingQty);
899
+ originalAmount = originalAmount.plus(remainingAmount);
900
+ finalAmount = finalAmount.plus(remainingAmount);
901
+ }
902
+ processedQuantityMap.set(key, processedQty + availableQty);
903
+ }
904
+ return {
905
+ products: result,
906
+ originalAmount: originalAmount.toNumber(),
907
+ finalAmount: finalAmount.toNumber()
908
+ };
909
+ }
910
+ normalizePositiveInteger(value, fallback) {
911
+ const n = Math.floor(Number(value));
912
+ return Number.isFinite(n) && n > 0 ? n : fallback;
913
+ }
914
+ getAvailablePromotionQuantity(products, processedQuantityMap, matchedBundleIndexMap) {
915
+ let total = 0;
916
+ for (const product of products) {
917
+ const key = this.getProductKey(product);
918
+ const processedQty = processedQuantityMap.get(key) || 0;
919
+ const availableQty = product.quantity - processedQty;
920
+ if (availableQty <= 0)
921
+ continue;
922
+ const matchedBundleIndex = matchedBundleIndexMap == null ? void 0 : matchedBundleIndexMap.get(product.id);
923
+ if (matchedBundleIndex !== void 0 && product.bundle) {
924
+ const bundleItem = product.bundle[matchedBundleIndex];
925
+ total += availableQty * ((bundleItem == null ? void 0 : bundleItem.quantity) || 1);
926
+ } else {
927
+ total += availableQty;
928
+ }
929
+ }
930
+ return total;
931
+ }
932
+ buildItemRewardTargetUnits(products, processedQuantityMap, detail) {
933
+ const units = [];
934
+ for (const product of products) {
935
+ const key = this.getProductKey(product);
936
+ const processedQty = processedQuantityMap.get(key) || 0;
937
+ const availableQty = product.quantity - processedQty;
938
+ if (availableQty <= 0)
939
+ continue;
940
+ const originalPrice = new import_decimal.default(product.price || 0);
941
+ const finalPrice = this.resolveItemRewardFinalPrice(originalPrice, detail);
942
+ const discountAmount = originalPrice.minus(finalPrice);
943
+ if (discountAmount.lte(0))
944
+ continue;
945
+ for (let i = 0; i < availableQty; i++) {
946
+ units.push({
947
+ product,
948
+ originalPrice,
949
+ finalPrice,
950
+ discountAmount
951
+ });
952
+ }
953
+ }
954
+ return units;
955
+ }
956
+ resolveItemRewardFinalPrice(originalPrice, detail) {
957
+ const rewardValue = new import_decimal.default(Number(detail.rewardValue) || 0);
958
+ let finalPrice;
959
+ switch (detail.rewardMethod) {
960
+ case "percent_off": {
961
+ const percent = import_decimal.default.min(100, import_decimal.default.max(0, rewardValue));
962
+ finalPrice = originalPrice.mul(new import_decimal.default(100).minus(percent)).div(100);
963
+ break;
964
+ }
965
+ case "amount_off":
966
+ finalPrice = originalPrice.minus(import_decimal.default.max(0, rewardValue));
967
+ break;
968
+ case "fixed_price":
969
+ finalPrice = import_decimal.default.max(0, rewardValue);
970
+ break;
971
+ case "free":
972
+ default:
973
+ finalPrice = new import_decimal.default(0);
974
+ break;
975
+ }
976
+ return import_decimal.default.max(0, import_decimal.default.min(originalPrice, finalPrice)).toDecimalPlaces(2, import_decimal.default.ROUND_HALF_UP);
977
+ }
978
+ resolveItemRewardLimit(detail, conditionQuantity, triggerQuantity, rewardQuantity, targetUnitCount) {
979
+ let limit = 0;
980
+ switch (detail.quantityRule) {
981
+ case "all_targets":
982
+ limit = targetUnitCount;
983
+ break;
984
+ case "max_per_order":
985
+ limit = this.normalizePositiveInteger(
986
+ detail.maxRewardQuantity ?? detail.rewardQuantity,
987
+ rewardQuantity
988
+ );
989
+ break;
990
+ case "per_condition_quantity":
991
+ default:
992
+ limit = Math.floor(conditionQuantity / triggerQuantity) * rewardQuantity;
993
+ break;
994
+ }
995
+ return Math.min(Math.max(0, limit), targetUnitCount);
996
+ }
771
997
  /**
772
998
  * 获取未参与任何促销的商品
773
999
  */
@@ -839,8 +1065,17 @@ var PromotionEvaluator = class {
839
1065
  * 检查商品是否在策略的适用范围内
840
1066
  */
841
1067
  isProductInStrategy(product, config) {
1068
+ if (this.shouldSkipStrategyForProduct(product, config)) {
1069
+ return false;
1070
+ }
842
1071
  return this.getProductMatchInfo(product, config).isMatch;
843
1072
  }
1073
+ shouldSkipStrategyForProduct(product, config) {
1074
+ return Boolean(product._promotionOnlyForItemReward) && !this.hasActionType(config, import_type.PROMOTION_ACTION_TYPES.ITEM_REWARD);
1075
+ }
1076
+ hasActionType(config, actionType) {
1077
+ return (config.actions || []).some((action) => (action == null ? void 0 : action.type) === actionType);
1078
+ }
844
1079
  /**
845
1080
  * 查找商品匹配规则
846
1081
  */
@@ -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
+ };
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  var examples_exports = {};
21
21
  __export(examples_exports, {
22
22
  BUY_X_GET_Y_FREE_STRATEGY: () => BUY_X_GET_Y_FREE_STRATEGY,
23
+ ITEM_REWARD_STRATEGY: () => ITEM_REWARD_STRATEGY,
23
24
  X_ITEMS_FOR_Y_PRICE_STRATEGY: () => X_ITEMS_FOR_Y_PRICE_STRATEGY
24
25
  });
25
26
  module.exports = __toCommonJS(examples_exports);
@@ -185,8 +186,98 @@ var BUY_X_GET_Y_FREE_STRATEGY = {
185
186
  }
186
187
  ]
187
188
  };
189
+ var ITEM_REWARD_STRATEGY = {
190
+ metadata: {
191
+ id: "STRATEGY_003",
192
+ name: {
193
+ en: "Infant ticket free with child ticket",
194
+ "zh-CN": "儿童票触发婴儿票免费",
195
+ "zh-HK": "兒童票觸發嬰兒票免費"
196
+ },
197
+ type: "promotion",
198
+ custom: {
199
+ display: {
200
+ product_card: {
201
+ text: {
202
+ en: "Infant ticket free",
203
+ "zh-CN": "婴儿票免费",
204
+ "zh-HK": "嬰兒票免費"
205
+ },
206
+ type: "tag"
207
+ }
208
+ }
209
+ }
210
+ },
211
+ conditions: {
212
+ operator: "and",
213
+ rules: [
214
+ {
215
+ type: "operator",
216
+ field: "currentDateTime",
217
+ value: "2023-01-01 00:00:00",
218
+ operator: ">="
219
+ },
220
+ {
221
+ type: "operator",
222
+ field: "currentDateTime",
223
+ value: "2030-01-01 00:00:00",
224
+ operator: "<="
225
+ },
226
+ {
227
+ type: "operator",
228
+ field: "productIdAndVariantId",
229
+ value: [
230
+ {
231
+ product_id: 1001,
232
+ // 条件商品:儿童票
233
+ product_variant_id: 0
234
+ // 0 = 匹配任意变体
235
+ }
236
+ ],
237
+ operator: "product_match"
238
+ }
239
+ ],
240
+ actionIds: ["1"]
241
+ },
242
+ actions: [
243
+ {
244
+ id: "1",
245
+ type: "ITEM_REWARD",
246
+ value: {
247
+ rewardMethod: "free",
248
+ // free | percent_off | amount_off | fixed_price
249
+ rewardValue: 0,
250
+ // free 可省略;其他 rewardMethod 使用
251
+ quantityRule: "per_condition_quantity",
252
+ // per_condition_quantity | all_targets | max_per_order
253
+ triggerQuantity: 1,
254
+ // N:每满足 N 个条件商品
255
+ rewardQuantity: 1,
256
+ // Y:奖励 Y 个目标商品
257
+ maxRewardQuantity: 1
258
+ // max_per_order 使用;本例保留为配置示意
259
+ },
260
+ valueType: "object",
261
+ target: "product",
262
+ priority: 1,
263
+ config: {
264
+ rewardTargets: [
265
+ {
266
+ product_id: 2001,
267
+ // 奖励对象:婴儿票
268
+ product_variant_id: 0
269
+ // 0 = 匹配任意变体
270
+ }
271
+ ],
272
+ targetSelection: "consumer_best"
273
+ // consumer_best = 按可节省金额从高到低选择
274
+ }
275
+ }
276
+ ]
277
+ };
188
278
  // Annotate the CommonJS export names for ESM import in node:
189
279
  0 && (module.exports = {
190
280
  BUY_X_GET_Y_FREE_STRATEGY,
281
+ ITEM_REWARD_STRATEGY,
191
282
  X_ITEMS_FOR_Y_PRICE_STRATEGY
192
283
  });
@@ -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';
@@ -0,0 +1,51 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+
29
+ // src/model/strategy/adapter/promotion/index.ts
30
+ var promotion_exports = {};
31
+ __export(promotion_exports, {
32
+ BUY_X_GET_Y_FREE_STRATEGY: () => import_examples.BUY_X_GET_Y_FREE_STRATEGY,
33
+ ITEM_REWARD_STRATEGY: () => import_examples.ITEM_REWARD_STRATEGY,
34
+ PromotionAdapter: () => import_adapter.PromotionAdapter,
35
+ PromotionEvaluator: () => import_evaluator.PromotionEvaluator,
36
+ X_ITEMS_FOR_Y_PRICE_STRATEGY: () => import_examples.X_ITEMS_FOR_Y_PRICE_STRATEGY,
37
+ default: () => import_adapter2.default
38
+ });
39
+ module.exports = __toCommonJS(promotion_exports);
40
+ var import_evaluator = require("./evaluator");
41
+ var import_adapter = require("./adapter");
42
+ var import_adapter2 = __toESM(require("./adapter"));
43
+ var import_examples = require("./examples");
44
+ // Annotate the CommonJS export names for ESM import in node:
45
+ 0 && (module.exports = {
46
+ BUY_X_GET_Y_FREE_STRATEGY,
47
+ ITEM_REWARD_STRATEGY,
48
+ PromotionAdapter,
49
+ PromotionEvaluator,
50
+ X_ITEMS_FOR_Y_PRICE_STRATEGY
51
+ });