# Feature Specification: 商品规格(SKU 规格)管理 **Feature Branch**: 在 `test` 分支直接开发(按用户要求不创建特性分支) **Created**: 2026-07-15 **Status**: Draft **Input**: User description: "将 cte_server 的商品规格功能移植到 foodie_server,包括商品规格的管理、商品使用规格、商品列表返回规格信息" ## User Scenarios & Testing *(mandatory)* ### User Story 1 - 商家管理门店规格模板 (Priority: P1) 商家在商家端维护本门店可复用的"规格"(如甜度、加料、辣度)。每个规格是一个**规格组**,组下有若干**规格值**(如"无糖/半糖/全糖"、"珍珠/椰果"),每个规格值可设定加价金额。规格组支持单选/多选、必选/可选、排序、启用/停用、软删除。 **Why this priority**: 规格模板是商品使用规格的前提;没有它,商品无处挂规格,是整个功能的基础。 **Independent Test**: 商家新增一个"甜度"规格(单选/必选),下设"无糖(加价0)/半糖(0)/全糖(0)"三个规格值,保存后在规格列表可见、可编辑、可停用。 **Acceptance Scenarios**: 1. **Given** 商家已登录其门店,**When** 商家新增规格组并填入名称、选择类型(单选/多选)、必选/可选、若干规格值及加价,**Then** 保存成功且列表可见。 2. **Given** 规格组已存在,**When** 商家编辑修改名称或增删规格值,**Then** 保存后详情反映最新内容。 3. **Given** 规格组/规格值已存在,**When** 商家停用某规格组或某规格值,**Then** 该规格组/值在"商品可用规格"中不再出现。 4. **Given** 规格组已存在,**When** 商家删除规格组,**Then** 执行软删除,商品可用规格列表不再显示,但已生成订单与历史快照不受影响。 --- ### User Story 2 - 商品使用规格 (Priority: P1) 商家在新增/编辑商品时,可从本门店"可用规格"中勾选若干规格组挂到该商品上。保存商品时,商品与所选规格组建立关联;重新勾选时,旧的关联被清除、新的关联被建立。 **Why this priority**: 这是规格功能的落地环节,没有它规格模板毫无意义,与 Story 1 同属 MVP。 **Independent Test**: 商家编辑"珍珠奶茶",勾选"甜度""加料"两个规格组,保存后商品详情能返回这两个规格。 **Acceptance Scenarios**: 1. **Given** 门店已有可用规格,**When** 商家编辑商品并勾选规格组,**Then** 保存后商品与规格建立关联。 2. **Given** 商品已挂规格,**When** 商家编辑时重新勾选(增减规格组),**Then** 保存后关联更新(旧关联清除、新关联建立)。 3. **Given** 商品未挂任何规格,**When** 商家不勾选,**Then** 商品按无规格售卖(兼容现有行为)。 --- ### User Story 3 - 商品列表与详情返回规格 (Priority: P1) 商品列表(按分类、按门店、搜索分页)和商品详情接口返回该商品的规格结构(规格组 + 规格值 + 加价),前端据此渲染规格选择。顾客选定规格值后,所选规格值的加价累加到商品基础价。 **Why this priority**: 规格必须能被下单链路消费,否则规格只是摆设,属于核心价值闭环。 **Independent Test**: 顾客打开一个挂了规格的商品详情,能看到规格选项及对应加价。 **Acceptance Scenarios**: 1. **Given** 商品挂了规格,**When** 查询商品列表/详情,**Then** 返回完整规格结构(仅含启用的规格组与规格值)。 2. **Given** 商品未挂规格,**When** 查询,**Then** 返回空规格(与现有无规格行为兼容)。 3. **Given** 顾客选了若干规格值,**When** 计算价格,**Then** 实付单价 = 商品基础价 + 所选规格值加价之和。 --- ### User Story 4 - 规格与订单/促销价格口径统一 (Priority: P2) 顾客选规格下单时,订单商品快照保存所选规格及加价;促销算价将规格加价计入。与现有 `specPrice`/`otherPrice` 加价口径对齐,保证下单、促销、订单各环节金额一致。 **Why this priority**: 保证规格价格在跨环节一致、避免算错钱;属正确性保障,依赖前三条已具备能力,故 P2。 **Independent Test**: 顾客选加价规格下单,订单快照含规格与加价,且金额与所选规格加价完全一致。 **Acceptance Scenarios**: 1. **Given** 顾客选规格下单,**When** 生成订单,**Then** 订单商品快照记录所选规格及加价。 2. **Given** 订单含规格加价,**When** 参与促销算价,**Then** 规格加价正确叠加到单价。 --- ### Edge Cases - 商品已挂的规格组随后被门店停用/删除:列表与详情返回时自动剔除该规格组(顾客不可选)。 - 规格值加价在商品售卖期间发生变更:已生成的订单快照不受影响(快照独立、不可变)。 - 多语言:规格组/规格值名称需按门店 + 语言维度管理(项目支持 vi/zh/tw/en)。 - 门店隔离:A 门店的规格对 B 门店完全不可见。 - 商品保存同时携带结构化规格关联与旧版 `food_sku` JSON:以结构化规格关联为权威来源,`food_sku` JSON 作为冗余兼容字段同步更新。 - 后台管理通道(平台端商品新增/编辑)当前不写规格信息:需统一收口,保证两条保存通道都能持久化规格。 ## Requirements *(mandatory)* ### Functional Requirements - **FR-001**: 系统 MUST 支持门店级"规格组"的增、删(软删)、改、查;每个规格组包含名称、选择类型(单选/多选)、是否必选、排序、启用状态、所属门店、语言。 - **FR-002**: 每个规格组下 MUST 能管理若干"规格值",每个规格值包含名称、加价金额、备注、启用状态。 - **FR-003**: 规格组与规格值 MUST 支持启用/停用,停用后在"商品可用规格"中不可见。 - **FR-004**: 商家编辑商品时 MUST 能从本门店可用规格中多选规格组挂到商品。 - **FR-005**: 保存商品时系统 MUST 维护商品与规格组的关联(重新勾选时清除旧关联、建立新关联)。 - **FR-006**: 商品列表(按分类、按门店、搜索分页)与商品详情接口 MUST 返回该商品的规格结构(仅启用的规格组与规格值)。 - **FR-007**: 顾客选定规格后,实付单价 MUST 等于商品基础价加上所选规格值加价之和。 - **FR-008**: 下单时订单商品快照 MUST 记录所选规格及加价,且促销算价将规格加价计入(与现有 `specPrice`/`otherPrice` 口径一致)。 - **FR-009**: 规格数据 MUST 按门店隔离,A 门店的规格对 B 门店不可见。 - **FR-010**: 规格组与规格值 MUST 支持多语言(vi/zh/tw/en)。 - **FR-011**: 后台管理通道(平台端商品新增/编辑)保存商品时也 MUST 能正确持久化规格信息,与商家端通道行为一致。 ### Key Entities *(include if feature involves data)* - **规格组 (Spec Group)**: 一个可复用的规格维度,如"甜度""加料"。属于某门店、某种语言。含选择类型(单选/多选)、是否必选、排序、启用状态。下挂多个规格值。 - **规格值 (Spec Value)**: 规格组下的一个可选项,如"无糖""珍珠"。含名称、加价金额、备注、启用状态。从属于一个规格组。 - **商品-规格关联 (Food-Spec Relation)**: 商品与规格组的多对多关联,表达"该商品使用了哪些规格组"。 - **商品 (Food)**: 已有实体,本次扩展其与规格的关联能力;保留既有 `food_sku` JSON 冗余字段以兼容。 - **门店 (Store)**: 规格组的归属维度(规格组门店级共享、商品复用)。 ## Success Criteria *(mandatory)* ### Measurable Outcomes - **SC-001**: 商家能在 1 分钟内为门店新建一个含 3 个规格值的规格组并保存成功。 - **SC-002**: 商家编辑商品勾选/取消规格后,保存即时生效,商品详情立即反映正确规格(成功率 100%)。 - **SC-003**: 顾客打开任意挂规格的商品详情,规格选项与加价 100% 正确展示。 - **SC-004**: 顾客选规格下单,订单金额与所选规格加价完全一致(误差为 0)。 - **SC-005**: 规格数据严格按门店隔离,跨门店查询规格返回为空(100%)。 ## Assumptions - 采用源项目 cte_server 的"规格加价"模型(规格组 + 规格值 + 关联表),而非电商式笛卡尔积 SKU(每组合独立价格/库存/编码)。规格值是对商品基础价的加价累加。 - 规格组为门店级共享(一个门店定义规格,本门店多个商品复用),与源项目一致。 - 规格组带 `language` 字段,按门店 + 语言维度管理(参照源项目 `FoodSpecs.language`)。 - 保留 foodie 现有 `pos_food.food_sku` JSON 字段作为冗余兼容,与结构化规格关联双写,避免破坏现有前端/订单读取逻辑。 - 规格管理入口放在商家端(与现有商品管理入口一致),不在 C 端顾客侧。 - 价格单位沿用项目现有约定(商品价格 `price` 为数值,规格加价同口径)。 - 复用项目既有技术栈与基础设施(Spring Boot + MyBatis-Plus + fastjson + 若依 `BaseController`/`AjaxResult` + 自定义 `@Auth`/`JwtUtil`)。 - 规格组/规格值名称的多语言由数据本身的 `language` 字段承载(商家录入的多语言数据);前端 i18n 框架仅用于固定 UI 文案。 - 本期聚焦后端能力 + 商家端规格管理/商品编辑交互;具体前端组件与落地范围在 plan 阶段依据现状进一步确定。