tasks.md 26 KB

Tasks: 促销 + 优惠券系统

Input: Design documents from /specs/008-promotion-coupon/ Prerequisites: plan.md (required), spec.md (required for user stories)

Format: [ID] [P?] [Story] Description

  • [P]: Can run in parallel (different files, no dependencies)
  • [Story]: Which user story this task belongs to (US1=促销活动, US2=优惠券)
  • Include exact file paths in descriptions

Phase 1: Setup (数据库 + 基础设施)

Purpose: 创建数据库表,为后续所有任务提供基础

  • T001 追加 6 张新表 DDL 到 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 章节
  • T002 手动执行 SQL 建表(提醒开发者操作)

Phase 2: Foundational (后端实体 + 数据层)

Purpose: 后端核心数据层,所有 Controller 和前端页面依赖此阶段完成

实体类 (5个文件,全部可并行)

  • T003 [P] 创建 PromotionActivity 实体 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
  • T004 [P] 创建 PromotionActivityRule 实体 ruoyi-system/src/main/java/com/ruoyi/system/domain/PromotionActivityRule.java — 字段: id, activityId, productId, threshold(BigDecimal), reduceAmount(BigDecimal), discountRate(BigDecimal), minQuantity(Integer);非持久化: productName, productImage, productPrice
  • T005 [P] 创建 PromotionCouponBatch 实体 ruoyi-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 editable
  • T006 [P] 创建 PromotionCouponRule 实体 ruoyi-system/src/main/java/com/ruoyi/system/domain/PromotionCouponRule.java — 字段: id, batchId, productId, isMutex(Integer), threshold(BigDecimal), amount(BigDecimal), discountRate(BigDecimal);非持久化: productName, productImage, productPrice
  • T007 [P] 创建 PromotionUserCoupon 实体 ruoyi-system/src/main/java/com/ruoyi/system/domain/PromotionUserCoupon.java — 字段: id, userId, batchId, storeId, status, orderId, receiveTime, useTime, expireTime
  • T007a [P] 创建 PosOrderPromotion 实体 ruoyi-system/src/main/java/com/ruoyi/system/domain/PosOrderPromotion.java — 字段: id, orderId, promoType, promoSubType, promoId, userCouponId, promoName, promoDetail, reduceAmount, pathSummary, createTime
  • Mapper 接口 (全部可并行)

    • T008 [P] 创建 PromotionActivityMapper ruoyi-system/src/main/java/com/ruoyi/system/mapper/PromotionActivityMapper.java — extends BaseMapper
    • T009 [P] 创建 PromotionActivityRuleMapper ruoyi-system/src/main/java/com/ruoyi/system/mapper/PromotionActivityRuleMapper.java — extends BaseMapper,额外方法 List selectRulesByActivityId(Long activityId)
    • T010 [P] 创建 PromotionCouponBatchMapper ruoyi-system/src/main/java/com/ruoyi/system/mapper/PromotionCouponBatchMapper.java — extends BaseMapper
    • T011 [P] 创建 PromotionCouponRuleMapper ruoyi-system/src/main/java/com/ruoyi/system/mapper/PromotionCouponRuleMapper.java — extends BaseMapper,额外方法 PromotionCouponRule selectRuleByBatchId(Long batchId)
    • T012 [P] 创建 PromotionUserCouponMapper ruoyi-system/src/main/java/com/ruoyi/system/mapper/PromotionUserCouponMapper.java — extends BaseMapper
    • T012a [P] 创建 PosOrderPromotionMapper ruoyi-system/src/main/java/com/ruoyi/system/mapper/PosOrderPromotionMapper.java — extends BaseMapper
    • Mapper XML (全部可并行)

      • T013 [P] 创建 PromotionActivityMapper.xml ruoyi-system/src/main/resources/mapper/system/PromotionActivityMapper.xml — resultMap 映射全部字段
      • T014 [P] 创建 PromotionActivityRuleMapper.xml ruoyi-system/src/main/resources/mapper/system/PromotionActivityRuleMapper.xml — resultMap + selectRulesByActivityId (LEFT JOIN pos_food 获取 productName/productImage/productPrice)
      • T015 [P] 创建 PromotionCouponBatchMapper.xml ruoyi-system/src/main/resources/mapper/system/PromotionCouponBatchMapper.xml — resultMap 映射全部字段
      • T016 [P] 创建 PromotionCouponRuleMapper.xml ruoyi-system/src/main/resources/mapper/system/PromotionCouponRuleMapper.xml — resultMap + selectRuleByBatchId (LEFT JOIN pos_food)
      • T017 [P] 创建 PromotionUserCouponMapper.xml ruoyi-system/src/main/resources/mapper/system/PromotionUserCouponMapper.xml — resultMap 映射全部字段
      • T017a [P] 创建 PosOrderPromotionMapper.xml ruoyi-system/src/main/resources/mapper/system/PosOrderPromotionMapper.xml — resultMap 映射全部字段

      Checkpoint: 数据层完成,可进入 Service 和 Controller


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

      Goal: 商家能创建满减/折扣/第二份半价/新客立减活动,查看活动列表,结束进行中的活动

      Independent Test: 商家创建一个满减活动(满20减5/满40减12),列表显示进行中,能查看详情,能结束活动

      Service 层 (US1)

      • T018 [US1] 创建 IPromotionActivityService ruoyi-system/src/main/java/com/ruoyi/system/service/IPromotionActivityService.java — extends IService,方法: createActivity(activity, rules), selectActivityWithRules(id), endActivity(id)
      • T019 [US1] 创建 PromotionActivityServiceImpl ruoyi-system/src/main/java/com/ruoyi/system/service/impl/PromotionActivityServiceImpl.java — 实现 createActivity (@Transactional: 插入activity+批量插入rules, 根据 startTime 设置初始 status), selectActivityWithRules (查activity+查rules), endActivity (status→2)
      • T020 [US1] 创建 IPromotionActivityRuleService + PromotionActivityRuleServiceImpl ruoyi-system/src/main/java/com/ruoyi/system/service/IPromotionActivityRuleService.javaimpl/PromotionActivityRuleServiceImpl.java — selectRulesByActivityId 委托给 mapper
      • Controller (US1)

        • T021 [US1] 创建 ShPromotionActivityController ruoyi-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)

        前端 API (US1)

        • T022 [US1] 创建 promotionActivity.js foodie-store/src/api/promotionActivity.js — 导出 listPromotionActivity(query), getPromotionActivity(id), addPromotionActivity(data), endPromotionActivity(id),参考 src/api/promotion.js 模式

        前端页面 (US1)

        • T023 [US1] 创建 PromotionActivity.vue foodie-store/src/views/PromotionActivity.vue — 促销活动管理页面:
          • 列表区域: 面包屑 + 门店选择(复用xuanzhemd模式) + 状态筛选radio(全部/未开始/进行中/已结束) + el-table(ID/名称/类型el-tag/状态el-tag/时间/操作-结束按钮) + el-pagination
          • 创建对话框 el-dialog width=750px + el-tabs v-model=activeTab:
          • Tab1 满减(type=1): 活动名称 + 时间范围 + 动态档位行(v-for, 每行两个el-input-number:满X减Y) + 添加/删除档位按钮
          • Tab2 折扣(type=2): 活动名称 + 时间范围 + 折扣组列表(v-for, 每组含折扣率+已选商品table+添加商品按钮) + 添加/删除折扣区按钮。同一商品只能分配到一个折扣区,添加时自动从其他折扣区移除
          • Tab3 第二份半价(type=3): 活动名称 + 时间范围 + 已选商品table + 选择商品按钮
          • Tab4 新客立减(type=4): 活动名称 + 时间范围 + 减免金额el-input-number
          • 商品选择弹窗: 分类el-select + 搜索el-input + 商品el-table + el-pagination (复用Quanyi.vue商品加载API模式)
          • 提交逻辑 tijiaobaocun(): 根据activeTab转换数据为 {storeId,type,name,startTime,endTime,rules:[...]},折扣组展开为扁平rules,调用addPromotionActivity
          • 互斥提示: 创建对话框顶部展示互斥规则提示文案(FR-006):"满减、折扣、第二份半价三选一(系统自动选最优),新客立减可叠加"
          • i18n: 所有文字用 $t('promoActivity.xxx')

        Checkpoint: 促销活动完整功能可用——创建4种类型活动、查看列表、查看详情、结束活动


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

        Goal: 商家能创建满减券/商品券,设置同享/互斥属性和库存,查看列表,下架券

        Independent Test: 商家创建"满30减5"满减券100张,列表显示,能查看详情,能下架

        Service 层 (US2)

        • T024 [P] [US2] 创建 IPromotionCouponBatchService ruoyi-system/src/main/java/com/ruoyi/system/service/IPromotionCouponBatchService.java — 方法: createBatch(batch, rule), selectBatchWithRule(id), offShelfBatch(id)
        • T025 [P] [US2] 创建 PromotionCouponBatchServiceImpl ruoyi-system/src/main/java/com/ruoyi/system/service/impl/PromotionCouponBatchServiceImpl.java — 实现 createBatch (@Transactional: remainCount=totalCount, 插入batch+rule), selectBatchWithRule (查batch+查rule), offShelfBatch (status→3)
        • T026 [P] [US2] 创建 IPromotionCouponRuleService + PromotionCouponRuleServiceImpl ruoyi-system/src/main/java/com/ruoyi/system/service/IPromotionCouponRuleService.javaimpl/PromotionCouponRuleServiceImpl.java — selectRuleByBatchId 委托给 mapper

        Controller (US2)

        • T027 [US2] 创建 ShPromotionCouponController ruoyi-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}/offShelf

        前端 API (US2)

        • T028 [P] [US2] 创建 promotionCoupon.js foodie-store/src/api/promotionCoupon.js — 导出 listPromotionCoupon(query), getPromotionCoupon(id), addPromotionCoupon(data), offShelfPromotionCoupon(id)

        前端页面 (US2)

        • T029 [US2] 创建 CouponBatch.vue foodie-store/src/views/CouponBatch.vue — 优惠券管理页面:
          • 列表区域: 面包屑 + 门店选择 + 状态筛选(全部/未开始/进行中/已结束/已下架) + el-table(ID/名称/类型/总库存/剩余/已领取/状态/时间/有效期/操作-下架按钮) + el-pagination
          • 创建对话框 el-dialog width=600px:
          • 券类型 el-radio-group (满减券/商品券)
          • 通用字段: 名称el-input, 门店el-select, 总量el-input-number, 有效天数el-input-number, 时间范围el-date-picker
          • 满减券(v-if couponType===1): 叠加属性el-radio(同享/互斥), 使用门槛el-input-number, 减免金额el-input-number
          • 商品券(v-if couponType===2): 选择商品按钮(复用商品弹窗), 折扣率el-input-number
          • 提交逻辑 tijiaobaocun(): 构建 {storeId,name,couponType,totalCount,validDays,startTime,endTime,rule:{isMutex,threshold,amount,...}},调用addPromotionCoupon
          • i18n: 所有文字用 $t('couponBatch.xxx')

        Checkpoint: 优惠券完整功能可用——创建满减券/商品券、查看列表、查看详情、下架券


        Phase 5: Menu + Router + i18n (跨 US)

        Purpose: 前端菜单、路由、多语言——连接所有页面的基础设施

        注意: 前端文件使用 CRLF 换行,必须用 Python 脚本编辑,不能用 Edit 工具。

        • T030 在 foodie-store/src/router/index.js 的 /manage children 中添加路由 promotion-activity → PromotionActivity.vue 和 coupon-batch → CouponBatch.vue
        • T031 在 foodie-store/src/components/Aside.vue 末尾(el-submenu index="6" 之后)添加新的 el-submenu index="7"「营销管理」,含两个 menu-item: 促销活动(/manage/promotion-activity) + 优惠券管理(/manage/coupon-batch)
        • T032 [P] 在 foodie-store/src/lang/zh.js 末尾追加 promoMenu(营销管理/促销活动/优惠券管理)、promoActivity(~40个key覆盖页面全部文案)、couponBatch(~30个key)三个 i18n section
        • T033 [P] 在 foodie-store/src/lang/tw.js 追加同结构繁体中文翻译
        • T034 [P] 在 foodie-store/src/lang/en.js 追加同结构英文翻译
        • T035 [P] 在 foodie-store/src/lang/vi.js 追加同结构越南语翻译

        Checkpoint: 侧边栏显示新菜单,页面可访问,多语言正常


        Phase 5.5: 用户端后端接口 (FR-011~022)

        Purpose: 实现用户端的领券、查券、算价计算后端接口(前端界面由小程序实施,不做)

        Goal: 用户端小程序可调用后端 API 完成领券、查看优惠券、结算页优惠计算

        Independent Test: 用 Postman 调用算价接口,传入购物车商品,返回两条路径对比 + 最优方案 + 可用券列表

        Service 层

        • T039 [P] 创建 IPromotionUserCouponService + PromotionUserCouponServiceImpl ruoyi-system/src/main/java/com/ruoyi/system/service/IPromotionUserCouponService.javaimpl/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)
        • T040 创建 IPromotionCalcService + PromotionCalcServiceImpl ruoyi-system/src/main/java/com/ruoyi/system/service/IPromotionCalcService.javaimpl/PromotionCalcServiceImpl.java — 核心算价逻辑:
          • calculate(storeId, items[{productId,quantity}], userId, couponId?, forcePath?) → 返回路径对比结果
          • 内部逻辑:
          • 根据productId从数据库查询商品实际价格(不信任前端传价)
          • 查门店所有进行中的促销活动(满减、折扣、第二份半价、新客立减)
          • 路径A(走折扣/第二份半价):分别计算折扣和第二份半价各一遍,取更优作为路径A结果。折扣商品按discountRate,第二份半价按每2件第2件半价,非活动商品原价
          • 路径B(走满减):所有商品原价 → 匹配满减最高可达档位 → 合计
          • 对比选最优(金额相等默认选B);无促销时两条路径同价返回原价
          • 查用户是否为该门店新客(查询该门店已完成订单数=0),新客则叠加新客立减
          • 如传couponId:检查互斥规则(互斥券不能与满减/折扣叠加,商品券对应商品如在折扣/第二份半价活动中则该券不可用),同享券threshold基于促销后金额判断
          • 所有金额用BigDecimal计算,最终ROUND_HALF_UP保留2位小数,最低¥0.01
          • 返回 availableCoupons(可用券列表)供前端展示
          • 返回两条路径明细 + optimalPath + 最终金额 + details数组

        Controller

        • T041 创建 UserPromotionCouponController 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)
        • T042 创建 UserPromotionCalcController 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)

        Mapper 补充查询

        • T043 [P] 在 PromotionActivityMapper 中添加 selectActiveByStoreId(storeId) — 查询门店所有进行中的促销活动,算价时使用
        • T044 [P] 在 PromotionActivityRuleMapper 中添加 selectActiveRulesByStoreId(storeId) — 一次性查出门店所有进行中活动的规则(LEFT JOIN promotion_activity ON status=1),算价时使用
        • T045 [P] 在 PromotionCouponBatchMapper 中添加 selectActiveByStoreId(storeId) — 查询门店进行中且有余量的券批次(status=1 AND remain_count>0)

        Checkpoint: 用户端 API 可用——领券、查券、算价计算接口通过 Postman 测试通过


        Phase 5.6: 促销活动修改/删除 (FR-005a, FR-005b)

        Purpose: 商家能修改未开始的活动、删除未开始的活动,参考美团商家端交互

        Goal: 活动列表操作列按状态显示不同按钮(未开始=编辑+删除,进行中=结束,已结束=无)

        后端

        • T046 在 IPromotionActivityService 中添加 updateActivity(activity, rules) — 校验 status=0 才允许修改;@Transactional 先删除旧规则再重新插入
        • T047 在 IPromotionActivityService 中添加 deleteActivity(id) — 校验 status=0 才允许删除;@Transactional 删除活动+关联规则
        • T048 在 ShPromotionActivityController 中添加 PUT / (updateActivity) 和 DELETE /{id} (deleteActivity) 端点

        前端 API

        • T049 在 foodie-store/src/api/promotionActivity.js 中添加 updatePromotionActivity(data)deletePromotionActivity(id)

        前端页面

        • T050 修改 foodie-store/src/views/PromotionActivity.vue
          • 操作列按状态显示按钮:未开始(编辑+删除) / 进行中(结束) / 已结束(无)
          • 编辑:点击后调用 getPromotionActivity(id) 获取详情,回填到创建对话框,tijiaobaocun 判断有 id 则调用 updatePromotionActivity
          • 删除:二次确认后调用 deletePromotionActivity(id)
          • 新增 data: editingId: nulltijiaobaocun 判断 editingId 有值走修改、无值走新增

        i18n

        • T051 [P] 在 4 个语言文件(zh/tw/en/vi)的 promoActivity section 中追加:editBtn、deleteBtn、editActivity、deleteConfirm 等新 key

        Checkpoint: 未开始的活动可编辑(修改名称/时间/规则)、可删除(二次确认后删除),进行中只能结束


        Phase 5.7: 优惠券修改/删除 (FR-009a, FR-009b)

        Purpose: 商家能修改未开始/进行中的优惠券、删除未开始的优惠券

        Goal: 优惠券列表操作列按状态显示不同按钮(未开始=编辑+删除,进行中=编辑+下架,已结束/已下架=无)

        后端

        • T052 在 IPromotionCouponBatchService 中添加 updateBatch(batch, rule) — 校验 status=0或1 才允许修改;@Transactional 先删除旧规则再重新插入
        • T053 在 IPromotionCouponBatchService 中添加 deleteBatch(id) — 校验 status=0 才允许删除;@Transactional 删除券批次+关联规则
        • T054 在 ShPromotionCouponController 中添加 PUT / (updateBatch) 和 DELETE /{id} (deleteBatch) 端点

        前端 API

        • T055 在 foodie-store/src/api/promotionCoupon.js 中添加 updatePromotionCoupon(data)deletePromotionCoupon(id)

        前端页面

        • T056 修改 foodie-store/src/views/CouponBatch.vue
          • 操作列按状态显示按钮:未开始(编辑+删除) / 进行中(编辑+下架) / 已结束/已下架(无)
          • 编辑:点击后调用 getPromotionCoupon(id) 获取详情,回填到创建对话框,tijiaobaocun 判断有 id 则调用 updatePromotionCoupon
          • 删除:二次确认后调用 deletePromotionCoupon(id)
          • 新增 data: editingId: nulltijiaobaocun 判断 editingId 有值走修改、无值走新增

        i18n

        • T057 [P] 在 4 个语言文件(zh/tw/en/vi)的 couponBatch section 中追加:editBtn、deleteBtn、editCoupon、deleteConfirm 等新 key

        Phase 5.8: 免配送费券 (FR-008a)

        Purpose: 新增 couponType=3 免配送费券,商家可创建减免配送费的优惠券

        Goal: 创建券对话框新增"免配送费券"选项,配置使用门槛+减免金额+同享/互斥

        数据库

        • T058 在 updatesql/sql.md 追加 ALTER 语句更新 promotion_coupon_batch.coupon_type 的 COMMENT 为 '1=满减券 2=商品券 3=免配送费券'(无需实际 ALTER,仅记录)

        前端页面

        • T059 修改 foodie-store/src/views/CouponBatch.vue 创建对话框:券类型 radio-group 新增第三个选项"免配送费券"(couponType=3),选中后显示:使用门槛 el-input-number + 减免金额 el-input-number + 叠加属性 radio(同享/互斥),隐藏商品选择

        i18n

        • T060 [P] 在 4 个语言文件(zh/tw/en/vi)的 couponBatch section 中追加:typeFreeDelivery(免配送费券)等 key

        Checkpoint: 创建券时可选"免配送费券",提交数据 couponType=3,含 threshold + amount + isMutex

        Checkpoint: 未开始的券可编辑+删除,进行中的券可编辑+下架,已结束/已下架无操作


        Phase 5.9: 折扣商品改为逐商品设折扣 (需求变更)

        Purpose: 将折扣商品 UI 从「折扣区分组」改为「逐商品设折扣」(参考美团),提升商家体验

        Goal: 商家创建折扣活动时直接选择商品并逐个设折扣率,不再需要先建折扣区

        背景: 经美团/饿了么调研,主流外卖平台都是逐商品设折扣,不使用折扣区分组。后端数据模型已支持(promotion_activity_rule 每行有独立 productId + discountRate),改动集中在前端 UI。

        前端页面

        • T061 修改 foodie-store/src/views/PromotionActivity.vue 折扣 Tab (Tab2) 的 UI 和逻辑:
          • 移除:折扣区分组 UI(建区/删区按钮、折扣组 v-for 循环)
          • 改为扁平商品列表,每行 = 商品名称 + 原价 + 折扣率控件(-/+按钮+输入框,范围10%~99%,步长0.1) + 折后价(自动计算) + 移除按钮
          • 添加商品按钮:点击打开商品选择弹窗(复用已有弹窗),选中后默认折扣率90%,添加到列表
          • 重复商品限制:选择商品时如已存在于列表则提示并跳过
          • 折后价实时计算:原价 × 折扣率,显示在每行
          • 提交逻辑简化tijiaobaocun() 中折扣 Tab 直接构建 rules: [{productId, discountRate}, ...],无需展开分组
          • 编辑回填:编辑折扣活动时,从 rules 数组直接回填到商品列表,每行设置对应的 discountRate
          • 折扣率校验:前端提交时校验所有折扣率在 0.10~0.99 范围内,超出范围阻止提交

        i18n

        • T062 [P] 更新 4 个语言文件 foodie-store/src/lang/ 的 zh.js、tw.js、en.js、vi.js:
          • 移除折扣区相关 key(如 addDiscountZone、deleteZone、discountZone 等)
          • 新增/修改:discountRate(折扣率)、discountedPrice(折后价)、originalPrice(原价)、removeProduct(移除)、addDiscountProduct(添加折扣商品)、discountRateRange(范围10%-99%)、productAlreadyAdded(商品已添加)等 key
          • 更新 promoActivity section 中与折扣 Tab 相关的所有文案

        后端验证

        • T063 验证后端接口兼容性:确认 ShPromotionActivityController 的 POST/PUT 端点已支持接收扁平的 rules: [{productId, discountRate}] 格式(不需要改动),确认编辑回填接口 GET /{id} 返回的 rules 中每条包含 productId 和 discountRate(已有 LEFT JOIN pos_food)

        Checkpoint: 商家创建折扣活动时 UI 为扁平商品列表,每个商品独立设折扣率,不再有折扣区分组概念。编辑时正确回填。折后价实时计算。后端接口无需改动。


        Phase 6: Polish & 验证

        Purpose: 端到端验证

        • T036 后端启动验证:用 Postman 测试 4 个促销 API + 4 个优惠券 API(商家端)+ 3 个用户端领券 API + 1 个算价 API 的完整流程
        • T036a 算价接口专项验证:构造测试数据(门店有满减+折扣活动+优惠券),验证两条路径计算正确、互斥规则生效、新客立减可叠加
        • T037 前端启动验证:npm run dev → 侧边栏新菜单 → 创建满减活动(3档位) → 创建折扣活动(选择5个商品逐个设不同折扣率) → 创建第二份半价 → 创建新客立减 → 创建满减券 → 创建商品券 → 列表筛选 → 结束活动 → 下架券 → 编辑折扣活动验证回填
        • T038 [P] 切换4种语言验证所有文案显示正确

        Dependencies & Execution Order

        Phase Dependencies

        • Phase 1 (Setup): 无依赖,立即开始
        • Phase 2 (Foundational): 依赖 Phase 1 的表已创建
        • Phase 3 (US1 促销): 依赖 Phase 2 的实体和 Mapper
        • Phase 4 (US2 优惠券): 依赖 Phase 2,可与 Phase 3 并行
        • Phase 5 (Menu/i18n): 依赖 Phase 3 + Phase 4 的页面文件存在
        • Phase 5.5 (用户端接口): 依赖 Phase 2 的实体/Mapper + Phase 3 的促销 Activity Service + Phase 4 的优惠券 Service
        • Phase 6 (验证): 依赖全部完成(包括 Phase 5.9 折扣改动)

        Parallel Opportunities

        • T003-T007a (6个实体) 可并行
        • T008-T012a (6个Mapper接口) 可并行
        • T013-T017a (6个Mapper XML) 可并行
        • Phase 3 和 Phase 4 可并行(不同文件,不同 Controller)
        • T039 和 T040 可并行(不同 Service)
        • T032-T035 (4个语言文件) 可并行

        Recommended Execution Order

        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, 验证)
        

        Notes

        • [P] tasks = 不同文件,无依赖,可并行
        • [Story] label 映射到 spec 中的 User Story (US1=促销, US2=优惠券)
        • 每个任务完成后 commit
        • 前端文件 CRLF 换行,用 Python 脚本编辑
        • 旧代码(SalesPromotion, VipQuanyi)不动
        • PromotionUserCoupon 实体和 Mapper 在 Phase 2 创建,Service/Controller 在 Phase 5.5 实现
        • PosOrderPromotion 实体和 Mapper 在 Phase 2 创建,算价 Service 在 Phase 5.5 使用
        • 用户端前端(小程序界面)不在本范围,只做后端 API