--- description: "Task list for 商品规格(SKU 规格)管理 feature implementation" --- # Tasks: 商品规格(SKU 规格)管理 **Input**: Design documents from `/specs/013-food-spec/` **Prerequisites**: plan.md, spec.md, research.md, data-model.md, contracts/api.md, quickstart.md ## 本期实现状态(2026-07-15) **已完成(后端,mvn compile 通过):** T001(DDL)、T002-T011(实体/Mapper/Service/XML)、T012-T013(FoodSpecController)、T018-T020(PosFoodController setposfood/add/edit 收口)、T023-T027(列表/详情注入规格)、T032(编译)。 **本期暂缓(用户决定 2026-07-15):** - 前端 foodie-store:T014-T017(规格管理页/API/路由/i18n)、T021-T022(商品编辑规格区)、T028(列表展示)——后续单独安排 - US4 价格口径:T029-T031(订单 otherPrice / 促销 specPrice 与规格加价统一)——留作后续 **待开发者执行:** T033——先执行 updatesql/sql.md 建表,再按 quickstart.md 跑场景。 **Tests**: 不包含自动化测试任务(项目无测试框架,spec 未要求 TDD);验证通过 quickstart.md 手动场景完成。 **Organization**: 按用户故事分组,便于独立实现与验证。US1/US2/US3 为 P1,US4 为 P2。 ## Format: `[ID] [P?] [Story] Description` - **[P]**: 可并行(不同文件、无依赖) - **[Story]**: 所属用户故事(US1/US2/US3/US4) - 描述含确切文件路径 ## Path Conventions - 后端实体/mapper/service:`ruoyi-system/src/main/java/com/ruoyi/system/...`,XML 在 `ruoyi-system/src/main/resources/mapper/system/` - 后端 controller:`ruoyi-admin/src/main/java/com/ruoyi/app/mendian/...`(与 PosFoodController 同包) - 前端商家端:`E:\QtwCode\foodie\foodie-store\src\...` - SQL:`updatesql/sql.md` --- ## Phase 1: Setup (Shared Infrastructure) **Purpose**: 数据库结构准备 - [x] T001 在 `updatesql/sql.md` 追加三张规格表 DDL(`food_specs`、`food_specs_value`、`food_spec_relation`,字段/类型见 data-model.md,规格加价 `price` 用 decimal 与 pos_food.price 同口径)并确认 `pos_food.food_sku` 列存在;标注日期 2026-07-15 与用途注释 --- ## Phase 2: Foundational (Blocking Prerequisites) **Purpose**: 规格体系基础数据层(实体/Mapper/Service),所有用户故事共用 **⚠️ CRITICAL**: 本阶段完成前不得开始任何用户故事 - [x] T002 [P] 新建实体 `FoodSpecs`(`ruoyi-system/src/main/java/com/ruoyi/system/domain/FoodSpecs.java`):`@TableName("food_specs")`,字段 id/title/type/state/language/remark/mdId/sort/isOpen/isDelete,transient `foodSpecsItems: List` - [x] T003 [P] 新建实体 `FoodSpecsValue`(`ruoyi-system/src/main/java/com/ruoyi/system/domain/FoodSpecsValue.java`):`@TableName("food_specs_value")`,字段 id/parentId/name/price(BigDecimal)/note/state/isOpen - [x] T004 [P] 新建实体 `FoodSpecRelation`(`ruoyi-system/src/main/java/com/ruoyi/system/domain/FoodSpecRelation.java`):`@TableName("food_spec_relation")`,字段 id/foodId/specsId - [x] T005 [P] 扩展 `PosFood`(`ruoyi-system/src/main/java/com/ruoyi/system/domain/PosFood.java`):新增 transient `foodSpecs: List`(`@TableField(exist=false)`),确认既有 transient `sku: JSONArray` 可用 - [x] T006 [P] 新建 `FoodSpecsMapper`(`ruoyi-system/.../mapper/FoodSpecsMapper.java`,`extends BaseMapper`)+ XML(`ruoyi-system/src/main/resources/mapper/system/FoodSpecsMapper.xml`,含 resultMap 与标准 CRUD) - [x] T007 [P] 新建 `FoodSpecsValueMapper` + XML(同上目录结构,`extends BaseMapper`) - [x] T008 [P] 新建 `FoodSpecRelationMapper` + XML(同上,`extends BaseMapper`) - [x] T009 [P] 新建 `IFoodSpecsService`(`extends IService`)+ `FoodSpecsServiceImpl`(`ruoyi-system/.../service/` 与 `service/impl/`) - [x] T010 [P] 新建 `IFoodSpecsValueService` + `FoodSpecsValueServiceImpl`(同上模式) - [x] T011 [P] 新建 `IFoodSpecRelationService` + `FoodSpecRelationServiceImpl`(同上模式) **Checkpoint**: 规格三实体/Mapper/Service 就绪,可开始用户故事 --- ## Phase 3: User Story 1 - 商家管理门店规格模板 (Priority: P1) 🎯 MVP **Goal**: 商家在商家端对本门店规格组(含级联规格值)做增删改查、启停、软删 **Independent Test**: 商家新建"甜度"(单选/必选)+3 个规格值,保存后在列表可见、可编辑、可停用、可软删;跨门店不可见 ### Implementation for User Story 1 - [x] T012 [US1] 新建 `FoodSpecController`(`ruoyi-admin/src/main/java/com/ruoyi/app/mendian/FoodSpecController.java`,基路径 `/chanting/foodSpec`,`@Anonymous @Auth`):实现 `foodSpecPageList`/`getSpecs`/`deleteFoodSpec`(软删 isDelete=1)/`getAvailableSpecsList`(isOpen=1 & isDelete=0,按 mdId 隔离)/`changeOpen`/`changeSpecValueOpen` - [x] T013 [US1] 在 `FoodSpecController` 实现 `saveFoodSpec(@RequestBody List)`:规格组 id≤0 新增否则更新,级联 saveOrUpdate 其下 `foodSpecsItems`(规格值),事务保证一致(依赖 T009/T010) - [ ] T014 [P] [US1] 前端 API 封装 `foodie-store/src/api/specs.js`(foodSpecPageList/saveFoodSpec/getSpecs/deleteFoodSpec/getAvailableSpecsList/changeOpen/changeSpecValueOpen) - [ ] T015 [US1] 前端规格管理页 `foodie-store/src/views/FoodSpec.vue`(或遵循 store 现有命名):分页列表(按门店切换)+ 新增/编辑弹窗(嵌套规格值子表增删改 name/price/note/isOpen)+ 启停开关 + 软删 - [ ] T016 [US1] 前端路由/菜单接入规格管理页(按 foodie-store 现有路由约定) - [ ] T017 [P] [US1] 前端 i18n:在 `foodie-store/src/lang/` 的 zh.js/tw.js/en.js/vi.js 同一对象层级追加规格相关 key(有意义驼峰命名,四文件 key 一致) **Checkpoint**: US1 独立可用——规格模板完整管理闭环 --- ## Phase 4: User Story 2 - 商品使用规格 (Priority: P1) **Goal**: 商家编辑商品时可从本门店可用规格多选规格组挂到商品;保存建立/更新关联 **Independent Test**: 商家编辑"珍珠奶茶"勾选"甜度""加料",保存后详情返回这两个规格;重新勾选后关联正确更新 ### Implementation for User Story 2 - [x] T018 [US2] 在 `PosFoodController`(`ruoyi-admin/src/main/java/com/ruoyi/app/mendian/PosFoodController.java`)抽取私有方法 `handleFoodSpec(PosFood)`:删除该 foodId 旧关联 → 按 `foodSpecs[*].id` 批量插 `food_spec_relation` → `sku.toString()` 写入 `food_sku`(依赖 T011) - [x] T019 [US2] `setposfood` 保存商品后调用 `handleFoodSpec`(依赖 T018) - [x] T020 [US2] 后台 `add`/`edit` 也调用 `handleFoodSpec`,统一收口(FR-011,修复当前后台通道不写规格) - [ ] T021 [P] [US2] 前端 `foodie-store/src/api/food.js` 的 setposfood 提交体增加 `foodSpecs` 与 `sku` - [ ] T022 [US2] 前端商品编辑页规格选择区:`el-select multiple` 选 `getAvailableSpecsList` 结果,`handleSpecChange` 同时构造 `form.foodSpecs`(完整规格对象,写关联) 与 `form.sku`(精简 JSON,写 food_sku),编辑回显由详情 foodSpecs 抽 id(依赖 T014/T021) **Checkpoint**: US1+US2 可用——规格可挂到商品并持久化 --- ## Phase 5: User Story 3 - 商品列表与详情返回规格 (Priority: P1) **Goal**: 商品列表(分类/门店/搜索)与详情接口返回该商品规格结构(仅启用项),供前端渲染与下单 **Independent Test**: 挂规格的商品在 getfood/getidlist/stallFoodList/foodSearch 返回完整规格与加价;无规格商品返回空规格 ### Implementation for User Story 3 - [x] T023 [US3] 在 `PosFoodController` 抽取批量规格注入工具:按 foodId 集合一次查 `food_spec_relation` → 一次查相关 `food_specs`(isDelete=0,isOpen=1) → 一次查相关 `food_specs_value`(isOpen=1) → 内存分组,产出每商品的 `foodSpecs`+`foodSku`(命名统一为 `foodSpecsItems`,避免 N+1) - [x] T024 [US3] `getfood`(详情) 调用注入工具填 foodSpecs/foodSku(依赖 T023) - [x] T025 [US3] `getidlist` 批量注入规格(依赖 T023) - [x] T026 [US3] `stallFoodList` 批量注入规格(依赖 T023) - [x] T027 [US3] `foodSearch` 注入规格(依赖 T023) - [ ] T028 [US3] 前端商品列表/详情规格展示(遍历 foodSpecs 与 foodSpecsItems 显示名称/备注/加价),在 `foodie-store/src/views/` 相关商品组件实现 **Checkpoint**: US1+US2+US3 构成核心闭环(管理→挂载→展示)——可交付 MVP --- ## Phase 6: User Story 4 - 规格与订单/促销价格口径统一 (Priority: P2) **Goal**: 选规格下单时订单快照记录规格及加价,促销算价计入加价,三者口径一致 **Independent Test**: 顾客选加价规格下单,订单 otherPrice = 所选规格值加价之和,单价 = 商品价 + 加价;促销 specPrice 一致 ### Implementation for User Story 4 - [ ] T029 [US4] 确认/补全下单链路:订单商品快照写入所选规格及加价,`otherPrice` = 所选规格值加价之和(`ruoyi-admin/.../app/order/PosOrderController.java` 及 `OrderDTO`/`OrderCreatItem`,先勘察现有 otherPrice 写入点) - [ ] T030 [US4] 确认促销算价 `specPrice` 口径 = 所选规格值加价之和(`ruoyi-system/.../service/impl/PromotionCalcServiceImpl.java`,先勘察现有 specPrice 叠加点) - [ ] T031 [US4] 验证 otherPrice / specPrice / 规格加价三者一致(按 quickstart 场景 4) **Checkpoint**: 全链路价格一致 --- ## Phase 7: Polish & Cross-Cutting Concerns **Purpose**: 编译、验证、收尾 - [x] T032 [P] 编译验证:用 JDK21(graalvm-jdk-21.0.7)执行 `mvn -pl ruoyi-system,ruoyi-admin -am compile` 通过 - [ ] T033 按 `specs/013-food-spec/quickstart.md` 跑 5 个场景手动验证(规格管理/商品挂规格/列表返回/计价一致/兼容性) - [ ] T034 [P] 文档收尾:更新 spec 状态、在本会话 memory 记录 013-food-spec 要点 --- ## Dependencies & Execution Order ### Phase Dependencies - **Setup (Phase 1)**: 无依赖,立即开始(DDL 供开发者手动执行,不阻塞编码,但编码前最好先建表) - **Foundational (Phase 2)**: 依赖 Setup;**阻塞所有用户故事** - **US1 (Phase 3)**: 依赖 Foundational - **US2 (Phase 4)**: 依赖 Foundational;前端选规格依赖 US1 的 `getAvailableSpecsList`(T014) - **US3 (Phase 5)**: 依赖 Foundational;后端注入逻辑依赖 US2 已能写关联(否则无数据可返回,但代码可先行) - **US4 (Phase 6)**: 依赖 US2+US3(要先能挂规格、返回规格) - **Polish (Phase 7)**: 依赖所有目标 story 完成 ### Within Each User Story - 实体/Mapper/Service 先于 Controller - 后端先于前端 - 核心实现先于集成展示 ### Parallel Opportunities - Foundational 的实体(T002-T005)、Mapper(T006-T008)、Service(T009-T011) 各组内可并行(不同文件) - US1 的前端 API(T014) 与 i18n(T017) 可与页面(T015) 部分并行 - US3 的多个列表接口注入(T025-T027) 依赖同一工具(T023),T023 完成后可并行改造 --- ## Parallel Example: Foundational ```bash # 实体可并行创建(不同文件): Task: "FoodSpecs in domain/FoodSpecs.java" Task: "FoodSpecsValue in domain/FoodSpecsValue.java" Task: "FoodSpecRelation in domain/FoodSpecRelation.java" # Mapper 与 Service 依赖对应实体,实体就绪后各组并行 ``` --- ## Implementation Strategy ### MVP First(核心闭环 = US1+US2+US3) 1. 完成 Phase 1 Setup(DDL) 2. 完成 Phase 2 Foundational(**阻塞关键路径**) 3. 完成 US1 → 独立验证规格管理 4. 完成 US2 → 验证商品挂规格 5. 完成 US3 → 验证列表/详情返回规格 6. **STOP and VALIDATE**:US1+US2+US3 构成可交付 MVP ### Incremental Delivery - Foundational → US1(规格管理可用)→ +US2(商品可挂规格)→ +US3(规格可展示/下单可见)= MVP - +US4(价格口径增强,P2) ### Notes - [P] = 不同文件、无依赖,可并行 - [Story] 标签用于用户故事阶段任务追踪 - 价格一律与 `pos_food.price` 同口径(decimal/BigDecimal),不照搬源项目 Long(分) - 前端文件 CRLF,编辑用 Python 脚本替换;i18n 四语言 key 一致 - 后台 add/edit 收口(FR-011)与 setposfood 共用 `handleFoodSpec` - 规格名称是商家录入的动态多语言数据(按 language 字段),不做成 i18n key - 提交策略:按用户故事或逻辑分组提交(用户未要求建分支,直接在 test 分支)