spec.md 29 KB

Feature Specification: 商家促销 + 优惠券系统

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

工作范围

只有两部分:

  1. 后端 API(Java/Spring Boot)— 新建 Entity、Mapper、Service、Controller,包含商家端管理接口和用户端接口(领券、优惠券查询、算价计算)
  2. 商家 PC 端前端(foodie-store Vue.js)— 促销管理页面 + 优惠券管理页面

用户端(小程序)的前端界面不在本 spec 范围内,后续单独做。但后端提供的用户端接口(FR-011~022)在本期实施。

美团商家营销活动分类

美团外卖商家端有16种营销活动,分为三类:

  • 促销类(直接影响价格):满减、折扣商品、第二份半价、新客立减、减配送费、满减运费、爆品
  • 优惠券类(用户领券后使用):店内领券、店外发券、下单返券、集点返券、收藏有礼、售卖代金券
  • 其他营销:满赠、买赠、超值换购、好友助力、美团会员红包

foodie 范围裁剪

本需求只实现以下功能:

促销(4种):满减、折扣商品(逐商品设折扣)、第二份半价、新客立减

不做:爆品、买赠、减配送费、满减运费、好友助力等

优惠券:只做"店内领券"一种发放方式

不做:店外发券、下单返券、集点返券、收藏有礼、售卖代金券

促销的作用对象

促销不是都作用于同一个价格层级:

层级 作用对象 包含的促销类型
订单级 订单总价 满减、新客立减
商品级 指定商品单价 折扣商品、第二份半价

互斥规则

参考美团做法,用户下单时系统自动计算两条路径,选最优惠的:

  • 满减、折扣商品、第二份半价 → 三选一,系统算两遍自动选最优
    • 路径A(走折扣/第二份半价):系统分别计算「折扣」和「第二份半价」各一遍,取两者中更优的作为路径A结果。折扣商品按折扣价,第二份半价商品按半价规则,非活动商品原价
    • 路径B(走满减):所有商品恢复原价,匹配满减最高可达档位,再减满减金额
  • 新客立减 → 可与以上任意一种叠加
  • 互斥在下单时判断,不限制商家创建活动

叠加计算顺序

商品原价 → 促销(满减/折扣/第二份半价,三选一)→ 商家满减券 = 实付金额
商品券单独作用在指定商品上,不与满减券叠加
一个订单最多:1个促销 + 1张满减券

同享券门槛基准:满减券的 threshold(使用门槛)基于促销后的金额判断(如促销后¥33,券要求满30,¥33≥¥30 可用)。

商品券与促销互斥规则:商品券对应的商品如果正在参加折扣或第二份半价活动,该商品券不可用于该商品(放入不可用券列表),避免折上折。

User Scenarios & Testing

User Story 1 - 商家创建促销活动 (Priority: P1)

商家在后台创建促销活动(满减/折扣/第二份半价/新客立减),活动生效后用户下单时自动享受优惠。未开始的活动可以修改和删除,参考美团商家端交互。

Why this priority: 促销是商家最核心的营销需求,没有促销就没有优惠,后续优惠券也无法叠加使用。

Independent Test: 商家创建一个满减活动(满20减5),用户下单满20元后自动减5元,订单金额正确。

操作权限矩阵(参考美团):

活动状态 编辑 删除 结束
未开始(0)
进行中(1)
已结束(2)

Acceptance Scenarios:

  1. Given 商家未创建任何促销活动, When 商家创建满减活动(满20减5 / 满40减12), Then 活动列表显示进行中,用户端店铺显示满减标签
  2. Given 商家已创建折扣活动(宫保鸡丁7折、麻婆豆腐8折), When 用户下单宫保鸡丁(¥20) + 麻婆豆腐(¥18), Then 折扣价 ¥14 + ¥14.4 = ¥28.4
  3. Given 商家同时有满减(满40减12)和折扣(宫保鸡丁7折¥14), When 用户下单宫保鸡丁(¥20) + 鸡腿饭(¥25) = ¥45, Then 系统算两遍:走折扣¥39 vs 走满减¥33,自动选满减
  4. Given 商家创建新客立减3元 + 满减(满40减12), When 新用户下单¥45, Then 先减满减12 = ¥33,再减新客立减3 = ¥30
  5. Given 折扣活动采用逐商品设折扣, When 商家选择可乐设5折、宫保鸡丁设7折、麻婆豆腐设8折, Then 可乐¥6×0.5=¥3,宫保鸡丁¥20×0.7=¥14,麻婆豆腐¥18×0.8=¥14.4

