Input: Design documents from /specs/013-food-spec/
Prerequisites: plan.md, spec.md, research.md, data-model.md, contracts/api.md, quickstart.md
已完成(后端,mvn compile 通过): T001(DDL)、T002-T011(实体/Mapper/Service/XML)、T012-T013(FoodSpecController)、T018-T020(PosFoodController setposfood/add/edit 收口)、T023-T027(列表/详情注入规格)、T032(编译)。
本期暂缓(用户决定 2026-07-15):
待开发者执行: T033——先执行 updatesql/sql.md 建表,再按 quickstart.md 跑场景。
Tests: 不包含自动化测试任务(项目无测试框架,spec 未要求 TDD);验证通过 quickstart.md 手动场景完成。
Organization: 按用户故事分组,便于独立实现与验证。US1/US2/US3 为 P1,US4 为 P2。
[ID] [P?] [Story] Descriptionruoyi-system/src/main/java/com/ruoyi/system/...,XML 在 ruoyi-system/src/main/resources/mapper/system/ruoyi-admin/src/main/java/com/ruoyi/app/mendian/...(与 PosFoodController 同包)E:\QtwCode\foodie\foodie-store\src\...updatesql/sql.mdPurpose: 数据库结构准备
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 与用途注释Purpose: 规格体系基础数据层(实体/Mapper/Service),所有用户故事共用
⚠️ CRITICAL: 本阶段完成前不得开始任何用户故事
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>FoodSpecsValue(ruoyi-system/src/main/java/com/ruoyi/system/domain/FoodSpecsValue.java):@TableName("food_specs_value"),字段 id/parentId/name/price(BigDecimal)/note/state/isOpenFoodSpecRelation(ruoyi-system/src/main/java/com/ruoyi/system/domain/FoodSpecRelation.java):@TableName("food_spec_relation"),字段 id/foodId/specsIdPosFood(ruoyi-system/src/main/java/com/ruoyi/system/domain/PosFood.java):新增 transient foodSpecs: List<FoodSpecs>(@TableField(exist=false)),确认既有 transient sku: JSONArray 可用FoodSpecsMapper(ruoyi-system/.../mapper/FoodSpecsMapper.java,extends BaseMapper<FoodSpecs>)+ XML(ruoyi-system/src/main/resources/mapper/system/FoodSpecsMapper.xml,含 resultMap 与标准 CRUD)FoodSpecsValueMapper + XML(同上目录结构,extends BaseMapper<FoodSpecsValue>)FoodSpecRelationMapper + XML(同上,extends BaseMapper<FoodSpecRelation>)IFoodSpecsService(extends IService<FoodSpecs>)+ FoodSpecsServiceImpl(ruoyi-system/.../service/ 与 service/impl/)IFoodSpecsValueService + FoodSpecsValueServiceImpl(同上模式)IFoodSpecRelationService + FoodSpecRelationServiceImpl(同上模式)Checkpoint: 规格三实体/Mapper/Service 就绪,可开始用户故事
Goal: 商家在商家端对本门店规格组(含级联规格值)做增删改查、启停、软删
Independent Test: 商家新建"甜度"(单选/必选)+3 个规格值,保存后在列表可见、可编辑、可停用、可软删;跨门店不可见
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/changeSpecValueOpenFoodSpecController 实现 saveFoodSpec(@RequestBody List<FoodSpecs>):规格组 id≤0 新增否则更新,级联 saveOrUpdate 其下 foodSpecsItems(规格值),事务保证一致(依赖 T009/T010)foodie-store/src/api/specs.js(foodSpecPageList/saveFoodSpec/getSpecs/deleteFoodSpec/getAvailableSpecsList/changeOpen/changeSpecValueOpen)foodie-store/src/views/FoodSpec.vue(或遵循 store 现有命名):分页列表(按门店切换)+ 新增/编辑弹窗(嵌套规格值子表增删改 name/price/note/isOpen)+ 启停开关 + 软删foodie-store/src/lang/ 的 zh.js/tw.js/en.js/vi.js 同一对象层级追加规格相关 key(有意义驼峰命名,四文件 key 一致)Checkpoint: US1 独立可用——规格模板完整管理闭环
Goal: 商家编辑商品时可从本门店可用规格多选规格组挂到商品;保存建立/更新关联
Independent Test: 商家编辑"珍珠奶茶"勾选"甜度""加料",保存后详情返回这两个规格;重新勾选后关联正确更新
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)setposfood 保存商品后调用 handleFoodSpec(依赖 T018)add/edit 也调用 handleFoodSpec,统一收口(FR-011,修复当前后台通道不写规格)foodie-store/src/api/food.js 的 setposfood 提交体增加 foodSpecs 与 skuel-select multiple 选 getAvailableSpecsList 结果,handleSpecChange 同时构造 form.foodSpecs(完整规格对象,写关联) 与 form.sku(精简 JSON,写 food_sku),编辑回显由详情 foodSpecs 抽 id(依赖 T014/T021)Checkpoint: US1+US2 可用——规格可挂到商品并持久化
Goal: 商品列表(分类/门店/搜索)与详情接口返回该商品规格结构(仅启用项),供前端渲染与下单
Independent Test: 挂规格的商品在 getfood/getidlist/stallFoodList/foodSearch 返回完整规格与加价;无规格商品返回空规格
PosFoodController 抽取批量规格注入工具:按 foodId 集合一次查 food_spec_relation → 一次查相关 food_specs(isDelete=0,isOpen=1) → 一次查相关 food_specs_value(isOpen=1) → 内存分组,产出每商品的 foodSpecs+foodSku(命名统一为 foodSpecsItems,避免 N+1)getfood(详情) 调用注入工具填 foodSpecs/foodSku(依赖 T023)getidlist 批量注入规格(依赖 T023)stallFoodList 批量注入规格(依赖 T023)foodSearch 注入规格(依赖 T023)foodie-store/src/views/ 相关商品组件实现Checkpoint: US1+US2+US3 构成核心闭环(管理→挂载→展示)——可交付 MVP
Goal: 选规格下单时订单快照记录规格及加价,促销算价计入加价,三者口径一致
Independent Test: 顾客选加价规格下单,订单 otherPrice = 所选规格值加价之和,单价 = 商品价 + 加价;促销 specPrice 一致
otherPrice = 所选规格值加价之和(ruoyi-admin/.../app/order/PosOrderController.java 及 OrderDTO/OrderCreatItem,先勘察现有 otherPrice 写入点)specPrice 口径 = 所选规格值加价之和(ruoyi-system/.../service/impl/PromotionCalcServiceImpl.java,先勘察现有 specPrice 叠加点)Checkpoint: 全链路价格一致
Purpose: 编译、验证、收尾
mvn -pl ruoyi-system,ruoyi-admin -am compile 通过specs/013-food-spec/quickstart.md 跑 5 个场景手动验证(规格管理/商品挂规格/列表返回/计价一致/兼容性)getAvailableSpecsList(T014)# 实体可并行创建(不同文件):
Task: "FoodSpecs in domain/FoodSpecs.java"
Task: "FoodSpecsValue in domain/FoodSpecsValue.java"
Task: "FoodSpecRelation in domain/FoodSpecRelation.java"
# Mapper 与 Service 依赖对应实体,实体就绪后各组并行
pos_food.price 同口径(decimal/BigDecimal),不照搬源项目 Long(分)handleFoodSpec