Feature Branch: 008-promotion-coupon
Created: 2026-05-29
Status: Draft
Input: 参考美团外卖商家端营销活动机制,为 foodie 系统实现促销和优惠券功能
foodie 系统目前缺少商家端的营销工具。商家无法设置满减、折扣等促销活动,也无法发放优惠券。需要参考美团外卖的机制,实现一套适合 foodie 的促销 + 优惠券系统。
系统中已有旧的促销/优惠券代码,全部废弃不使用,不修改:
| 旧代码 | 位置 | 说明 |
|---|---|---|
SalesPromotion.java |
ruoyi-system/domain | 旧促销活动实体,表 sales_promotion |
VipQuanyi.java |
ruoyi-system/domain | 旧优惠券模板,表 vip_quanyi |
VipUserQuanyi.java |
ruoyi-system/domain | 旧用户券,表 vip_user_quanyi |
SalesPromotionController |
ruoyi-admin/app/promotion | 平台管理端促销接口 |
ShSalesPromotionController |
ruoyi-admin/app/mendian | 商家端促销接口 |
VipUserQuanyiController |
ruoyi-admin/app/user | 用户端优惠券接口 |
原则:所有新功能新建文件、新表、新接口,不动旧代码。
所有新建数据库表使用 promotion_ 前缀,与旧表区分:
| 新表名 | 说明 |
|---|---|
promotion_activity |
促销活动表(替代旧 sales_promotion) |
promotion_activity_rule |
促销规则明细表 |
promotion_coupon_batch |
券批次表(替代旧 vip_quanyi) |
promotion_coupon_rule |
券规则表 |
promotion_user_coupon |
用户券表(替代旧 vip_user_quanyi) |
只有两部分:
用户端(小程序)的前端界面不在本 spec 范围内,后续单独做。但后端提供的用户端接口(FR-011~022)在本期实施。
美团外卖商家端有16种营销活动,分为三类:
本需求只实现以下功能:
促销(4种):满减、折扣商品(逐商品设折扣)、第二份半价、新客立减
不做:爆品、买赠、减配送费、满减运费、好友助力等
优惠券:只做"店内领券"一种发放方式
不做:店外发券、下单返券、集点返券、收藏有礼、售卖代金券
促销不是都作用于同一个价格层级:
| 层级 | 作用对象 | 包含的促销类型 |
|---|---|---|
| 订单级 | 订单总价 | 满减、新客立减 |
| 商品级 | 指定商品单价 | 折扣商品、第二份半价 |
参考美团做法,用户下单时系统自动计算两条路径,选最优惠的:
商品原价 → 促销(满减/折扣/第二份半价,三选一)→ 商家满减券 = 实付金额
商品券单独作用在指定商品上,不与满减券叠加
一个订单最多:1个促销 + 1张满减券
同享券门槛基准:满减券的 threshold(使用门槛)基于促销后的金额判断(如促销后¥33,券要求满30,¥33≥¥30 可用)。
商品券与促销互斥规则:商品券对应的商品如果正在参加折扣或第二份半价活动,该商品券不可用于该商品(放入不可用券列表),避免折上折。
商家在后台创建促销活动(满减/折扣/第二份半价/新客立减),活动生效后用户下单时自动享受优惠。未开始的活动可以修改和删除,参考美团商家端交互。
Why this priority: 促销是商家最核心的营销需求,没有促销就没有优惠,后续优惠券也无法叠加使用。
Independent Test: 商家创建一个满减活动(满20减5),用户下单满20元后自动减5元,订单金额正确。
操作权限矩阵(参考美团):
| 活动状态 | 编辑 | 删除 | 结束 |
|---|---|---|---|
| 未开始(0) | ✅ | ✅ | ❌ |
| 进行中(1) | ❌ | ❌ | ✅ |
| 已结束(2) | ❌ | ❌ | ❌ |
Acceptance Scenarios:
商家创建优惠券(满减券/商品券/免配送费券),设置库存和有效期。用户进店时看到领券区,领取后下单使用。未开始和进行中的优惠券可以修改,未开始的可以删除。
Why this priority: 优惠券在促销基础上进一步促进复购和转化,但依赖促销系统先完成。
Independent Test: 商家创建"满30减5"优惠券100张,用户进店领取,下单满30元时选择使用,实付减5元。
操作权限矩阵(参考美团):
| 券状态 | 编辑 | 删除 | 下架 |
|---|---|---|---|
| 未开始(0) | ✅ | ✅ | ❌ |
| 进行中(1) | ✅ | ❌ | ✅ |
| 已结束(2) | ❌ | ❌ | ❌ |
| 已下架(3) | ❌ | ❌ | ❌ |
Acceptance Scenarios:
用户在结算页看到优惠明细,系统自动选择最优方案,用户可手动切换。
Why this priority: 结算页的优惠展示和计算是用户最终看到的结果,需要在促销和优惠券都完成后实现。
Independent Test: 用户选好商品进入结算页,页面展示优惠明细(满减/折扣自动选最优 + 可用券列表),选择券后实付金额实时更新。
Acceptance Scenarios:
WHERE remain_count > 0,只有一人成功,其余返回"已领完"BigDecimal,中间过程不截断,最终结果 ROUND_HALF_UP(四舍五入)保留2位小数expire_time = 领取日期 + valid_days 天后的 23:59:59(如6月1日领券,valid_days=7,则6月8日 23:59:59 过期)expire_time < NOW() 则标记为已过期(status→2),不依赖定时任务docs/research-meituan-discount-product.mdUPDATE remain_count = remain_count - 1, received_count = received_count + 1 WHERE id = #{id} AND remain_count > 0,affected rows=0 则返回"已领完"deliveryFee,前端传入当前配送费,后端计算 min(coupon.amount, deliveryFee) 作为实际减免额所有新建表使用 promotion_ 前缀,与旧表(sales_promotion、vip_quanyi、vip_user_quanyi)完全独立。
CREATE TABLE promotion_activity (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
store_id BIGINT NOT NULL COMMENT '门店ID',
type TINYINT NOT NULL COMMENT '类型: 1=满减 2=折扣 3=第二份半价 4=新客立减',
name VARCHAR(100) NOT NULL COMMENT '活动名称',
status TINYINT DEFAULT 0 COMMENT '0=未开始 1=进行中 2=已结束',
start_time DATETIME NOT NULL COMMENT '开始时间',
end_time DATETIME NOT NULL COMMENT '结束时间',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_store_type (store_id, type)
) COMMENT '促销活动表';
CREATE TABLE promotion_activity_rule (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
activity_id BIGINT NOT NULL COMMENT '关联促销活动',
product_id BIGINT DEFAULT NULL COMMENT '商品ID(折扣/第二份半价用)',
threshold DECIMAL(10,2) DEFAULT NULL COMMENT '满减门槛(满X元)',
reduce_amount DECIMAL(10,2) DEFAULT NULL COMMENT '减免金额(满减/新客立减)',
discount_rate DECIMAL(3,2) DEFAULT NULL COMMENT '折扣率(0.70=7折 / 0.50=半价)',
min_quantity INT DEFAULT NULL COMMENT '最低数量(第二份半价=2)',
INDEX idx_activity (activity_id)
) COMMENT '促销规则明细';
CREATE TABLE promotion_coupon_batch (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
store_id BIGINT NOT NULL COMMENT '门店ID',
name VARCHAR(100) NOT NULL COMMENT '券名称',
coupon_type TINYINT NOT NULL COMMENT '1=满减券 2=商品券 3=免配送费券',
total_count INT NOT NULL COMMENT '发放总量',
remain_count INT NOT NULL COMMENT '剩余数量',
received_count INT DEFAULT 0 COMMENT '已领取数量',
status TINYINT DEFAULT 0 COMMENT '0=未开始 1=进行中 2=已结束 3=已下架',
start_time DATETIME NOT NULL COMMENT '领取开始时间',
end_time DATETIME NOT NULL COMMENT '领取结束时间',
valid_days INT NOT NULL COMMENT '领取后有效天数',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_store_status (store_id, status)
) COMMENT '券批次表(模板)';
CREATE TABLE promotion_coupon_rule (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
batch_id BIGINT NOT NULL COMMENT '关联券批次',
product_id BIGINT DEFAULT NULL COMMENT '商品ID(商品券用,满减券=NULL)',
is_mutex TINYINT DEFAULT 0 COMMENT '0=同享券 1=互斥券(仅满减券使用;商品券此字段为NULL,互斥逻辑由促销活动判断)',
threshold DECIMAL(10,2) DEFAULT NULL COMMENT '使用门槛:满减券=促销后订单金额门槛,商品券=该商品小计金额门槛(单价×数量)',
amount DECIMAL(10,2) DEFAULT NULL COMMENT '减免金额(满减券/抵用券)',
discount_rate DECIMAL(3,2) DEFAULT NULL COMMENT '折扣率(商品折扣券用)',
INDEX idx_batch (batch_id)
) COMMENT '券规则';
CREATE TABLE promotion_user_coupon (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL COMMENT '用户ID',
batch_id BIGINT NOT NULL COMMENT '券批次ID',
store_id BIGINT NOT NULL COMMENT '门店ID',
status TINYINT DEFAULT 0 COMMENT '0=未使用 1=已使用 2=已过期 3=冻结',
order_id BIGINT DEFAULT NULL COMMENT '使用的订单ID',
receive_time DATETIME NOT NULL COMMENT '领取时间',
use_time DATETIME DEFAULT NULL COMMENT '使用时间',
expire_time DATETIME NOT NULL COMMENT '过期时间',
INDEX idx_user_status (user_id, status),
INDEX idx_store (store_id)
) COMMENT '用户领券记录';
下单时快照记录每个订单命中的优惠信息,优惠类型级别(每条记录 = 一种优惠),不做 SKU 级分摊。由算价接口在下单时写入。
CREATE TABLE pos_order_promotion (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
order_id BIGINT NOT NULL COMMENT '关联订单ID',
promo_type TINYINT NOT NULL COMMENT '优惠类型: 1=促销活动 2=商家优惠券',
promo_sub_type TINYINT DEFAULT NULL COMMENT '促销子类型: 1=满减 2=折扣 3=第二份半价 4=新客立减 (promo_type=1时有效)',
promo_id BIGINT DEFAULT NULL COMMENT '促销活动ID(promo_type=1) 或 券批次ID(promo_type=2)',
user_coupon_id BIGINT DEFAULT NULL COMMENT '用户券ID (仅优惠券 promo_type=2 时有值, 关联 promotion_user_coupon.id)',
promo_name VARCHAR(200) NOT NULL COMMENT '快照名称: 如"午市满减(满40减12)"',
promo_detail VARCHAR(500) DEFAULT NULL COMMENT '快照详情JSON: 如{"threshold":40,"reduce":12}',
reduce_amount DECIMAL(10,2) NOT NULL COMMENT '减免金额',
path_summary VARCHAR(500) DEFAULT NULL COMMENT '路径对比摘要, 仅第一条记录有值, 如"满减路径¥33 vs 折扣路径¥39, 选择满减"',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_order (order_id)
) COMMENT '订单优惠明细';
| 字段 | 作用 | 示例 |
|---|---|---|
promo_type |
区分是促销还是优惠券 | 1=促销, 2=优惠券 |
promo_sub_type |
促销的具体子类型 | 1=满减, 2=折扣, 3=第二份半价, 4=新客立减;优惠券时为 null |
promo_id |
关联原始活动或券批次 | promo_type=1 时为 promotion_activity.id;promo_type=2 时为 promotion_coupon_batch.id |
user_coupon_id |
关联用户领取的券实例 | promo_type=2 时为 promotion_user_coupon.id;促销时为 null |
promo_name |
下单时的快照名称 | "午市满减(满40减12)" |
promo_detail |
规则快照 JSON | {"type":"manjian","threshold":40,"reduce":12} |
reduce_amount |
实际减免金额 | 12.00 |
path_summary |
路径对比摘要 | "满减路径¥33 vs 折扣路径¥39, 选择满减",仅第一条记录有值 |
订单:宫保鸡丁¥20 + 鸡腿饭¥25 = ¥45,商家有满减(满40减12)和折扣(宫保鸡丁7折),用户有满30减5同享券,新用户
| promo_type | promo_sub_type | promo_name | reduce_amount | path_summary |
|---|---|---|---|---|
| 1(促销) | 1(满减) | 午市满减(满40减12) | 12.00 | 满减路径¥33 vs 折扣路径¥39, 选择满减 |
| 2(优惠券) | null | 满30减5(同享券) | 5.00 | null |
| 1(促销) | 4(新客立减) | 新客立减 | 3.00 | null |
下单时双写:
pos_order 的 mdSalesReduction、mdDiscountAmount 等汇总字段照常写入pos_order_promotion 写入每条优惠明细(快照)查询场景:
pos_order 汇总字段pos_order_promotion 获取明细path_summary 字段记录系统计算的路径对比结果(如满减 vs 折扣),仅存在该订单的第一条明细记录上,其余记录该字段为 nullmdSalesReduction、mdDiscountAmount 等字段,新表明细作为补充信息促销模块:
promotion_activity 1 → N promotion_activity_rule
优惠券模块:
promotion_coupon_batch 1 → 1 promotion_coupon_rule
promotion_coupon_batch 1 → N promotion_user_coupon
订单优惠:
pos_order 1 → N pos_order_promotion
pos_order_promotion N → 1 promotion_activity (promo_type=1 时)
pos_order_promotion N → 1 promotion_coupon_batch (promo_type=2 时)
pos_order_promotion N → 1 promotion_user_coupon (promo_type=2 时)
SalesPromotion、VipQuanyi、VipUserQuanyi 等旧实体和旧接口全部废弃,新功能完全新建文件promotion_:与旧表(sales_promotion、vip_quanyi)完全独立pos_order_promotion(订单优惠明细表):下单时快照记录优惠明细,单表扁平设计,优惠类型级别,不做 SKU 分摊。本期建表并实现写入逻辑pos_order 表的优惠汇总字段(mdSalesReduction、mdDiscountAmount 等)保留不动,pos_order_promotion 明细表作为补充updatesql/sql.md,由开发者手动执行meituan-promotion-types-demo.html、merchant-promotion-ui-demo.html、meituan-checkout-demo.html