User Story 2 - 商家创建优惠券 (Priority: P2)

商家创建优惠券(满减券/商品券/免配送费券),设置库存和有效期。用户进店时看到领券区,领取后下单使用。未开始和进行中的优惠券可以修改,未开始的可以删除。

Why this priority: 优惠券在促销基础上进一步促进复购和转化,但依赖促销系统先完成。

Independent Test: 商家创建"满30减5"优惠券100张,用户进店领取,下单满30元时选择使用,实付减5元。

操作权限矩阵(参考美团):

券状态 编辑 删除 下架
未开始(0)
进行中(1)
已结束(2)
已下架(3)

Acceptance Scenarios:

  1. Given 商家创建满减券(同享券,满30减5,100张), When 用户进店看到领券区并领取, Then 用户"我的优惠券"列表显示该券,状态为未使用
  2. Given 用户已领取满减券(满30减5)且商家有满减活动(满40减12), When 用户下单¥45, Then 先减满减12=¥33,再减券5=¥28(同享券可叠加)
  3. Given 用户已领取互斥券(满30减5), When 用户下单且订单有满减活动, Then 互斥券不可与满减叠加,用户只能二选一
  4. Given 商家创建商品折扣券(宫保鸡丁5折), When 用户下单宫保鸡丁(¥20)并使用该券, Then 宫保鸡丁按¥10计算
  5. Given 优惠券库存为100张,已领取100张, When 新用户进店, Then 领券区显示"已领完"

User Story 3 - 用户下单时优惠计算和展示 (Priority: P3)

用户在结算页看到优惠明细,系统自动选择最优方案,用户可手动切换。

Why this priority: 结算页的优惠展示和计算是用户最终看到的结果,需要在促销和优惠券都完成后实现。

Independent Test: 用户选好商品进入结算页,页面展示优惠明细(满减/折扣自动选最优 + 可用券列表),选择券后实付金额实时更新。

Acceptance Scenarios:

  1. Given 商家有满减(满40减12)和折扣(宫保鸡丁7折), When 用户进入结算页, Then 系统展示两条路径对比,默认选最优
  2. Given 用户有3张可用券, When 用户点击优惠券行, Then 弹出券列表,可用券和不可用券分Tab展示,选中券后金额重算
  3. Given 用户选了互斥券, When 订单同时有满减活动, Then 满减自动取消,金额重新计算

Edge Cases

  • 商家创建了满减活动后又创建折扣活动,两个同时进行中,用户下单时怎么处理?→ 系统自动算两遍选最优,不限制商家创建
  • 用户领了券但下单时未达到门槛(券要求满30但订单只有25)→ 券在"不可用"Tab显示,提示"未达到¥30"
  • 用户领券后商家修改/下架了该券批次 → 已领取的券仍可使用(按领取时的规则),但不能再领取新券
  • 满减活动设了3个档位,订单金额正好在两个档位之间 → 命中低档位(如¥39命中满20减5而非满40减12)
  • 第二份半价商品用户只买了1件 → 不享受优惠,按原价
  • 第二份半价商品用户买了多件 → 每2件为一组,每组第2件半价(如买3件:第1、3件全价,第2件半价;买4件:第1、3件全价,第2、4件半价)
  • 商家结束促销活动时,正在进行中的订单 → 不影响已下单的订单,只影响新订单
  • 优惠券过期 → 过期券自动标记为已过期,不可使用
  • 折扣活动中同一商品重复添加 → 前端限制同一商品只能添加一次,重复选择时提示并忽略
  • 多用户同时领取同一优惠券(库存仅剩1张)→ 数据库原子扣减 WHERE remain_count > 0,只有一人成功,其余返回"已领完"

