---

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<FoodSpecsValue>`
- [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<FoodSpecs>`（`@TableField(exist=false)`），确认既有 transient `sku: JSONArray` 可用
- [x] T006 [P] 新建 `FoodSpecsMapper`（`ruoyi-system/.../mapper/FoodSpecsMapper.java`，`extends BaseMapper<FoodSpecs>`）+ XML（`ruoyi-system/src/main/resources/mapper/system/FoodSpecsMapper.xml`，含 resultMap 与标准 CRUD）
- [x] T007 [P] 新建 `FoodSpecsValueMapper` + XML（同上目录结构，`extends BaseMapper<FoodSpecsValue>`）
- [x] T008 [P] 新建 `FoodSpecRelationMapper` + XML（同上，`extends BaseMapper<FoodSpecRelation>`）
- [x] T009 [P] 新建 `IFoodSpecsService`（`extends IService<FoodSpecs>`）+ `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<FoodSpecs>)`：规格组 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 分支）
