# API Contracts: 商品规格

**Feature**: 013-food-spec | **Date**: 2026-07-15

> 接口风格沿用 foodie 现有：`@Anonymous` + 自定义 `@Auth`(JWT)，返回 `AjaxResult`/`TableDataInfo`，fastjson 拼装。价格单位与 `pos_food.price` 一致（decimal）。

## 一、规格管理（新建 FoodSpecController，基路径 `/chanting/foodSpec`）

| Method | Path | 入参 | 出参 | 说明 |
|--------|------|------|------|------|
| GET | /foodSpecPageList | token,pageNum,pageSize,mdId,language | IPage<FoodSpecs>（每条带 foodSpecsItems） | 规格组分页 |
| POST | /saveFoodSpec | @RequestBody List<FoodSpecs>（含级联 foodSpecsItems） | AjaxResult | 新增/编辑规格组+级联值 |
| GET | /getSpecs | id | FoodSpecs（带 foodSpecsItems） | 规格详情 |
| GET | /deleteFoodSpec | id | AjaxResult | 软删除(is_delete=1) |
| GET | /getAvailableSpecsList | mdId,language | List<FoodSpecs>（is_open=1 & is_delete=0，带 foodSpecsItems） | 商品编辑页选规格用 |
| GET | /changeOpen | id,isOpen | AjaxResult | 启停规格组 |
| GET | /changeSpecValueOpen | id,isOpen | AjaxResult | 启停规格值 |

### 保存请求体示例（POST /saveFoodSpec）

```json
[
  {
    "id": 0,
    "title": "甜度",
    "type": "1",
    "state": "1",
    "language": "zh",
    "mdId": 3,
    "sort": 0,
    "isOpen": true,
    "foodSpecsItems": [
      { "id": 0, "name": "无糖", "price": 0, "note": "", "isOpen": true },
      { "id": 0, "name": "全糖", "price": 0, "isOpen": true }
    ]
  }
]
```

## 二、商品接口规格部分（改 PosFoodController，基路径 `/chanting/food`）

### 1. 保存商品 POST /setposfood（及后台 add/edit）

请求体 `PosFood` 复用/增加：
- `foodSpecs: List<FoodSpecs>` —— 所选规格组（写 `food_spec_relation`）
- `sku: JSONArray` —— 规格精简 JSON（toString 写 `pos_food.food_sku`）

后端处理（setposfood 与 add/edit 共用同一私有方法）：
1. 保存商品本身。
2. 删除该 `food_id` 的旧关联，按 `foodSpecs[*].id` 批量插入新关联。
3. `sku.toString()` 写入 `food_sku`。

### 2. 商品详情 GET /getfood?id=

响应在商品对象上增加：
- `foodSpecs` —— 结构化规格（仅启用，明细字段名 `foodSpecsItems`）
- `foodSku` —— 兼容 JSON（明细字段名统一为 `foodSpecsItems`）

### 3. 商品列表（需注入规格的接口）

- GET /getidlist（商家端分类列表）
- GET /stallFoodList（C 端摊位扫码列表）
- GET /foodSearch（搜索）

均批量注入 `foodSpecs` + `foodSku`（批量查关联/规格组/规格值 + 内存分组，避免 N+1）。

> 后台管理 GET /list、GET /{id} 为管理用途，本期不强制返回规格结构（可不含），以降低改动面。

### 列表/详情响应规格结构示例

```json
{
  "id": 100,
  "name": "珍珠奶茶",
  "price": 20.00,
  "foodSpecs": [
    {
      "id": 7, "title": "甜度", "type": "1", "state": "1",
      "mdId": 3, "sort": 0, "isOpen": true,
      "foodSpecsItems": [
        { "id": 15, "parentId": 7, "name": "无糖", "price": 0, "isOpen": true },
        { "id": 16, "parentId": 7, "name": "全糖", "price": 0, "isOpen": true }
      ]
    }
  ],
  "foodSku": [
    { "id": 7, "title": "甜度", "foodSpecsItems": [ "..." ] }
  ]
}
```

## 三、计价契约

- 顾客选定规格值集合 S，实付单价 = `pos_food.price` + Σ(`S.price`)。
- 该加价和 = 订单快照 `otherPrice` = 促销算价 `specPrice`（三者统一来源）。