算价精度规则

  • 所有金额计算使用 BigDecimal,中间过程不截断,最终结果 ROUND_HALF_UP(四舍五入)保留2位小数
  • 最终实付金额最低为 ¥0.01,不出现零或负数
  • 两条路径金额完全相等时,默认选路径B(满减路径),因满减对商家毛利更优
  • 无促销活动时两条路径结果相同,直接返回原价
  • 优惠券有效期计算:expire_time = 领取日期 + valid_days 天后的 23:59:59(如6月1日领券,valid_days=7,则6月8日 23:59:59 过期)
  • 用户券过期判断:查询时实时判断 expire_time < NOW() 则标记为已过期(status→2),不依赖定时任务
  • 同一门店同一促销类型只允许一个进行中的活动,创建时校验
  • 结束活动不可逆,已结束活动不可重新激活
  • 商品价格由后端根据 productId 从数据库查询,不信任前端传入的价格

Clarifications

Session 2026-06-01

  • Q: 折扣活动中同一商品重复添加怎么处理? → A: 前端限制同一商品只能添加一次,重复选择时提示并忽略(改为逐商品设折扣,不再有折扣区分组概念)
  • Q: 折扣商品采用「折扣区分组」还是「逐商品设折扣」? → A: 改为逐商品设折扣(参考美团),每个商品独立设置折扣率,不再使用折扣区/折扣档位分组。调研记录见 docs/research-meituan-discount-product.md
  • Q: 新客立减的"新客"定义? → A: 该门店首次下单的用户(该门店无已完成订单记录)
  • Q: 算价接口前端传入价格有篡改风险? → A: 算价接口只传productId+quantity,后端根据productId查实际价格
  • Q: 第二份半价属于路径A还是独立路径? → A: 作为路径A的变体,系统分别算折扣和第二份半价,取更优作为路径A结果
  • Q: 同享券门槛基于促销前还是促销后金额? → A: 基于促销后金额
  • Q: 商品在折扣活动中,商品券能否用于该商品? → A: 不可用,放入不可用券列表
  • Q: 金额精度和舍入规则? → A: BigDecimal计算,ROUND_HALF_UP保留2位小数,最低¥0.01
  • Q: 免配送费券如何获取配送费金额? → A: 算价接口新增可选参数deliveryFee,前端传入当前配送费,后端计算min(coupon.amount, deliveryFee)作为减免额
  • Q: 删除促销活动是物理删除还是软删除? → A: 物理删除(DELETE),因为未开始活动不会有订单引用,安全且简单
  • Q: 编辑进行中的优惠券时,已领取用户的券规则是否跟随变更? → A: 只影响新领取的券,已领取的券保持原规则不变(快照机制)
  • Q: 免配送费券在叠加计算顺序中的位置? → A: 在促销+满减券之后独立叠加:实付 = 促销后金额 - 券减免 - min(配送费券.amount, deliveryFee)
  • Q: 优惠券删除条件? → A: 仅当 status=0 且 receivedCount=0 时允许删除(双重校验,防御性编程)

Session 2026-06-02

  • Q: 折扣率允许范围和 UI 控件? → A: 1折~9.9折(10%~99%),步长 0.1,用 -/+ 按钮或直接输入。超出范围前端阻止
  • Q: 用户领券并发超发怎么防止? → A: 数据库原子扣减:UPDATE remain_count = remain_count - 1, received_count = received_count + 1 WHERE id = #{id} AND remain_count > 0,affected rows=0 则返回"已领完"

Requirements

Functional Requirements

促销活动管理(商家端)

  • FR-001: 商家 MUST 能创建满减活动,支持设置多个档位(如满20减5 / 满40减12 / 满60减20)
  • FR-002: 商家 MUST 能创建折扣商品活动,采用逐商品设折扣方式(参考美团):从商品列表勾选参与的商品,每个商品单独设置折扣率。一个活动内不同商品可以有不同折扣率(如宫保鸡丁7折、麻婆豆腐8折、可乐5折)。不使用「折扣区/折扣档位」分组概念。折扣率范围:10%~99%(即1折~9.9折),步长 0.1,前端用 -/+ 按钮或直接输入
  • FR-003: 商家 MUST 能创建第二份半价活动,从商品列表勾选参加的商品
  • FR-004: 商家 MUST 能创建新客立减活动,设置固定减免金额。「新客」定义为:该门店首次下单的用户(该门店无已完成订单记录)
  • FR-005: 商家 MUST 能查看活动列表(进行中/未开始/已结束),能结束进行中的活动
  • FR-005a: 商家 MUST 能修改未开始的活动(编辑活动名称、时间、规则),复用创建对话框回填数据
  • FR-005b: 商家 MUST 能删除未开始的活动(物理删除活动及关联规则),删除前需二次确认
  • FR-006: 系统 MUST 展示互斥规则提示:满减、折扣、第二份半价三选一,新客立减可叠加

