docs(7511): 额外成本按景区口径接入供应商关系 Changelog(supplierFullName / 固定 COST_ITEM 类型 / 原因可省略)Refs #7511
changelog-filename-gate / validate (push) Successful in 2s
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_01UqNs52DGnGU29PN8Ey37Ms
这个提交包含在:
@@ -0,0 +1,556 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "7511"
|
||||
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: "2026-09-12"
|
||||
status_note: "后端已部署并经 Gateway 验证;待前端展示额外成本列表供应商全称并接入额外成本供应商关系操作。"
|
||||
updated_at: "2026-09-12"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 额外成本:按景区口径接入供应商关系
|
||||
|
||||
> **服务**: hl-resource-service(8082)
|
||||
> **PR**: #7573
|
||||
> **Issue**: #7511
|
||||
> **日期**: 2026-09-12
|
||||
> **影响范围**: 管理端额外成本(费用项)列表、额外成本供应商查询/设置/改绑/解绑、供应商候选列表
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
- 额外成本列表每条记录固定返回 `supplierFullName`;无当前供应商时为 `null`。
|
||||
- 额外成本供应商类型由「每次必须显式传入」改为固定 `COST_ITEM`:`requiredTypeCode` 可省略,传其他类型返回 `400 requiredTypeCode与资源模块不匹配`。
|
||||
- 额外成本首次绑定、改绑、解绑都可以省略 `changeReason`;此前设置、改绑缺原因返回 `400 changeReason不能为空`。
|
||||
- 供应商候选列表带 `resourceModule=COST_ITEM` 时不再返回 `400 requiredTypeCode不能为空`,改为返回具备 `COST_ITEM` 类型的合作中供应商。
|
||||
- **额外成本不检查订单**:改绑、解绑不做未结束订单门禁,也不新增任何错误码。
|
||||
|
||||
## 一、背景
|
||||
|
||||
额外成本此前是十个资源模块中唯一没有固定供应商类型的模块:每次绑定必须显式传 `requiredTypeCode`,候选列表因拿不到类型直接报错,管理端列表也不展示供应商全称,且设置、改绑必须填写变更原因。本次按景区、餐厅、游玩项目、酒店、服务现行口径冻结额外成本所需的展示字段、调用参数和原因规则;与前述模块不同的是,额外成本与订单没有资源 ID 级别的关联,因此不接订单门禁。
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | 额外成本列表 | GET | `/admin/cost/items` | 响应字段新增 | 每条记录固定返回 `supplierFullName` |
|
||||
| 2 | 查询额外成本当前供应商 | GET | `/admin/supplier/resource-relations/{resourceModule}/{resourceId}/view` | 调用契约补充 | 额外成本传 `resourceModule=COST_ITEM` |
|
||||
| 3 | 设置或改绑额外成本供应商 | PUT | `/admin/supplier/resource-relations/{resourceModule}/{resourceId}/update` | 请求与行为修改 | 可省略原因与类型;传其他类型被拒 |
|
||||
| 4 | 解绑额外成本供应商 | POST | `/admin/supplier/resource-relations/{resourceModule}/{resourceId}/unbind` | 行为修改 | 可省略原因(此前已可省略,保持) |
|
||||
| 5 | 供应商候选列表 | GET | `/admin/supplier/items/list` | 行为修复 | 带额外成本上下文时不再返回 400 |
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 额外成本列表 `GET /admin/cost/items`
|
||||
|
||||
**VO**: `CostItemQueryRequest / PageResult<CostItemAdminListVO>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
额外成本管理列表初始化、翻页、筛选或刷新时调用;列表「供应商」列直接读取 `supplierFullName`。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| `keyword` | Query | String | 否 | 最长 100 字 | 名称模糊搜索 |
|
||||
| `categoryCode` | Query | String | 否 | - | 费用分类编码 |
|
||||
| `applyRole` | Query | String | 否 | - | 适用角色 |
|
||||
| `status` | Query | Integer | 否 | `0` 下架,`1` 上架 | 状态筛选 |
|
||||
| `sortBy` | Query | String | 否 | 最长 50 字 | 排序字段 |
|
||||
| `sortDir` | Query | String | 否 | `asc` 或 `desc` | 排序方向 |
|
||||
| `page` | Query | Integer | 否 | 最小 1,默认 1 | 页码 |
|
||||
| `pageSize` | Query | Integer | 否 | 1~100,默认 20 | 每页条数 |
|
||||
|
||||
以上入参均为既有参数,本次不变。
|
||||
|
||||
#### 出参 `Result<PageResult<CostItemAdminListVO>>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `code` / `message` / `success` | Integer / String / Boolean | 业务结果 |
|
||||
| `data.total` / `page` / `pageSize` | Long / Integer / Integer | 分页信息 |
|
||||
| `data.records[].costId` | String | 费用项 ID |
|
||||
| `data.records[].name` | String | 费用名称 |
|
||||
| `data.records[].supplierFullName` | String 或 null | 当前有效关系对应供应商的法定全称;未关联或供应商已删除时为 `null`,字段始终存在 |
|
||||
| `data.records[]` 其他字段 | Object | 原额外成本列表字段保持不变 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
无请求体:
|
||||
|
||||
```http
|
||||
GET /admin/cost/items?page=1&pageSize=20 HTTP/1.1
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"total": 2,
|
||||
"page": 1,
|
||||
"pageSize": 20,
|
||||
"records": [
|
||||
{"costId": "2", "name": "导游餐补", "categoryCode": "meal_subsidy", "unitPrice": 30.0, "unit": "餐", "status": 0, "supplierFullName": "示例额外成本供应商有限公司"},
|
||||
{"costId": "3", "name": "司机住宿补贴", "categoryCode": "accommodation_subsidy", "unitPrice": 50.0, "unit": "晚", "status": 0, "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` 处理,不要根据字段是否存在分支。
|
||||
- 内部接口 `/internal/cost/**` 使用另一套 `CostItemVO`,不含该字段,本次不变。
|
||||
|
||||
### 2. 查询额外成本当前供应商 `GET /admin/supplier/resource-relations/{resourceModule}/{resourceId}/view`
|
||||
|
||||
**VO**: `SupplierResourceRelationRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
打开额外成本供应商弹窗或在写操作后刷新当前关系时调用。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| `resourceModule` | Path | String | 是 | 额外成本固定为 `COST_ITEM` | 资源模块 |
|
||||
| `resourceId` | Path | String | 是 | 正整数 | 费用项 ID(`costId`) |
|
||||
|
||||
#### 出参 `Result<SupplierResourceRelationRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `code` / `message` / `success` | Integer / String / Boolean | 业务结果 |
|
||||
| `data.relationId` | String | 当前关系 ID |
|
||||
| `data.supplierId` / `supplierNo` / `supplierName` | String | 当前供应商 ID、编号和全称 |
|
||||
| `data.resourceModule` / `moduleName` | String | `COST_ITEM` / 额外成本 |
|
||||
| `data.resourceId` / `resourceName` | String | 费用项 ID 和名称 |
|
||||
| `data.requiredTypeCode` / `requiredTypeName` | String | `COST_ITEM` / 额外成本 |
|
||||
| `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/COST_ITEM/2/view HTTP/1.1
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"relationId": "2098600000000000201",
|
||||
"supplierId": "2091381643661983746",
|
||||
"supplierNo": "SUP2091381643661983746",
|
||||
"supplierName": "示例额外成本供应商有限公司",
|
||||
"resourceModule": "COST_ITEM",
|
||||
"moduleName": "额外成本",
|
||||
"resourceId": "2",
|
||||
"resourceName": "导游餐补",
|
||||
"requiredTypeCode": "COST_ITEM",
|
||||
"requiredTypeName": "额外成本",
|
||||
"remark": null,
|
||||
"available": true,
|
||||
"unavailableReasons": [],
|
||||
"createTime": "2026-09-12 11:40:00",
|
||||
"updateTime": "2026-09-12 11:40: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`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
额外成本当前无关系时首次设置供应商,或选择另一供应商后改绑。候选供应商用 `GET /admin/supplier/items/list?resourceModule=COST_ITEM&resourceId={costId}` 获取(本次由报错修复为可用,见第 5 节)。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| `resourceModule` | Path | String | 是 | 额外成本固定为 `COST_ITEM` | 资源模块 |
|
||||
| `resourceId` | Path | String | 是 | 正整数 | 费用项 ID |
|
||||
| `supplierId` | Body | String | 是 | 正整数 | 目标供应商 ID,必须是合作中且具备 `COST_ITEM` 类型 |
|
||||
| `requiredTypeCode` | Body | String | 否 | **本次改为可省略**;传值只能为 `COST_ITEM` | 关系要求的供应商类型,由后端固定 |
|
||||
| `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` | String | 生效关系 ID |
|
||||
| `data.supplierId` / `supplierNo` / `supplierName` | String | 生效供应商 ID、编号和全称 |
|
||||
| `data.resourceModule` / `moduleName` | String | `COST_ITEM` / 额外成本 |
|
||||
| `data.resourceId` / `resourceName` | String | 费用项 ID 和名称 |
|
||||
| `data.requiredTypeCode` / `requiredTypeName` | String | 固定 `COST_ITEM` / 额外成本 |
|
||||
| `data.remark` | String 或 null | 关系备注 |
|
||||
| `data.available` / `unavailableReasons` | Boolean / Array | 当前关系是否仍可用及不可用原因 |
|
||||
| `data.createTime` / `updateTime` | String | `yyyy-MM-dd HH:mm:ss`;后续改绑、解绑使用最新 `updateTime` |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
首次绑定时不要传两个 `expected*` 字段,也无需传 `requiredTypeCode`、`changeReason`:
|
||||
|
||||
```json
|
||||
{"supplierId":"2091381643661983746"}
|
||||
```
|
||||
|
||||
改绑时两个版本字段必须来自最新关系回读:
|
||||
|
||||
```json
|
||||
{"supplierId":"2098428486107402242","expectedCurrentSupplierId":"2091381643661983746","expectedRelationUpdateTime":"2026-09-12 11:40:00"}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"relationId": "2098600000000000202",
|
||||
"supplierId": "2098428486107402242",
|
||||
"supplierNo": "SUP260002",
|
||||
"supplierName": "示例新额外成本供应商有限公司",
|
||||
"resourceModule": "COST_ITEM",
|
||||
"moduleName": "额外成本",
|
||||
"resourceId": "2",
|
||||
"resourceName": "导游餐补",
|
||||
"requiredTypeCode": "COST_ITEM",
|
||||
"requiredTypeName": "额外成本",
|
||||
"remark": null,
|
||||
"available": true,
|
||||
"unavailableReasons": [],
|
||||
"createTime": "2026-09-12 11:41:00",
|
||||
"updateTime": "2026-09-12 11:41:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
无空成功结果;权限、字典或供应商资格等必要依赖无法明确核验时返回失败且不改变关系。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{"code":400,"message":"requiredTypeCode与资源模块不匹配","success":false,"data":null}
|
||||
```
|
||||
|
||||
```json
|
||||
{"code":395010,"message":"请先完成供应商注册","success":false,"data":null}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 当前无关系时是首次绑定:不传版本对。
|
||||
- 当前有关系且目标供应商不同才是真正改绑:必须传完整、最新版本对。
|
||||
- **额外成本不检查订单**:无论该费用项是否已被订单或产品引用(`refCount > 0`),改绑与解绑都不会因此被拒绝。
|
||||
- 目标供应商必须是合作中状态且已挂 `COST_ITEM` 类型,否则分别返回 `395010` 与 `400 requiredTypeCode与资源模块不匹配`。
|
||||
- 版本过期返回 `395014`;失败不改变关系或审计。
|
||||
|
||||
### 4. 解绑额外成本供应商 `POST /admin/supplier/resource-relations/{resourceModule}/{resourceId}/unbind`
|
||||
|
||||
**VO**: `SupplierResourceUnbindReqVO / Result<Void>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
用户确认清除额外成本当前供应商关系时调用。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| `resourceModule` | Path | String | 是 | 额外成本固定为 `COST_ITEM` | 资源模块 |
|
||||
| `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":"2098428486107402242","expectedRelationUpdateTime":"2026-09-12 11:41:00"}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{"code":200,"message":"成功","success":true,"data":null}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
当前无关系时返回 `395038`,不执行解绑。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{"code":395014,"message":"数据已被其他操作修改,请刷新后重试","success":false,"data":null}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 解绑必须使用当前关系最新版本对;版本过期返回 `395014`,原关系不变。
|
||||
- 额外成本解绑同样不检查订单。
|
||||
- 登录、`supplier:resource:manage` 服务端权限、数据范围、幂等、锁和审计规则保持不变;失败零写入。
|
||||
|
||||
### 5. 供应商候选列表 `GET /admin/supplier/items/list`
|
||||
|
||||
**VO**: `SupplierListReqVO / List<SupplierListItemVO>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
打开额外成本供应商选择弹窗时拉取可选供应商。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| `resourceModule` | Query | String | 否 | 与 `resourceId` 同时提供或同时省略;额外成本传 `COST_ITEM` | 资源上下文模块 |
|
||||
| `resourceId` | Query | String | 否 | 与 `resourceModule` 成对 | 费用项 ID |
|
||||
|
||||
#### 出参 `Result<List<SupplierListItemVO>>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `code` / `message` / `success` | Integer / String / Boolean | 业务结果 |
|
||||
| `data[].supplierId` | String | 供应商 ID |
|
||||
| `data[].supplierNo` | String | 供应商编号 |
|
||||
| `data[].fullName` | String | 供应商法定全称 |
|
||||
| `data[].shortName` | String 或 null | 供应商简称 |
|
||||
| `data[].status` / `statusName` | String | 供应商状态;此处固定为 `ACTIVE` / 合作中 |
|
||||
| `data[].creditLevel` | String 或 null | 信用等级 |
|
||||
| `data[].types[].typeCode` / `typeName` | String | 经营类型编码与名称;结果集均含 `COST_ITEM` / 额外成本 |
|
||||
| `data[].types[].isPrimary` | Boolean | 是否主类型 |
|
||||
|
||||
以上均为既有字段,本次不新增、不修改字段结构。
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /admin/supplier/items/list?resourceModule=COST_ITEM&resourceId=2 HTTP/1.1
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{"code":200,"message":"成功","success":true,"data":[{"supplierId":"2091381643661983746","fullName":"示例额外成本供应商有限公司","status":"ACTIVE","types":[{"typeCode":"COST_ITEM","typeName":"额外成本","isPrimary":false}]}]}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
没有符合条件的供应商时返回空数组,不是错误。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
改动前该请求必然返回:
|
||||
|
||||
```json
|
||||
{"code":400,"message":"requiredTypeCode不能为空","success":false,"data":null}
|
||||
```
|
||||
|
||||
改动后不再出现该响应。
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- `resourceModule` 与 `resourceId` 必须成对出现,否则返回 `400 resourceModule与resourceId必须同时提供或同时省略`。
|
||||
- 该接口不返回已归档、未生效或不具备 `COST_ITEM` 类型的供应商。
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
| 场景 | 正确 payload | 调用结果 |
|
||||
|---|---|---|
|
||||
| 拉候选供应商 | `?resourceModule=COST_ITEM&resourceId=2` | 返回合作中且具备额外成本类型的供应商 |
|
||||
| 查询当前关系 | 无请求体,`resourceModule=COST_ITEM` | 返回关系;无关系为 `395038` |
|
||||
| 首次绑定 | `{"supplierId":"22"}` | 不传版本对、类型和原因 |
|
||||
| 改绑 | `{"supplierId":"33","expectedCurrentSupplierId":"22","expectedRelationUpdateTime":"2026-09-12 11:40:00"}` | 两个版本字段必须成对且取最新关系值 |
|
||||
| 解绑 | `{"expectedCurrentSupplierId":"33","expectedRelationUpdateTime":"2026-09-12 11:41:00"}` | 可省略 `changeReason` |
|
||||
| 错误:传其他类型 | `{"supplierId":"33","requiredTypeCode":"SCENIC"}` | 返回 `400 requiredTypeCode与资源模块不匹配`,不写入 |
|
||||
| 错误:版本字段只传一个 | `{"supplierId":"33","expectedCurrentSupplierId":"22"}` | 返回 `400`,不写入 |
|
||||
|
||||
每次改绑或解绑前先回读当前关系,成功后重新刷新关系和额外成本列表。业务失败可能仍为 HTTP 200,必须同时判断响应体 `code` 与 `success`。
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
| 前端操作 | 外部可观察结果 |
|
||||
|---|---|
|
||||
| 首次绑定 | 生成一个当前有效关系,`requiredTypeCode=COST_ITEM`;未填原因时不虚构默认原因 |
|
||||
| 改绑 | 旧关系转为历史,新供应商成为唯一当前关系,并保留正常变更审计 |
|
||||
| 解绑 | 当前关系消失并保留正常解绑审计;额外成本列表返回 `supplierFullName: null` |
|
||||
| 版本、权限、类型或资格校验失败 | 当前关系和审计均不变化 |
|
||||
|
||||
本次不迁移或回填任何数据,不修改费用项自身业务数据,不修改订单业务、订单数据或订单供应商快照。
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 未登录返回 `401`;关系查询要求查看权限,设置、改绑、解绑要求维护权限及对应数据范围。
|
||||
- 额外成本未绑定供应商时,列表返回 `supplierFullName: null`,关系查询返回 `395038`。
|
||||
- `changeReason` 最长 500 字;省略、`null`、空串或纯空白均按未填写处理。
|
||||
- `requiredTypeCode` 省略时按 `COST_ITEM` 生效;传入任何其他值一律返回 `400 requiredTypeCode与资源模块不匹配`。
|
||||
- 额外成本改绑、解绑**不做订单核验**,已被订单引用(`refCount > 0`)的费用项同样可以改绑和解绑。
|
||||
|
||||
## 六.5、枚举 / 数据字典
|
||||
|
||||
### `resourceModule` / `requiredTypeCode`(额外成本供应商关系)
|
||||
|
||||
**所属字段**: Path `resourceModule`、Body/Response `requiredTypeCode` | **类型**: `String`
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|---|---|---|
|
||||
| `COST_ITEM` | 额外成本 | 额外成本关系固定值;`requiredTypeCode` 可省略并由后端确定 |
|
||||
|
||||
### 业务错误码
|
||||
|
||||
本次**未新增**任何业务错误码。涉及的既有错误码:
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|---|---|---|
|
||||
| `400` | requiredTypeCode与资源模块不匹配 | 显式传入非 `COST_ITEM` 类型时 |
|
||||
| `395038` | 供应商资源关联不存在 | 查询或解绑时当前无有效关系 |
|
||||
| `395014` | 数据已被其他操作修改,请刷新后重试 | 关系版本过期 |
|
||||
| `395010` | 请先完成供应商注册 | 目标供应商非合作中状态 |
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
### 字段级对比
|
||||
|
||||
| 字段 | 改前 | 改后 |
|
||||
|---|---|---|
|
||||
| `GET /admin/cost/items` 的 `records[].supplierFullName` | 不返回 | 每条固定返回 `String` 或 `null` |
|
||||
| 额外成本设置/改绑的 `requiredTypeCode` | 必填,否则 `400 requiredTypeCode不能为空` | 可省略,按 `COST_ITEM` 生效;传其他值 `400 requiredTypeCode与资源模块不匹配` |
|
||||
| 额外成本设置/改绑的 `changeReason` | 必填 | 可省略;原有非空值继续兼容 |
|
||||
| 额外成本解绑的 `changeReason` | 已可省略 | 保持可省略 |
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 行为 | 改前 | 改后 |
|
||||
|---|---|---|
|
||||
| 额外成本列表展示供应商 | 列表无名称字段 | 直接读取 `supplierFullName` |
|
||||
| 供应商候选列表带额外成本上下文 | 必然返回 `400 requiredTypeCode不能为空`,弹窗无法选人 | 返回具备 `COST_ITEM` 类型的合作中供应商 |
|
||||
| 额外成本改绑、解绑 | 不检查订单 | 仍不检查订单(本次未引入门禁) |
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **是否破坏向后兼容**: 否;新增可空字段、放宽两个可选入参、修复原本不可用的候选列表。原来显式传 `requiredTypeCode=COST_ITEM` 的调用继续有效。
|
||||
- **前端是否必须同步上线**: 是。
|
||||
- **前端 workaround 清理点**: 额外成本列表直接展示 `supplierFullName`;复用供应商关系弹窗并传 `COST_ITEM`;删除额外成本设置/改绑的原因弹框与必填校验;删除为绕过候选列表 400 而写的任何临时逻辑(如手工传类型或改用全量供应商列表)。
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- **仅影响**: 管理端额外成本列表、额外成本供应商关系操作、带额外成本上下文的供应商候选列表。
|
||||
- **零影响**: 订单创建、状态流转、结算、支付、退款、订单数据和订单供应商快照;费用项自身的创建、修改、审批、价格日历与引用计数;内部接口 `/internal/cost/**` 及其 `CostItemVO`。
|
||||
- 景区、餐厅、备品、组合配品、游玩项目、酒店、服务、服务人员、车队各模块的字段、类型、原因规则和订单门禁保持原契约;数据库结构、配置、Redis、MQ 和 Gateway 路由无变化。
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
TEST 部署提交 `ab7644ea4cb98920fed71062369b272c06a30211`(hl-resource-service,Deploy Panel API 规范客户端,双实例健康启用 2/2);2026-09-12 经 Gateway 以真实 TEST 身份实测 25/25 通过:
|
||||
|
||||
| 场景 | 结果 |
|
||||
|---|---|
|
||||
| `GET /admin/cost/items` 每条记录含 `supplierFullName`;未关联为 `null`,已关联为当前供应商全称 | 通过 |
|
||||
| 未绑定费用项查询关系返回 `395038` | 通过 |
|
||||
| `GET /admin/supplier/items/list?resourceModule=COST_ITEM&resourceId=` 由 400 改为 200,返回 2 家具备 `COST_ITEM` 类型的合作中供应商 | 通过 |
|
||||
| 首次绑定省略 `changeReason` 与 `requiredTypeCode` 成功,生效类型为 `COST_ITEM` | 通过 |
|
||||
| 改绑到另一家供应商、省略 `changeReason` 成功,生效类型仍为 `COST_ITEM` | 通过 |
|
||||
| 解绑省略 `changeReason` 成功,之后查询回到 `395038`、列表字段回到 `null` | 通过 |
|
||||
| 传 `requiredTypeCode=SCENIC` 返回 `400 requiredTypeCode与资源模块不匹配`,且原关系保持不变 | 通过 |
|
||||
| 已被订单引用(`refCount=11`)的费用项,首次绑定、改绑、解绑均成功(证明不接订单门禁) | 通过 |
|
||||
| 验收前后 `cost_item` 业务数据摘要一致 | 通过 |
|
||||
|
||||
**当前状态:后端已部署并验证;待前端处理。**
|
||||
|
||||
## 九、相关历史 PR
|
||||
|
||||
| PR | Issue | 说明 | 是否仍有效 |
|
||||
|---|---|---|---|
|
||||
| #7358 | #7357 | 景区改绑供应商免原因 | 是 |
|
||||
| #7401 | #7400 | 全部资源供应商解绑免原因 | 是 |
|
||||
| #7493 | #7387 | 餐厅列表、原因规则与订单门禁 | 是 |
|
||||
| #7507 | #7500 | 游玩项目列表、原因规则与订单门禁 | 是 |
|
||||
| #7552 | #7509 | 酒店列表、原因规则与订单门禁 | 是 |
|
||||
| #7554 | #7510 | 服务列表、原因规则与订单门禁 | 是 |
|
||||
| #7575 | #7512 | 服务人员列表与原因规则(无订单门禁) | 是 |
|
||||
| **#7573** | **#7511** | 额外成本列表、固定类型与原因规则(不含订单门禁) | **是,额外成本最新契约** |
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 关联 Issue:[#7511](https://git.1814.love:8443/wx/HL/issues/7511)
|
||||
- 后端 PR:[#7573](https://git.1814.love:8443/wx/HL/pulls/7573)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#7511](https://git.1814.love:8443/wx/HL/issues/7511)
|
||||
- **PR**: [#7573](https://git.1814.love:8443/wx/HL/pulls/7573)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @lc
|
||||
在新工单中引用
屏蔽一个用户