Input: Design documents from /specs/008-promotion-coupon/
Prerequisites: plan.md (required), spec.md (required for user stories)
[ID] [P?] [Story] DescriptionPurpose: 创建数据库表,为后续所有任务提供基础
foodie_server/updatesql/sql.md(promotion_activity, promotion_activity_rule, promotion_coupon_batch, promotion_coupon_rule, promotion_user_coupon, pos_order_promotion),DDL 来自 spec.md Data Model 章节Purpose: 后端核心数据层,所有 Controller 和前端页面依赖此阶段完成
ruoyi-system/src/main/java/com/ruoyi/system/domain/PromotionActivity.java — 字段: id, storeId, type, name, status, startTime, endTime, createTime, updateTime;非持久化: List rules, boolean editable。使用 @Data @TableName @TableId(type=IdType.AUTO) @JsonFormat 注解,参考 SalesPromotion.java
ruoyi-system/src/main/java/com/ruoyi/system/domain/PromotionActivityRule.java — 字段: id, activityId, productId, threshold(BigDecimal), reduceAmount(BigDecimal), discountRate(BigDecimal), minQuantity(Integer);非持久化: productName, productImage, productPriceruoyi-system/src/main/java/com/ruoyi/system/domain/PromotionCouponBatch.java — 字段: id, storeId, name, couponType, totalCount, remainCount, receivedCount, status, startTime, endTime, validDays, createTime, updateTime;非持久化: PromotionCouponRule rule, boolean editableruoyi-system/src/main/java/com/ruoyi/system/domain/PromotionCouponRule.java — 字段: id, batchId, productId, isMutex(Integer), threshold(BigDecimal), amount(BigDecimal), discountRate(BigDecimal);非持久化: productName, productImage, productPriceruoyi-system/src/main/java/com/ruoyi/system/domain/PromotionUserCoupon.java — 字段: id, userId, batchId, storeId, status, orderId, receiveTime, useTime, expireTimeruoyi-system/src/main/java/com/ruoyi/system/domain/PosOrderPromotion.java — 字段: id, orderId, promoType, promoSubType, promoId, userCouponId, promoName, promoDetail, reduceAmount, pathSummary, createTimeruoyi-system/src/main/java/com/ruoyi/system/mapper/PromotionActivityMapper.java — extends BaseMapper
ruoyi-system/src/main/java/com/ruoyi/system/mapper/PromotionActivityRuleMapper.java — extends BaseMapper,额外方法 List selectRulesByActivityId(Long activityId)
ruoyi-system/src/main/java/com/ruoyi/system/mapper/PromotionCouponBatchMapper.java — extends BaseMapper
ruoyi-system/src/main/java/com/ruoyi/system/mapper/PromotionCouponRuleMapper.java — extends BaseMapper,额外方法 PromotionCouponRule selectRuleByBatchId(Long batchId)
ruoyi-system/src/main/java/com/ruoyi/system/mapper/PromotionUserCouponMapper.java — extends BaseMapper
ruoyi-system/src/main/java/com/ruoyi/system/mapper/PosOrderPromotionMapper.java — extends BaseMapper
ruoyi-system/src/main/resources/mapper/system/PromotionActivityMapper.xml — resultMap 映射全部字段ruoyi-system/src/main/resources/mapper/system/PromotionActivityRuleMapper.xml — resultMap + selectRulesByActivityId (LEFT JOIN pos_food 获取 productName/productImage/productPrice)ruoyi-system/src/main/resources/mapper/system/PromotionCouponBatchMapper.xml — resultMap 映射全部字段ruoyi-system/src/main/resources/mapper/system/PromotionCouponRuleMapper.xml — resultMap + selectRuleByBatchId (LEFT JOIN pos_food)ruoyi-system/src/main/resources/mapper/system/PromotionUserCouponMapper.xml — resultMap 映射全部字段ruoyi-system/src/main/resources/mapper/system/PosOrderPromotionMapper.xml — resultMap 映射全部字段Checkpoint: 数据层完成,可进入 Service 和 Controller
Goal: 商家能创建满减/折扣/第二份半价/新客立减活动,查看活动列表,结束进行中的活动
Independent Test: 商家创建一个满减活动(满20减5/满40减12),列表显示进行中,能查看详情,能结束活动
ruoyi-system/src/main/java/com/ruoyi/system/service/IPromotionActivityService.java — extends IService,方法: createActivity(activity, rules), selectActivityWithRules(id), endActivity(id)
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/PromotionActivityServiceImpl.java — 实现 createActivity (@Transactional: 插入activity+批量插入rules, 根据 startTime 设置初始 status), selectActivityWithRules (查activity+查rules), endActivity (status→2)ruoyi-system/src/main/java/com/ruoyi/system/service/IPromotionActivityRuleService.java 和 impl/PromotionActivityRuleServiceImpl.java — selectRulesByActivityId 委托给 mapperruoyi-admin/src/main/java/com/ruoyi/app/mendian/ShPromotionActivityController.java — @RestController @RequestMapping("/system/shPromotionActivity"),参考 ShSalesPromotionController。端点: GET /list (page,size,storeId,type?,status? → LambdaQueryWrapper 分页), GET /{id} (selectActivityWithRules), POST / (RequestBody含rules→createActivity), PUT /{id}/end (endActivity)foodie-store/src/api/promotionActivity.js — 导出 listPromotionActivity(query), getPromotionActivity(id), addPromotionActivity(data), endPromotionActivity(id),参考 src/api/promotion.js 模式foodie-store/src/views/PromotionActivity.vue — 促销活动管理页面:
Checkpoint: 促销活动完整功能可用——创建4种类型活动、查看列表、查看详情、结束活动
Goal: 商家能创建满减券/商品券,设置同享/互斥属性和库存,查看列表,下架券
Independent Test: 商家创建"满30减5"满减券100张,列表显示,能查看详情,能下架
ruoyi-system/src/main/java/com/ruoyi/system/service/IPromotionCouponBatchService.java — 方法: createBatch(batch, rule), selectBatchWithRule(id), offShelfBatch(id)ruoyi-system/src/main/java/com/ruoyi/system/service/impl/PromotionCouponBatchServiceImpl.java — 实现 createBatch (@Transactional: remainCount=totalCount, 插入batch+rule), selectBatchWithRule (查batch+查rule), offShelfBatch (status→3)ruoyi-system/src/main/java/com/ruoyi/system/service/IPromotionCouponRuleService.java 和 impl/PromotionCouponRuleServiceImpl.java — selectRuleByBatchId 委托给 mapperruoyi-admin/src/main/java/com/ruoyi/app/mendian/ShPromotionCouponController.java — @RestController @RequestMapping("/system/shPromotionCoupon")。端点: GET /list (page,size,storeId,couponType?,status?), GET /{id} (selectBatchWithRule), POST / (RequestBody含rule→createBatch), PUT /{id}/offShelffoodie-store/src/api/promotionCoupon.js — 导出 listPromotionCoupon(query), getPromotionCoupon(id), addPromotionCoupon(data), offShelfPromotionCoupon(id)foodie-store/src/views/CouponBatch.vue — 优惠券管理页面:
Checkpoint: 优惠券完整功能可用——创建满减券/商品券、查看列表、查看详情、下架券
Purpose: 前端菜单、路由、多语言——连接所有页面的基础设施
注意: 前端文件使用 CRLF 换行,必须用 Python 脚本编辑,不能用 Edit 工具。
foodie-store/src/router/index.js 的 /manage children 中添加路由 promotion-activity → PromotionActivity.vue 和 coupon-batch → CouponBatch.vuefoodie-store/src/components/Aside.vue 末尾(el-submenu index="6" 之后)添加新的 el-submenu index="7"「营销管理」,含两个 menu-item: 促销活动(/manage/promotion-activity) + 优惠券管理(/manage/coupon-batch)foodie-store/src/lang/zh.js 末尾追加 promoMenu(营销管理/促销活动/优惠券管理)、promoActivity(~40个key覆盖页面全部文案)、couponBatch(~30个key)三个 i18n sectionfoodie-store/src/lang/tw.js 追加同结构繁体中文翻译foodie-store/src/lang/en.js 追加同结构英文翻译foodie-store/src/lang/vi.js 追加同结构越南语翻译Checkpoint: 侧边栏显示新菜单,页面可访问,多语言正常
Purpose: 实现用户端的领券、查券、算价计算后端接口(前端界面由小程序实施,不做)
Goal: 用户端小程序可调用后端 API 完成领券、查看优惠券、结算页优惠计算
Independent Test: 用 Postman 调用算价接口,传入购物车商品,返回两条路径对比 + 最优方案 + 可用券列表
ruoyi-system/src/main/java/com/ruoyi/system/service/IPromotionUserCouponService.java 和 impl/PromotionUserCouponServiceImpl.java — 方法: receiveCoupon(userId, batchId, storeId)(检查限领1张、乐观锁扣库存 UPDATE remain_count=remain_count-1 WHERE remain_count>0、计算expireTime=领取日期+validDays天23:59:59、@Transactional), selectStoreCouponsForUser(userId, storeId)(查询门店进行中券批次+用户是否已领标记,不分页), selectMyCoupons(userId, status?, storeId?)(返回含storeName,实时判断expire_time<NOW()则status→2)ruoyi-system/src/main/java/com/ruoyi/system/service/IPromotionCalcService.java 和 impl/PromotionCalcServiceImpl.java — 核心算价逻辑:
ruoyi-admin/src/main/java/com/ruoyi/app/user/UserPromotionCouponController.java — @RestController @RequestMapping("/app/userPromotionCoupon")。端点: GET /storeCoupons (storeId → selectStoreCouponsForUser), POST /receive (batchId,storeId → receiveCoupon), GET /myCoupons (status?,storeId? → selectMyCoupons)ruoyi-admin/src/main/java/com/ruoyi/app/user/UserPromotionCalcController.java — @RestController @RequestMapping("/app/userPromotionCalc")。端点: POST /calculate (RequestBody含storeId+items[{productId,quantity}]+couponId?+forcePath?,不传price,通过登录态获取userId → calculate)Checkpoint: 用户端 API 可用——领券、查券、算价计算接口通过 Postman 测试通过
Purpose: 商家能修改未开始的活动、删除未开始的活动,参考美团商家端交互
Goal: 活动列表操作列按状态显示不同按钮(未开始=编辑+删除,进行中=结束,已结束=无)
updateActivity(activity, rules) — 校验 status=0 才允许修改;@Transactional 先删除旧规则再重新插入deleteActivity(id) — 校验 status=0 才允许删除;@Transactional 删除活动+关联规则PUT / (updateActivity) 和 DELETE /{id} (deleteActivity) 端点foodie-store/src/api/promotionActivity.js 中添加 updatePromotionActivity(data) 和 deletePromotionActivity(id)foodie-store/src/views/PromotionActivity.vue:
editingId: null,tijiaobaocun 判断 editingId 有值走修改、无值走新增Checkpoint: 未开始的活动可编辑(修改名称/时间/规则)、可删除(二次确认后删除),进行中只能结束
Purpose: 商家能修改未开始/进行中的优惠券、删除未开始的优惠券
Goal: 优惠券列表操作列按状态显示不同按钮(未开始=编辑+删除,进行中=编辑+下架,已结束/已下架=无)
updateBatch(batch, rule) — 校验 status=0或1 才允许修改;@Transactional 先删除旧规则再重新插入deleteBatch(id) — 校验 status=0 才允许删除;@Transactional 删除券批次+关联规则PUT / (updateBatch) 和 DELETE /{id} (deleteBatch) 端点foodie-store/src/api/promotionCoupon.js 中添加 updatePromotionCoupon(data) 和 deletePromotionCoupon(id)foodie-store/src/views/CouponBatch.vue:
editingId: null,tijiaobaocun 判断 editingId 有值走修改、无值走新增Purpose: 新增 couponType=3 免配送费券,商家可创建减免配送费的优惠券
Goal: 创建券对话框新增"免配送费券"选项,配置使用门槛+减免金额+同享/互斥
updatesql/sql.md 追加 ALTER 语句更新 promotion_coupon_batch.coupon_type 的 COMMENT 为 '1=满减券 2=商品券 3=免配送费券'(无需实际 ALTER,仅记录)foodie-store/src/views/CouponBatch.vue 创建对话框:券类型 radio-group 新增第三个选项"免配送费券"(couponType=3),选中后显示:使用门槛 el-input-number + 减免金额 el-input-number + 叠加属性 radio(同享/互斥),隐藏商品选择Checkpoint: 创建券时可选"免配送费券",提交数据 couponType=3,含 threshold + amount + isMutex
Checkpoint: 未开始的券可编辑+删除,进行中的券可编辑+下架,已结束/已下架无操作
Purpose: 将折扣商品 UI 从「折扣区分组」改为「逐商品设折扣」(参考美团),提升商家体验
Goal: 商家创建折扣活动时直接选择商品并逐个设折扣率,不再需要先建折扣区
背景: 经美团/饿了么调研,主流外卖平台都是逐商品设折扣,不使用折扣区分组。后端数据模型已支持(promotion_activity_rule 每行有独立 productId + discountRate),改动集中在前端 UI。
foodie-store/src/views/PromotionActivity.vue 折扣 Tab (Tab2) 的 UI 和逻辑:
tijiaobaocun() 中折扣 Tab 直接构建 rules: [{productId, discountRate}, ...],无需展开分组rules 数组直接回填到商品列表,每行设置对应的 discountRatefoodie-store/src/lang/ 的 zh.js、tw.js、en.js、vi.js:
ShPromotionActivityController 的 POST/PUT 端点已支持接收扁平的 rules: [{productId, discountRate}] 格式(不需要改动),确认编辑回填接口 GET /{id} 返回的 rules 中每条包含 productId 和 discountRate(已有 LEFT JOIN pos_food)Checkpoint: 商家创建折扣活动时 UI 为扁平商品列表,每个商品独立设折扣率,不再有折扣区分组概念。编辑时正确回填。折后价实时计算。后端接口无需改动。
Purpose: 端到端验证
T001 → T002(手动)
→ T003-T017a (Phase 2, 大量并行)
→ T018-T023 (Phase 3, 促销活动) + T024-T029 (Phase 4, 优惠券) [可并行]
→ T030-T035 (Phase 5, 菜单路由i18n)
→ T039-T045 (Phase 5.5, 用户端接口)
→ T061-T063 (Phase 5.9, 折扣商品改为逐商品设折扣) ← 当前待实施
→ T036-T038 (Phase 6, 验证)