优惠券管理(商家端)

  • FR-007: 商家 MUST 能创建满减券,设置同享/互斥属性、使用门槛、减免金额、发放总量、有效天数
  • FR-008: 商家 MUST 能创建商品券(折扣券/抵用券),关联指定商品,设置折扣率。商品券优惠作用于该商品的每一件(如买3个宫保鸡丁,5折券则全部按5折计算)
  • FR-008a: 商家 MUST 能创建免配送费券,设置使用门槛(满X元可用)、减免金额(固定减免配送费Y元,减免金额≥实际配送费时等同于免配送费)、同享/互斥属性、发放总量、有效天数。算价接口新增可选参数 deliveryFee,前端传入当前配送费,后端计算 min(coupon.amount, deliveryFee) 作为实际减免额
  • FR-009: 商家 MUST 能查看优惠券列表,展示领取/使用情况、库存,能下架进行中的优惠券
  • FR-009a: 商家 MUST 能修改未开始/进行中的优惠券(编辑名称、时间、规则等),复用创建对话框回填数据
  • FR-009b: 商家 MUST 能删除未开始的优惠券(物理删除券批次+规则),仅当 status=0 且 receivedCount=0 时允许删除,删除前需二次确认
  • FR-010: 优惠券发放方式只有一种:店内领券(用户进店时在领券区领取)

用户端接口(后端API本期实施,前端界面[延后]由小程序实施)

  • FR-011: 用户 MUST 能在店铺首页看到领券区 → 后端提供「查询门店可领优惠券列表」接口
  • FR-012: 用户 MUST 能领取优惠券,每人每批次限领1张 → 后端提供「领取优惠券」接口
  • FR-013: 用户 MUST 能在"我的优惠券"中查看已领取的券 → 后端提供「查询用户优惠券列表」接口
  • FR-014: 用户 MUST 能在结算页看到促销优惠明细 → 后端提供「计算订单优惠」接口
  • FR-015: 用户 MUST 能在结算页选择使用/不使用优惠券 → 由算价接口参数控制
  • FR-016: 系统 MUST 在结算页自动计算满减和折扣两条路径,默认选最优 → 算价接口返回两条路径对比

算价逻辑(后端Service本期实施,前端展示[延后]由小程序实施)

  • FR-017: 下单时系统 MUST 同时计算两条路径:
    • 路径A(走折扣):折扣商品按折扣价,非折扣商品原价,再扣券
    • 路径B(走满减):所有商品恢复原价,扣满减,再扣券
  • FR-018: 系统 MUST 默认选择实付金额更低的路径
  • FR-019: 用户 MUST 能在结算页手动切换路径 → 算价接口支持指定路径参数
  • FR-020: 同享券 MUST 能与满减/折扣叠加使用。满减券的 threshold 基于促销后金额判断
  • FR-021: 互斥券 MUST 不能与满减/折扣/第二份半价叠加
  • FR-022: 一个订单最多使用 1个促销 + 1张满减券。商品券对应的商品如正在参加折扣/第二份半价活动,该券不可用于该商品

Key Entities

  • 促销活动 (promotion_activity): 商家创建的促销规则,包含类型(满减/折扣/第二份半价/新客立减)、状态、时间范围
  • 促销规则 (promotion_activity_rule): 促销活动的具体规则数据,一个活动对应多条规则(满减档位/折扣商品/第二份半价商品)
  • 券批次 (promotion_coupon_batch): 优惠券模板,包含券名称、类型、库存、有效期
  • 券规则 (promotion_coupon_rule): 优惠券的使用规则,包含门槛、金额、折扣率、同享/互斥属性
  • 用户券 (promotion_user_coupon): 用户领取的券实例,绑定用户和门店,记录状态和使用情况

