--- schema: "hl-changelog/v2" ticket: "6717" title: "车队关联供应商并展示供应商全名" consumer: "admin" author: "wx(GIT)" change_type: "修改接口" backend_status: "deployed" gateway_status: "verified" frontend_status: "pending" frontend_owner: "mmg" frontend_ref: "" target_release: "v2.1" verified_at: "2026-08-30" status_note: "PR #6753 已合并 dev-v3(merge commit 447b92f5);Deploy Panel 任务 2fe21f84(hl-fleet-service,2026-08-30 17:20)与 f29a293f(hl-resource-service,2026-08-30 17:22)均已部署测试服成功。真实 TEST 身份已验证 5 条负向链路全绿 + 列表字段结构正确,测试数据已清理。车队列表操作列的「供应商」按钮需下线,改为在车队新增/编辑弹窗选择供应商。" updated_at: "2026-08-30" base: "dev-v3" --- # 车队管理:车队关联供应商并展示供应商全名 > **存放目录**: > - 二期(v3,`order-v3` 标签的工单)→ `changelogs-v2/2026-08/` > > **服务**: hl-fleet-service(端口 8094)/ hl-resource-service(内部依赖) > **PR**: #6753 > **Issue**: #6717 > **日期**: 2026-08-30 > **影响范围**: 管理后台「车辆管理 - 车队管理」列表/新增/编辑/启用 车队管理新增供应商归属字段(可空),供应商只能随车队新增/编辑一起提交,不再提供单独配置入口。 --- ## ⚠️ 关键变化 - 车队列表操作列的「供应商」按钮(`views/fleet/teams/index.vue` 弹窗 `SupplierResourceRelModal`)**需要下线**;供应商关联改为在车队新增/编辑弹窗里选择。 - 「不选供应商不能启动,只能停用」:未关联供应商的车队调用启用接口会被 601108 拦截;新建时不选供应商则车队落停用态。 - 已有订单(名下车辆存在非取消派单)的车队,**不允许更换或清除供应商**;允许首次绑定。 --- ## 一、背景 车队此前没有供应商归属字段;供应商域(`supplier_main`,类型字典 `supplier_type`)已支持车队类型供应商(FLEET),但车队主数据与供应商档案之间未建立关联。工单 #6717 要求车队管理列表展示供应商全名,并在车队新增/编辑时明确供应商归属。 **最终口径(2026-08-30 用户确认,覆盖工单原文「必选」语义)**: 1. 供应商字段**不必填**(可选);不选 → 不能启用,只能停用。 2. 车队已有订单时,不允许**更换**或**清除**供应商(含已有订单的历史车队豁免存量,不动存量数据)。 3. 去掉单独配置供应商接口入口:前端车队列表的「供应商」按钮下线;供应商关联只随车队新增/编辑一起提交。 4. 供应商全名以**写时快照**存于 `fleet_team.supplier_name`;列表/详情零 Feign 读,供应商改名后陈旧、重新编辑可刷新。 5. 供应商资格校验走跨服务 Feign(fleet → resource `GET /internal/supplier/{supplierId}/fleet-eligibility`):主体存在 + 状态 ACTIVE + 类型关联含 FLEET。 --- ## 二、变更接口清单 | # | 接口 | 方法 | 路径 | 变更类型 | 说明 | |---|------|------|------|----------|------| | 1 | 分页查询车队 | GET | `/admin/fleet/teams/page` | 响应新增字段 | `records[]` 增加 `supplierId`(String/null) + `supplierName`(String/null) | | 2 | 查询车队详情 | GET | `/admin/fleet/teams/{fleetTeamId}` | 响应新增字段 | 同 #1 | | 3 | 新增车队 | POST | `/admin/fleet/teams` | 请求体新增可选字段 + 响应新增字段 | 请求体加 `supplierId`(Long 字符串/可空);响应同 #2 | | 4 | 编辑车队 | PUT | `/admin/fleet/teams/{fleetTeamId}` | 请求体新增可选字段 + 响应新增字段 + 失败语义 | 请求体加 `supplierId`;更换/清除时已有订单被 601111 拦截;清除且当前 ACTIVE 会强制落 DISABLED | | 5 | 启用车队 | POST | `/admin/fleet/teams/{fleetTeamId}/enable` | 失败语义新增 | `supplierId==null` 时抛 601108「车队未关联供应商,不能启用」 | --- ## 三、接口详情 ### 1. 分页查询车队 `GET /admin/fleet/teams/page` **VO**: `FleetTeamPageReqVO` / `Result>` #### 使用场景 管理后台「车辆管理 - 车队管理」列表页加载时调用。供应商列新增展示。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | `page` | Query | Integer | 否 | ≥1,默认 1 | 页码 | | `pageSize` | Query | Integer | 否 | 1..100,默认 10 | 每页大小 | | `keyword` | Query | String | 否 | ≤64 | 车队名称/负责人姓名模糊搜索 | | `teamType` | Query | String | 否 | `SELF_OPERATED`/`COOPERATIVE` | 车队类型筛选 | | `status` | Query | String | 否 | `ACTIVE`/`DISABLED` | 状态筛选 | | `settleType` | Query | String | 否 | `cash`/`sign`/`company` | 付款方式筛选 | #### 出参 `Result>` `records[]` 每一项 `FleetTeamRespVO` 字段(与改前相比只多两列,其余字段语义不变): | 字段 | 类型 | 说明 | |------|------|------| | `fleetTeamId` | String | 车队 ID(雪花序列化为字符串,禁止转 Number) | | `teamCode` | String | 内部稳定编码(`ft_xxx`) | | `teamName` | String | 车队名称 | | `teamType` | String | `SELF_OPERATED`/`COOPERATIVE` | | `leaderName` | String | 负责人姓名 | | `leaderPhone` | String | 负责人电话(**分页脱敏**:`138****8000`) | | `settleType` | String | `cash`/`sign`/`company` | | `status` | String | `ACTIVE`/`DISABLED` | | `sortOrder` | Integer | 排序值 | | `remark` | String | 备注 | | `vehicleCount` | Integer | 名下车辆总数(含停用) | | `activeVehicleCount` | Integer | 名下在役车辆数 | | **`supplierId`** | **String/null** | **关联供应商 ID(雪花字符串;未关联为 null)** | | **`supplierName`** | **String/null** | **关联供应商全称快照(写时同步,未关联为 null)** | | `createTime` | String | 创建时间 `yyyy-MM-dd HH:mm:ss` | | `updateTime` | String | 最后更新时间 `yyyy-MM-dd HH:mm:ss` | #### 请求示例 ```http GET /admin/fleet/teams/page?page=1&pageSize=10&status=ACTIVE Authorization: Bearer ``` #### 响应示例 ```json { "code": 200, "message": "成功", "success": true, "data": { "total": 2, "page": 1, "pageSize": 10, "records": [ { "fleetTeamId": "2102345678901234567", "teamCode": "ft_2x9k3m", "teamName": "合作车队A", "teamType": "COOPERATIVE", "leaderName": "测试负责人甲", "leaderPhone": "138****0001", "settleType": "cash", "status": "ACTIVE", "sortOrder": 10, "remark": "由 fleet_attribution/历史业务数据迁移", "vehicleCount": 12, "activeVehicleCount": 9, "supplierId": "2091381911266967553", "supplierName": "内蒙古呼籁旅游服务有限公司", "createTime": "2026-06-01 10:00:00", "updateTime": "2026-08-30 15:30:00" }, { "fleetTeamId": "2102345678901234568", "teamCode": "ft_2x9k3n", "teamName": "自有车队", "teamType": "SELF_OPERATED", "leaderName": "自有负责人", "leaderPhone": "139****0002", "settleType": "company", "status": "DISABLED", "sortOrder": 20, "remark": "", "vehicleCount": 0, "activeVehicleCount": 0, "supplierId": null, "supplierName": null, "createTime": "2026-08-01 09:00:00", "updateTime": "2026-08-30 15:30:00" } ] } } ``` #### 空数据 / 降级响应 无车队时 `records=[]` `total=0`,`code=200`。供应商字段未关联时为 `null`(不是空串),前端按未关联渲染。 #### 错误响应 分页查询无业务错误分支。网关/框架错误: ```json { "code": 401, "message": "未登录", "success": false, "data": null } ``` #### 业务边界 - 只读查询;返回的 `supplierId`/`supplierName` 来自 `fleet_team.supplier_id/supplier_name` 列快照,不做实时跨服务取数。 - 供应商改名后列表展示旧名(快照口径);如需最新名称,让用户重新编辑车队保存触发刷新。 - 车队停用车队(`status=DISABLED`)也可被列表查到(不带状态筛选时默认返回全部状态)。 --- ### 2. 查询车队详情 `GET /admin/fleet/teams/{fleetTeamId}` **VO**: `Result` #### 使用场景 管理后台编辑车队弹窗初始化时调用,回填 `supplierId`/`supplierName` 到供应商选择器。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | `fleetTeamId` | Path | String(Long) | 是 | 正整数 ID 字符串 | 目标车队 ID(雪花,不得转 Number) | #### 出参 `Result` 字段与 #1 `records[]` 项结构一致(含新增 `supplierId`/`supplierName`): | 字段 | 类型 | 说明 | |------|------|------| | `fleetTeamId` | String | 车队 ID(雪花序列化为字符串,禁止转 Number) | | `teamCode` | String | 内部稳定编码(`ft_xxx`) | | `teamName` | String | 车队名称 | | `teamType` | String | `SELF_OPERATED`/`COOPERATIVE` | | `leaderName` | String | 负责人姓名 | | `leaderPhone` | String | 负责人电话(**详情返回原值**,编辑场景回填用) | | `settleType` | String | `cash`/`sign`/`company` | | `status` | String | `ACTIVE`/`DISABLED` | | `sortOrder` | Integer | 排序值 | | `remark` | String | 备注 | | `vehicleCount` | Integer | 名下车辆总数(含停用) | | `activeVehicleCount` | Integer | 名下在役车辆数 | | `supplierId` | String/null | 关联供应商 ID(雪花字符串;未关联为 null) | | `supplierName` | String/null | 关联供应商全称快照(写时同步) | | `createTime` | String | 创建时间 `yyyy-MM-dd HH:mm:ss` | | `updateTime` | String | 最后更新时间 `yyyy-MM-dd HH:mm:ss` | #### 请求示例 ```http GET /admin/fleet/teams/2102345678901234567 Authorization: Bearer ``` #### 响应示例 ```json { "code": 200, "message": "成功", "success": true, "data": { "fleetTeamId": "2102345678901234567", "teamCode": "ft_2x9k3m", "teamName": "合作车队A", "teamType": "COOPERATIVE", "leaderName": "测试负责人甲", "leaderPhone": "13800000001", "settleType": "cash", "status": "ACTIVE", "sortOrder": 10, "remark": "由 fleet_attribution/历史业务数据迁移", "vehicleCount": 12, "activeVehicleCount": 9, "supplierId": "2091381911266967553", "supplierName": "内蒙古呼籁旅游服务有限公司", "createTime": "2026-06-01 10:00:00", "updateTime": "2026-08-30 15:30:00" } } ``` #### 空数据 / 降级响应 车队不存在或已软删除(业务错误,见下方错误响应);无空数据分支。 #### 错误响应 车队不存在或已软删除: ```json { "code": 601100, "message": "车队不存在", "success": false, "data": null } ``` 未登录(网关拦截): ```json { "code": 401, "message": "未登录", "success": false, "data": null } ``` #### 业务边界 - 详情接口返回 `leaderPhone` 为原值(编辑场景需要回填),分页接口脱敏。 - `supplierId` 必须按字符串处理,禁止转 JavaScript Number。 --- ### 3. 新增车队 `POST /admin/fleet/teams` **VO**: `FleetTeamSaveReqVO` / `Result` #### 使用场景 管理后台「车辆管理 - 车队管理」点击「新增车队」,在表单里可选填供应商。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | `teamName` | Body | String | 是 | ≤64,唯一 | 车队名称 | | `teamType` | Body | String | 是 | `SELF_OPERATED`/`COOPERATIVE` | 车队类型 | | `leaderName` | Body | String | 是 | ≤64 | 负责人姓名 | | `leaderPhone` | Body | String | 是 | 电话格式正则 | 负责人电话 | | `settleType` | Body | String | 是 | `cash`/`sign`/`company` | 付款方式 | | `sortOrder` | Body | Integer | 是 | ≥0 | 排序 | | `remark` | Body | String | 否 | ≤256 | 备注 | | **`supplierId`** | Body | **String(Long)/null** | **否** | **`@Positive`** | **关联供应商 ID(雪花字符串;不填=不选供应商,新建车队落 DISABLED)** | #### 出参 `Result` 响应结构同 #2;`supplierId`/`supplierName` 按请求回填(选了供应商且校验通过则写入快照,否则为 null)。 | 字段 | 类型 | 说明 | |------|------|------| | `fleetTeamId` | String | 新车队 ID(雪花序列化为字符串) | | `supplierId` | String/null | 按请求回填(选了且校验通过) | | `supplierName` | String/null | 写时同步的供应商全称快照;未选为 null | | `status` | String | 选供应商=`ACTIVE`;不选=`DISABLED` | | `createTime` | String | 创建时间 `yyyy-MM-dd HH:mm:ss` | | `updateTime` | String | 创建时间(与 createTime 相同) | | (其余字段) | - | 与详情 #2 同构 | #### 请求示例 ```http POST /admin/fleet/teams Authorization: Bearer Content-Type: application/json { "teamName": "新合作车队B", "teamType": "COOPERATIVE", "leaderName": "王队长", "leaderPhone": "13800138000", "settleType": "sign", "sortOrder": 20, "remark": "新签约车队", "supplierId": "2091381911266967553" } ``` #### 响应示例 ```json { "code": 200, "message": "成功", "success": true, "data": { "fleetTeamId": "2103456789012345678", "teamCode": "ft_3a1b2c", "teamName": "新合作车队B", "teamType": "COOPERATIVE", "leaderName": "王队长", "leaderPhone": "13800138000", "settleType": "sign", "status": "ACTIVE", "sortOrder": 20, "remark": "新签约车队", "vehicleCount": 0, "activeVehicleCount": 0, "supplierId": "2091381911266967553", "supplierName": "内蒙古呼籁旅游服务有限公司", "createTime": "2026-08-30 16:00:00", "updateTime": "2026-08-30 16:00:00" } } ``` #### 空数据 / 降级响应 未选供应商创建(落 DISABLED,供应商字段为 null): ```json { "code": 200, "message": "成功", "success": true, "data": { "fleetTeamId": "2103456789012345679", "teamCode": "ft_3a1b2d", "teamName": "临时车队", "teamType": "COOPERATIVE", "leaderName": "临时负责人", "leaderPhone": "13900139000", "settleType": "cash", "status": "DISABLED", "sortOrder": 30, "remark": "", "vehicleCount": 0, "activeVehicleCount": 0, "supplierId": null, "supplierName": null, "createTime": "2026-08-30 16:00:00", "updateTime": "2026-08-30 16:00:00" } } ``` #### 错误响应 供应商不存在/未生效/不含车队类型(不写库): ```json { "code": 601109, "message": "供应商不存在、未生效或不包含车队类型", "success": false, "data": null } ``` 供应商校验依赖故障(Feign 不可用,不写库): ```json { "code": 601110, "message": "暂时无法校验供应商,请稍后重试", "success": false, "data": null } ``` 车队名称重复(`uk_fleet_team_name` 唯一索引兜底): ```json { "code": 601101, "message": "车队名称已存在", "success": false, "data": null } ``` Bean Validation 校验失败: ```json { "code": 400, "message": "供应商ID必须为正数", "success": false, "data": null } ``` #### 业务边界 - 幂等:同一 teamName + 请求摘要 10 秒窗口内重复提交只生效一次(@Idempotent key=`fleet:team:create:{teamName}`)。 - 分布式锁:同 teamName 创建串行化(@Lock4j)。 - 选供应商时先 Feign 校验(事务外),通过后写库(事务内),Feign 失败/不合格一律不写库(失败关闭)。 - 不写供应商时新建车队 status=DISABLED,需后续编辑绑定供应商后才能启用。 --- ### 4. 编辑车队 `PUT /admin/fleet/teams/{fleetTeamId}` **VO**: `FleetTeamSaveReqVO` / `Result` #### 使用场景 管理后台编辑车队弹窗提交。可修改基础字段 + 供应商;**不允许修改 status**(启停用走独立接口)。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | `fleetTeamId` | Path | String(Long) | 是 | 正整数 ID 字符串 | 目标车队 ID(不得转 Number) | | (其余字段) | Body | String | 同新增 | 同新增 | 全字段提交(SaveReqVO 整体语义) | | `supplierId` | Body | String(Long)/null | 否 | `@Positive` | 供应商 ID;更换/清除受订单围栏 | #### 出参 `Result` 同 #2;`supplierId`/`supplierName` 反映最新写入值。 | 字段 | 类型 | 说明 | |------|------|------| | `fleetTeamId` | String | 车队 ID(路径回显) | | `supplierId` | String/null | 最新写入值(更换/清除后刷新) | | `supplierName` | String/null | 最新写入快照(更换时取新供应商 full_name;清除时为 null) | | `status` | String | 清除供应商且当前 ACTIVE 时强制落 `DISABLED`;其余场景不变 | | `updateTime` | String | 本次写入时间(秒级严格递增) | | (其余字段) | - | 与详情 #2 同构 | #### 请求示例 ```http PUT /admin/fleet/teams/2102345678901234567 Authorization: Bearer Content-Type: application/json { "teamName": "合作车队A", "teamType": "COOPERATIVE", "leaderName": "测试负责人甲", "leaderPhone": "13800000001", "settleType": "cash", "sortOrder": 10, "remark": "", "supplierId": "2091381911266967553" } ``` #### 响应示例 成功(更换供应商且无订单): ```json { "code": 200, "message": "成功", "success": true, "data": { "fleetTeamId": "2102345678901234567", "teamCode": "ft_2x9k3m", "teamName": "合作车队A", "teamType": "COOPERATIVE", "leaderName": "测试负责人甲", "leaderPhone": "13800000001", "settleType": "cash", "status": "ACTIVE", "sortOrder": 10, "remark": "", "vehicleCount": 12, "activeVehicleCount": 9, "supplierId": "2091381911266967553", "supplierName": "内蒙古呼籁旅游服务有限公司", "createTime": "2026-06-01 10:00:00", "updateTime": "2026-08-30 16:30:00" } } ``` #### 空数据 / 降级响应 清除供应商(原值 → null)且车队无订单:快照清空,若当前 ACTIVE 强制落 DISABLED(保持不变量:ACTIVE ⇒ 已绑供应商)。 ```json { "code": 200, "message": "成功", "success": true, "data": { "fleetTeamId": "2102345678901234567", "teamCode": "ft_2x9k3m", "teamName": "合作车队A", "teamType": "COOPERATIVE", "status": "DISABLED", "supplierId": null, "supplierName": null, "updateTime": "2026-08-30 16:30:00" } } ``` #### 错误响应 车队已有订单时更换/清除供应商(含从原供应商换到新供应商、从原供应商清为空): ```json { "code": 601111, "message": "车队已关联订单,不能更换供应商", "success": false, "data": null } ``` 供应商校验失败(同新增 #3 的 601109/601110);车队不存在: ```json { "code": 601100, "message": "车队不存在", "success": false, "data": null } ``` #### 业务边界 - 幂等:同 fleetTeamId + 请求摘要(含 supplierId 参与摘要)10 秒窗口内重复提交只生效一次(@Idempotent key=`fleet:team:update:{fleetTeamId}:{sha256}`)。 - 分布式锁:同 fleetTeamId 编辑串行化(@Lock4j)。 - 更换供应商校验顺序:订单围栏(601111)→ Feign 资格校验(601109/601110)→ 写库。 - 供应商不变时(含 null→null)不触发订单围栏,也不调 Feign。 - 首次绑定(null → 新值)即使已有订单也允许。 - `status` 不在 SaveReqVO,本接口不修改启停状态;清除供应商且当前 ACTIVE 时强制落 DISABLED 是唯一例外。 --- ### 5. 启用车队 `POST /admin/fleet/teams/{fleetTeamId}/enable` **VO**: `Result` #### 使用场景 管理后台对已停用车队点击「启用」。 #### 入参 | 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------|------| | `fleetTeamId` | Path | String(Long) | 是 | 正整数 ID 字符串 | 目标车队 ID | #### 出参 `Result` 成功时 `data=null`: | 字段 | 类型 | 说明 | |------|------|------| | `code` | Integer | 恒为 `200` 表示成功 | | `data` | null | 启用接口无返回体 | #### 空数据 / 降级响应 启用成功恒返回 `{"code":200,"success":true,"data":null}`;无空数据分支。已是启用态时幂等放行不重复写(返回同样结构)。 #### 请求示例 ```http POST /admin/fleet/teams/2102345678901234567/enable Authorization: Bearer ``` #### 响应示例 ```json { "code": 200, "message": "成功", "success": true, "data": null } ``` #### 错误响应 未关联供应商(新增语义): ```json { "code": 601108, "message": "车队未关联供应商,不能启用", "success": false, "data": null } ``` 已是启用态(幂等放行,不重复写): ```json { "code": 200, "message": "成功", "success": true, "data": null } ``` 车队不存在:`601100`。 #### 业务边界 - 幂等:同 fleetTeamId 重复调用只生效一次(@Idempotent key=`fleet:team:enable:{fleetTeamId}`)。 - 分布式锁:同 fleetTeamId 启停用串行化(@Lock4j key=`fleet:team:status:{fleetTeamId}`)。 - 存量迁移车队(own/coopA/coopB)当前 ACTIVE 且无供应商:enable 若已被置 DISABLED 后会被 601108 拦截,需先编辑绑定供应商再启用。 --- ## 四、契约约束与正确调用方式 ### ✅ 正确 / ❌ 错误 payload 对照 | 场景 | 调用 / 结果 | |---|---| | ✅ 新增时不选供应商 | `{ "supplierId": null }`(或不传)→ 车队落 DISABLED,待绑定后启用 | | ✅ 新增时选供应商 | `{ "supplierId": "2091381911266967553" }` → Feign 校验通过后落 ACTIVE | | ✅ 编辑时供应商不变 | 原 supplierId 原样传回(或省略由后端按原值处理?——必须原样传,SaveReqVO 整份语义) | | ✅ 首次绑定 | 原 supplierId=null,新 supplierId=有效 FLEET 供应商 → 允许(不受订单围栏) | | ✅ 供应商改名后刷新快照 | 重新编辑车队并保存(supplierId 原样),快照重新取最新 full_name | | ❌ 供应商 ID 转 Number | 雪花精度丢失;必须按字符串传输 | | ❌ 已有订单车队换供应商 | 601111,不写库 | | ❌ 已有订单车队清供应商 | 601111,不写库 | | ❌ 清除供应商后期望仍 ACTIVE | 无订单时快照清空+强制 DISABLED;前端收到 200 但 status=DISABLED 属于预期行为 | ### 关键提示(当前 TEST 构建) - `supplierId`/`supplierName` 在所有响应中均按字符串序列化(`@JsonSerialize(ToStringSerializer)`);**禁止**前端用 `Number()`/`parseInt()`/一元 `+` 转换。 - 供应商全名快照为**写时取数**:`supplier_main.full_name` 改名后列表展示旧值,重新编辑车队保存触发刷新。 - 供应商选择器数据源:可复用 `GET /admin/supplier/items/list?typeCode=FLEET&status=ACTIVE`(mmg 自查前端是否已有该接口封装;无则后端再补)。 --- ## 五、数据库行为 - **写操作只影响 `fleet_team` 一行**(新增 insert / 编辑 update / 启停用 update),不跨服务写 `supplier_resource_rel` 或 `supplier_main`。 - 快照列:`supplier_id`(关联 ID)+ `supplier_name`(全称快照)同列写入;清除时同列置 NULL。 - 幂等窗口内重复请求只写一次;锁键串行化同车队写。 - 迁移:`V20260830_001__add_supplier_to_fleet_team.sql` 对 `fleet_team` 加 `supplier_id BIGINT NULL` + `supplier_name VARCHAR(500) NULL` + 索引 `idx_fleet_team_supplier(supplier_id)`;存量车队两列均为 NULL。 - 不动 `supplier_resource_rel`(车队供应商不走资源关系表;关系表仅用于九大资源模块)。 --- ## 六、边界行为 - 未登录/登录失效:业务码 `401`(网关拦截)。 - 车队不存在:`601100`。 - 车队名称重复:`601101`(含 DuplicateKeyException 翻译)。 - 车队已停用仍选该车:`601102`(既有口径,本工单不改)。 - 车队下有在役车辆时禁止停用:`601103`(既有口径)。 - 车队已关联车辆时禁止改自有/合作类型:`601104`(既有口径)。 - 车队名下仍有车辆/司机时禁止删除:`601107`(既有口径)。 - 车队未关联供应商禁止启用:`601108`(新增)。 - 供应商不存在/未生效/不含车队类型:`601109`(新增)。 - 供应商校验依赖故障:`601110`(新增,失败关闭不写库)。 - 车队已有订单禁止更换/清除供应商:`601111`(新增)。 - Bean Validation 校验失败:`400`。 - 跨服务 Feign 不可用:写接口一律失败关闭(601110);读接口(列表/详情)读快照列,不受影响。 --- ## 六.5、枚举 / 数据字典 ### `status`(车队启停状态) **所属字段**: `FleetTeamRespVO.status` | **类型**: `String` | 值 | 中文 | 说明 | |----|------|------| | `ACTIVE` | 启用 | 可被车辆选择;**前提:已关联供应商** | | `DISABLED` | 停用 | 不可被车辆选择;新建未选供应商时默认落此态 | ### `teamType`(车队类型) **所属字段**: `FleetTeamSaveReqVO.teamType` / `FleetTeamRespVO.teamType` | **类型**: `String` | 值 | 中文 | 说明 | |----|------|------| | `SELF_OPERATED` | 自有 | 自有车队 | | `COOPERATIVE` | 合作 | 合作车队 | ### `settleType`(付款方式,字典 `resource_settle_type`) **所属字段**: `FleetTeamSaveReqVO.settleType` / `FleetTeamRespVO.settleType` | **类型**: `String` | 值 | 中文 | 说明 | |----|------|------| | `cash` | 现付 | - | | `sign` | 挂账签单 | - | | `company` | 公司月结 | - | --- ## 六.6、修改前后对比 ### 字段级对比 | 字段 | 改前 | 改后 | |------|------|------| | `FleetTeamSaveReqVO.supplierId` | 无 | 新增可选字段,@Positive,参与幂等摘要 | | `FleetTeamRespVO.supplierId` | 无 | 新增,String/null(ToStringSerializer) | | `FleetTeamRespVO.supplierName` | 无 | 新增,String/null(快照) | ### 行为级对比 | 行为 | 改前 | 改后 | |------|------|------| | 新建车队默认状态 | ACTIVE | 选供应商=ACTIVE;不选=DISABLED | | 启用校验 | 只查当前状态 | 增加 supplierId==null → 601108 | | 编辑车队供应商 | 无此字段 | 有订单禁换/禁清(601111);清除+ACTIVE→强制 DISABLED | | 供应商配置入口 | 前端有独立「供应商」按钮(调通用资源关系接口) | 下线;改为车队新增/编辑内嵌选择 | --- ## 六.7、影响评估 - **是否破坏向后兼容**: 否(新增可选字段;响应只多两列,前端旧版忽略即兼容) - **前端是否必须同步上线**: 是(车队列表「供应商」按钮需下线,否则用户仍能从旧入口调通用接口——但通用接口对车队模块本工单起后端侧保留不拦,是前端入口下线) - **前端 workaround 清理点**: 车队列表的「供应商」操作入口(`views/fleet/teams/index.vue` 中 `supplierRelaShow`/`supplierRelaRow`/`openSupplierRelation` 相关代码)整体删除 --- ## 七、不影响范围 - **仅影响**: 管理后台「车辆管理 - 车队管理」列表/新增/编辑/启用 4 个端点 + 详情 1 个端点。 - **零影响**: - 车辆档案(`fleet_vehicle`)/ 司机档案(`fleet_driver`)/ 派单(`fleet_assignment`)等车队下游域——它们继续经 `FleetTeamService.resolveForVehicle` 解析车队,供应商字段不影响车辆选车队。 - 供应商域九大资源模块(景区/餐厅/备品/组合/游玩项目/酒店/服务/额外成本/服务人员/车辆)的独立供应商关系维护(`/admin/supplier/resource-relations/{module}/{id}/update` 等通用接口保留不动)。 - 订单/对账/看板读路径(只读 fleet_team 既有字段,新增两列不影响)。 - Gateway 路由(`/admin/fleet/**` 通配已覆盖;`/internal/**` 不走网关)。 - Redis/MQ(无新增 key/消息)。 --- ## 八、测试环境已验证 真实 TEST 环境实测(2026-08-30,网关 `https://api.test.1814.love:9443`,admin token 走 `/admin/auth/login`): ``` POST /admin/fleet/teams 无供应商新建 → 200, status=DISABLED, supplierId=null, supplierName=null ✓ POST /admin/fleet/teams/{id}/enable 无供应商启用 → 601108「车队未关联供应商,不能启用」 ✓ PUT /admin/fleet/teams/{id} 绑定 DRAFT 供应商 → 601109 ✓ PUT /admin/fleet/teams/{id} 绑定不存在供应商 → 601109 ✓ PUT /admin/fleet/teams/{id} 绑定非 FLEET 类型 ACTIVE 供应商(RESTAURANT) → 601109 ✓ GET /admin/fleet/teams/page → records[] 含 supplierId/supplierName 键(未关联为 null) ✓ ``` - 部署:Deploy Panel 任务 `2fe21f84`(hl-fleet-service,2026-08-30 17:20 success)+ `f29a293f`(hl-resource-service,2026-08-30 17:22 success),预期/实际提交均为 `447b92f5143e9ca4381d590d4f868d518cf66e0a`(dev-v3 HEAD)。 - 正向链路(绑定合格 FLEET ACTIVE 供应商 → ACTIVE + 快照写 supplierName):测试服当前无 ACTIVE 状态的 FLEET 类型供应商(供应商审批链要求必备资质,`supplier_type_qualification_rule` 规则表为空,无法造出合格供应商);该路径本地单测已覆盖(`FleetTeamServiceTest#create_withEligibleSupplier_activeAndSnapshot` 等 24 用例全绿),建议 mmg 联调时在真实数据上补验。 - 有订单换供应商(601111)链路:测试服车队均无订单派单可安全构造验证数据,本地单测覆盖(`FleetTeamServiceTest#update_changeSupplierWithOrders_rejected` / `update_clearSupplierWithOrders_rejected`)。 - 本地自动化:fleet 全量 3883 项 0 失败 0 错误(含 FleetRedLineArchTest 13 项门禁);resource 全量 0 失败 0 错误;spotless:check 绿。 - 数据清理:测试车队(352385968453586944)与测试供应商(2093993745543299074)均已删除;未触碰同事真实订单;admin token 已登出。 --- ## 九、相关历史 PR | PR | Issue | 说明 | 是否仍有效 | |----|-------|------|------------| | #6753 | #6717 | 车队关联供应商并展示供应商全名(本次) | ✅ 最新 | --- ## 十、相关文档 - 关联 Issue: [wx/HL#6717](https://git.1814.love:8443/wx/HL/issues/6717) - 关联 PR: [wx/HL#6753](https://git.1814.love:8443/wx/HL/pulls/6753) - 任务设计文档: `docs/tasks/6717-fleet-team-supplier.md`(worktree `D:\work2\HL-v3-0830-fleetsup`) ## 撤回 1. 管理端先恢复车队列表「供应商」操作入口(参照改前版本),保持线上可用。 2. 从最新 `dev-v3` 建独立回退分支,回退 PR #6753 的合并 commit(`72c9018ab` 及其后续如有),验证后经独立 PR 合入。 3. 仅需下线新供应商字段时可先保留两列(快照保留无副作用),只回退 Controller/Service 逻辑与 Feign 校验。 4. 使用 Deploy Panel 滚动部署 `hl-fleet-service` 与 `hl-resource-service`;数据库列保留不删(`supplier_id`/`supplier_name` 允许 NULL,回退后不影响)。 5. 撤回后经 Gateway 验证新增/编辑/启用接口按改前口径通过;车队列表的供应商列展示空白或下线列头。 ## 关联 / 联系人 ### 链接 - **Issue**: [#6717](https://git.1814.love:8443/wx/HL/issues/6717) - **PR**: [#6753](https://git.1814.love:8443/wx/HL/pulls/6753) - **Merge commit**: 待合并后回填 ### 联系人 - **后端负责人**: @wx - **前端联动**: @mmg(下线车队列表「供应商」按钮 + 车队新增/编辑弹窗加供应商选择器)