文件
hl-api-changelog/changelogs-v2/2026-09/12_7511_额外成本按景区口径接入供应商关系-修改接口-管理后台.md
Mimingguang 10502ccdd3
changelog-filename-gate / validate (push) Failing after 2s
chore(changelog): 补回写漏网 8 条(7510×2/7511/7512 verified+not_required;7513/7530/7531/7535 not_required)
7510/7511/7512 代码早已随供应商关系系列交付(a17a2c2a/4bedc8be),实证后回写 verified 挂 a17a2c2a;
11_7510 为正本 12_7510 的重复副本(canonical_path),标 not_required 消除 pending;
7513/7530/7531/7535 实证前端零改动 not_required。
2026-09-13 09:51:52 +08:00

25 KiB

schema, ticket, title, consumer, author, change_type, backend_status, gateway_status, frontend_status, frontend_owner, frontend_ref, target_release, verified_at, status_note, updated_at, base
schema ticket title consumer author change_type backend_status gateway_status frontend_status frontend_owner frontend_ref target_release verified_at status_note updated_at base
hl-changelog/v2 7511 额外成本按景区口径接入供应商关系 admin lc(GIT) 修改接口 deployed verified verified mmg a17a2c2a 2026-09-12 后端已部署并经 Gateway 验证;待前端展示额外成本列表供应商全称并接入额外成本供应商关系操作。 前端 2026-09-13 闭环 verified:实证代码早已随供应商关系系列交付(hl-admin a17a2c2a/4bedc8be),extra-charge/index.vue 已读 supplierFullName、SupplierRelationModal 挂 COST_ITEM,免原因弹窗已整体移除全 10 模块不弹原因框 body 不带 changeReason,候选传 resourceModule+resourceId,订单门禁走兜底透原文原关系不变。零新增改动,仅补回写凭证。 2026-09-13 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 原额外成本列表字段保持不变

请求示例

无请求体:

GET /admin/cost/items?page=1&pageSize=20 HTTP/1.1
Authorization: Bearer <token>

响应示例

{
  "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}
    ]
  }
}

空数据 / 降级响应

{"code":200,"message":"成功","success":true,"data":{"total":0,"page":1,"pageSize":20,"records":[]}}

错误响应

{"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

请求示例

无请求体:

GET /admin/supplier/resource-relations/COST_ITEM/2/view HTTP/1.1
Authorization: Bearer <token>

响应示例

{
  "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。

错误响应

{"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:

{"supplierId":"2091381643661983746"}

改绑时两个版本字段必须来自最新关系回读:

{"supplierId":"2098428486107402242","expectedCurrentSupplierId":"2091381643661983746","expectedRelationUpdateTime":"2026-09-12 11:40:00"}

响应示例

{
  "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"
  }
}

空数据 / 降级响应

无空成功结果;权限、字典或供应商资格等必要依赖无法明确核验时返回失败且不改变关系。

错误响应

{"code":400,"message":"requiredTypeCode与资源模块不匹配","success":false,"data":null}
{"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

请求示例

{"expectedCurrentSupplierId":"2098428486107402242","expectedRelationUpdateTime":"2026-09-12 11:41:00"}

响应示例

{"code":200,"message":"成功","success":true,"data":null}

空数据 / 降级响应

当前无关系时返回 395038,不执行解绑。

错误响应

{"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 是否主类型

以上均为既有字段,本次不新增、不修改字段结构。

请求示例

GET /admin/supplier/items/list?resourceModule=COST_ITEM&resourceId=2 HTTP/1.1
Authorization: Bearer <token>

响应示例

{"code":200,"message":"成功","success":true,"data":[{"supplierId":"2091381643661983746","fullName":"示例额外成本供应商有限公司","status":"ACTIVE","types":[{"typeCode":"COST_ITEM","typeName":"额外成本","isPrimary":false}]}]}

空数据 / 降级响应

没有符合条件的供应商时返回空数组,不是错误。

错误响应

改动前该请求必然返回:

{"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 额外成本列表、固定类型与原因规则(不含订单门禁) 是,额外成本最新契约

十、相关文档

关联 / 联系人

链接

联系人

  • 后端负责人: @lc