# 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 阶段依据现状进一步确定。
