docs(changelog): #7500 游玩项目按景区口径接入供应商关系
changelog-filename-gate / validate (push) Successful in 2s

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CKcJu3x9Ct9LutqH7AMjJb
这个提交包含在:
lc
2026-09-11 14:28:56 +08:00
共同撰写人 Claude Opus 5
父节点 78ddcae40e
当前提交 0c30d2c966
@@ -0,0 +1,558 @@
---
schema: "hl-changelog/v2"
ticket: "7500"
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-11"
base: "dev-v3"
---
# 游玩项目管理:按景区口径接入供应商关系
> **服务**: hl-resource-service(8082)
> **PR**: #7507
> **Issue**: #7500
> **日期**: 2026-09-11
> **影响范围**: 管理端游玩项目列表、游玩项目供应商查询/设置/改绑/解绑,以及带游玩项目上下文的供应商候选列表
## ⚠️ 关键变化
- 游玩项目列表每条记录固定返回 `supplierFullName`;无当前供应商时为 `null`。
- 游玩项目使用通用资源供应商关系接口,路径参数固定传 `resourceModule=ACTIVITY`。
- 游玩项目供应商类型固定为 `ACTIVITY`:设置、改绑可省略 `requiredTypeCode`;传入其他类型编码返回 `400`。此前该模块要求前端显式传类型。
- 供应商候选列表携带 `resourceModule=ACTIVITY` 与 `resourceId` 时返回具备 `ACTIVITY` 类型的合作中供应商;此前该组合返回 `400`。
- 游玩项目首次绑定、改绑和解绑都可以省略 `changeReason`;真正改绑或解绑前会检查 V2、V3 订单,存在未结束订单时返回 `395061`。
## 一、背景
游玩项目管理此前无法从列表直接取得当前供应商全称,候选供应商列表在游玩项目上下文下直接报错,关系操作还要求前端传类型和原因。本次按景区、餐厅现行口径冻结游玩项目所需的展示字段、调用参数、类型与原因规则以及订单状态边界。
## 二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|---|---|---|---|---|
| 1 | 游玩项目列表 | GET | `/admin/activity/items` | 响应字段新增 | 每条记录固定返回 `supplierFullName` |
| 2 | 供应商候选列表 | GET | `/admin/supplier/items/list` | 行为修改 | `resourceModule=ACTIVITY` 时按 `ACTIVITY` 类型筛选合作中供应商 |
| 3 | 查询游玩项目当前供应商 | GET | `/admin/supplier/resource-relations/{resourceModule}/{resourceId}/view` | 调用契约补充 | 游玩项目传 `resourceModule=ACTIVITY` |
| 4 | 设置或改绑游玩项目供应商 | PUT | `/admin/supplier/resource-relations/{resourceModule}/{resourceId}/update` | 请求与行为修改 | 可省略类型与原因;真正改绑增加未结束订单门禁 |
| 5 | 解绑游玩项目供应商 | POST | `/admin/supplier/resource-relations/{resourceModule}/{resourceId}/unbind` | 行为修改 | 可省略原因;增加未结束订单门禁 |
## 三、接口详情
### 1. 游玩项目列表 `GET /admin/activity/items`
**VO**: `ActivityQueryRequest / PageResult<ActivityAdminListVO>`
#### 使用场景
游玩项目管理列表初始化、翻页、筛选或刷新时调用;列表列直接读取 `supplierFullName`。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| `keyword` | Query | String | 否 | 最长 100 字 | 名称/副标题模糊搜索 |
| `cityKeyword` | Query | String | 否 | 最长 100 字 | 城市名、省份、地址模糊搜索 |
| `categoryCode` | Query | String | 否 | 现有字典值 | 分类编码 |
| `billingType` | Query | String | 否 | `PER_PERSON` / `PER_GROUP` / `PER_TIME` | 计费类型 |
| `physicalLevel` | Query | String | 否 | `LOW` / `MEDIUM` / `HIGH` | 体力要求 |
| `environmentType` | Query | String | 否 | `INDOOR` / `OUTDOOR` / `BOTH` | 环境类型 |
| `weatherDependent` | Query | Integer | 否 | `0` 否,`1` 是 | 是否受天气影响 |
| `status` | Query | Integer | 否 | `0` 下架,`1` 上架 | 状态筛选 |
| `tagId` / `tagIds` | Query | String | 否 | `tagIds` 用逗号分隔 | 标签筛选 |
| `city` | Query | String | 否 | - | 城市 |
| `seasons` / `productLineId` | Query | Array / String | 否 | 现有规则 | 季节过滤(不变) |
| `sortBy` | Query | String | 否 | 最长 50 字 | 排序字段 |
| `sortDir` | Query | String | 否 | `asc` 或 `desc` | 排序方向 |
| `page` | Query | Integer | 否 | 最小 1,默认 1 | 页码 |
| `pageSize` | Query | Integer | 否 | 1~100,默认 20 | 每页条数 |
#### 出参 `Result<PageResult<ActivityAdminListVO>>`
| 字段 | 类型 | 说明 |
|---|---|---|
| `code` / `message` / `success` | Integer / String / Boolean | 业务结果 |
| `data.total` / `page` / `pageSize` | Long / Integer / Integer | 分页信息 |
| `data.records[].activityId` | String | 游玩项目 ID |
| `data.records[].name` | String | 游玩项目名称 |
| `data.records[].supplierFullName` | String 或 null | 当前有效供应商的法定全称;未关联或供应商已删除时为 `null`,字段始终存在 |
| `data.records[]` 其他字段 | Object | 原游玩项目列表字段保持不变 |
#### 请求示例
```http
GET /admin/activity/items?page=1&pageSize=20&sortDir=desc HTTP/1.1
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"total": 2,
"page": 1,
"pageSize": 20,
"records": [
{"activityId": "2029000000000000001", "name": "示例游玩项目 A", "status": 1, "supplierFullName": "示例游玩供应商有限公司"},
{"activityId": "2029000000000000002", "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/items/list`
**VO**: `SupplierListReqVO / List<SupplierListItemRespVO>`
#### 使用场景
游玩项目供应商弹窗中选择目标供应商时调用,携带当前游玩项目上下文,后端按 `ACTIVITY` 类型过滤。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| `resourceModule` | Query | String | 游玩项目场景是 | 固定 `ACTIVITY`;与 `resourceId` 同时传或同时省略 | 资源模块 |
| `resourceId` | Query | String | 游玩项目场景是 | 正整数 | 游玩项目 ID |
| `keyword` | Query | String | 否 | 最长 500 字 | 编码、全称、简称模糊匹配,税号等值匹配 |
| `limit` | Query | Integer | 否 | 1~200,默认 50 | 最大返回条数 |
携带资源上下文时,类型固定取 `ACTIVITY`、状态固定为合作中(`ACTIVE`),不需要也不应再传 `typeCode` / `status`。
#### 出参 `Result<List<SupplierListItemRespVO>>`
| 字段 | 类型 | 说明 |
|---|---|---|
| `code` / `message` / `success` | Integer / String / Boolean | 业务结果 |
| `data[].supplierId` | String | 供应商 ID(选中后作为设置/改绑的 `supplierId`) |
| `data[].supplierNo` / `fullName` / `shortName` | String | 编码、法定全称、简称 |
| `data[].types[]` | Array | `typeCode` 类型字典值、`typeName` 类型名称、`isPrimary` 是否主类型 |
| `data[].status` / `statusName` | String | 生命周期状态(此场景均为 `ACTIVE`)与展示名 |
| `data[].creditLevel` / `totalScore` | String / Number | 信用等级、综合评分 |
| `data[].activeAccountCount` | Integer | 有效收款账户数 |
| `data[].contactPhone` | String | 公司联系电话 |
| `data[].createTime` / `updateTime` | String | `yyyy-MM-dd HH:mm:ss` |
#### 请求示例
```http
GET /admin/supplier/items/list?resourceModule=ACTIVITY&resourceId=2029000000000000001&limit=50 HTTP/1.1
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"success": true,
"data": [
{
"supplierId": "2097899397319684099",
"supplierNo": "SUP2097899397319684099",
"fullName": "示例游玩供应商有限公司",
"shortName": "示例游玩",
"types": [{"typeCode": "ACTIVITY", "typeName": "游玩项目管理", "isPrimary": true}],
"status": "ACTIVE",
"statusName": "合作中",
"creditLevel": "A",
"totalScore": 90,
"activeAccountCount": 1,
"contactPhone": "0471-0000000",
"createTime": "2026-09-01 10:00:00",
"updateTime": "2026-09-01 10:00:00"
}
]
}
```
#### 空数据 / 降级响应
```json
{"code":200,"message":"成功","success":true,"data":[]}
```
#### 错误响应
```json
{"code":400,"message":"resourceModule与resourceId必须同时提供或同时省略","success":false,"data":null}
```
#### 业务边界
- 仅 `resourceModule=ACTIVITY` 的行为变化:由 `400 requiredTypeCode不能为空` 改为按 `ACTIVITY` 类型返回合作中供应商;其他模块和不带上下文的通用列表不变。
- 需要列表权限及资源查看权限、数据范围;游玩项目不存在时返回资源不存在错误。
### 3. 查询游玩项目当前供应商 `GET /admin/supplier/resource-relations/{resourceModule}/{resourceId}/view`
**VO**: `SupplierResourceRelationRespVO`
#### 使用场景
打开游玩项目供应商弹窗或在写操作后刷新当前关系时调用。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| `resourceModule` | Path | String | 是 | 游玩项目固定为 `ACTIVITY` | 资源模块 |
| `resourceId` | Path | String | 是 | 正整数 | 游玩项目 ID |
#### 出参 `Result<SupplierResourceRelationRespVO>`
| 字段 | 类型 | 说明 |
|---|---|---|
| `code` / `message` / `success` | Integer / String / Boolean | 业务结果 |
| `data.relationId` | String | 当前关系 ID |
| `data.supplierId` / `supplierNo` / `supplierName` | String | 当前供应商 ID、编号和全称 |
| `data.resourceModule` / `moduleName` | String | `ACTIVITY` / 游玩项目管理 |
| `data.resourceId` / `resourceName` | String | 游玩项目 ID 和名称 |
| `data.requiredTypeCode` / `requiredTypeName` | String | `ACTIVITY` / 游玩项目管理 |
| `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/ACTIVITY/2029000000000000001/view HTTP/1.1
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"relationId": "2098200000000000001",
"supplierId": "2097899397319684099",
"supplierNo": "SUP2097899397319684099",
"supplierName": "示例游玩供应商有限公司",
"resourceModule": "ACTIVITY",
"moduleName": "游玩项目管理",
"resourceId": "2029000000000000001",
"resourceName": "示例游玩项目 A",
"requiredTypeCode": "ACTIVITY",
"requiredTypeName": "游玩项目管理",
"remark": null,
"available": true,
"unavailableReasons": [],
"createTime": "2026-09-11 20:00:00",
"updateTime": "2026-09-11 20:00:00"
}
}
```
#### 空数据 / 降级响应
当前没有有效关系时不是空成功,返回 `395038`。
#### 错误响应
```json
{"code":395038,"message":"供应商资源关联不存在","success":false,"data":null}
```
#### 业务边界
- 使用现有登录凭证及 `supplier:resource:view` 服务端权限;前端按钮可见性不能替代后端判权。
- 未关联时按 `395038` 展示“未设置供应商”;不要把它当成系统异常。
### 4. 设置或改绑游玩项目供应商 `PUT /admin/supplier/resource-relations/{resourceModule}/{resourceId}/update`
**VO**: `SupplierResourceReassignReqVO / SupplierResourceRelationRespVO`
#### 使用场景
游玩项目当前无关系时首次设置供应商,或选择另一供应商后改绑。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| `resourceModule` | Path | String | 是 | 游玩项目固定为 `ACTIVITY` | 资源模块 |
| `resourceId` | Path | String | 是 | 正整数 | 游玩项目 ID |
| `supplierId` | Body | String | 是 | 正整数 | 目标供应商 ID |
| `requiredTypeCode` | Body | String | 否 | 可省略;传值只能为 `ACTIVITY` | 关系要求的供应商类型,由后端固定 |
| `remark` | Body | String | 否 | 最长 500 字 | 关系备注 |
| `expectedCurrentSupplierId` | Body | String | 改绑时是 | 与版本时间同时传或同时省略 | 当前关系的 `supplierId` |
| `expectedRelationUpdateTime` | Body | String | 改绑时是 | `yyyy-MM-dd HH:mm:ss` | 当前关系的 `updateTime` |
| `changeReason` | Body | String | 否 | 最长 500 字 | 首次设置和改绑均可省略;不要补默认原因 |
#### 出参 `Result<SupplierResourceRelationRespVO>`
| 字段 | 类型 | 说明 |
|---|---|---|
| `code` / `message` / `success` | Integer / String / Boolean | 业务结果 |
| `data.relationId` / `supplierId` / `supplierNo` / `supplierName` | String | 生效关系及供应商信息 |
| `data.resourceModule` / `moduleName` / `resourceId` / `resourceName` | String | 游玩项目模块和资源信息 |
| `data.requiredTypeCode` / `requiredTypeName` | String | 固定 `ACTIVITY` / 游玩项目管理 |
| `data.remark` | String 或 null | 关系备注 |
| `data.available` / `unavailableReasons` | Boolean / Array | 当前可用性 |
| `data.createTime` / `updateTime` | String | 当前关系时间与后续操作版本 |
#### 请求示例
首次绑定时不要传两个 `expected*` 字段,也无需传 `requiredTypeCode`、`changeReason`:
```json
{"supplierId":"2097899397319684099"}
```
改绑时两个版本字段必须来自最新关系回读:
```json
{"supplierId":"2097849575707475970","expectedCurrentSupplierId":"2097899397319684099","expectedRelationUpdateTime":"2026-09-11 20:00:00"}
```
#### 响应示例
```json
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"relationId": "2098200000000000002",
"supplierId": "2097849575707475970",
"supplierNo": "SUP2097849575707475970",
"supplierName": "示例新游玩供应商有限公司",
"resourceModule": "ACTIVITY",
"moduleName": "游玩项目管理",
"resourceId": "2029000000000000001",
"resourceName": "示例游玩项目 A",
"requiredTypeCode": "ACTIVITY",
"requiredTypeName": "游玩项目管理",
"remark": null,
"available": true,
"unavailableReasons": [],
"createTime": "2026-09-11 20:01:00",
"updateTime": "2026-09-11 20:01:00"
}
}
```
#### 空数据 / 降级响应
无空成功结果;订单服务、权限或字典等必要依赖无法明确核验时返回失败且不改变关系。
#### 错误响应
```json
{"code":395061,"message":"游玩项目已关联订单,不能更换供应商","success":false,"data":null}
```
```json
{"code":400,"message":"requiredTypeCode与资源模块不匹配","success":false,"data":null}
```
#### 业务边界
- 当前无关系时是首次绑定:不传版本对;即使游玩项目已有订单也允许绑定。
- 当前有关系且目标供应商不同才是真正改绑:必须传完整、最新版本对;仅关联 `COMPLETED`、`CANCELLED` 订单(包括两者混合)时允许,存在其他状态时返回 `395061`,原关系不变。
- `requiredTypeCode` 传 `ACTIVITY` 以外的值返回 `400`;目标供应商必须具备 `ACTIVITY` 类型并满足合作状态与资格要求。
- 版本过期返回 `395014`;必要依赖无法明确判断返回 `395039`;失败不改变关系或审计。
### 5. 解绑游玩项目供应商 `POST /admin/supplier/resource-relations/{resourceModule}/{resourceId}/unbind`
**VO**: `SupplierResourceUnbindReqVO / Result<Void>`
#### 使用场景
用户确认清除游玩项目当前供应商关系时调用。
#### 入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| `resourceModule` | Path | String | 是 | 游玩项目固定为 `ACTIVITY` | 资源模块 |
| `resourceId` | Path | String | 是 | 正整数 | 游玩项目 ID |
| `expectedCurrentSupplierId` | Body | String | 是 | 正整数 | 当前关系的 `supplierId` |
| `expectedRelationUpdateTime` | Body | String | 是 | `yyyy-MM-dd HH:mm:ss` | 当前关系的 `updateTime` |
| `changeReason` | Body | String | 否 | 最长 500 字 | 可省略、`null`、空串或空白;不要补默认原因 |
#### 出参 `Result<Void>`
| 字段 | 类型 | 说明 |
|---|---|---|
| `code` / `message` / `success` | Integer / String / Boolean | 业务结果 |
| `data` | null | 成功时固定为 `null` |
#### 请求示例
```json
{"expectedCurrentSupplierId":"2097849575707475970","expectedRelationUpdateTime":"2026-09-11 20:01:00"}
```
#### 响应示例
```json
{"code":200,"message":"成功","success":true,"data":null}
```
#### 空数据 / 降级响应
当前无关系时返回 `395038`;必要依赖无法明确核验时返回 `395039`,均不执行解绑。
#### 错误响应
```json
{"code":395061,"message":"游玩项目已关联订单,不能更换供应商","success":false,"data":null}
```
#### 业务边界
- 解绑必须使用当前关系最新版本对;版本过期返回 `395014`,原关系不变。
- 仅关联 `COMPLETED`、`CANCELLED` 订单(包括两者混合)时允许解绑;存在其他状态时返回 `395061`。
- 登录、`supplier:resource:manage` 服务端权限、数据范围、幂等、锁和审计规则保持不变;失败零写入。
## 四、契约约束与正确调用方式
| 场景 | 正确 payload | 调用结果 |
|---|---|---|
| 候选列表 | `?resourceModule=ACTIVITY&resourceId=66` | 返回 `ACTIVITY` 类型合作中供应商 |
| 查询当前关系 | 无请求体,`resourceModule=ACTIVITY` | 返回关系;无关系为 `395038` |
| 首次绑定 | `{"supplierId":"22"}` | 不传版本对、类型和原因 |
| 改绑 | `{"supplierId":"33","expectedCurrentSupplierId":"22","expectedRelationUpdateTime":"2026-09-11 20:00:00"}` | 两个版本字段必须成对且取最新关系值 |
| 解绑 | `{"expectedCurrentSupplierId":"33","expectedRelationUpdateTime":"2026-09-11 20:01:00"}` | 可省略 `changeReason` |
| 错误:传其他类型 | `{"supplierId":"33","requiredTypeCode":"SCENIC"}` | 返回 `400`,不写入 |
| 错误:版本字段只传一个 | `{"supplierId":"33","expectedCurrentSupplierId":"22"}` | 返回 `400`,不写入 |
每次改绑或解绑前先回读当前关系,成功后重新刷新关系和游玩项目列表。业务失败可能仍为 HTTP 200,必须同时判断响应体 `code` 与 `success`。
## 五、数据库行为
| 前端操作 | 外部可观察结果 |
|---|---|
| 首次绑定 | 生成一个当前有效关系,`requiredTypeCode=ACTIVITY`;未填原因时不虚构默认原因 |
| 改绑 | 旧关系转为历史,新供应商成为唯一当前关系,并保留正常变更审计 |
| 解绑 | 当前关系消失并保留正常解绑审计;游玩项目列表返回 `supplierFullName: null` |
| 订单、版本、权限、类型或资格校验失败 | 当前关系和审计均不变化 |
本次不迁移或回填任何数据,不修改订单业务、订单数据或订单供应商快照。
## 六、边界行为
- 未登录返回 `401`;关系查询要求查看权限,设置、改绑、解绑要求维护权限及对应数据范围。
- 游玩项目未绑定供应商时,列表返回 `supplierFullName: null`,关系查询返回 `395038`。
- `changeReason` 最长 500 字;省略、`null`、空串或纯空白均按未填写处理。
- 未结束订单包含除 `COMPLETED`、`CANCELLED` 以外的状态以及未知状态;V2 或 V3 任一服务确认存在即返回 `395061`。
- 无法从任一订单服务得到明确结果时返回 `395039`,不把依赖异常当成无订单。
## 六.5、枚举 / 数据字典
### `resourceModule` / `requiredTypeCode`(游玩项目供应商关系)
**所属字段**: Path/Query `resourceModule`、Body/Response `requiredTypeCode`、`types[].typeCode` | **类型**: `String`
| 值 | 中文 | 说明 |
|---|---|---|
| `ACTIVITY` | 游玩项目管理 | 游玩项目关系固定值;`requiredTypeCode` 可省略并由后端确定 |
### 业务错误码
| 值 | 中文 | 说明 |
|---|---|---|
| `395061` | 游玩项目已关联订单,不能更换供应商 | 本次新增;真正改绑或解绑时存在未结束订单 |
## 六.6、修改前后对比
### 字段级对比
| 字段 | 改前 | 改后 |
|---|---|---|
| `GET /admin/activity/items` 的 `records[].supplierFullName` | 不返回 | 每条固定返回 `String` 或 `null` |
| 游玩项目设置/改绑的 `requiredTypeCode` | 必填,任意类型 | 可省略,固定 `ACTIVITY`,传其他值 `400` |
| 游玩项目设置/改绑的 `changeReason` | 必填 | 可省略;原有非空值继续兼容 |
| 游玩项目解绑的 `changeReason` | 已可省略 | 保持可省略 |
### 行为级对比
| 行为 | 改前 | 改后 |
|---|---|---|
| 游玩项目列表展示供应商 | 列表无名称字段 | 直接读取 `supplierFullName` |
| 候选列表带 `ACTIVITY` 上下文 | 返回 `400 requiredTypeCode不能为空` | 返回 `ACTIVITY` 类型合作中供应商 |
| 游玩项目真正改绑、解绑 | 不检查订单 | 仅完成/取消订单放行;其他状态返回 `395061` |
| 尚未绑定供应商的游玩项目 | 可首次绑定 | 保持可首次绑定,即使已有订单 |
## 六.7、影响评估
- **是否破坏向后兼容**: 仅游玩项目收紧:`requiredTypeCode` 只接受 `ACTIVITY`(TEST 当前无游玩项目供应商关系存量);其余为新增可空字段、放宽可选入参与业务保护。
- **前端是否必须同步上线**: 是。
- **前端 workaround 清理点**: 游玩项目列表直接展示 `supplierFullName`;复用供应商关系弹窗并传 `ACTIVITY`;候选列表传 `resourceModule=ACTIVITY&resourceId`;删除游玩项目类型选择、设置/改绑/解绑原因弹框和必填校验;收到 `395061` 时提示原文并保持当前关系。
## 七、不影响范围
- **仅影响**: 管理端游玩项目列表、游玩项目供应商关系操作,以及带游玩项目上下文的候选列表。
- **零影响**: 订单创建、状态流转、结算、支付、退款、订单数据和订单供应商快照;小程序游玩项目列表。
- 景区、餐厅及其他资源模块的字段、类型、原因规则和订单门禁保持原契约;数据库结构、配置、Redis、MQ 和 Gateway 路由无变化。
## 八、测试环境已验证
- `GET /admin/activity/items`:HTTP 200、业务码 200;当前页每条记录均含 `supplierFullName`;目标游玩项目未关联时为 `null`,绑定后为供应商全称,解绑后恢复 `null`;匿名请求返回业务码 401。
- `GET /admin/supplier/items/list?resourceModule=ACTIVITY&resourceId={id}`:业务码 200,返回的候选均具备 `ACTIVITY` 类型且状态为 `ACTIVE`。
- `GET /admin/supplier/resource-relations/ACTIVITY/{resourceId}/view`:未绑定返回 `395038`,绑定后返回 `requiredTypeCode=ACTIVITY`;匿名请求返回业务码 401。
- `PUT .../update`:传 `requiredTypeCode=SCENIC` 返回 `400 requiredTypeCode与资源模块不匹配` 且零写入;省略 `changeReason` 与 `requiredTypeCode` 的首次绑定、无未结束订单时的改绑均成功。
- `POST .../unbind`:省略 `changeReason` 解绑成功;验收后目标游玩项目恢复为未绑定。
- 订单侧只读:验收前后目标游玩项目的 V3 行程节点与未结束订单摘要哈希一致。存在未结束订单时返回 `395061`、已有订单仍可首次绑定由资源门禁测试及 V2/V3 隔离 MySQL 测试覆盖。
**当前状态:后端已部署并验证;待前端处理。**
## 九、相关历史 PR
| PR | Issue | 说明 | 是否仍有效 |
|---|---|---|---|
| #7358 | #7357 | 景区改绑供应商免原因 | 是 |
| #7401 | #7400 | 全部资源供应商解绑免原因 | 是 |
| #7493 | #7387 | 餐厅列表、原因规则与订单门禁 | 是,游玩项目沿用相同口径 |
| **#7507** | **#7500** | 游玩项目列表、固定类型、候选列表、原因规则与订单门禁 | **是,游玩项目最新契约** |
## 十、相关文档
- 关联 Issue:[#7500](https://git.1814.love:8443/wx/HL/issues/7500)
- 后端 PR:[#7507](https://git.1814.love:8443/wx/HL/pulls/7507)
- 合并提交:[3076168](https://git.1814.love:8443/wx/HL/commit/30761683e709e9664595d355ee173646e566c7e2)
## 关联 / 联系人
### 链接
- **Issue**: [#7500](https://git.1814.love:8443/wx/HL/issues/7500)
- **PR**: [#7507](https://git.1814.love:8443/wx/HL/pulls/7507)
- **Merge commit**: [3076168](https://git.1814.love:8443/wx/HL/commit/30761683e709e9664595d355ee173646e566c7e2)
### 联系人
- **后端负责人**: @lc