# 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_promotion`、`vip_quanyi`、`vip_user_quanyi`)完全独立。 ### promotion_activity — 促销活动表 ```sql 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 — 促销规则明细表 ```sql 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 — 券批次表(模板) ```sql 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 — 券规则表 ```sql 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 — 用户券表(实例) ```sql 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 级分摊。由算价接口在下单时写入。 ```sql 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_order` 的 `mdSalesReduction`、`mdDiscountAmount` 等汇总字段照常写入 - `pos_order_promotion` 写入每条优惠明细(快照) 查询场景: - 订单列表:只看 `pos_order` 汇总字段 - 订单详情/退款:查 `pos_order_promotion` 获取明细 #### 设计决策 - **单表扁平设计**:一张表存储所有优惠明细,每条记录代表一种优惠,一个订单通常 3-5 条记录 - **优惠类型级别**:不做 SKU 级优惠分摊,所有优惠都是商家级别 - **快照机制**:下单时将促销名称、规则、金额拍快照存入,后续促销活动修改不影响已下单的订单 - **路径对比摘要**:`path_summary` 字段记录系统计算的路径对比结果(如满减 vs 折扣),仅存在该订单的第一条明细记录上,其余记录该字段为 null - **pos_order 现有字段保留**:不删除现有的 `mdSalesReduction`、`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 时) ``` ## Success Criteria ### Measurable Outcomes - **SC-001**: 商家能在3分钟内完成一个满减活动的创建(包括设置3个档位) - **SC-002**: 商家能在3分钟内完成一个折扣活动的创建(选择5个商品 + 逐个设折扣率) - **SC-003**: 商家能在2分钟内完成一张优惠券的创建 - **SC-004**: 用户下单时优惠计算正确,结算页实时展示优惠明细 - **SC-005**: 满100%覆盖互斥规则:满减/折扣/第二份半价三选一,新客立减可叠加 ## Assumptions - **旧代码不动**:`SalesPromotion`、`VipQuanyi`、`VipUserQuanyi` 等旧实体和旧接口全部废弃,新功能完全新建文件 - **新表前缀 `promotion_`**:与旧表(`sales_promotion`、`vip_quanyi`)完全独立 - **工作范围**:后端 API(商家端管理接口 + 用户端接口)+ 商家 PC 端前端(foodie-store) - **用户端前端不在本范围**:小程序端的领券页面、结算页展示后续单独做,但后端接口(FR-011~022)本期实施 - 促销和优惠券都是门店级别(storeId),不是跨店通用 - `pos_order_promotion`(订单优惠明细表):下单时快照记录优惠明细,单表扁平设计,优惠类型级别,不做 SKU 分摊。本期建表并实现写入逻辑 - 现有 `pos_order` 表的优惠汇总字段(`mdSalesReduction`、`mdDiscountAmount` 等)保留不动,`pos_order_promotion` 明细表作为补充 - 平台券不在本需求范围内,只做商家级促销和商家券 - SQL 变更写入 `updatesql/sql.md`,由开发者手动执行 ## 参考资源 - 美团外卖商家活动创建攻略(知乎) - 美团外卖优惠规则(官方) - 美团外卖平台市场营销规则(rules-center.meituan.com) - 优惠券系统设计(知乎) - 大厂优惠券系统设计(掘金) - Demo文件:`meituan-promotion-types-demo.html`、`merchant-promotion-ui-demo.html`、`meituan-checkout-demo.html` - [美团折扣商品调研报告](../../docs/research-meituan-discount-product.md) — 美团/饿了么折扣商品设置方式对比,逐商品设折扣 vs 折扣区分组