Data Model

所有新建表使用 promotion_ 前缀,与旧表(sales_promotionvip_quanyivip_user_quanyi)完全独立。

promotion_activity — 促销活动表

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 '促销活动表';

promotion_activity_rule — 促销规则明细表

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 '促销规则明细';

promotion_coupon_batch — 券批次表(模板)

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 '券批次表(模板)';

promotion_coupon_rule — 券规则表

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 '券规则';

promotion_user_coupon — 用户券表(实例)

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 '用户领券记录';

pos_order_promotion — 订单优惠明细表

下单时快照记录每个订单命中的优惠信息,优惠类型级别(每条记录 = 一种优惠),不做 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 现有字段的关系

下单时双写:

  • pos_ordermdSalesReductionmdDiscountAmount 等汇总字段照常写入
  • pos_order_promotion 写入每条优惠明细(快照)

查询场景:

  • 订单列表:只看 pos_order 汇总字段
  • 订单详情/退款:查 pos_order_promotion 获取明细

设计决策

  • 单表扁平设计:一张表存储所有优惠明细,每条记录代表一种优惠,一个订单通常 3-5 条记录
  • 优惠类型级别:不做 SKU 级优惠分摊,所有优惠都是商家级别
  • 快照机制:下单时将促销名称、规则、金额拍快照存入,后续促销活动修改不影响已下单的订单
  • 路径对比摘要path_summary 字段记录系统计算的路径对比结果(如满减 vs 折扣),仅存在该订单的第一条明细记录上,其余记录该字段为 null
  • pos_order 现有字段保留:不删除现有的 mdSalesReductionmdDiscountAmount 等字段,新表明细作为补充信息

表关系

促销模块:
  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 时)

Success Criteria

Measurable Outcomes

  • SC-001: 商家能在3分钟内完成一个满减活动的创建(包括设置3个档位)
  • SC-002: 商家能在3分钟内完成一个折扣活动的创建(选择5个商品 + 逐个设折扣率)
  • SC-003: 商家能在2分钟内完成一张优惠券的创建
  • SC-004: 用户下单时优惠计算正确,结算页实时展示优惠明细
  • SC-005: 满100%覆盖互斥规则:满减/折扣/第二份半价三选一,新客立减可叠加

Assumptions

  • 旧代码不动SalesPromotionVipQuanyiVipUserQuanyi 等旧实体和旧接口全部废弃,新功能完全新建文件
  • 新表前缀 promotion_:与旧表(sales_promotionvip_quanyi)完全独立
  • 工作范围:后端 API(商家端管理接口 + 用户端接口)+ 商家 PC 端前端(foodie-store)
  • 用户端前端不在本范围:小程序端的领券页面、结算页展示后续单独做,但后端接口(FR-011~022)本期实施
  • 促销和优惠券都是门店级别(storeId),不是跨店通用
  • pos_order_promotion(订单优惠明细表):下单时快照记录优惠明细,单表扁平设计,优惠类型级别,不做 SKU 分摊。本期建表并实现写入逻辑
  • 现有 pos_order 表的优惠汇总字段(mdSalesReductionmdDiscountAmount 等)保留不动,pos_order_promotion 明细表作为补充
  • 平台券不在本需求范围内,只做商家级促销和商家券
  • SQL 变更写入 updatesql/sql.md,由开发者手动执行

参考资源

  • 美团外卖商家活动创建攻略(知乎)
  • 美团外卖优惠规则(官方)
  • 美团外卖平台市场营销规则(rules-center.meituan.com)
  • 优惠券系统设计(知乎)
  • 大厂优惠券系统设计(掘金)
  • Demo文件:meituan-promotion-types-demo.htmlmerchant-promotion-ui-demo.htmlmeituan-checkout-demo.html
  • 美团折扣商品调研报告 — 美团/饿了么折扣商品设置方式对比,逐商品设折扣 vs 折扣区分组