标准满减类型参考(o_promotion)

o_promotion 七种 type 与 rule_param 样例。流水线见 promotion-discount-checkout.md §5

与实现冲突时以 PromotionHandlerServicePromotionService::discountPrice() 为准。

目录


1. 七种 type

type常量含义计算入口
1PROMOTION_FULL_AMOUNT_MINUS_AMOUNT满额减元calGeneralDiscount
2PROMOTION_FULL_NUM_MINUS_OFF满件打折calGeneralDiscount
3PROMOTION_FULL_NUM_MINUS_AMOUNT满件减元calGeneralDiscount
4PROMOTION_FULL_AMOUNT_MINUS_OFF满额打折calGeneralDiscount
5PROMOTION_SINCE_COUNT_DISCOUNT第 X 件 Y 折calSinceDiscount
6PROMOTION_FIXED_PRICE满件一口价calFixedPriceDiscount
7PROMOTION_BUY_X_FREE_Y满 X 免 Y 件calBuyXFeeY

PromotionModel 部分常量旁注释与业务不一致,以 Service 分支为准。


2. rule_param 通用结构

Type 1–4 常见骨架:

{
  "type": 1,
  "allocation_limit": 0,
  "rule": [
    { "ge": 100, "value": 10 },
    { "ge": 200, "value": 25 }
  ]
}
字段说明
type与活动 type 一致(1–7)
allocation_limit999ALLOCATION_LIMIT)= 上不封顶,按 floor(总额或件数/ge) 倍数计
rule[].ge门槛:满额类比 金额,满件类比 件数
rule[].valuetype1/3:减 ;type2/4:折扣 百分比(如 20 表示 20% off)

多档规则:取 满足条件的最大 ge 对应 value(非最优组合)。


3. 各 type 样例 JSON

Type 1 — 满额减元

满 100 减 10;满 200 减 25(取最高档):

{
  "type": 1,
  "allocation_limit": 0,
  "rule": [{ "ge": 100, "value": 10 }, { "ge": 200, "value": 25 }]
}

上不封顶示例(每满 100 减 10):

{
  "type": 1,
  "allocation_limit": 999,
  "rule": [{ "ge": 100, "value": 10 }]
}

Type 2 — 满件打折

满 3 件打 8 折(value=20 表示减 20%):

{
  "type": 2,
  "allocation_limit": 0,
  "rule": [{ "ge": 3, "value": 20 }]
}

Type 3 — 满件减元

满 2 件减 15 元:

{
  "type": 3,
  "allocation_limit": 0,
  "rule": [{ "ge": 2, "value": 15 }]
}

Type 4 — 满额打折

满 200 打 9 折(value=10):

{
  "type": 4,
  "allocation_limit": 0,
  "rule": [{ "ge": 200, "value": 10 }]
}

Type 5 — 第 X 件 Y 折

结构较复杂,含 top_promotionpromotion_xy_product_price_sort 等;见 PromotionHandlerService::calSinceDiscount

示意(第 2 件 5 折):

{
  "type": 5,
  "top_promotion": 0,
  "rule": [{ "ge": 2, "value": 50 }]
}

Type 6 — 满件一口价

满 3 件共 99 元;见 calFixedPriceDiscount

Type 7 — 满 X 免 Y 件

满 3 免 1(低价优先);见 calBuyXFeeY


4. 范围与多活动规则

商品范围优先级product_range_sort 升序):指定商品 < 专辑 < 全场。

getCartPromotion 三步

  1. 剔除 mutexPromotionVariantUniqKey 捆绑 SKU
  2. 读缓存活动,构建 not_ranges(先到先得占用 SKU/专辑)
  3. 按 type 调 cal*discount=0 也返回(前端需过滤)

多活动:非取最优,先到先得 + discount 相加

缓存cartPromotion() / cartPromotionRange($id) TTL 7 天;活动 CRUD 时 expire。


5. 与 diy_offer / 券的关系

关系说明
vs bundlesale捆绑 SKU 整行 不参与 满减
vs minmaxofferminmax 不挡满减;挡其它 diy_offer
vs 券券在满减 之后;替换型券可 清零 满减(checkCouponUseWithPromotionStatus
COD可能 走替换型券逻辑 → 双计风险

5.5 o_promotion 与 o_diy_offer 改价对比

⚡⚡⚡ o_promotion 不改 cart 行价,优惠额汇总后创单时分摊;o_diy_offer type=promotion 直接改 cart 行价。

体系是否改 cart 行价优惠额存在哪里
o_promotion(满减 type1–7)❌ 否,行价不变cart.promotion_price(负值汇总)→ 创单 saveProducts 时分摊到 order_product.discount_price
o_diy_offer type=promotion(限时促销)✅ 是,直接改行 price/final_line_pricediy_offers.discount=0(InlinePrice);行 price 即已是折后价

o_promotion 典型流程

购物车内行价(不变)
    ↓ getCartPromotion
cart.promotion_price +=满减优惠额(负值)
    ↓ 创单 saveProducts
OrderPriceService::calOrderProductDiscountPrice → 分摊到 order_product.discount_price

o_diy_offer type=promotion 典型流程

购物车内行价(原价)
    ↓ calDiscount → changeCartPrice
行 price = 原价 × (1 - value/100)   ← 直接改
或行 price = max(原价 - value, 0)   ← reduction
或行 price = value ← definite_price
diy_offers.discount = 0

bundlesale / skubundlesale(AccruedDiscount):改行价 + diy_offers[].discount 记优惠额(负值)→ 进 cart.promotion_price 汇总。

gift(LineRebuild):不改行价,而是增删行,赠品行 final_price=0


6. 订单字段

字段来源
current_promotion_pricecart.promotion_price(含满减 + diy_offers.discount)
o_order.promotion_id订单头 常为空/0;满减 ID 多在 order_product.promotion_id
promotion_name行上汇总或订单头拼接

分摊:PromotionHandlerService::attachPromotionToCartOrderPriceService 满减段。


7. 单测要点

场景要点
多档 rule取 max(ge),非最优
allocation_limit=999倍数 floor
两活动同 SKUnot_ranges 先到先得
捆绑 + 满减mutex 行不应进 promotion 计算
discount=0getCartPromotion 仍返回,需过滤
替换型券promotion 清零、订单 current_promotion_price reconcile

8. 代码索引

模块路径
流水线common/services/PromotionHandlerService.php
算法common/services/PromotionService.phpdiscountPrice
挂载行PromotionHandlerService::attachPromotionToCart
Cart 入口CartService::getList 548–550 行