--- schema: "hl-changelog/v2" ticket: "7947" title: "餐食删除编码,关联餐厅(含全部),增加桌/人" consumer: "admin" author: "lc(GIT)" change_type: "修改接口" backend_status: "deployed" gateway_status: "verified" frontend_status: "verified" frontend_owner: "mmg" frontend_ref: "e0d613c113fb5268e9cd2f790885046fce8ab1d0" target_release: "" verified_at: "2026-09-20" status_note: "餐食接口去掉编码 dishCode;新增餐厅 restaurantId(不传为全部)与桌/人 priceUnit(person/table,不传按人);出参新增 restaurantId、restaurantName(全部返回「全部」)、priceUnit。编码、图片 URL、餐厅 ID 不需要展示。前端需按本文对接。前端已交付(hl-ui v2.1 @ e0d613c1):列表删编码列、加餐厅列(restaurantName,餐厅已删 null 显 -)、单价带 /人/桌后缀、keyword placeholder 去编码;编辑弹窗删 dishCode 表单项/rules/回填/提交,加餐厅远程候选(分页 options 首焦拉首页+防抖+请求序号防旧响应,编辑回显餐厅不在候选时注入当前项,清空=「全部」create 不带键/update 传 null)与桌/人 radio(默认 person、编辑带回显值);两 spec 重写 17/17,scoped checkpoint 全绿。" 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` #### 使用场景 餐食管理列表分页查询。 #### 入参字段表 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | 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 ``` #### 响应示例 ```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` #### 使用场景 餐食下拉数据源(只返回上架餐食,不按餐厅过滤)。 #### 入参字段表 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | keyword | Query | String | 否 | 去首尾空格后 ≤64 | **只模糊匹配名称** | | settleType / limit | Query | — | 否 | 不变 | 不变 | #### 出参字段表 | 字段 | 类型 | 说明 | |------|------|------| | [] | — | 字段同接口 1 的 `records[]`:去掉 `dishCode`,新增 `restaurantId`、`restaurantName`、`priceUnit` | #### 请求示例 ```http GET /admin/dish/items/list?limit=50 Authorization: Bearer ``` #### 响应示例 ```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 ``` #### 响应示例 ```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