docs(changelog): #7947 餐食删除编码、关联餐厅(含全部)、增加桌/人(修改接口·管理后台)
changelog-filename-gate / validate (push) Failing after 2s

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
这个提交包含在:
lc
2026-09-19 00:08:27 +08:00
共同撰写人 Claude Opus 5
父节点 81934d4957
当前提交 554c11bef7
@@ -0,0 +1,522 @@
---
schema: "hl-changelog/v2"
ticket: "7947"
title: "餐食删除编码,关联餐厅(含全部),增加桌/人"
consumer: "admin"
author: "lc(GIT)"
change_type: "修改接口"
backend_status: "deployed"
gateway_status: "verified"
frontend_status: "pending"
frontend_owner: ""
frontend_ref: ""
target_release: ""
verified_at: ""
status_note: "餐食接口去掉编码 dishCode;新增餐厅 restaurantId(不传为全部)与桌/人 priceUnit(person/table,不传按人);出参新增 restaurantId、restaurantName(全部返回「全部」)、priceUnit。编码、图片 URL、餐厅 ID 不需要展示。前端需按本文对接。"
updated_at: "2026-09-19"
base: "dev-v3"
---
# resource: 餐食删除编码,关联餐厅(含全部),增加桌/人
> **服务**: hl-resource-service(dish 表随服务部署由 Flyway 迁移)
> **PR**: #7958
> **Issue**: #7947
> **日期**: 2026-09-19
> **影响范围**: 管理后台「餐食管理」5 个接口(分页、列表、详情、新建、修改)
---
## ⚠️ 关键变化
- 🗑️ **编码 `dishCode` 删除**:新建、修改不再有编码,分页不再按编码筛选,分页、列表、详情不再返回编码。340002「编码已存在」停用。
- 🆕 **餐食关联餐厅**:入参新增 `restaurantId`(餐厅资源里的餐厅 ID),**不传为「全部」**;出参新增 `restaurantId`(全部为 `null`)与 `restaurantName`(全部返回「全部」)。
- 🆕 **桌/人 `priceUnit`**:`person` 按人 / `table` 按桌,表示单价是每人还是每桌;**不传按人**。
- 🔁 **名称唯一范围变了**:同一餐厅(「全部」算一组)内名称不能重复,报 340003;不同餐厅可以同名、单价各自维护。
- 👁️ **不需要展示**:编码、图片 URL、餐厅 ID。
---
## 一、背景
餐食不再需要编码;餐食按餐厅维护,同一道餐食在不同餐厅价格不同,另有一种餐厅是「全部」(没有餐厅 ID);新增「桌/人」。已有餐食上线后都归「全部」、桌/人为「人」。
---
## 二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|------|------|------|----------|------|
| 1 | 分页查询餐食 | GET | `/admin/dish/items/page` | 修改 | 去掉查询参数 `dishCode`,`keyword` 只匹配名称;出参去掉 `dishCode`,加 `restaurantId`/`restaurantName`/`priceUnit` |
| 2 | 查询餐食列表 | GET | `/admin/dish/items/list` | 修改 | `keyword` 只匹配名称;出参同接口 1 |
| 3 | 查询餐食详情 | GET | `/admin/dish/items/{dishId}/view` | 修改 | 出参同接口 1 |
| 4 | 新建餐食 | POST | `/admin/dish/items/add` | 修改 | 去掉入参 `dishCode`;加 `restaurantId`、`priceUnit`;重名按餐厅分组 |
| 5 | 修改餐食 | PUT | `/admin/dish/items/{dishId}/update` | 修改 | 同接口 4;整份覆盖,`restaurantId` 不传即改为全部,`priceUnit` 不传即按人 |
---
## 三、接口详情
### 1. 分页查询餐食 `GET /admin/dish/items/page`
**VO**: `DishPageReqVO` → `PageResult<DishListItemRespVO>`
#### 使用场景
餐食管理列表分页查询。
#### 入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| keyword | Query | String | 否 | 去首尾空格后 ≤64 | **只模糊匹配名称**(原来同时匹配编码) |
| ~~dishCode~~ | Query | — | — | — | **已删除**;继续传会被忽略 |
| status / settleType / createdByName / page / pageSize | Query | — | 否 | 不变 | 不变 |
#### 出参字段表
| 字段 | 类型 | 说明 |
|------|------|------|
| records[].~~dishCode~~ | — | **已删除** |
| records[].restaurantId | String \| null | **新增**。餐厅 ID;「全部」为 `null` |
| records[].restaurantName | String \| null | **新增**。餐厅名称;「全部」返回 `"全部"`;餐厅已删除为 `null` |
| records[].priceUnit | String | **新增**。`person` 按人 / `table` 按桌 |
| records[] 其余字段、total / page / pageSize | — | 不变 |
#### 请求示例
```http
GET /admin/dish/items/page?page=1&pageSize=20&keyword=团队
Authorization: Bearer <admin token>
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"records": [
{
"dishId": "2100767707048636418",
"restaurantId": null,
"restaurantName": "全部",
"dishName": "团队标准餐(八菜一汤)",
"unitPrice": 40.00,
"priceUnit": "person",
"imageUrl": null,
"settleType": "cash",
"status": 1,
"remark": null,
"createdBy": "1900000000000000001",
"createdByName": "张三",
"createdAt": "2026-09-18 10:02:11"
},
{
"dishId": "2100767707048636501",
"restaurantId": "2023382100664676353",
"restaurantName": "菌香园火锅",
"dishName": "团队升级餐(十菜一汤)",
"unitPrice": 600.00,
"priceUnit": "table",
"imageUrl": null,
"settleType": "cash",
"status": 1,
"remark": "含一道特色菜",
"createdBy": "1900000000000000001",
"createdByName": "张三",
"createdAt": "2026-09-18 10:05:40"
}
],
"total": 2,
"page": 1,
"pageSize": 20
}
}
```
#### 空数据 / 降级响应
不变:无数据时 `records` 为 `[]`、`total` 为 0。
#### 错误响应
```json
{ "code": 400, "message": "每页条数最大为100", "success": false, "data": null }
```
#### 业务边界
- 旧前端继续传 `dishCode` 查询参数不报错,但不再筛选。
---
### 2. 查询餐食列表 `GET /admin/dish/items/list`
**VO**: `DishListReqVO` → `List<DishListItemRespVO>`
#### 使用场景
餐食下拉数据源(只返回上架餐食,不按餐厅过滤)。
#### 入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| keyword | Query | String | 否 | 去首尾空格后 ≤64 | **只模糊匹配名称** |
| settleType / limit | Query | — | 否 | 不变 | 不变 |
#### 出参字段表
| 字段 | 类型 | 说明 |
|------|------|------|
| [] | — | 字段同接口 1 的 `records[]`:去掉 `dishCode`,新增 `restaurantId`、`restaurantName`、`priceUnit` |
#### 请求示例
```http
GET /admin/dish/items/list?limit=50
Authorization: Bearer <admin token>
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"success": true,
"data": [
{
"dishId": "2100767707048636418",
"restaurantId": null,
"restaurantName": "全部",
"dishName": "团队标准餐(八菜一汤)",
"unitPrice": 40.00,
"priceUnit": "person",
"imageUrl": null,
"settleType": "cash",
"status": 1,
"remark": null,
"createdBy": "1900000000000000001",
"createdByName": "张三",
"createdAt": "2026-09-18 10:02:11"
}
]
}
```
#### 空数据 / 降级响应
不变:没有上架餐食时 `data` 为 `[]`。
#### 错误响应
```json
{ "code": 400, "message": "limit最大为200", "success": false, "data": null }
```
#### 业务边界
- 同名餐食在「全部」和各餐厅下各有一条时会同时出现,用 `restaurantName` 区分。
---
### 3. 查询餐食详情 `GET /admin/dish/items/{dishId}/view`
**VO**: `DishRespVO`
#### 使用场景
编辑回显。
#### 入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| dishId | Path | String | 是 | 正数 | 餐食 ID(不变) |
#### 出参字段表
| 字段 | 类型 | 说明 |
|------|------|------|
| 全部字段 | — | 同接口 1 的 `records[]`:去掉 `dishCode`,新增 `restaurantId`、`restaurantName`、`priceUnit` |
#### 请求示例
```http
GET /admin/dish/items/2100767707048636501/view
Authorization: Bearer <admin token>
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"dishId": "2100767707048636501",
"restaurantId": "2023382100664676353",
"restaurantName": "菌香园火锅",
"dishName": "团队升级餐(十菜一汤)",
"unitPrice": 600.00,
"priceUnit": "table",
"imageUrl": null,
"settleType": "cash",
"status": 1,
"remark": "含一道特色菜",
"createdBy": "1900000000000000001",
"createdByName": "张三",
"createdAt": "2026-09-18 10:05:40"
}
}
```
#### 空数据 / 降级响应
不变:餐食不存在或已删除返回 340001。
#### 错误响应
```json
{ "code": 340001, "message": "餐食不存在", "success": false, "data": null }
```
#### 业务边界
- 回显时 `restaurantId` 为 `null` 即「全部」。
---
### 4. 新建餐食 `POST /admin/dish/items/add`
**VO**: `DishCreateReqVO` → `DishWriteRespVO`
#### 使用场景
新建餐食。
#### 入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| ~~dishCode~~ | Body | — | — | — | **已删除**;继续传会被忽略,不再校验 |
| restaurantId | Body | String | 否 | 餐厅资源里的餐厅 ID | **新增**。不传为「全部」 |
| priceUnit | Body | String | 否 | `person` / `table` | **新增**。不传按 `person` 存 |
| dishName | Body | String | 是 | 去首尾空格后 1–500 | 同一餐厅(全部算一组)内不能重复 |
| unitPrice / imageUrl / settleType / status / remark | Body | — | — | 不变 | 不变 |
#### 出参字段表
| 字段 | 类型 | 说明 |
|------|------|------|
| dishId | String | 新餐食 ID(不变) |
| dishName | String | 餐食名称(不变) |
#### 请求示例
```json
{
"restaurantId": "2023382100664676353",
"dishName": "团队升级餐(十菜一汤)",
"unitPrice": 600.00,
"priceUnit": "table",
"settleType": "cash",
"remark": "含一道特色菜"
}
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"success": true,
"data": { "dishId": "2100767707048636501", "dishName": "团队升级餐(十菜一汤)" }
}
```
#### 空数据 / 降级响应
不变:要么 200 写入,要么返回错误码且零写入。
#### 错误响应
```json
{ "code": 340003, "message": "餐食名称已存在:团队升级餐(十菜一汤)", "success": false, "data": null }
```
```json
{ "code": 400, "message": "桌/人必须是 person 或 table", "success": false, "data": null }
```
#### 业务边界
- 同名餐食可以在「全部」和不同餐厅下各建一条;同一餐厅(或都不传餐厅)下重名报 340003。
---
### 5. 修改餐食 `PUT /admin/dish/items/{dishId}/update`
**VO**: `DishUpdateReqVO` → `DishWriteRespVO`
#### 使用场景
修改餐食(整份覆盖)。
#### 入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|------|------|------|------|------|------|
| dishId | Path | String | 是 | 正数 | 餐食 ID(不变) |
| ~~dishCode~~ | Body | — | — | — | **已删除**;继续传会被忽略 |
| restaurantId | Body | String | 否 | 餐厅资源里的餐厅 ID | **新增**。整份覆盖:不传即改为「全部」,编辑时须带回原值 |
| priceUnit | Body | String | 否 | `person` / `table` | **新增**。整份覆盖:不传即按 `person` 存,编辑时须带回原值 |
| 其余入参 | Body | — | — | 不变 | dishName、unitPrice、imageUrl、settleType、status、remark 不变 |
#### 出参字段表
| 字段 | 类型 | 说明 |
|------|------|------|
| dishId | String | 餐食 ID(不变) |
| dishName | String | 修改后的名称(不变) |
#### 请求示例
```json
{
"restaurantId": "2023382100664676353",
"dishName": "团队升级餐(十菜一汤)",
"unitPrice": 620.00,
"priceUnit": "table",
"imageUrl": null,
"settleType": "cash",
"status": 1,
"remark": "含一道特色菜"
}
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"success": true,
"data": { "dishId": "2100767707048636501", "dishName": "团队升级餐(十菜一汤)" }
}
```
#### 空数据 / 降级响应
不变:要么 200 覆盖写入,要么返回错误码且不改动数据。
#### 错误响应
```json
{ "code": 340003, "message": "餐食名称已存在:团队升级餐(十菜一汤)", "success": false, "data": null }
```
#### 业务边界
- 查重在修改后的餐厅内进行,排除自己。
---
## 四、契约约束与正确调用方式
| 场景 | payload |
|------|---------|
| ✅ 「全部」下按人的餐食 | `{ "dishName": "素斋套餐", "unitPrice": 50.00 }` |
| ✅ 某餐厅下按桌的餐食 | `{ "restaurantId": "2023382100664676353", "dishName": "团队升级餐(十菜一汤)", "unitPrice": 600.00, "priceUnit": "table" }` |
| ✅ 同名餐食在另一家餐厅 | 换一个 `restaurantId` 再建同名,单价可不同 |
| ❌ 同一餐厅(或「全部」)下重名 | 340003「餐食名称已存在」 |
| ❌ `priceUnit` 传 `seat` | 400「桌/人必须是 person 或 table」 |
| ⚠️ 修改时漏传 `restaurantId` / `priceUnit` | 整份覆盖:会改成「全部」/按人 |
---
## 五、数据库行为
`dish` 表删除 `dish_code`,新增 `restaurant_id`(空即全部)、`price_unit`(默认 person);名称唯一改为同一餐厅(全部算一组)内唯一。已有餐食迁移后都是「全部」、按人,其余字段不变。
---
## 六、边界行为
- 未登录 → 401(网关拦截),不变。
- 餐厅被删除后,其下餐食的 `restaurantName` 为 `null`,`restaurantId` 仍返回。
---
## 六.6、修改前后对比
### 字段级对比
| 字段 | 改前 | 改后 |
|------|------|------|
| `dishCode`(入参、出参、分页查询参数) | 必填,≤20,唯一 | 删除 |
| `restaurantId`(入参、出参) | 无 | 新增,空即全部 |
| `restaurantName`(出参) | 无 | 新增,全部返回「全部」 |
| `priceUnit`(入参、出参) | 无 | 新增,person / table,不传按人 |
| `keyword` | 匹配编码或名称 | 只匹配名称 |
### 行为级对比
| 行为 | 改前 | 改后 |
|------|------|------|
| 名称唯一 | 全部未删除餐食内唯一 | 同一餐厅(全部算一组)内唯一 |
| 340002 编码已存在 | 会返回 | 停用 |
## 六.7、影响评估
- **是否破坏向后兼容**: 是(出参去掉 `dishCode`;旧前端多传的 `dishCode` 会被忽略,不报错)
- **前端是否必须同步上线**: 是,按本文对接餐厅与桌/人;编码、图片 URL、餐厅 ID 不需要展示
- **前端 workaround 清理点**: 去掉编码相关的传参与展示
## 七、不影响范围
- **仅影响**: 餐食接口 1–5
- **零影响**:
- 餐食上下架 `PUT /admin/dish/items/{dishId}/status/update`、删除 `DELETE /admin/dish/items/{dishId}/del`
- 订单用餐信息、用餐模版接口(其中的「餐食编码快照」以后不会再有值)
---
## 八、测试环境已验证
部署提交 `4cbccc26b`,经 Gateway 用真实身份实测 34 项全部通过:
```
已有 9 行餐食(含已删除) → 编码列已删,其余列与部署前一致;全部归「全部」、按人 ✓
分页、列表、7 个详情 → 不再有 dishCode,其余字段与部署前逐字段一致 ✓
不传编码新建、修改;传空串 / 25 字符 / 重复编码 → 均成功,编码被忽略 ✓
按 22 行数据表新建(全部 10 道 + 3 家餐厅各 4 道) → 全部成功,详情逐字段一致;同名在全部与 3 家餐厅各一条、单价不同 ✓
不传 priceUnit 新建、修改 → 存为 person ✓
全部下 / 同一餐厅下重名,修改成已有名称 → 340003,零写入 ✓
Swagger 部署前后 → 餐食模型只去 dishCode、加 restaurantId/restaurantName/priceUnit,其余一致 ✓
```
---
## 十、相关文档
- 关联 Issue: [wx/HL#7947](https://git.1814.love:8443/wx/HL/issues/7947)
- 关联 PR: [wx/HL#7958](https://git.1814.love:8443/wx/HL/pulls/7958)
## 关联 / 联系人
### 链接
- **Issue**: [#7947](https://git.1814.love:8443/wx/HL/issues/7947)
- **PR**: [#7958](https://git.1814.love:8443/wx/HL/pulls/7958)
- **Merge commit**: [4cbccc26b](https://git.1814.love:8443/wx/HL/commit/4cbccc26b3d8856c61e61902098bb79afba83147)
### 联系人
- **后端负责人**: @lc