From 95d178979377be70ed820fd0f51067345ea38860 Mon Sep 17 00:00:00 2001 From: lc Date: Thu, 10 Sep 2026 22:17:51 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E4=BA=A4=E6=8E=A5=E9=A4=90=E5=8E=85?= =?UTF-8?q?=E4=BE=9B=E5=BA=94=E5=95=86=E5=85=B3=E7=B3=BB=E5=A5=91=E7=BA=A6?= =?UTF-8?q?=EF=BC=88#7387=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...Œ‰景区口径接入供应商关系-修改接口-管理后台.md | 463 ++++++++++++++++++ 1 file changed, 463 insertions(+) create mode 100644 changelogs-v2/2026-09/10_7387_餐厅按景区口径接入供应商关系-修改接口-管理后台.md diff --git a/changelogs-v2/2026-09/10_7387_餐厅按景区口径接入供应商关系-修改接口-管理后台.md b/changelogs-v2/2026-09/10_7387_餐厅按景区口径接入供应商关系-修改接口-管理后台.md new file mode 100644 index 00000000..2a077678 --- /dev/null +++ b/changelogs-v2/2026-09/10_7387_餐厅按景区口径接入供应商关系-修改接口-管理后台.md @@ -0,0 +1,463 @@ +--- +schema: "hl-changelog/v2" +ticket: "7387" +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: "后端已部署并经 Gateway 验证;待前端展示餐厅列表供应商全称并接入餐厅供应商关系操作。" +updated_at: "2026-09-10" +base: "dev-v3" +--- + +# 餐厅管理:按景区口径接入供应商关系 + +> **服务**: hl-resource-service(8082) +> **PR**: #7493 +> **Issue**: #7387 +> **日期**: 2026-09-10 +> **影响范围**: 管理端餐厅列表和餐厅供应商查询、设置、改绑、解绑 + +## ⚠️ 关键变化 + +- 餐厅列表每条记录固定返回 `supplierFullName`;无当前供应商时为 `null`。 +- 餐厅使用通用资源供应商关系接口,路径参数固定传 `resourceModule=RESTAURANT`。 +- 餐厅首次绑定、改绑和解绑都可以省略 `changeReason`;真正改绑或解绑前会检查 V2、V3 订单,存在未结束订单时返回 `395060`。 + +## 一、背景 + +餐厅管理此前无法从列表直接取得当前供应商全称,前端也未按餐厅模块接入通用供应商关系操作。本次冻结餐厅所需的展示字段、调用参数、原因规则和订单状态边界。 + +## 二、变更接口清单 + +| # | 接口 | 方法 | 路径 | 变更类型 | 说明 | +|---|---|---|---|---|---| +| 1 | 餐厅列表 | GET | `/admin/restaurant/items` | 响应字段新增 | 每条记录固定返回 `supplierFullName` | +| 2 | 查询餐厅当前供应商 | GET | `/admin/supplier/resource-relations/{resourceModule}/{resourceId}/view` | 调用契约补充 | 餐厅传 `resourceModule=RESTAURANT` | +| 3 | 设置或改绑餐厅供应商 | PUT | `/admin/supplier/resource-relations/{resourceModule}/{resourceId}/update` | 请求与行为修改 | 餐厅可省略原因;真正改绑增加未结束订单门禁 | +| 4 | 解绑餐厅供应商 | POST | `/admin/supplier/resource-relations/{resourceModule}/{resourceId}/unbind` | 行为修改 | 餐厅可省略原因;增加未结束订单门禁 | + +## 三、接口详情 + +### 1. 餐厅列表 `GET /admin/restaurant/items` + +**VO**: `RestaurantQueryRequest / PageResult` + +#### 使用场景 + +餐厅管理列表初始化、翻页、筛选或刷新时调用;列表列直接读取 `supplierFullName`。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|---|---|---|---|---|---| +| `keyword` | Query | String | 否 | 最长 100 字 | 按名称或副标题模糊查询 | +| `categoryCode` | Query | String | 否 | 现有字典值 | 餐厅分类 | +| `cuisineType` | Query | String | 否 | 现有字典值 | 菜系类型 | +| `province` / `cityName` / `city` | Query | String | 否 | 现有规则 | 地区筛选 | +| `status` | Query | Integer | 否 | `0` 下架,`1` 上架 | 状态筛选 | +| `recommended` | Query | Integer | 否 | `0` 否,`1` 是 | 推荐筛选 | +| `tagId` / `tagIds` | Query | String | 否 | `tagIds` 用逗号分隔 | 标签筛选 | +| `sortBy` | Query | String | 否 | 最长 50 字 | 现有排序字段 | +| `sortDir` | Query | String | 否 | `asc` 或 `desc` | 排序方向 | +| `page` | Query | Integer | 否 | 最小 1,默认 1 | 页码 | +| `pageSize` | Query | Integer | 否 | 1~100,默认 20 | 每页条数 | + +#### 出参 `Result>` + +| 字段 | 类型 | 说明 | +|---|---|---| +| `code` / `message` / `success` | Integer / String / Boolean | 业务结果 | +| `data.total` / `page` / `pageSize` | Long / Integer / Integer | 分页信息 | +| `data.records[].restaurantId` | String | 餐厅 ID | +| `data.records[].name` | String | 餐厅名称 | +| `data.records[].supplierFullName` | String 或 null | 当前有效供应商的法定全称;未关联或供应商已删除时为 `null`,字段始终存在 | +| `data.records[]` 其他字段 | Object | 原餐厅列表字段保持不变 | + +#### 请求示例 + +```http +GET /admin/restaurant/items?page=1&pageSize=20&sortDir=desc HTTP/1.1 +``` + +#### 响应示例 + +```json +{ + "code": 200, + "message": "成功", + "success": true, + "data": { + "total": 2, + "page": 1, + "pageSize": 20, + "records": [ + { + "restaurantId": "2023382100664676353", + "name": "示例餐厅 A", + "status": 1, + "supplierFullName": "示例餐饮供应商有限公司" + }, + { + "restaurantId": "2023382106075328513", + "name": "示例餐厅 B", + "status": 1, + "supplierFullName": null + } + ] + } +} +``` + +#### 空数据 / 降级响应 + +```json +{"code":200,"message":"成功","success":true,"data":{"total":0,"page":1,"pageSize":20,"records":[]}} +``` + +#### 错误响应 + +```json +{"code":401,"message":"缺少有效的 Authorization 头","success":false,"data":null} +``` + +#### 业务边界 + +- 本次不新增筛选、排序或分页规则;`supplierFullName` 只用于展示,不是供应商名称快照。 +- 供应商改名后返回当前全称;无当前有效关系时必须按 `null` 处理,不要根据字段是否存在分支。 + +### 2. 查询餐厅当前供应商 `GET /admin/supplier/resource-relations/{resourceModule}/{resourceId}/view` + +**VO**: `SupplierResourceRelationRespVO` + +#### 使用场景 + +打开餐厅供应商弹窗或在写操作后刷新当前关系时调用。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|---|---|---|---|---|---| +| `resourceModule` | Path | String | 是 | 餐厅固定为 `RESTAURANT` | 资源模块 | +| `resourceId` | Path | String | 是 | 正整数 | 餐厅 ID | + +#### 出参 `Result` + +| 字段 | 类型 | 说明 | +|---|---|---| +| `code` / `message` / `success` | Integer / String / Boolean | 业务结果 | +| `data.relationId` | String | 当前关系 ID | +| `data.supplierId` / `supplierNo` / `supplierName` | String | 当前供应商 ID、编号和全称 | +| `data.resourceModule` / `moduleName` | String | `RESTAURANT` / 餐厅管理 | +| `data.resourceId` / `resourceName` | String | 餐厅 ID 和名称 | +| `data.requiredTypeCode` / `requiredTypeName` | String | `RESTAURANT` / 餐厅管理 | +| `data.remark` | String 或 null | 关系备注 | +| `data.available` / `unavailableReasons` | Boolean / Array | 当前关系是否仍可用及不可用原因 | +| `data.createTime` / `updateTime` | String | `yyyy-MM-dd HH:mm:ss`;写操作使用最新 `updateTime` | + +#### 请求示例 + +```http +GET /admin/supplier/resource-relations/RESTAURANT/2023382100664676353/view HTTP/1.1 +``` + +#### 响应示例 + +```json +{ + "code": 200, + "message": "成功", + "success": true, + "data": { + "relationId": "2098015198429298690", + "supplierId": "2097899397319684098", + "supplierNo": "SUP2097899397319684098", + "supplierName": "示例餐饮供应商有限公司", + "resourceModule": "RESTAURANT", + "moduleName": "餐厅管理", + "resourceId": "2023382100664676353", + "resourceName": "示例餐厅 A", + "requiredTypeCode": "RESTAURANT", + "requiredTypeName": "餐厅管理", + "remark": null, + "available": true, + "unavailableReasons": [], + "createTime": "2026-09-10 20:00:00", + "updateTime": "2026-09-10 20:00:00" + } +} +``` + +#### 空数据 / 降级响应 + +当前没有有效关系时不是空成功,返回 `395038`。 + +#### 错误响应 + +```json +{"code":395038,"message":"供应商资源关联不存在","success":false,"data":null} +``` + +#### 业务边界 + +- 使用现有登录凭证及 `supplier:resource:view` 服务端权限;前端按钮可见性不能替代后端判权。 +- 未关联时按 `395038` 展示“未设置供应商”;不要把它当成系统异常。 + +### 3. 设置或改绑餐厅供应商 `PUT /admin/supplier/resource-relations/{resourceModule}/{resourceId}/update` + +**VO**: `SupplierResourceReassignReqVO / SupplierResourceRelationRespVO` + +#### 使用场景 + +餐厅当前无关系时首次设置供应商,或选择另一供应商后改绑。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|---|---|---|---|---|---| +| `resourceModule` | Path | String | 是 | 餐厅固定为 `RESTAURANT` | 资源模块 | +| `resourceId` | Path | String | 是 | 正整数 | 餐厅 ID | +| `supplierId` | Body | String | 是 | 正整数 | 目标供应商 ID | +| `requiredTypeCode` | Body | String | 否 | 餐厅可省略;传值只能为 `RESTAURANT` | 关系要求的供应商类型 | +| `remark` | Body | String | 否 | 最长 500 字 | 关系备注 | +| `expectedCurrentSupplierId` | Body | String | 改绑时是 | 与版本时间同时传或同时省略 | 当前关系的 `supplierId` | +| `expectedRelationUpdateTime` | Body | String | 改绑时是 | `yyyy-MM-dd HH:mm:ss` | 当前关系的 `updateTime` | +| `changeReason` | Body | String | 否 | 最长 500 字 | 餐厅首次设置和改绑均可省略;不要补默认原因 | + +#### 出参 `Result` + +| 字段 | 类型 | 说明 | +|---|---|---| +| `code` / `message` / `success` | Integer / String / Boolean | 业务结果 | +| `data.relationId` / `supplierId` / `supplierNo` / `supplierName` | String | 生效关系及供应商信息 | +| `data.resourceModule` / `moduleName` / `resourceId` / `resourceName` | String | 餐厅模块和资源信息 | +| `data.requiredTypeCode` / `requiredTypeName` | String | 生效的餐厅供应商类型 | +| `data.remark` | String 或 null | 关系备注 | +| `data.available` / `unavailableReasons` | Boolean / Array | 当前可用性 | +| `data.createTime` / `updateTime` | String | 当前关系时间与后续操作版本 | + +#### 请求示例 + +首次绑定时不要传两个 `expected*` 字段: + +```json +{"supplierId":"2097899397319684098"} +``` + +改绑时两个版本字段必须来自最新关系回读;可以不传 `changeReason`: + +```json +{"supplierId":"2097849575707475969","expectedCurrentSupplierId":"2097899397319684098","expectedRelationUpdateTime":"2026-09-10 20:00:00"} +``` + +#### 响应示例 + +```json +{ + "code": 200, + "message": "成功", + "success": true, + "data": { + "relationId": "2098100000000000001", + "supplierId": "2097849575707475969", + "supplierNo": "SUP2097849575707475969", + "supplierName": "示例新餐饮供应商有限公司", + "resourceModule": "RESTAURANT", + "moduleName": "餐厅管理", + "resourceId": "2023382100664676353", + "resourceName": "示例餐厅 A", + "requiredTypeCode": "RESTAURANT", + "requiredTypeName": "餐厅管理", + "remark": null, + "available": true, + "unavailableReasons": [], + "createTime": "2026-09-10 20:01:00", + "updateTime": "2026-09-10 20:01:00" + } +} +``` + +#### 空数据 / 降级响应 + +无空成功结果;订单服务、权限或字典等必要依赖无法明确核验时返回失败且不改变关系。 + +#### 错误响应 + +```json +{"code":395060,"message":"餐厅已关联订单,不能更换供应商","success":false,"data":null} +``` + +#### 业务边界 + +- 当前无关系时是首次绑定:不传版本对;即使餐厅已有订单也允许绑定。 +- 当前有关系且目标供应商不同才是真正改绑:必须传完整、最新版本对;仅关联 `COMPLETED`、`CANCELLED` 订单(包括两者混合)时允许,存在其他状态时返回 `395060`,原关系不变。 +- 目标与当前供应商相同且其他业务字段未变时沿用无变化返回;目标供应商仍须满足合作状态、餐厅类型和资格要求。 +- 版本过期返回 `395014`;必要依赖无法明确判断返回 `395039`;失败不改变关系或审计。 + +### 4. 解绑餐厅供应商 `POST /admin/supplier/resource-relations/{resourceModule}/{resourceId}/unbind` + +**VO**: `SupplierResourceUnbindReqVO / Result` + +#### 使用场景 + +用户确认清除餐厅当前供应商关系时调用。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|---|---|---|---|---|---| +| `resourceModule` | Path | String | 是 | 餐厅固定为 `RESTAURANT` | 资源模块 | +| `resourceId` | Path | String | 是 | 正整数 | 餐厅 ID | +| `expectedCurrentSupplierId` | Body | String | 是 | 正整数 | 当前关系的 `supplierId` | +| `expectedRelationUpdateTime` | Body | String | 是 | `yyyy-MM-dd HH:mm:ss` | 当前关系的 `updateTime` | +| `changeReason` | Body | String | 否 | 最长 500 字 | 可省略、`null`、空串或空白;不要补默认原因 | + +#### 出参 `Result` + +| 字段 | 类型 | 说明 | +|---|---|---| +| `code` / `message` / `success` | Integer / String / Boolean | 业务结果 | +| `data` | null | 成功时固定为 `null` | + +#### 请求示例 + +```json +{"expectedCurrentSupplierId":"2097849575707475969","expectedRelationUpdateTime":"2026-09-10 20:01:00"} +``` + +#### 响应示例 + +```json +{"code":200,"message":"成功","success":true,"data":null} +``` + +#### 空数据 / 降级响应 + +当前无关系时返回 `395038`;必要依赖无法明确核验时返回 `395039`,均不执行解绑。 + +#### 错误响应 + +```json +{"code":395060,"message":"餐厅已关联订单,不能更换供应商","success":false,"data":null} +``` + +#### 业务边界 + +- 解绑必须使用当前关系最新版本对;版本过期返回 `395014`,原关系不变。 +- 仅关联 `COMPLETED`、`CANCELLED` 订单(包括两者混合)时允许解绑;存在其他状态时返回 `395060`。 +- 登录、`supplier:resource:manage` 服务端权限、数据范围、幂等、锁和审计规则保持不变;失败零写入。 + +## 四、契约约束与正确调用方式 + +| 场景 | 正确 payload | 调用结果 | +|---|---|---| +| 查询当前关系 | 无请求体,`resourceModule=RESTAURANT` | 返回关系;无关系为 `395038` | +| 首次绑定 | `{"supplierId":"22"}` | 不传版本对,可省略 `changeReason` | +| 改绑 | `{"supplierId":"33","expectedCurrentSupplierId":"22","expectedRelationUpdateTime":"2026-09-10 20:00:00"}` | 两个版本字段必须成对且取最新关系值 | +| 解绑 | `{"expectedCurrentSupplierId":"33","expectedRelationUpdateTime":"2026-09-10 20:01:00"}` | 可省略 `changeReason` | +| 错误:版本字段只传一个 | `{"supplierId":"33","expectedCurrentSupplierId":"22"}` | 返回 `400`,不写入 | + +每次改绑或解绑前先回读当前关系,成功后重新刷新关系和餐厅列表。业务失败可能仍为 HTTP 200,必须同时判断响应体 `code` 与 `success`。 + +## 五、数据库行为 + +| 前端操作 | 外部可观察结果 | +|---|---| +| 首次绑定 | 生成一个当前有效关系;未填原因时不虚构默认原因 | +| 改绑 | 旧关系转为历史,新供应商成为唯一当前关系,并保留正常变更审计 | +| 解绑 | 当前关系消失并保留正常解绑审计;餐厅列表返回 `supplierFullName: null` | +| 订单、版本、权限或资格校验失败 | 当前关系和审计均不变化 | + +本次不迁移或回填任何数据,不修改订单业务、订单数据或订单供应商快照。 + +## 六、边界行为 + +- 未登录返回 `401`;关系查询要求查看权限,设置、改绑、解绑要求维护权限及对应数据范围。 +- 餐厅未绑定供应商时,列表返回 `supplierFullName: null`,关系查询返回 `395038`。 +- `changeReason` 最长 500 字;省略、`null`、空串或纯空白均按未填写处理。 +- 未结束订单包含除 `COMPLETED`、`CANCELLED` 以外的状态以及未知状态;V2 或 V3 任一服务确认存在即返回 `395060`。 +- 无法从任一订单服务得到明确结果时返回 `395039`,不把依赖异常当成无订单。 + +## 六.5、枚举 / 数据字典 + +### `resourceModule` / `requiredTypeCode`(餐厅供应商关系) + +**所属字段**: Path `resourceModule`、Body/Response `requiredTypeCode` | **类型**: `String` + +| 值 | 中文 | 说明 | +|---|---|---| +| `RESTAURANT` | 餐厅管理 | 餐厅关系固定值;`requiredTypeCode` 可省略并由后端确定 | + +## 六.6、修改前后对比 + +### 字段级对比 + +| 字段 | 改前 | 改后 | +|---|---|---| +| `GET /admin/restaurant/items` 的 `records[].supplierFullName` | 不返回 | 每条固定返回 `String` 或 `null` | +| 餐厅设置/改绑的 `changeReason` | 必填 | 可省略;原有非空值继续兼容 | +| 餐厅解绑的 `changeReason` | 已可省略 | 保持可省略 | + +### 行为级对比 + +| 行为 | 改前 | 改后 | +|---|---|---| +| 餐厅列表展示供应商 | 需额外取关系且列表无名称字段 | 直接读取 `supplierFullName` | +| 餐厅真正改绑、解绑 | 不检查餐厅订单 | 仅完成/取消订单放行;其他状态返回 `395060` | +| 尚未绑定供应商的餐厅 | 可首次绑定 | 保持可首次绑定,即使已有订单 | + +## 六.7、影响评估 + +- **是否破坏向后兼容**: 否;仅新增可空响应字段、放宽可选入参并增加业务保护。 +- **前端是否必须同步上线**: 是。 +- **前端 workaround 清理点**: 餐厅列表直接展示 `supplierFullName`;复用供应商关系弹窗并传 `RESTAURANT`;删除餐厅设置、改绑、解绑的原因弹框和必填校验;收到 `395060` 时提示原文并保持当前关系。 + +## 七、不影响范围 + +- **仅影响**: 管理端餐厅列表及餐厅供应商关系操作。 +- **零影响**: 订单创建、状态流转、结算、支付、退款、订单数据和订单供应商快照。 +- 景区及其他资源模块的字段、原因规则和订单门禁保持原契约;数据库结构、配置、Redis、MQ 和 Gateway 路由无变化。 + +## 八、测试环境已验证 + +- `GET /admin/restaurant/items`:HTTP 200、业务码 200;当前页每条记录均含 `supplierFullName`,同时实测非空全称与 `null`。 +- `GET /admin/supplier/resource-relations/RESTAURANT/{resourceId}/view`:已绑定关系返回 200;未绑定关系返回 `395038`;匿名请求返回业务码 401。 +- `PUT .../update` 与 `POST .../unbind`:完全省略 `changeReason` 的改绑、解绑和再次首次绑定均成功,列表与关系回读同步;验收后原供应商、类型和备注已恢复。 +- 两版订单、行程及 V3 产品快照在验收前后的只读摘要一致;完成、取消、混合放行、未结束双入口 `395060` 零写入及已有订单首次绑定由隔离 MySQL/资源门禁测试覆盖。 + +**当前状态:后端已部署并验证;待前端处理。** + +## 九、相关历史 PR + +| PR | Issue | 说明 | 是否仍有效 | +|---|---|---|---| +| #6315 | #6313 | 景区列表增加 `supplierFullName` | 是,餐厅沿用相同字段语义 | +| #7282 | #7274 | 景区首次设置供应商免原因 | 是 | +| #7358 | #7357 | 景区改绑供应商免原因 | 是 | +| #7401 | #7400 | 全部资源供应商解绑免原因 | 是 | +| **#7493** | **#7387** | 餐厅列表、原因规则与订单门禁 | **是,餐厅最新契约** | + +## 十、相关文档 + +- 关联 Issue:[#7387](https://git.1814.love:8443/wx/HL/issues/7387) +- 后端 PR:[#7493](https://git.1814.love:8443/wx/HL/pulls/7493) +- 合并提交:[33b994d](https://git.1814.love:8443/wx/HL/commit/33b994deb2345cdda614144d03f9f1e2d3958c76) + +## 关联 / 联系人 + +### 链接 + +- **Issue**: [#7387](https://git.1814.love:8443/wx/HL/issues/7387) +- **PR**: [#7493](https://git.1814.love:8443/wx/HL/pulls/7493) +- **Merge commit**: [33b994d](https://git.1814.love:8443/wx/HL/commit/33b994deb2345cdda614144d03f9f1e2d3958c76) + +### 联系人 + +- **后端负责人**: @lc