# Data Model: 商品规格 **Feature**: 013-food-spec | **Date**: 2026-07-15 ## 实体关系 ```text pos_store (门店) │ 1 │ │ N food_specs (规格组) ──< food_specs_value (规格值) │ N │ │ N (多对多,通过关联表) pos_food (商品) ──< food_spec_relation ──> food_specs ``` ## 新增表 ### food_specs(规格组) 门店级可复用规格维度(如"甜度""加料")。 | 列 | 类型 | 说明 | |----|------|------| | id | bigint PK auto | 主键 | | title | varchar(64) | 规格名(如"甜度") | | type | char(1) | 1=单选 2=多选 | | state | char(1) | 1=必选 2=可选 | | language | varchar(8) | 语言(沿用现有 language 取值约定) | | remark | varchar(255) | 备注 | | md_id | bigint | 所属门店 id | | sort | int | 排序 | | is_open | tinyint(1) | 是否启用 | | is_delete | tinyint(1) | 软删除标记 | **transient 字段**(不入库):`foodSpecsItems: List` —— 承载级联规格值,保存时作为入参、查询时作为出参。 ### food_specs_value(规格值) 规格组下的可选项(如"无糖""珍珠")。 | 列 | 类型 | 说明 | |----|------|------| | id | bigint PK auto | 主键 | | parent_id | bigint | 所属规格组 id(→ food_specs.id) | | name | varchar(64) | 规格值名 | | price | decimal(10,2) | 加价金额(与 pos_food.price 同口径) | | note | varchar(255) | 备注 | | state | char(1) | 状态 | | is_open | tinyint(1) | 是否启用 | ### food_spec_relation(商品-规格关联) 商品与规格组的多对多关联。 | 列 | 类型 | 说明 | |----|------|------| | id | bigint PK auto | 主键 | | food_id | bigint | 商品 id(→ pos_food.id) | | specs_id | bigint | 规格组 id(→ food_specs.id) | ## 既有表扩展 ### pos_food - 保留现有 `food_sku`(text/json)列:冗余存规格 JSON,与结构化关联双写。 - **transient 字段**(不入库): - `foodSpecs: List` —— 保存商品时作为入参(写关联表),查询时作为出参(带规格结构)。 - `sku: JSONArray` —— 保存时作为入参(toString 写入 `food_sku` 列);foodie 已有此字段,复用。 ## 校验规则(来自需求) - 规格组 `type ∈ {1,2}`,`state ∈ {1,2}`。 - 删除规格组为软删(`is_delete=1`);关联表记录保留(已生成订单不受影响)。 - 商品可用规格 = `is_open=1` 且 `is_delete=0` 的规格组,及其下 `is_open=1` 的规格值。 - 门店隔离:查询规格组强制按 `md_id` 过滤。 ## 索引建议 - `food_specs`: `(md_id, language, is_delete, is_open)` - `food_specs_value`: `(parent_id, is_open)` - `food_spec_relation`: `(food_id)`, `(specs_id)` ## 外键 不建物理外键(与项目现有约定一致),靠应用层维护引用完整性。