比较提交
33
次代码提交
| 作者 | SHA1 | 提交日期 | |
|---|---|---|---|
|
|
cfc3aa8b1e | ||
|
|
72949d869c | ||
|
|
a031039140 | ||
|
|
c9efecebc5 | ||
|
|
4c12c1db01 | ||
|
|
937f353b41 | ||
|
|
43bf3936d1 | ||
|
|
33a8f7cb5b | ||
|
|
5ce53bcd47 | ||
|
|
430d825904 | ||
|
|
2b60fc3323 | ||
|
|
070c937a08 | ||
|
|
e28f675c8f | ||
|
|
787b9a007b | ||
|
|
d81fea420a | ||
|
|
0e1ab992e8 | ||
|
|
95212ad9a5 | ||
|
|
2ebb4644a6 | ||
|
|
17bb0ef2bf | ||
|
|
5c52926269 | ||
|
|
a45b51d8a8 | ||
|
|
73a4518181 | ||
|
|
8821d1df6f | ||
|
|
51708dc7a6 | ||
|
|
b28bd34ceb | ||
|
|
1ba3299afa | ||
|
|
bd235c2e1d | ||
|
|
eb628f56b0 | ||
|
|
b54c4cc8d7 | ||
|
|
4621f11e36 | ||
|
|
1f608928b7 | ||
|
|
a4aba37568 | ||
|
|
60a28339e0 |
@@ -7,9 +7,9 @@ author: "lc(GIT)"
|
||||
change_type: "新增接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "pending"
|
||||
frontend_status: "verified"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "pending"
|
||||
frontend_ref: "55a3a056"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-08-29"
|
||||
status_note: "PR #6559、#6591、#6606 已合并 dev-v3;hl-resource-service 已通过 Deploy Panel 任务 ca2fe4d7 部署最终提交 c579c87014f56452fea2fce5075b31ae6fd12a35。#6591 的写后回读、395052–395058、金额 scale 等值与草稿合同存在门禁均已进入 TEST。#6544 基线曾完成独立合同写链路实证;本次最终提交因运行时身份缺少 supplier:update,按用户确认冻结权限依赖的真实写复测,未配置权限、未发业务写请求,不把缺失权限记为通过。"
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "6620"
|
||||
title: "供应商编辑页主体证件号未绑定 tax_no"
|
||||
consumer: "admin"
|
||||
author: "lc(GIT)"
|
||||
change_type: "前端缺陷"
|
||||
backend_status: "not_required"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "verified"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "9d5dcbab"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-08-29"
|
||||
status_note: "hl-admin 端实际绑定 formData.taxNo(非 socialCreditCode,该定位对端不符);经用户拍板采用「tax_no 回显、DRAFT 可改、非 DRAFT 锁定」折中,非 DRAFT 恒不携带防 395033,未按字面「保持可编辑」。契约库记 002 tax_no 脱敏与本条称完整值不一致,回填已加脱敏守卫兼容。"
|
||||
updated_at: "2026-08-29"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 供应商编辑页主体证件号未绑定 `tax_no`
|
||||
|
||||
后端契约没有变化。打开已有供应商时,详情响应已经返回完整 `tax_no`,管理端需在表单适配层消费该字段。
|
||||
|
||||
## 接口与字段
|
||||
|
||||
- 接口:`GET /admin/supplier/items/{supplierId}/basic-info/view`
|
||||
- 响应字段:`data.tax_no`,类型为 `string`,值为完整主体证件号。
|
||||
- 创建、更新请求字段仍为 `taxNo`。响应与请求字段命名不同,前端不得改读 `data.taxNo` 或用 `socialCreditCode` 代替响应字段。
|
||||
|
||||
## 前端处理要求
|
||||
|
||||
1. 详情加载后,将非空 `data.tax_no` 映射为“主体证件号”输入框的实际值,并保持可编辑。
|
||||
2. 详情值必须覆盖表单初始空值;初始化或重置逻辑不得在映射后再次清空。
|
||||
3. 只有接口实际返回空值时才显示空输入框;占位提示不能代替已有证件号。
|
||||
4. 保存时继续按既有写契约提交 `taxNo`,不要新增或要求后端返回重复字段。
|
||||
|
||||
## 后端核验结论
|
||||
|
||||
- 响应 VO 将 Java 属性 `taxNo` 固定序列化为 JSON 字段 `tax_no`。
|
||||
- 查询服务直接取供应商主体的 `taxNo` 写入响应,持久化读取兼容密文和历史明文。
|
||||
- 供应商回显、查询、Controller 契约与聚合写入相关测试共 60 项通过,失败、错误和跳过均为 0。
|
||||
- #6499 / PR #6505 已在 TEST 验证详情返回的 `tax_no` 与数据库原值一致;当前 `dev-v3` 已包含该实现。本条目不引入后端代码、接口、数据库、配置、Redis 或 MQ 变更。
|
||||
|
||||
## 只读前端定位
|
||||
|
||||
现有管理端表单显示字段绑定为 `socialCreditCode`,详情适配逻辑没有把后端 `tax_no` 映射到该输入框,因此页面会保留初始空值。该定位仅用于联调交接,本工单未修改前端源码。
|
||||
|
||||
## 撤回
|
||||
|
||||
如需撤回本联调通知,只需回退本 Changelog 文件;既有后端 `tax_no` 契约和运行数据不受影响。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
- Issue:[#6620](https://git.1814.love:8443/wx/HL/issues/6620)
|
||||
- 后端联系人:@lc
|
||||
@@ -0,0 +1,389 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "6643"
|
||||
title: "供应商编辑页地址营业执照与备注回显"
|
||||
consumer: "admin"
|
||||
author: "lc(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "verified"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "bab3673b"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-08-29"
|
||||
status_note: "PR #6645 已合并 dev-v3;hl-resource-service 已通过 Deploy Panel 任务 954419f9 部署提交 d206d1d577a3e3f8cec228e73e139fcb36c00fd5。真实 TEST Gateway 已验证 address、licenseImageUrl、remark 原值回显、更新后回读、空地址保留、营业执照顶层字段权威读取、395014 零写入及测试草稿清理。"
|
||||
updated_at: "2026-08-29"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 🔧 供应商编辑页地址、营业执照与备注回显
|
||||
|
||||
供应商编辑页应直接使用详情根对象的 `address`、`licenseImageUrl`、`remark` 初始化表单。其中 `licenseImageUrl` 是营业执照上传控件的权威字段,不得再从 `qualifications[].imageUrl` 推导或覆盖。
|
||||
|
||||
本次没有新增接口路径、请求必填项、权限点或业务错误码。
|
||||
|
||||
## 一、关键变化
|
||||
|
||||
| 字段 | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| `data.address` | 已有详情字段,但编辑和空值保留规则缺少本次回归锁定 | 返回已保存地址;提交非空新值后可回读,更新时留空保持原值 |
|
||||
| `data.licenseImageUrl` | 详情根对象没有稳定的营业执照权威回显,调用方可能从资质数组取值 | 详情根对象稳定返回营业执照;只读取顶层权威值,不再从资质数组反向派生 |
|
||||
| `data.remark` | 已有详情字段,但编辑回显和更新规则缺少本次回归锁定 | 返回已保存内部备注;提交非空新值后可回读 |
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---:|---|---|---|---|---|
|
||||
| 1 | 查询供应商基本信息 | GET | `/admin/supplier/items/{supplierId}/basic-info/view` | 响应修改 | 根对象稳定返回 `address`、`licenseImageUrl`、`remark` |
|
||||
| 2 | 更新供应商资料 | PUT | `/admin/supplier/items/{supplierId}/update` | 行为修改 | 三字段按增量更新和并发版本规则保存;写后重新查询可得到新值 |
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 查询供应商基本信息 `GET /admin/supplier/items/{supplierId}/basic-info/view`
|
||||
|
||||
**VO**: `SupplierBasicInfoRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
管理端进入供应商详情或编辑页时获取完整表单快照。营业执照上传控件必须绑定根对象 `data.licenseImageUrl`;`qualifications[]` 继续用于独立资质列表,不是该控件的取值来源。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---:|---|---|
|
||||
| `supplierId` | Path | String | 是 | 正整数 ID 字符串 | 目标供应商;不得转为 JavaScript Number |
|
||||
|
||||
无 Query 参数,无请求体。
|
||||
|
||||
#### 出参 `Result<SupplierBasicInfoRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `data.address` | String/null | 注册地址或经营地址;未填写时为 `null` |
|
||||
| `data.licenseImageUrl` | String/null | 营业执照影像权威地址;未填写时为 `null` |
|
||||
| `data.remark` | String/null | 供应商主体内部备注;不等同于审批意见或变更原因 |
|
||||
| `data.qualifications[].imageUrl` | String/null | 单项资质影像;与根对象营业执照字段分别读取 |
|
||||
| `data.updateTime` | String | 主体并发版本,格式 `yyyy-MM-dd HH:mm:ss` |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /admin/supplier/items/2092800000000000001/basic-info/view
|
||||
Authorization: Bearer <admin-token>
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"supplierId": "2092800000000000001",
|
||||
"supplierNo": "SUP2092800000000000001",
|
||||
"fullName": "示例旅行服务有限公司",
|
||||
"shortName": "示例旅行",
|
||||
"tax_no": "91350211M000100Y46",
|
||||
"legalRepresentative": "示例法人",
|
||||
"legalRepresentativeIdNo": "11010519491231002X",
|
||||
"legalRepresentativeIdNoMask": "11010519491231002X",
|
||||
"legalRepresentativeIdCardFrontUrl": null,
|
||||
"legalRepresentativeIdCardBackUrl": null,
|
||||
"contactPhone": "13800138000",
|
||||
"contactPhoneMask": "13800138000",
|
||||
"establishDate": "2020-01-01",
|
||||
"registeredCapital": "100万元",
|
||||
"businessScope": "境内旅游服务",
|
||||
"address": "福建省厦门市示例路 2 号",
|
||||
"staffScale": "LT50",
|
||||
"mainCooperation": "酒店与景区资源合作",
|
||||
"licenseImageUrl": "https://files.example.com/supplier/business-license-new.jpg",
|
||||
"remark": "地址与营业执照已复核",
|
||||
"status": "DRAFT",
|
||||
"creditLevel": "B",
|
||||
"totalScore": null,
|
||||
"types": [],
|
||||
"primaryTypeCode": null,
|
||||
"primaryTypeName": null,
|
||||
"contacts": [],
|
||||
"qualifications": [
|
||||
{
|
||||
"qualificationId": "2092800000000000011",
|
||||
"qualType": "BUSINESS_LICENSE",
|
||||
"qualTypeName": "营业执照",
|
||||
"certNo": null,
|
||||
"certNoMask": null,
|
||||
"imageUrl": "https://files.example.com/supplier/business-license-new.jpg",
|
||||
"expiryDate": null,
|
||||
"permanentValid": true,
|
||||
"daysUntilExpiry": null,
|
||||
"validityStatus": "VALID",
|
||||
"validityStatusName": "有效",
|
||||
"isRequired": false,
|
||||
"expired": false,
|
||||
"updateTime": "2026-08-29 14:40:01"
|
||||
}
|
||||
],
|
||||
"contracts": [],
|
||||
"updateTime": "2026-08-29 14:40:02"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
未填写三字段时返回明确的 `null`,前端显示空控件即可,不要用资质数组补写营业执照:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"supplierId": "2092800000000000001",
|
||||
"address": null,
|
||||
"licenseImageUrl": null,
|
||||
"remark": null,
|
||||
"qualifications": [],
|
||||
"updateTime": "2026-08-29 14:40:02"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
供应商不存在、已删除或超出可见范围:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 395001,
|
||||
"message": "供应商不存在",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 要求可信管理身份、可读角色、`supplier:view` 平台权限和既有数据范围。
|
||||
- 根对象 `licenseImageUrl` 是编辑页营业执照的唯一权威回显;即使 `qualifications[]` 中同类型影像不同,也不得覆盖根对象值。
|
||||
- 历史记录没有可用营业执照时返回 `null`,不猜测默认图片。
|
||||
- 本接口只读,不推进版本、不触发审批或其他业务副作用。
|
||||
|
||||
### 2. 更新供应商资料 `PUT /admin/supplier/items/{supplierId}/update`
|
||||
|
||||
**VO**: `SupplierUpdateReqVO / SupplierWriteRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
管理端保存供应商编辑表单。请求只发送实际修改字段,并携带详情中的最新 `updateTime` 和本次 `changeReason`。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---:|---|---|
|
||||
| `supplierId` | Path | String | 是 | 正整数 ID 字符串 | 目标供应商 |
|
||||
| `address` | Body | String | 否 | 最长 500 字符 | 非空时更新;省略、`null`、空串或纯空格时保留原值 |
|
||||
| `licenseImageUrl` | Body | String | 否 | 最长 500 字符 | 省略时保留;传非空值时更新权威营业执照;传空串时清空 |
|
||||
| `remark` | Body | String | 否 | - | 非空时更新;省略、`null`、空串或纯空格时保留原值 |
|
||||
| `changeReason` | Body | String | 是 | 去空白后非空,最长 500 字符 | 本次资料变更原因 |
|
||||
| `expectedUpdateTime` | Body | String | 是 | `yyyy-MM-dd HH:mm:ss` | 详情最新主体并发版本 |
|
||||
|
||||
其他既有可选字段和集合快照规则保持不变。
|
||||
|
||||
#### 出参 `Result<SupplierWriteRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `data.supplierId` | String | 供应商 ID |
|
||||
| `data.supplierNo` | String | 供应商业务编号 |
|
||||
| `data.status` | String | 当前生命周期状态 |
|
||||
| `data.onboardingStage` | String | 当前注册阶段 |
|
||||
| `data.initialAccounts` | Array | 初始收款账户摘要 |
|
||||
| `data.updateTime` | String | 写入后的新主体并发版本 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
PUT /admin/supplier/items/2092800000000000001/update
|
||||
Authorization: Bearer <admin-token>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"address": "福建省厦门市示例路 2 号",
|
||||
"licenseImageUrl": "https://files.example.com/supplier/business-license-new.jpg",
|
||||
"remark": "地址与营业执照已复核",
|
||||
"changeReason": "更新供应商编辑资料",
|
||||
"expectedUpdateTime": "2026-08-29 14:40:00"
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"supplierId": "2092800000000000001",
|
||||
"supplierNo": "SUP2092800000000000001",
|
||||
"status": "DRAFT",
|
||||
"onboardingStage": "PROFILE_DRAFT",
|
||||
"initialAccounts": [],
|
||||
"updateTime": "2026-08-29 14:40:02"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
写响应不重复返回 `address`、`licenseImageUrl`、`remark`。前端需要完整表单时,应使用新 `updateTime` 重新查询详情。
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
成功更新时 `data` 恒为写入结果对象,不返回 `null`。当前请求没有实际变化时不会伪造成功或推进版本,而是返回业务失败:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 400,
|
||||
"message": "未检测到实际变化",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 395014,
|
||||
"message": "数据已被他人修改,请刷新后重试",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
收到 `395014` 后先重新获取详情,由用户确认后再提交;不得用旧表单自动覆盖。
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 要求可信管理身份、`FINANCE` 或 `SUPER_ADMIN` 写角色及 `supplier:update` 平台权限。
|
||||
- 成功写入后推进主体 `updateTime`;权限、参数、状态或并发失败时三字段及版本均不变化。
|
||||
- 顶层 `licenseImageUrl` 更新时会继续兼容同步营业执照资质影像;反向只修改 `qualifications[].imageUrl` 不会改写顶层权威字段。
|
||||
- `remark` 是主体内部备注;`changeReason` 是本次变更审计原因,两者不得互相替代。
|
||||
- 统一响应可能以 HTTP 200 承载业务失败,调用方必须同时判断 `code`、`success`、`message` 和 `data`。
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
### 编辑页初始化
|
||||
|
||||
```text
|
||||
地址输入框 ← data.address
|
||||
营业执照上传控件 ← data.licenseImageUrl
|
||||
内部备注输入框 ← data.remark
|
||||
并发版本 ← data.updateTime
|
||||
```
|
||||
|
||||
不得把 `qualifications.find(item => item.qualType === "BUSINESS_LICENSE").imageUrl` 作为营业执照上传控件的回显值或顶层字段兜底。
|
||||
|
||||
### 更新规则对照
|
||||
|
||||
| 场景 | 请求 | 结果 |
|
||||
|---|---|---|
|
||||
| 更新三个字段 | 发送三个非空新值 + 原因 + 最新版本 | 更新成功;重新查询返回三个新值 |
|
||||
| 地址留空,修改备注 | `address: " "`,`remark` 为新值 | 地址保持原值,备注更新 |
|
||||
| 省略营业执照 | 不发送 `licenseImageUrl` | 顶层营业执照保持原值 |
|
||||
| 清空营业执照 | `licenseImageUrl: ""` | 顶层营业执照清空,兼容影像同步清空 |
|
||||
| 只改资质数组影像 | 发送 `qualifications[]`,不发送顶层字段 | 资质影像独立变化,顶层营业执照保持原值 |
|
||||
| 使用旧版本 | 发送过期 `expectedUpdateTime` | 返回 `395014`,零写入 |
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
本节只描述接口可观察结果,不要求前端感知存储结构:
|
||||
|
||||
- 三字段更新成功后,再次查询详情返回新值,且主体 `updateTime` 推进。
|
||||
- `address` 或 `remark` 留空时保存其他字段,重新查询仍返回原地址或原备注。
|
||||
- 顶层 `licenseImageUrl` 更新成功后,详情根对象与兼容营业执照资质影像均返回新值。
|
||||
- 只更新资质数组影像时,详情根对象 `licenseImageUrl` 保持原值。
|
||||
- 权限、参数、状态或 `395014` 并发失败时,三字段和主体版本均不变化。
|
||||
- 部署时已对可识别的历史营业执照影像完成一次性兼容初始化;之后详情根对象不再从资质数组动态派生。
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 未登录或 Token 无效由 Gateway 拒绝,不产生业务写入。
|
||||
- 无 `supplier:view` 时详情读取失败;无 `supplier:update` 或不属于允许写角色时更新返回 `395002`,零写入。
|
||||
- 供应商不存在、已删除或不可见时返回 `395001`。
|
||||
- `address` 或 `licenseImageUrl` 超过各自长度上限时返回 `400`,零写入。
|
||||
- 缺少 `changeReason`、缺少 `expectedUpdateTime` 或没有实际变化时返回 `400`,零写入。
|
||||
- 版本过期返回 `395014`;客户端必须刷新详情,不能自动覆盖。
|
||||
- `ARCHIVED` 等不可修改状态继续由既有状态门禁拒绝。
|
||||
- 业务失败可能仍使用 HTTP 200,客户端必须检查统一响应体。
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
### 字段级对比
|
||||
|
||||
| 字段 | 改前 | 改后 |
|
||||
|---|---|---|
|
||||
| `data.address` | 字段已存在,但本次编辑回归未锁定 | 原值、更新值及留空保留规则均已锁定 |
|
||||
| `data.licenseImageUrl` | 根对象没有稳定权威回显 | 根对象返回可空权威值,不从 `qualifications[]` 反向派生 |
|
||||
| `data.remark` | 字段已存在,但本次编辑回归未锁定 | 原值和更新值均可稳定回读 |
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 行为 | 改前 | 改后 |
|
||||
|---|---|---|
|
||||
| 编辑页初始化营业执照 | 可能从资质数组搜索影像 | 固定读取根对象 `licenseImageUrl` |
|
||||
| 更新营业执照 | 根对象回读来源不稳定 | 提交顶层字段后,写后查询返回同一新值 |
|
||||
| 单独修改资质影像 | 可能被误当成主体营业执照 | 不改变根对象营业执照权威值 |
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **是否破坏向后兼容**:否。详情根对象增加可空字段并固定既有字段行为;旧客户端可忽略未知字段。
|
||||
- **前端是否必须同步上线**:需要。营业执照上传控件必须改为读取并提交顶层 `licenseImageUrl`;地址和备注继续直接绑定根对象字段。
|
||||
- **前端 workaround 清理点**:删除从 `qualifications[]` 搜索营业执照影像并覆盖表单值的逻辑。
|
||||
- **QA 重点**:已有值回显、三字段更新后刷新、地址留空保留、资质影像与顶层营业执照相互独立、旧版本失败零写入。
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- **仅影响**:管理后台供应商详情与编辑表单的地址、营业执照和内部备注。
|
||||
- **零影响**:
|
||||
- 接口路径、Gateway 路由和认证级别。
|
||||
- 供应商既有角色、平台权限和数据范围。
|
||||
- 生命周期状态机、审批、合同、收款账户和资源关系。
|
||||
- 既有业务错误码、Redis、MQ、Feign 和其他服务。
|
||||
- 小程序、C 端、Web、H5 和桌面端接口。
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
- 本地:供应商定向 62 项通过;`hl-resource-service` Reactor 全量 2,186 项通过,0 失败、0 错误,38 项既有条件跳过。
|
||||
- 合并:后端 PR [#6645](https://git.1814.love:8443/wx/HL/pulls/6645) 已合并,合并提交为 `d206d1d577a3e3f8cec228e73e139fcb36c00fd5`。
|
||||
- 部署:Deploy Panel API 任务 `954419f9` 成功,TEST 目标与实际提交均为上述合并提交;任务期 6 个采样均至少保持 2 个进程与 2 个健康启用实例匹配,0 个不可用采样。
|
||||
- 真实 Gateway:唯一测试草稿依次验证原值回显、资质影像独立变化时顶层营业执照不变、三字段更新后回读、空地址保留、旧版本 `395014` 零写入及未认证拒绝。
|
||||
- 清理:测试草稿已通过业务删除接口软删除并确认详情不可查询;仅保留系统规定的脱敏业务审计事实。
|
||||
|
||||
## 九、撤回
|
||||
|
||||
如后端撤回本次契约,前端在回退部署前停止依赖顶层 `licenseImageUrl`,并以同步发布的撤回 Changelog 为准;不得自行恢复未冻结的资质数组反向覆盖逻辑。地址与备注仍按既有 #6476 契约处理。
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 关联 Issue:[#6643](https://git.1814.love:8443/wx/HL/issues/6643)
|
||||
- 关联 PR:[#6645](https://git.1814.love:8443/wx/HL/pulls/6645)
|
||||
- 既有地址与备注契约:[#6476](./27_6476_供应商详情补齐地址备注并统一表单校验-修改接口-管理后台.md)
|
||||
- 当前前端状态:已消费(`frontend_status: verified`)。前端本就从顶层控件读执照、从未从资质数组推导;本次补齐编辑态 `licenseImageUrl` 顶层权威回显(快照比对:改携带新值/清空携带空串/未动不携带)。address/remark 沿用既有 #6476 语义(remark 回填快照比对、address 级联不回显),与本条一致未改动。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#6643](https://git.1814.love:8443/wx/HL/issues/6643)
|
||||
- **PR**: [#6645](https://git.1814.love:8443/wx/HL/pulls/6645)
|
||||
- **Merge commit**: [`d206d1d5`](https://git.1814.love:8443/wx/HL/commit/d206d1d577a3e3f8cec228e73e139fcb36c00fd5)
|
||||
- **既有地址/备注契约**: [#6476](./27_6476_供应商详情补齐地址备注并统一表单校验-修改接口-管理后台.md)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @lc
|
||||
- **管理端负责人**: 待认领(`frontend_status: pending`)
|
||||
@@ -0,0 +1,389 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "6654"
|
||||
title: "供应商合同字段可空与编辑信息入口"
|
||||
consumer: "admin"
|
||||
author: "lc(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "verified"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "5fe4e6fa"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-08-29"
|
||||
status_note: "PR #6665 已合并 dev-v3;hl-resource-service 已通过 Deploy Panel 任务 3dde08b9 部署提交 65ac86e9312b22dd2fc7313049a693fdb0ccad59。真实 TEST Gateway 已验证合同业务字段全空登记、补全、清空、删除,changeReason 必填失败零写入,expectedUpdateTime 省略成功及过期值拒绝;测试草稿已清理。合同与结算 table 的页面顺序和消费映射待前端处理。"
|
||||
updated_at: "2026-08-29"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 供应商合同字段可空与编辑信息入口
|
||||
|
||||
供应商合同继续走独立登记接口,不并入供应商档案聚合。合同的 12 个业务字段现在全部可空,`changeReason` 继续必填;合同更新、删除的 `expectedUpdateTime` 改为可选,但一旦提供,过期版本仍会被拒绝。
|
||||
|
||||
管理端新建供应商时可完全不登记合同,取得 `supplierId` 后再补录;编辑页通过基本信息详情读取合同,通过既有账户接口读取和维护结算信息。页面按“资质证照 → 合同信息 → 结算信息”排列属于前端消费工作,当前状态为待前端处理。
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---:|---|---|---|---|---|
|
||||
| 1 | 独立登记供应商合同 | POST | `/admin/supplier/items/{supplierId}/contracts/add` | 修改请求 | 12 个合同业务字段全部可空,`changeReason` 仍必填 |
|
||||
| 2 | 独立更新供应商合同 | PUT | `/admin/supplier/items/{supplierId}/contracts/{contractId}/update` | 修改请求 | 合同业务字段与新增一致且可清空,`expectedUpdateTime` 可选 |
|
||||
| 3 | 独立删除供应商合同 | DELETE | `/admin/supplier/items/{supplierId}/contracts/{contractId}/del` | 修改请求 | `expectedUpdateTime` 可选,`changeReason` 仍必填 |
|
||||
| 4 | 查询供应商基本信息 | GET | `/admin/supplier/items/{supplierId}/basic-info/view` | 修改响应语义 | `contracts[]` 的全部业务字段允许返回 `null` |
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 独立登记供应商合同 `POST /admin/supplier/items/{supplierId}/contracts/add`
|
||||
|
||||
**VO**: `SupplierContractCreateReqVO / SupplierContractRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
供应商已经创建但合同资料尚未齐全时,可先登记一条纯合同记录,之后再补充。若暂时不需要合同记录,供应商新建请求直接省略废弃的 `contracts` 字段即可。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---:|---|---|
|
||||
| `supplierId` | Path | String | 是 | 正整数 ID 字符串 | 目标供应商 |
|
||||
| `contractName` | Body | String | 否 | 最长 500 字符 | 合同名称 |
|
||||
| `contractNo` | Body | String | 否 | 最长 100 字符 | 合同编号 |
|
||||
| `contractType` | Body | String | 否 | `FRAME` / `SINGLE_TRIP` / `PURCHASE` | 合同类型 |
|
||||
| `signDate` | Body | String | 否 | `yyyy-MM-dd` | 签署日期 |
|
||||
| `startDate` | Body | String | 否 | `yyyy-MM-dd` | 有效期开始;仅与同时提供的结束日期做区间校验 |
|
||||
| `endDate` | Body | String | 否 | `yyyy-MM-dd` | 有效期结束;两端都提供时不得早于开始日期 |
|
||||
| `amount` | Body | String | 否 | 非负,最多 10 位整数和 2 位小数 | 合同金额 |
|
||||
| `pricingMode` | Body | String | 否 | 最长 100 字符 | 计价方式 |
|
||||
| `settleCycle` | Body | String | 否 | 最长 32 字符 | 结算周期说明 |
|
||||
| `status` | Body | String | 否 | `DRAFT` / `ACTIVE` / `EXPIRED` | 合同状态 |
|
||||
| `scanFileUrl` | Body | String | 否 | 最长 1000 字符 | 扫描件永久地址 |
|
||||
| `remark` | Body | String | 否 | 最长 500 字符 | 合同备注 |
|
||||
| `changeReason` | Body | String | 是 | 去空白后非空,最长 500 字符 | 审计原因,不属于可空业务字段 |
|
||||
|
||||
#### 出参 `Result<SupplierContractRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `data.contractId` | String | 新合同雪花 ID |
|
||||
| `data.contractName` 等 12 个业务字段 | 对应类型/null | 未填写的字段返回 `null` |
|
||||
| `data.updateTime` | String | 服务端合同版本,格式 `yyyy-MM-dd HH:mm:ss` |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{
|
||||
"changeReason": "合同资料暂缺,先登记记录"
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"contractId": "2094000000000000001",
|
||||
"contractName": null,
|
||||
"contractNo": null,
|
||||
"contractType": null,
|
||||
"signDate": null,
|
||||
"startDate": null,
|
||||
"endDate": null,
|
||||
"amount": null,
|
||||
"pricingMode": null,
|
||||
"settleCycle": null,
|
||||
"status": null,
|
||||
"scanFileUrl": null,
|
||||
"remark": null,
|
||||
"updateTime": "2026-08-29 16:10:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
请求体除 `changeReason` 外可以不包含任何字段;成功后返回合同对象,业务字段全部为 `null`。不要把 `null` 自动替换为默认合同类型、状态、日期或金额。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
缺少审计原因时失败且不新增合同:
|
||||
|
||||
```json
|
||||
{ "code": 400, "message": "变更原因不能为空", "success": false, "data": null }
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 要求可信管理员、`FINANCE`/`SUPER_ADMIN` 写角色和 `supplier:update` 平台权限。
|
||||
- 合同登记不进入供应商审批,不推进供应商主体 `updateTime`。
|
||||
- `POST /admin/supplier/items/add` 和供应商资料更新中的废弃 `contracts` 字段仍被忽略;前端必须先获得 `supplierId`,再按需调用本接口。
|
||||
|
||||
### 2. 独立更新供应商合同 `PUT /admin/supplier/items/{supplierId}/contracts/{contractId}/update`
|
||||
|
||||
**VO**: `SupplierContractUpdateReqVO / SupplierContractRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
补齐、修改或清空一条已登记合同。该接口执行完整替换:未传或传 `null` 的业务字段会保存为 `null`,不是“保持原值”。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---:|---|---|
|
||||
| `supplierId` | Path | String | 是 | 正整数 ID 字符串 | 目标供应商 |
|
||||
| `contractId` | Path | String | 是 | 正整数 ID 字符串 | 目标合同 |
|
||||
| 新增接口的 12 个业务字段 | Body | 对应类型 | 否 | 与新增一致 | 完整替换;省略即清空对应字段 |
|
||||
| `changeReason` | Body | String | 是 | 去空白后非空,最长 500 字符 | 审计原因 |
|
||||
| `expectedUpdateTime` | Body | String | 否 | `yyyy-MM-dd HH:mm:ss` | 提供时必须等于当前合同版本;省略时由服务端锁串行更新 |
|
||||
|
||||
#### 出参 `Result<SupplierContractRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `data.contractId` | String | 合同雪花 ID |
|
||||
| `data.contractName` 等 12 个业务字段 | 对应类型/null | 完整替换后的值,允许为 `null` |
|
||||
| `data.updateTime` | String | 更新后的合同版本 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{
|
||||
"contractName": "2026 年度框架合同",
|
||||
"contractType": "FRAME",
|
||||
"startDate": "2026-09-01",
|
||||
"endDate": "2027-08-31",
|
||||
"status": "ACTIVE",
|
||||
"changeReason": "补齐已签署合同"
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"contractId": "2094000000000000001",
|
||||
"contractName": "2026 年度框架合同",
|
||||
"contractType": "FRAME",
|
||||
"startDate": "2026-09-01",
|
||||
"endDate": "2027-08-31",
|
||||
"status": "ACTIVE",
|
||||
"updateTime": "2026-08-29 16:10:01"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
仅发送 `changeReason` 可把 12 个业务字段全部清空。若替换后的业务载荷与当前记录完全相同,返回“未检测到合同实际变化”,不会伪造新版本。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
提供过期版本时继续失败且零写入:
|
||||
|
||||
```json
|
||||
{ "code": 395014, "message": "数据已被修改,请刷新后重试", "success": false, "data": null }
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- `expectedUpdateTime` 省略不等于关闭并发保护;分布式合同锁和数据库行锁仍串行化同一合同写入。
|
||||
- 客户端若选择发送版本,必须使用最近一次详情或写响应中的 `updateTime`。
|
||||
- `changeReason` 始终必填,失败时合同和审计均不写入。
|
||||
|
||||
### 3. 独立删除供应商合同 `DELETE /admin/supplier/items/{supplierId}/contracts/{contractId}/del`
|
||||
|
||||
**VO**: `SupplierContractDeleteReqVO / Void`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
删除不再保留的合同登记。删除为软删除,并记录完整审计原因。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---:|---|---|
|
||||
| `supplierId` | Path | String | 是 | 正整数 ID 字符串 | 目标供应商 |
|
||||
| `contractId` | Path | String | 是 | 正整数 ID 字符串 | 目标合同 |
|
||||
| `expectedUpdateTime` | Body | String | 否 | `yyyy-MM-dd HH:mm:ss` | 提供时执行版本匹配 |
|
||||
| `changeReason` | Body | String | 是 | 去空白后非空,最长 500 字符 | 删除审计原因 |
|
||||
|
||||
#### 出参 `Result<Void>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `code` | Integer | 成功时为 `200` |
|
||||
| `success` | Boolean | 成功时为 `true` |
|
||||
| `data` | null | 删除成功不返回业务对象 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{
|
||||
"changeReason": "合同登记作废"
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{ "code": 200, "message": "成功", "success": true, "data": null }
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
`expectedUpdateTime` 可省略,但请求体不能省略,且必须包含非空 `changeReason`。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{ "code": 400, "message": "变更原因不能为空", "success": false, "data": null }
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 提供的过期 `expectedUpdateTime` 仍返回 `395014`,不删除、不写审计。
|
||||
- 删除成功后基本信息详情的 `contracts[]` 不再返回该记录。
|
||||
- 权限、锁、幂等、审计和软删除边界均保持原有实现。
|
||||
|
||||
### 4. 查询供应商基本信息 `GET /admin/supplier/items/{supplierId}/basic-info/view`
|
||||
|
||||
**VO**: `SupplierBasicInfoRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
进入供应商编辑页时读取主体、资质和合同。前端将 `data.contracts` 绑定到“合同信息”table。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---:|---|---|
|
||||
| `supplierId` | Path | String | 是 | 正整数 ID 字符串 | 目标供应商 |
|
||||
|
||||
无 Query 参数,无请求体。
|
||||
|
||||
#### 出参 `Result<SupplierBasicInfoRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `data.contracts` | Array | 当前未软删除合同,按 `contractId` 升序 |
|
||||
| `data.contracts[].contractId` | String | 合同雪花 ID |
|
||||
| `data.contracts[]` 的 12 个业务字段 | 对应类型/null | 合同登记未填写时返回 `null` |
|
||||
| `data.contracts[].updateTime` | String | 合同当前版本 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /admin/supplier/items/2094000000000000000/basic-info/view
|
||||
Authorization: Bearer <admin-token>
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"supplierId": "2094000000000000000",
|
||||
"contracts": [
|
||||
{
|
||||
"contractId": "2094000000000000001",
|
||||
"contractName": null,
|
||||
"contractType": null,
|
||||
"startDate": null,
|
||||
"endDate": null,
|
||||
"status": null,
|
||||
"updateTime": "2026-08-29 16:10:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
没有合同登记时 `data.contracts` 返回空数组;单份合同没有填写的业务字段返回 `null`,二者含义不同。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{ "code": 395001, "message": "供应商不存在", "success": false, "data": null }
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 只读接口要求可信管理员、可读角色和 `supplier:view` 平台权限。
|
||||
- 查询不推进主体或合同版本,不触发审批和写副作用。
|
||||
- 合同 table 的新增、编辑、删除分别调用前三个独立写接口,不把 `contracts` 回传给供应商聚合更新接口。
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
- 新建供应商可完全省略合同;若需登记,先调用 `POST /admin/supplier/items/add` 获取字符串 `supplierId`,再调用合同新增接口。
|
||||
- 编辑页合同读取使用 `GET /admin/supplier/items/{supplierId}/basic-info/view` 的 `data.contracts`。
|
||||
- 编辑页结算读取继续使用 `GET /admin/supplier/items/{supplierId}/account-info/list`;新增账户继续使用 `POST /admin/supplier/items/{supplierId}/bank-accounts/add`,其审批和主体状态门禁不变。
|
||||
- 新建的 `initialAccounts[]` 与独立账户新增的 `accounts[]` 继续复用同一 `SupplierBankAccountReqVO`,可填写项一致:`accountType`、`bankName`、`bankBranch`、`accountNo`、`proofFileUrls`、`settleMode`、`accountPeriod`、`invoiceType`、`taxRate`。新建供应商最多携带 1 项初始账户。
|
||||
- `supplierId`、`contractId`、`accountId` 和 `amount` 按字符串处理,禁止转为 JavaScript Number。
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
- Flyway migration `V20260829_002` 将 `supplier_contract.contract_name`、`contract_type`、`start_date`、`end_date`、`status` 从 `NOT NULL` 调整为可空。
|
||||
- 合同创建、更新、删除继续在本服务 schema 内完成;审计与业务写同事务提交或回滚。
|
||||
- 没有跨 schema 写入,没有新增 Redis、MQ、配置或路由变更。
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 业务字段为空不等于审计字段可空:合同新增、更新、删除均必须有 `changeReason`。
|
||||
- `expectedUpdateTime` 仅在合同更新和删除中可省略;提供时仍执行秒级版本比较。
|
||||
- 合同日期只在 `startDate` 与 `endDate` 同时存在时校验先后顺序。
|
||||
- 合同枚举字段为空时不校验;非空时仍只接受既有枚举值。
|
||||
- 账户查询和新增的既有状态、审批、权限及敏感附件读取规则未改变。
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
| 项目 | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| 合同名称、类型、开始日、结束日、状态 | 必填 | 可空 |
|
||||
| 其余 7 个合同业务字段 | 可空 | 仍可空 |
|
||||
| `changeReason` | 新增、更新、删除均必填 | 继续必填 |
|
||||
| `expectedUpdateTime` | 更新、删除必填 | 更新、删除可选;提供过期值仍拒绝 |
|
||||
| 结算信息字段模型 | 新建与独立账户新增复用同一 VO | 不变,编辑页继续复用既有账户接口 |
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **是否破坏向后兼容**: 否;原先完整载荷继续有效,旧客户端继续发送版本也有效。
|
||||
- **前端是否必须同步上线**: 是;需要增加合同/结算 table、允许合同业务控件为空,并保留 `changeReason` 必填。
|
||||
- **前端 workaround 清理点**: 删除合同业务字段的前端强制必填;不要删除 `changeReason` 校验。
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- 不修改供应商新建、更新、提交请求中的废弃 `contracts` 聚合字段语义。
|
||||
- 不修改结算账户字段、审批、状态机、默认账户、权限或接口路径。
|
||||
- 不修改供应商主体的 `changeReason`、`expectedUpdateTime` 必填规则。
|
||||
- 不新增业务错误码、Gateway 路由、Redis、MQ 或 Nacos 配置。
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
- 合同信息整体省略不影响供应商草稿创建;空业务字段合同可登记并由详情读回。
|
||||
- 合同可在不传 `expectedUpdateTime` 时补全、清空和删除;传入过期版本返回并发失败且零写入。
|
||||
- 新增、更新、删除缺少 `changeReason` 均失败且零写入。
|
||||
- 编辑供应商后合同和初始结算账户摘要保持;结算读取入口可正常访问。
|
||||
- TEST 验收产生的供应商草稿、合同和初始账户已软删除,并通过详情与结算入口读回确认不可见。
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- Issue: [#6654](https://git.1814.love:8443/wx/HL/issues/6654)
|
||||
- PR: [#6665](https://git.1814.love:8443/wx/HL/pulls/6665)
|
||||
- 合并提交: `65ac86e9312b22dd2fc7313049a693fdb0ccad59`
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
- **后端负责人**: @lc
|
||||
- **前端状态**: 已消费(`frontend_status: verified`)。页面「资质→合同→结算」排列现状已符(资质/合同在基本信息区、结算独立账户 Tab),DetailModal 合同表格对全 null 已 EMPTY/renderStatusTag 兜底安全;实质改动在 SupplierContractEditModal——12 业务字段全可空(去 contractName/contractType/status/startDate 必填、status 不再默认 DRAFT)、日期仅两端同填校验、expectedUpdateTime 编辑仅拿到版本才携带,changeReason 仍必填。
|
||||
@@ -0,0 +1,359 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "6669"
|
||||
title: "供应商草稿结算信息编辑与可选字段清空"
|
||||
consumer: "admin"
|
||||
author: "lc(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "verified"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "6678b97f"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-08-29"
|
||||
status_note: "#6669 与补充工单 #6676 已合并 dev-v3;最终提交 af5ea05df3d7490c97e6f4329c145a4014396bee 已由 Deploy Panel 任务 9be3a694 部署 TEST。真实 Gateway 已验证草稿结算创建、回读、修改、六个可选字段清空、整项清空、并发失败零写入和数据清理。当前状态:待前端处理。"
|
||||
updated_at: "2026-08-29"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 供应商草稿结算信息编辑与可选字段清空
|
||||
|
||||
供应商编辑接口新增 `initialAccounts` 完整快照。新建时登记的初始结算信息现在可在草稿编辑页读回、修改或清空;`changeReason` 和 `expectedUpdateTime` 继续必填。
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---:|---|---|---|---|---|
|
||||
| 1 | 编辑供应商 | PUT | `/admin/supplier/items/{supplierId}/update` | 修改请求与响应 | 新增可选 `initialAccounts` 完整快照,仅草稿态可维护 |
|
||||
| 2 | 查询供应商账户列表 | GET | `/admin/supplier/items/{supplierId}/account-info/list` | 修改响应语义 | 草稿供应商可读回其 `DRAFT` 初始账户 |
|
||||
| 3 | 查询收款账户详情 | GET | `/admin/supplier/bank-accounts/{accountId}/view` | 修改响应语义 | 所属供应商为草稿时可读回 `DRAFT` 账户详情 |
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 编辑供应商 `PUT /admin/supplier/items/{supplierId}/update`
|
||||
|
||||
**VO**: `SupplierUpdateReqVO / SupplierWriteRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
在供应商草稿编辑页维护与新建供应商相同的一项初始结算信息。省略 `initialAccounts` 不修改结算信息,空数组清空,1 项执行完整替换。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---:|---|---|
|
||||
| `supplierId` | Path | String | 是 | 正整数 ID 字符串 | 目标供应商 |
|
||||
| `initialAccounts` | Body | Array | 否 | 最多 1 项 | 省略不修改;`[]` 清空;1 项完整替换 |
|
||||
| `initialAccounts[].accountType` | Body | String | 项内是 | `CORPORATE` / `PERSONAL` | 账户类型 |
|
||||
| `initialAccounts[].bankName` | Body | String | 项内是 | 最长 500 字符 | 开户银行 |
|
||||
| `initialAccounts[].accountNo` | Body | String | 项内是 | 规范化后 8 至 32 位数字 | 收款账号 |
|
||||
| `initialAccounts[].bankBranch` | Body | String/null | 否 | 最长 500 字符 | 省略或 `null` 可清空 |
|
||||
| `initialAccounts[].proofFileUrls` | Body | Array/null | 否 | 最多 20 项 HTTPS 地址 | 省略、`null` 或 `[]` 可清空 |
|
||||
| `initialAccounts[].settleMode` | Body | String/null | 否 | `PREPAY` / `MONTHLY` / `SINGLE` | 可清空 |
|
||||
| `initialAccounts[].accountPeriod` | Body | String/null | 否 | 最长 50 字符 | 仅月结时填写,可清空 |
|
||||
| `initialAccounts[].invoiceType` | Body | String/null | 否 | `SPECIAL` / `NORMAL` / `NONE` | 可清空 |
|
||||
| `initialAccounts[].taxRate` | Body | String/null | 否 | 0% 至 100%,最多两位小数 | 可清空 |
|
||||
| `changeReason` | Body | String | 是 | 去空白后非空,最长 500 字符 | 审计原因,继续必填 |
|
||||
| `expectedUpdateTime` | Body | String | 是 | `yyyy-MM-dd HH:mm:ss` | 供应商主体并发版本,继续必填 |
|
||||
|
||||
#### 出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `data.supplierId` | String | 供应商雪花 ID |
|
||||
| `data.status` | String | 当前为 `DRAFT` |
|
||||
| `data.initialAccounts` | Array | 保存后的 0 或 1 项账户摘要 |
|
||||
| `data.updateTime` | String | 新的供应商并发版本 |
|
||||
|
||||
雪花 ID 均按字符串处理。
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{
|
||||
"changeReason": "维护草稿结算信息",
|
||||
"expectedUpdateTime": "2026-08-29 17:01:00",
|
||||
"initialAccounts": [
|
||||
{
|
||||
"accountType": "PERSONAL",
|
||||
"bankName": "示例银行",
|
||||
"accountNo": "6222000012345678"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"supplierId": "2094000000000000000",
|
||||
"status": "DRAFT",
|
||||
"initialAccounts": [
|
||||
{
|
||||
"accountId": "2094000000000000001",
|
||||
"accountType": "PERSONAL",
|
||||
"bankName": "示例银行",
|
||||
"bankBranch": null,
|
||||
"accountNo": "6222000012345678",
|
||||
"settleMode": null,
|
||||
"accountPeriod": null,
|
||||
"invoiceType": null,
|
||||
"taxRate": null
|
||||
}
|
||||
],
|
||||
"updateTime": "2026-08-29 17:01:01"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
- 省略 `initialAccounts`:不修改现有初始账户。
|
||||
- 传 `initialAccounts: []`:软删除草稿初始账户,写响应返回空数组。
|
||||
- 1 项中省略 6 个可选字段:`bankBranch`、`proofFileUrls`、`settleMode`、`accountPeriod`、`invoiceType`、`taxRate` 均清空;附件在读取响应中表现为空数组。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
缺少审计或并发字段时失败且账户零写入:
|
||||
|
||||
```json
|
||||
{ "code": 400, "message": "变更原因不能为空", "success": false, "data": null }
|
||||
```
|
||||
|
||||
```json
|
||||
{ "code": 400, "message": "expectedUpdateTime不能为空", "success": false, "data": null }
|
||||
```
|
||||
|
||||
过期版本继续返回既有并发错误:
|
||||
|
||||
```json
|
||||
{ "code": 395014, "message": "数据已被他人修改,请刷新后重试", "success": false, "data": null }
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- `initialAccounts` 仅允许供应商为 `DRAFT` 时维护;否则返回既有状态错误 `395005`,账户零写入。
|
||||
- 同账号完整替换保留原 `accountId`;账号全局唯一、事务、行锁、幂等和账户审计保持不变。
|
||||
- 生效后的账户继续走独立新增与审批接口,供应商编辑不得绕过账户状态机。
|
||||
|
||||
### 2. 查询供应商账户列表 `GET /admin/supplier/items/{supplierId}/account-info/list`
|
||||
|
||||
**VO**: `SupplierAccountInfoRespVO / SupplierBankAccountRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
进入供应商编辑页时加载“结算信息”table。供应商为草稿时,列表包含其初始 `DRAFT` 账户。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---:|---|---|
|
||||
| `supplierId` | Path | String | 是 | 正整数 ID 字符串 | 目标供应商 |
|
||||
|
||||
无 Query 参数,无请求体。
|
||||
|
||||
#### 出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `data.supplierId` | String | 供应商雪花 ID |
|
||||
| `data.bankAccounts` | Array | 当前可读账户列表 |
|
||||
| `data.bankAccounts[].status` | String | 草稿初始账户为 `DRAFT` |
|
||||
| `data.bankAccounts[].isDefault` | String | 草稿初始账户为 `NO` |
|
||||
| `data.updateTime` | String | 供应商主体并发版本 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /admin/supplier/items/2094000000000000000/account-info/list
|
||||
Authorization: Bearer <admin-token>
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"supplierId": "2094000000000000000",
|
||||
"bankAccounts": [
|
||||
{
|
||||
"accountId": "2094000000000000001",
|
||||
"accountType": "PERSONAL",
|
||||
"bankName": "示例银行",
|
||||
"bankBranch": null,
|
||||
"accountNo": "6222000012345678",
|
||||
"proofFileUrls": [],
|
||||
"settleMode": null,
|
||||
"accountPeriod": null,
|
||||
"invoiceType": null,
|
||||
"taxRate": null,
|
||||
"status": "DRAFT",
|
||||
"isDefault": "NO"
|
||||
}
|
||||
],
|
||||
"updateTime": "2026-08-29 17:01:01"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
没有可读账户时 `data.bankAccounts` 返回空数组。可选文本字段为空时返回 `null`;已获附件读取权限且附件为空时 `proofFileUrls` 返回空数组。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{ "code": 395001, "message": "供应商不存在", "success": false, "data": null }
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 仅当供应商当前为 `DRAFT` 时才扩展读取草稿账户;其他状态的既有可见范围不变。
|
||||
- 证明附件继续受独立权限和同步敏感读取审计约束;无权时不序列化附件原值。
|
||||
- 查询不推进供应商或账户版本,不产生业务写副作用。
|
||||
|
||||
### 3. 查询收款账户详情 `GET /admin/supplier/bank-accounts/{accountId}/view`
|
||||
|
||||
**VO**: `SupplierBankAccountDetailRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
草稿编辑页需要查看单个初始账户完整字段时,按列表返回的字符串 `accountId` 查询详情。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---:|---|---|
|
||||
| `accountId` | Path | String | 是 | 正整数 ID 字符串 | 目标账户 |
|
||||
|
||||
无 Query 参数,无请求体。
|
||||
|
||||
#### 出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `data.accountId` | String | 账户雪花 ID |
|
||||
| `data.accountType` 等账户字段 | 对应类型/null | 完整账户业务值,6 个可选字段允许为空 |
|
||||
| `data.status` | String | 草稿初始账户为 `DRAFT` |
|
||||
| `data.isDefault` | String | 草稿初始账户为 `NO` |
|
||||
| `data.updateTime` | String | 账户当前版本 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /admin/supplier/bank-accounts/2094000000000000001/view
|
||||
Authorization: Bearer <admin-token>
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"accountId": "2094000000000000001",
|
||||
"accountType": "PERSONAL",
|
||||
"bankName": "示例银行",
|
||||
"bankBranch": null,
|
||||
"accountNo": "6222000012345678",
|
||||
"proofFileUrls": [],
|
||||
"settleMode": null,
|
||||
"accountPeriod": null,
|
||||
"invoiceType": null,
|
||||
"taxRate": null,
|
||||
"status": "DRAFT",
|
||||
"isDefault": "NO",
|
||||
"updateTime": "2026-08-29 17:01:01"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
目标账户不存在、已软删除或不在当前供应商状态允许的读取范围时,不返回部分对象;统一按不存在失败关闭。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{ "code": 395001, "message": "供应商不存在", "success": false, "data": null }
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 草稿详情可见性由所属供应商当前状态决定,不能仅凭 `accountId` 绕过主体边界。
|
||||
- 完整账号沿用当前管理端授权语义;证明附件仍需独立权限与同步审计。
|
||||
- 详情查询不改变账户、供应商、审批或默认账户状态。
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
- 编辑页先调用账户列表接口加载结算 table,再把用户实际编辑后的 0 或 1 项完整快照放入供应商更新请求的 `initialAccounts`。
|
||||
- 用户未操作结算区域时可以省略 `initialAccounts`,避免无意义改写;明确清空时必须发送空数组。
|
||||
- 保存必须同时发送非空 `changeReason` 与最近读取到的供应商 `expectedUpdateTime`。
|
||||
- 账户业务字段使用与新建供应商一致的字段名;`supplierId`、`accountId` 按字符串处理,禁止转为 JavaScript Number。
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
- 草稿账户完整替换会把省略的 6 个可选字段持久化为空;重新查询不再回读旧值。
|
||||
- 空数组对草稿初始账户执行软删除;供应商、账户和审计仍在同一服务事务内提交或回滚。
|
||||
- 本次没有新增 migration、跨 schema 写入、Redis、MQ 或配置行为。
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- `accountType`、`bankName`、`accountNo` 仍为账户项必填字段;“可清空”只适用于其余 6 个可选字段。
|
||||
- `MONTHLY` 与 `accountPeriod`、开票类型与税率的既有组合校验继续生效。
|
||||
- 更新完整快照时未提供的可选字段保存为空,不是保持数据库旧值。
|
||||
- 写失败时供应商、账户和审计均在同一事务回滚;过期版本和非草稿状态均零写入。
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
| 项目 | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| 供应商编辑请求 | 无 `initialAccounts`,不能维护新建时的初始结算信息 | 可选完整快照:省略不改、空数组清空、1 项替换 |
|
||||
| 草稿账户列表/详情 | `DRAFT` 初始账户不在管理端账户读取范围 | 所属供应商为 `DRAFT` 时可读回 |
|
||||
| 可选字段清空 | 实体值虽置空,但默认更新策略可能保留数据库旧值 | 6 个可选字段显式持久化为空 |
|
||||
| 审计与并发字段 | `changeReason`、`expectedUpdateTime` 必填 | 继续必填 |
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **是否向后兼容**:是;不发送 `initialAccounts` 的原调用方保持原更新语义。
|
||||
- **前端是否需要接入**:是;编辑页结算 table 需调用账户列表并按完整快照保存。
|
||||
- **状态机影响**:无;仅草稿态开放聚合维护,其他状态继续走独立账户审批。
|
||||
- **撤回影响**:撤回后编辑页应停止发送 `initialAccounts`,草稿账户也不再通过账户列表/详情读回。
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- 不修改非草稿供应商的独立账户新增、审批、默认账户和账户状态机。
|
||||
- 不修改合同独立登记接口、供应商提交审批流程或合同字段可空契约。
|
||||
- 不新增错误码、数据库 migration、Gateway 路由、Redis、MQ、Nacos 或配置变更。
|
||||
- 不修改任何前端源码;页面 table 排列和字段消费由前端按本契约处理。
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
- 新建草稿携带完整初始结算信息后,列表可读回相同字段。
|
||||
- 草稿编辑把完整账户改为仅保留三个必填字段后,6 个可选字段保存并重新回读为空,账户 ID 保持不变。
|
||||
- 缺少 `changeReason`、缺少 `expectedUpdateTime` 和使用过期版本均失败且账户零写入。
|
||||
- 发送空数组后写响应和列表均为空;测试供应商删除后详情不可读,所有可恢复测试数据已清理。
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- Issue [#6669](https://git.1814.love:8443/wx/HL/issues/6669)
|
||||
- 补充 Issue [#6676](https://git.1814.love:8443/wx/HL/issues/6676)
|
||||
- PR [#6674](https://git.1814.love:8443/wx/HL/pulls/6674),合并提交 `35dba63d6a92159d933b38385fee057f617bdfed`
|
||||
- PR [#6677](https://git.1814.love:8443/wx/HL/pulls/6677),合并提交 `af5ea05df3d7490c97e6f4329c145a4014396bee`
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
- **后端负责人**:@lc
|
||||
- **前端负责人**:@mmg
|
||||
- **当前状态**:已消费(`frontend_status: verified`)。SupplierEditModal 结算 Tab 放开为「新建 || (编辑 && DRAFT)」,草稿编辑拉账户列表回读初始账户(含 accountId,accountNo 完整值可回填),buildEditBody 仅 DRAFT 按快照比对携带 initialAccounts(未动省略/清空 `[]`/1 项整份替换保留 accountId,6 可选字段+proofFileUrls 省略即显式清空),非 DRAFT 不开放不携带。
|
||||
@@ -0,0 +1,208 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "6684"
|
||||
title: "供应商草稿编辑变更原因可选且不写变更记录"
|
||||
consumer: "admin"
|
||||
author: "lc(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "verified"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "646a1565"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-08-30"
|
||||
status_note: "PR #6686 已合并 dev-v3,合并提交 ae7226ac8e2a23f0ce79ddda1a3a892542b81996 已由 Deploy Panel 任务 da7ffb3a 精确部署 TEST。真实 Gateway 已验证 DRAFT 草稿不传 changeReason 可成功保存、详情回读状态与修改值正确、主体变更记录仍为 0,并已删除合成草稿。当前状态:待前端处理。"
|
||||
updated_at: "2026-08-30"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 🔧 供应商草稿编辑变更原因可选且不写变更记录
|
||||
|
||||
供应商当前状态为 `DRAFT` 时,编辑接口不再要求填写 `changeReason`,并且本次草稿编辑不会新增主体变更记录。其他状态继续要求非空变更原因并保留原审计行为。
|
||||
|
||||
管理端需要按详情返回的当前状态调整表单校验:草稿隐藏或取消“变更原因”必填,非草稿继续必填。
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---:|---|---|---|---|---|
|
||||
| 1 | 编辑供应商 | PUT | `/admin/supplier/items/{supplierId}/update` | 修改请求校验与写入行为 | `DRAFT` 可省略 `changeReason` 且不新增主体变更记录;其他状态不变 |
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 编辑供应商 `PUT /admin/supplier/items/{supplierId}/update`
|
||||
|
||||
**VO**: `SupplierUpdateReqVO / SupplierWriteRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
在供应商编辑页继续完善尚未提交审批的草稿资料。调用方先读取供应商详情取得当前 `status` 和 `updateTime`,再按当前状态决定是否发送变更原因。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---:|---|---|
|
||||
| `supplierId` | Path | String | 是 | 正整数 ID 字符串 | 目标供应商 |
|
||||
| `changeReason` | Body | String | 条件必填 | 最长 500 字符 | 服务端当前状态为 `DRAFT` 时可省略或为空;其他状态去空白后必须非空 |
|
||||
| `expectedUpdateTime` | Body | String | 是 | `yyyy-MM-dd HH:mm:ss` | 最近一次详情或写响应返回的并发版本 |
|
||||
| `fullName`、`shortName` 等资料字段 | Body | 对应类型 | 否 | 沿用既有字段规则 | 仅发送本次需要修改的字段;示例使用 `remark` |
|
||||
|
||||
请求体不接收客户端自报的供应商状态;是否为草稿由服务端锁定并读取当前供应商后判定。
|
||||
|
||||
#### 出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `code` | Number | 业务码;成功为 `200` |
|
||||
| `success` | Boolean | 业务是否成功 |
|
||||
| `data.supplierId` | String | 供应商 ID |
|
||||
| `data.supplierNo` | String | 供应商业务编号 |
|
||||
| `data.status` | String | 保存后的当前状态;本场景为 `DRAFT` |
|
||||
| `data.onboardingStage` | String | 草稿为 `PROFILE_DRAFT` |
|
||||
| `data.initialAccounts` | Array | 当前初始结算账户摘要,未登记时为空数组 |
|
||||
| `data.updateTime` | String | 保存后的新并发版本 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
`DRAFT` 草稿请求体完全不发送 `changeReason`:
|
||||
|
||||
```json
|
||||
{
|
||||
"remark": "补充草稿内部备注",
|
||||
"expectedUpdateTime": "2026-08-30 08:15:29"
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"supplierId": "2093855088501035010",
|
||||
"supplierNo": "SUP2093855088501035010",
|
||||
"status": "DRAFT",
|
||||
"onboardingStage": "PROFILE_DRAFT",
|
||||
"initialAccounts": [],
|
||||
"updateTime": "2026-08-30 08:15:45"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
- `DRAFT` 时省略 `changeReason`、传 `null` 或只传空白,不会因变更原因被拒绝。
|
||||
- 省略其他可选资料字段仍表示“不修改该字段”,不自动清空既有值。
|
||||
- 依赖服务异常继续按既有失败关闭语义返回,不把未完成写入伪装成成功。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
非 `DRAFT` 供应商省略或传空白 `changeReason`,继续返回原业务错误且零写入:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 400,
|
||||
"message": "变更原因不能为空",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
并发版本过期继续返回既有错误:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 395014,
|
||||
"message": "数据已被他人修改,请刷新后重试",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 接口继续要求有效管理端身份、既有可信角色及 `supplier:update` 服务端权限。
|
||||
- 草稿判定使用服务端锁定后读取到的持久状态,不能通过请求体伪造 `DRAFT` 绕过非草稿审计。
|
||||
- `DRAFT` 成功编辑只更新草稿资料与并发版本,不新增主体变更记录。
|
||||
- 非 `DRAFT` 的原因必填、主体变更记录、事务、聚合锁、幂等和乐观并发规则均不变。
|
||||
- 参数、权限、状态或并发校验失败时不产生部分资料写入或变更记录。
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
1. 进入编辑页先调用 `GET /admin/supplier/items/{supplierId}/basic-info/view`,使用返回的 `status` 和 `updateTime`。
|
||||
2. `status=DRAFT` 时可以不渲染变更原因输入框,或取消其必填校验;保存请求可完全省略 `changeReason`。
|
||||
3. `status` 不是 `DRAFT` 时继续收集非空 `changeReason`,不要把草稿规则扩展到其他状态。
|
||||
4. 每次成功保存后使用响应中的新 `updateTime` 作为下一次编辑的 `expectedUpdateTime`。
|
||||
|
||||
| 场景 | payload | 结果 |
|
||||
|---|---|---|
|
||||
| `DRAFT` 省略原因 | `{ "remark": "补充资料", "expectedUpdateTime": "2026-08-30 08:15:29" }` | 成功,不新增主体变更记录 |
|
||||
| `DRAFT` 显式空原因 | `{ "changeReason": "", "expectedUpdateTime": "2026-08-30 08:15:29" }` | 成功,不新增主体变更记录 |
|
||||
| 非 `DRAFT` 省略原因 | `{ "remark": "调整资料", "expectedUpdateTime": "2026-08-30 08:15:29" }` | `400`,变更原因不能为空 |
|
||||
| 非 `DRAFT` 提供原因 | `{ "changeReason": "更新登记资料", "expectedUpdateTime": "2026-08-30 08:15:29" }` | 按既有编辑与审计规则处理 |
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
| 服务端当前状态 | 资料保存 | 主体变更记录 |
|
||||
|---|---|---|
|
||||
| `DRAFT` | 按既有增量编辑和并发规则保存 | 不新增本次编辑记录 |
|
||||
| 非 `DRAFT` 且原因有效 | 按既有编辑规则保存 | 继续新增完整变更记录并保留原因 |
|
||||
| 非 `DRAFT` 且原因为空 | 不保存 | 不新增记录 |
|
||||
|
||||
本次不新增数据库 migration,不改历史记录,不执行跨服务写入,也不引入 Redis、MQ 或配置写行为。
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 未登录请求继续由 Gateway 拒绝;本次不改变认证级别。
|
||||
- 无供应商写权限、供应商不存在、状态不允许或并发版本过期时继续按既有错误语义失败。
|
||||
- `changeReason` 长度上限仍为 500 字符;非草稿只传空白等同未填写。
|
||||
- 草稿成功编辑后,详情回读应保持 `status=DRAFT` 并返回本次修改值与新版本。
|
||||
- 草稿的主体变更记录分页在本次保存前后数量保持不变。
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
| 项目 | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| `DRAFT` 的 `changeReason` | 请求模型无条件必填,省略即返回“变更原因不能为空” | 可省略、为 `null` 或空白 |
|
||||
| `DRAFT` 编辑审计 | 每次成功编辑都会新增主体变更记录 | 不新增主体变更记录 |
|
||||
| 非 `DRAFT` 的 `changeReason` | 必填 | 继续必填 |
|
||||
| 非 `DRAFT` 编辑审计 | 成功后记录完整变化与原因 | 保持不变 |
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **是否破坏向后兼容**:否;原来已发送 `changeReason` 的草稿请求继续可用,非草稿契约不变。
|
||||
- **前端是否必须同步上线**:是;管理端当前草稿编辑表单仍把“变更原因”显示为必填,需要按详情 `status` 取消草稿必填。
|
||||
- **前端 workaround 清理点**:删除 `DRAFT` 草稿保存前对 `changeReason` 的必填拦截;不得删除非草稿校验。
|
||||
- **响应与错误码影响**:成功响应字段不变;只移除草稿缺少原因时的 `400`,不新增错误码。
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- 不修改供应商创建、注册提交、草稿删除、暂停、拉黑、归档、合同、账户或资源关系接口。
|
||||
- 不修改非草稿资料编辑的变更原因与主体变更记录语义。
|
||||
- 不修改 Gateway 路由、认证策略、角色或权限点。
|
||||
- 不新增数据库结构、历史数据回填、Redis、MQ、Nacos 或配置变更。
|
||||
- 本工单仅交付后端和接口 Changelog,不修改任何前端源码或资源。
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
- 合并提交 `ae7226ac8e2a23f0ce79ddda1a3a892542b81996` 已由 Deploy Panel 任务 `da7ffb3a` 精确部署到 TEST,Resource 双实例与 Nacos 注册均健康。
|
||||
- 通过真实 TEST Gateway 创建合成 `DRAFT` 草稿,并用完全不含 `changeReason` 的请求修改 `remark`;返回 `code=200`、`success=true`、`status=DRAFT` 和新的 `updateTime`。
|
||||
- 保存后再次查询详情,`remark` 为本次修改值且状态仍为 `DRAFT`。
|
||||
- 保存前后查询该供应商的 `UPDATE` 主体变更记录均返回 `total=0`、`records=[]`。
|
||||
- 验收后通过草稿删除接口清理合成数据;详情返回“供应商不存在”,按供应商编号查询列表为空。
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- Issue [#6684](https://git.1814.love:8443/wx/HL/issues/6684)
|
||||
- PR [#6686](https://git.1814.love:8443/wx/HL/pulls/6686)
|
||||
- 合并提交 [ae7226ac8e2a23f0ce79ddda1a3a892542b81996](https://git.1814.love:8443/wx/HL/commit/ae7226ac8e2a23f0ce79ddda1a3a892542b81996)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**:@lc
|
||||
- **当前状态**:已消费(`frontend_status: verified`)。SupplierEditModal 变更原因必填改仅非 DRAFT(mainLocked)挂载,DRAFT 取消必填、留空省略 changeReason 不写变更记录;buildEditBody 按「非 DRAFT 恒携带/DRAFT 填了才携带」;「至少一个实际变化字段」判定剔除元字段后看业务字段。非 DRAFT 继续必填拦截。
|
||||
@@ -0,0 +1,141 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "6710"
|
||||
title: "供应商联系人快照版本字段兼容"
|
||||
consumer: "admin"
|
||||
author: "lc(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "verified"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "95e1f917"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-08-30"
|
||||
status_note: "PR #6712 已合并 dev-v3,合并提交 7f3f944bf361b7ae41424c43b29262fb634fe791 已精确部署 TEST。后端后改判「当前前端已回传 updateTime 无需修改代码」,但前端实证:修复前 cleanContactRow 对已有联系人仅发 contactId 不带任何版本,必触发 400「联系人快照项不合法」;本次前端补回传条目级版本(commit 95e1f917),修复后方可正常保存。前端按实际修复记 verified。"
|
||||
updated_at: "2026-08-30"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 供应商联系人快照版本字段兼容
|
||||
|
||||
后端现已兼容详情联系人字段 `contacts[].updateTime`。当前前端已经回传该字段,无需修改代码;本记录仅保留历史审计,不构成前端任务。
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---:|---|---|---|---|---|
|
||||
| 1 | 编辑供应商 | PUT | `/admin/supplier/items/{supplierId}/update` | 请求兼容 | 已有联系人可用 `updateTime` 作为条目级并发版本 |
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 编辑供应商 `PUT /admin/supplier/items/{supplierId}/update`
|
||||
|
||||
**VO**: `SupplierUpdateReqVO / SupplierContactMergeReqVO / SupplierWriteRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
保留已有联系人并在一次编辑中新增多个联系人。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---:|---|---|
|
||||
| `expectedUpdateTime` | Body | String | 是 | `yyyy-MM-dd HH:mm:ss` | 主体并发版本 |
|
||||
| `contacts` | Body | Array | 否 | 最多 100 项 | 联系人完整快照 |
|
||||
| `contacts[].contactId` | Body | String | 已有项是 | 正整数 | 为空表示新增 |
|
||||
| `contacts[].expectedUpdateTime` / `updateTime` | Body | String | 已有项二选一 | 时间格式同上 | 条目级并发版本 |
|
||||
|
||||
姓名和联系电话允许与已有联系人或同批新增联系人重复。新增项仍按既有规则提供姓名、电话和角色,不传 ID 与版本。
|
||||
|
||||
#### 出参 `Result<SupplierWriteRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `data.updateTime` | String | 保存后的主体并发版本 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{"expectedUpdateTime":"2026-08-30 10:04:18","contacts":[{"contactId":"2093000000000000001","contactName":"张三","contactPhone":"18501941408","contactRole":"contentBus","updateTime":"2026-08-30 10:04:19"},{"contactName":"张三","contactPhone":"18501941408","contactRole":"contentMoney"}]}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{"code":200,"message":"成功","success":true,"data":{"updateTime":"2026-08-30 10:04:29"}}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
- 省略 `contacts` 或传 `null`:联系人不变。
|
||||
- 依赖异常:沿用既有失败关闭语义,不产生部分写入。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
版本过期时:
|
||||
|
||||
```json
|
||||
{"code":395014,"message":"数据已被他人修改,请刷新后重试","success":false,"data":null}
|
||||
```
|
||||
|
||||
已有联系人未携带任一版本字段时,仍返回 `400`、`联系人快照项不合法`。
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 已有联系人仍须提供 `contactId` 和有效版本;本次没有取消并发校验。
|
||||
- 姓名或联系电话重复本身不是错误条件。
|
||||
- 权限、状态、默认联系人及角色字典规则均不变。
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
1. 主体详情 `updateTime` 放入请求顶层 `expectedUpdateTime`。
|
||||
2. 已有联系人的 `updateTime` 可原样回传;新增联系人不传 ID 和版本。
|
||||
3. 保存后重新读取详情,使用最新版本继续编辑。
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
- 不新增数据库结构或姓名、电话唯一约束。
|
||||
- 校验失败时联系人和主体均不写入。
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 未登录、无权限、供应商不存在或版本过期时沿用既有错误。
|
||||
- `contacts=[]` 沿用既有清空语义。
|
||||
- 业务失败可能仍为 HTTP 200,调用方同时判断 `code` 和 `success`。
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
| 项目 | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| 联系人版本字段 | 仅识别 `expectedUpdateTime` | 同时识别 `expectedUpdateTime`、`updateTime` |
|
||||
| 姓名、电话重复 | 允许 | 继续允许 |
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **是否破坏向后兼容**:否。
|
||||
- **前端是否必须同步上线**:否,当前前端已回传 `updateTime`。
|
||||
- **前端 workaround 清理点**:无。
|
||||
- **响应与错误码影响**:无。
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- 不修改其他供应商接口、权限或状态机。
|
||||
- 不修改数据库结构、配置、Redis 或 MQ。
|
||||
- 不修改任何前端代码。
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
- 详情 `updateTime` 原样回传后,可保留已有联系人并新增多个联系人。
|
||||
- 多个联系人姓名、电话重复时保存成功。
|
||||
- 过期版本返回 `395014` 且零写入;验收草稿已清理。
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- [Issue #6710](https://git.1814.love:8443/wx/HL/issues/6710)
|
||||
- [PR #6712](https://git.1814.love:8443/wx/HL/pulls/6712)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
- **后端负责人**:@lc
|
||||
- **当前状态**:已消费(`frontend_status: verified`)。SupplierEditModal fillForm 回填详情联系人条目级 updateTime、createContactRow 新增行置 null;cleanContactRow 已有项(contactId 存在)加发 expectedUpdateTime=row.updateTime 原样回传,新增行仍不发版本;快照比对两侧同经 cleanContactRow,未改动不误携带。spec +3 例累计 71 例绿,commit 95e1f917 已推 v2.1。注:后端后改判「无需前端处理」系基于修复后前端,修复前旧前端仅发 contactId 必 400,本修复为必要。
|
||||
@@ -0,0 +1,805 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "6717"
|
||||
title: "车队关联供应商并展示供应商全名"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "verified"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "4faf23bb"
|
||||
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 条负向链路全绿 + 列表字段结构正确,测试数据已清理。车队列表操作列的「供应商」按钮需下线,改为在车队新增/编辑弹窗选择供应商。前端已消费(frontend_status: verified):车队列表下线操作列「供应商」入口并新增供应商列透传 supplierName 快照;新增/编辑弹窗内嵌供应商 NSelect(远程搜索 FLEET+ACTIVE、可清空、编辑回填并补快照选项防显原始 ID),提交体携带 supplierId 字符串;601108/601109/601110/601111 按 bizCode 精确提示,新建不选供应商落 DISABLED 并提示。前端 commit 4faf23bb 已推 v2.1。"
|
||||
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<PageResult<FleetTeamRespVO>>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
管理后台「车辆管理 - 车队管理」列表页加载时调用。供应商列新增展示。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| `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<PageResult<FleetTeamRespVO>>`
|
||||
|
||||
`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 <admin-token>
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```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<FleetTeamRespVO>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
管理后台编辑车队弹窗初始化时调用,回填 `supplierId`/`supplierName` 到供应商选择器。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| `fleetTeamId` | Path | String(Long) | 是 | 正整数 ID 字符串 | 目标车队 ID(雪花,不得转 Number) |
|
||||
|
||||
#### 出参 `Result<FleetTeamRespVO>`
|
||||
|
||||
字段与 #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 <admin-token>
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```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<FleetTeamRespVO>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
管理后台「车辆管理 - 车队管理」点击「新增车队」,在表单里可选填供应商。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| `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<FleetTeamRespVO>`
|
||||
|
||||
响应结构同 #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 <admin-token>
|
||||
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<FleetTeamRespVO>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
管理后台编辑车队弹窗提交。可修改基础字段 + 供应商;**不允许修改 status**(启停用走独立接口)。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| `fleetTeamId` | Path | String(Long) | 是 | 正整数 ID 字符串 | 目标车队 ID(不得转 Number) |
|
||||
| (其余字段) | Body | String | 同新增 | 同新增 | 全字段提交(SaveReqVO 整体语义) |
|
||||
| `supplierId` | Body | String(Long)/null | 否 | `@Positive` | 供应商 ID;更换/清除受订单围栏 |
|
||||
|
||||
#### 出参 `Result<FleetTeamRespVO>`
|
||||
|
||||
同 #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 <admin-token>
|
||||
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<Void>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
管理后台对已停用车队点击「启用」。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| `fleetTeamId` | Path | String(Long) | 是 | 正整数 ID 字符串 | 目标车队 ID |
|
||||
|
||||
#### 出参 `Result<Void>`
|
||||
|
||||
成功时 `data=null`:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `code` | Integer | 恒为 `200` 表示成功 |
|
||||
| `data` | null | 启用接口无返回体 |
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
启用成功恒返回 `{"code":200,"success":true,"data":null}`;无空数据分支。已是启用态时幂等放行不重复写(返回同样结构)。
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
POST /admin/fleet/teams/2102345678901234567/enable
|
||||
Authorization: Bearer <admin-token>
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```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(下线车队列表「供应商」按钮 + 车队新增/编辑弹窗加供应商选择器)
|
||||
@@ -0,0 +1,288 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "6739"
|
||||
title: "供应商三级地址保存与回显"
|
||||
consumer: "admin"
|
||||
author: "lc(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "verified"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "5f35aafd"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-08-30"
|
||||
status_note: "PR #6744 已合并 dev-v3,合并提交 2d24b329 已部署 TEST。前端实证 #6350 拼接单字符串 address 且 002 不回显与新契约相反,属 required。已实现:三级联动与详细地址独立成字段(countyId+address),RegionCascader 新增 getRegionPath 受控回显,编辑表单回填入快照增量比对、详情页 countyId 反查省市区名。前端 commit 5f35aafd 已推 v2.1。当前状态:已消费(frontend_status: verified)。"
|
||||
updated_at: "2026-08-30"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 供应商三级地址保存与回显
|
||||
|
||||
`countyId` 保存三级联动的最下级区县 ID,`address` 只保存详细地址,例如“某某路 1 号”。前端不要再使用 `addressId`。
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---:|---|---|---|---|---|
|
||||
| 1 | 新增供应商草稿 | POST | `/admin/supplier/items/add` | 请求字段 | 可传 `countyId`、`address` |
|
||||
| 2 | 修改供应商 | PUT | `/admin/supplier/items/{supplierId}/update` | 请求字段 | 可更新 `countyId`、`address` |
|
||||
| 3 | 提交供应商 | POST | `/admin/supplier/items/{supplierId}/submit` | 请求字段 | 完整表单可传 `countyId`、`address` |
|
||||
| 4 | 查询供应商基本信息 | GET | `/admin/supplier/items/{supplierId}/basic-info/view` | 响应字段 | 顶层返回三级 ID 与 `address` |
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 新增供应商草稿 `POST /admin/supplier/items/add`
|
||||
|
||||
**VO**: `SupplierDraftSaveReqVO / SupplierWriteRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
新增草稿时同时保存区县选择和详细地址。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| `countyId` | Body | String | 否 | 正整数 | 三级联动最后一级区县 ID |
|
||||
| `address` | Body | String | 否 | 最长 500 字符 | 详细地址,不拼接省市区名称 |
|
||||
| `fullName` / `taxNo` / `mainCooperation` | Body | String | 是 | 沿用既有规则 | 其他新增必填字段 |
|
||||
|
||||
#### 出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `data.supplierId` | String | 新供应商 ID |
|
||||
| `data.updateTime` | String | 后续修改使用的并发版本 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{"fullName":"示例旅行服务有限公司","taxNo":"91350211M000100Y46","countyId":"376","address":"某某路1号","mainCooperation":"景区合作"}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{"code":200,"message":"成功","success":true,"data":{"supplierId":"2093966831894056961","status":"DRAFT","updateTime":"2026-08-30 15:39:31"}}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
省略 `countyId` 或 `address` 时按 `null` 保存;依赖或业务校验失败时不产生部分写入。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{"code":400,"message":"区县ID必须为正数","success":false,"data":null}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 有三级联动选择结果时只传最后一级 `countyId`。
|
||||
- `addressId` 不是本接口字段。
|
||||
|
||||
### 2. 修改供应商 `PUT /admin/supplier/items/{supplierId}/update`
|
||||
|
||||
**VO**: `SupplierUpdateReqVO / SupplierWriteRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
编辑页修改区县或详细地址。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| `supplierId` | Path | String | 是 | 正整数 | 供应商 ID |
|
||||
| `countyId` | Body | String | 否 | 正整数 | 新的最下级区县 ID |
|
||||
| `address` | Body | String | 否 | 最长 500 字符 | 新的详细地址 |
|
||||
| `expectedUpdateTime` | Body | String | 是 | `yyyy-MM-dd HH:mm:ss` | 详情最新并发版本 |
|
||||
|
||||
#### 出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `data.supplierId` | String | 供应商 ID |
|
||||
| `data.updateTime` | String | 保存后的新并发版本 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{"countyId":"377","address":"某某路2号","expectedUpdateTime":"2026-08-30 15:39:31"}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{"code":200,"message":"成功","success":true,"data":{"supplierId":"2093966831894056961","status":"DRAFT","updateTime":"2026-08-30 15:39:42"}}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
省略 `countyId` 或 `address` 表示该字段不修改;失败时原值和版本保持不变。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{"code":400,"message":"区县ID必须为正数","success":false,"data":null}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 修改成功后使用响应中的新 `updateTime` 重新查询详情。
|
||||
- 非正数 `countyId` 在写入前拒绝。
|
||||
|
||||
### 3. 提交供应商 `POST /admin/supplier/items/{supplierId}/submit`
|
||||
|
||||
**VO**: `SupplierSubmitReqVO / SupplierApprovalCommandRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
提交完整供应商表单进入审批时一并保存区县和详细地址。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| `supplierId` | Path | String | 是 | 正整数 | 草稿供应商 ID |
|
||||
| `countyId` | Body | String | 否 | 正整数 | 最下级区县 ID |
|
||||
| `address` | Body | String | 否 | 最长 500 字符 | 详细地址 |
|
||||
| `expectedUpdateTime` | Body | String | 是 | `yyyy-MM-dd HH:mm:ss` | 当前并发版本 |
|
||||
|
||||
#### 出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `data.approvalLogId` | String | 审批记录 ID |
|
||||
| `data.approvalStatus` | String | 审批状态 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{"fullName":"示例旅行服务有限公司","taxNo":"91350211M000100Y46","countyId":"377","address":"某某路2号","mainCooperation":"景区合作","expectedUpdateTime":"2026-08-30 15:39:42"}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{"code":200,"message":"成功","success":true,"data":{"approvalLogId":"2093967000000000001","approvalStatus":"APPROVED"}}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
`countyId`、`address` 为空时按完整表单既有规则处理;提交失败不推进审批状态。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{"code":400,"message":"区县ID必须为正数","success":false,"data":null}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 仍须提交完整表单和最新 `expectedUpdateTime`。
|
||||
- 本次不改变既有审批、幂等和状态门禁。
|
||||
|
||||
### 4. 查询供应商基本信息 `GET /admin/supplier/items/{supplierId}/basic-info/view`
|
||||
|
||||
**VO**: `SupplierBasicInfoRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
编辑页读取三级联动默认值和详细地址。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| `supplierId` | Path | String | 是 | 正整数 | 供应商 ID |
|
||||
|
||||
#### 出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `data.provinceId` | String/null | 省级 ID |
|
||||
| `data.prefectureId` | String/null | 市级 ID |
|
||||
| `data.countyId` | String/null | 已保存的最下级区县 ID |
|
||||
| `data.address` | String/null | 已保存的详细地址原值 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /admin/supplier/items/2093966831894056961/basic-info/view
|
||||
Authorization: Bearer <admin-token>
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{"code":200,"message":"成功","success":true,"data":{"supplierId":"2093966831894056961","provinceId":"1","prefectureId":"35","countyId":"377","address":"某某路2号","updateTime":"2026-08-30 15:39:42"}}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
历史供应商没有 `countyId` 时三个行政区划 ID 均为 `null`,`address` 仍按历史值返回。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{"code":395001,"message":"供应商不存在","success":false,"data":null}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 三个行政区划 ID 均按 JSON 字符串返回。
|
||||
- 行政区划父链不可用或不完整时失败关闭,不伪造 ID。
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
1. 用 `/admin/region/children` 逐级选择,将最后一级 ID 作为 `countyId`,门牌内容单独放入 `address`。
|
||||
2. 编辑页按 `provinceId → prefectureId → countyId` 设置默认选中,并用 `address` 初始化详细地址输入框。
|
||||
3. 前端字段统一命名为 `countyId`;保存后使用新版本重新查询详情。
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
- 写接口成功后,后续详情查询返回已保存的 `countyId` 和 `address`。
|
||||
- 非正数 `countyId` 校验失败时不修改原值或并发版本。
|
||||
- 历史供应商不自动补行政区划 ID。
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 未认证返回业务码 `401`;供应商不存在或已删除返回 `395001`。
|
||||
- 业务失败可能仍使用 HTTP 200,前端须同时判断 `code` 与 `success`。
|
||||
- `address` 不包含省市区中文文本;省市区展示由三级 ID 对应的选项生成。
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
| 项目 | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| 写请求 | 仅保存详细地址 | 可同时保存最下级 `countyId` 与详细地址 |
|
||||
| 详情回显 | 仅有 `address` | 顶层增加三个行政区划 ID,继续返回 `address` |
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **是否破坏向后兼容**:否,历史空值继续返回 `null`。
|
||||
- **前端是否必须同步上线**:是,需要传 `countyId` 并消费三级回显字段。
|
||||
- **前端 workaround 清理点**:移除 `addressId` 映射和自行解析完整中文地址的逻辑。
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- 不改变供应商权限、状态机、审批、并发和错误码。
|
||||
- 不改变行政区划联动接口契约,也不影响其他资源模块。
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
- 新增与修改后可回读对应 `countyId`、`address`,详情三个行政区划 ID 均为字符串。
|
||||
- 非正数 `countyId` 返回 `400` 且原数据不变;未认证返回 `401`;验收草稿已清理。
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- [Issue #6739](https://git.1814.love:8443/wx/HL/issues/6739)
|
||||
- [PR #6744](https://git.1814.love:8443/wx/HL/pulls/6744)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
- **合并提交**: [2d24b3290](https://git.1814.love:8443/wx/HL/commit/2d24b32908bffb0e7ac95fd31b33cdfe56ae69f5)
|
||||
- **后端负责人**: @lc
|
||||
- **当前状态**: 已消费(`frontend_status: verified`)。RegionCascader 新增受控回显(watch value→getRegionPath 反查父链逐级预载,零 emit,停用节点占位);EditModal 提交体改 countyId+address 独立字段、fillForm 回填+快照增量比对(改动才携带、清空都不携带),删拼接逻辑;DetailModal 地址改 countyId 反查省市区名+address。spec 84 例绿,commit 5f35aafd 已推 v2.1。
|
||||
@@ -0,0 +1,293 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "6811"
|
||||
title: "车队保存(无供应商)自动落停用;仍有在役车辆时以 601112 拒绝保存"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "verified"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "6985f0cf"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-08-31"
|
||||
status_note: "#6717 只在「本次保存清除供应商」时强制停用,存量无供应商车队原样再保存仍停留启用。本单把守卫改为按保存结果判定,并对齐 disable 的在役车辆不变量新增 601112。TEST 已实测两条分支。前端已消费:submitForm 补 601112 精确提示,保存后以响应/列表刷新状态展示。"
|
||||
updated_at: "2026-08-31"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-31T10:45:00+08:00"
|
||||
---
|
||||
|
||||
# 车队编辑保存:无供应商时自动落停用,仍有在役车辆则以 601112 拒绝
|
||||
|
||||
> **服务**: hl-fleet-service (端口 8087/8187)
|
||||
> **PR**: #6827
|
||||
> **Issue**: #6811
|
||||
> **日期**: 2026-08-31
|
||||
> **影响范围**: 管理后台车务 → 车队管理 → 编辑车队保存
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
- 保存车队时若**结果为无供应商**(`supplierId` 传 `null` 或不传),后端不再允许车队停留在「启用」:无在役车辆时自动把状态落为 `DISABLED`,前端保存成功后需按响应/列表刷新后的状态展示,不能沿用提交前的「启用」。
|
||||
- 上一版(#6717)只在「本次保存把已绑供应商清空」时才强制停用,存量无供应商车队原样再保存不变状态。**该行为已收口**:现在按保存结果判定,`null → null` 同样强制停用。
|
||||
- 新增错误码 **601112**:无供应商需自动停用但车队仍有在役车辆时,保存整体失败、零写入。
|
||||
|
||||
---
|
||||
|
||||
## 一、背景
|
||||
|
||||
`fleet_team` 的不变量是 **ACTIVE ⇒ 已绑供应商**。此前新建(无供应商落停用)和启用(无供应商 601108 拒绝)都有守卫,唯独编辑保存漏了「结果无供应商」这一路,导致管理后台车队列表出现「供应商列为 —(无供应商)+ 状态启用」的行,且反复保存也不收敛。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 修改车队 | PUT | `/admin/fleet/teams/{fleetTeamId}` | 状态副作用 + 新增错误码 | 保存结果无供应商时自动落停用;仍有在役车辆则 601112 拒绝 |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 修改车队 `PUT /admin/fleet/teams/{fleetTeamId}`
|
||||
|
||||
**VO**: `FleetTeamSaveReqVO` → `Result<FleetTeamRespVO>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
车务 → 车队管理 → 列表行「编辑」→ 弹窗改车队名称/类型/负责人/电话/付款方式/排序/备注/供应商后点「保存」。本次仅状态副作用与错误码变化,路径、请求字段、字段类型与必填性都不变。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| fleetTeamId | Path | String(雪花 ID) | ✅ | - | 车队 ID,字符串传递,禁止数值化 |
|
||||
| teamName | Body | String | ✅ | 非空、全局唯一 | 车队名称 |
|
||||
| teamType | Body | String | ✅ | `SELF_OPERATED` / `COOPERATIVE` | 已关联车辆时不可切换 |
|
||||
| leaderName | Body | String | ✅ | 非空 | 负责人 |
|
||||
| leaderPhone | Body | String | ✅ | 手机号 | 负责人电话 |
|
||||
| settleType | Body | String | ✅ | `cash` / `sign` / `company` | 付款方式(资源付款方式字典) |
|
||||
| sortOrder | Body | Integer | ✅ | ≥ 0 | 排序 |
|
||||
| remark | Body | String | ❌ | ≤ 256 | 备注,空串按 null 落库 |
|
||||
| supplierId | Body | String(雪花 ID) | ❌ | 供应商需生效且含 FLEET 类型 | **不传或传 null = 不选供应商**,触发本次自动停用规则 |
|
||||
|
||||
#### 出参 `Result<FleetTeamRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| fleetTeamId | String | 车队 ID |
|
||||
| teamName | String | 车队名称 |
|
||||
| teamType | String | `SELF_OPERATED` / `COOPERATIVE` |
|
||||
| status | String | **`ACTIVE` / `DISABLED`;保存结果无供应商时返回 `DISABLED`** |
|
||||
| settleType | String | 付款方式 |
|
||||
| sortOrder | Integer | 排序 |
|
||||
| supplierId | String / null | 关联供应商 ID,null = 未关联 |
|
||||
| supplierName | String / null | 供应商全称快照,供应商为 null 时同步清空 |
|
||||
| vehicleCount | Number | 名下车辆总数 |
|
||||
| activeVehicleCount | Number | 在役车辆数 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
无供应商的车队原样保存(触发自动停用):
|
||||
|
||||
```json
|
||||
{
|
||||
"teamName": "测试车队P4-1787131721",
|
||||
"teamType": "COOPERATIVE",
|
||||
"leaderName": "测试负责人",
|
||||
"leaderPhone": "13900000001",
|
||||
"settleType": "cash",
|
||||
"sortOrder": 99,
|
||||
"remark": "由 fleet_attribution/历史业务数据迁移,负责人待完善",
|
||||
"supplierId": null
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
无供应商 + 无在役车辆 → 200,且 `status` 已变 `DISABLED`:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"fleetTeamId": "348397854705979392",
|
||||
"teamName": "测试车队P4-1787131721",
|
||||
"teamType": "COOPERATIVE",
|
||||
"status": "DISABLED",
|
||||
"settleType": "cash",
|
||||
"sortOrder": 99,
|
||||
"supplierId": null,
|
||||
"supplierName": null,
|
||||
"vehicleCount": 0,
|
||||
"activeVehicleCount": 0
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
本接口为写接口,不返回空集。供应商资格校验依赖(resource 侧)不可用时**失败关闭**,不降级放行:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 601110,
|
||||
"message": "暂时无法校验供应商,请稍后重试",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
新增(本次)——无供应商需自动停用但仍有在役车辆:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 601112,
|
||||
"message": "车队未关联供应商需自动停用,但仍有在役车辆,请先关联供应商或转移在役车辆",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
本接口其他错误码(本次未变,供自包含联调):
|
||||
|
||||
| code | message | 触发条件 |
|
||||
|------|---------|----------|
|
||||
| 601100 | 车队不存在 | `fleetTeamId` 无对应车队 |
|
||||
| 601101 | 车队名称已存在 | `teamName` 与其他车队重名 |
|
||||
| 601104 | 车队已关联车辆,不能修改自有/合作类型 | 名下有车辆时改 `teamType` |
|
||||
| 601106 | 付款方式不是有效的资源付款方式 | `settleType` 不在 `cash`/`sign`/`company` |
|
||||
| 601109 | 供应商不存在、未生效或不包含车队类型 | 绑定的 `supplierId` 不合格 |
|
||||
| 601110 | 暂时无法校验供应商,请稍后重试 | 供应商资格校验依赖不可用 |
|
||||
| 601111 | 车队已关联订单,不能更换供应商 | 已绑供应商的车队换绑/清除且名下车辆有非取消派单 |
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 鉴权:需登录且具备车务车队管理权限;未登录网关返 401。
|
||||
- **状态不由前端入参驱动**:请求体没有 `status` 字段,启停仍由 `/disable`、`/enable` 切;唯一例外是本次的自动停用副作用。
|
||||
- 供应商未变时不调供应商资格校验,也不查订单围栏;仅当 `supplierId` 发生变化才校验。
|
||||
- 已绑供应商的车队换绑或清除时,名下车辆若有非取消派单一律 601111(该守卫先于 601112 判定)。
|
||||
- 失败零写入:601112、601111、601109、601110 均在写库前抛出,名称、排序、备注等字段都不会部分保存。
|
||||
- 并发:同一 `fleetTeamId` 的保存互斥(分布式锁),重复提交不会产生中间态。
|
||||
- 已停用且无供应商的车队保存时保持停用,不报错。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
### ✅ 正确 / ❌ 错误 payload 对照
|
||||
|
||||
| 场景 | payload 与结果 |
|
||||
|------|---------------|
|
||||
| ✅ 绑定合法供应商保存 | `{"supplierId": "2094238127517319170", ...}` → 200,`status` 保持原值 |
|
||||
| ✅ 无供应商 + 无在役车辆 | `{"supplierId": null, ...}` → 200,`status=DISABLED` |
|
||||
| ✅ 已停用 + 无供应商 | `{"supplierId": null, ...}` → 200,`status` 保持 `DISABLED` |
|
||||
| ❌ 无供应商 + 有在役车辆 | `{"supplierId": null, ...}` → 601112,零写入 |
|
||||
| ❌ 靠不传 `supplierId` 保留原供应商 | 不传等同传 `null`,会被当成「清除供应商」 |
|
||||
|
||||
### 切换状态时的必要动作
|
||||
|
||||
- 编辑回显后提交必须**原样回传详情里的 `supplierId`**;字段缺失就是清除语义,不存在「不传 = 不改」。
|
||||
- 保存成功后不能沿用提交前的状态展示:用响应体 `data.status` 或重拉列表/详情。
|
||||
- 收到 601112 时弹后端 message 即可;修复路径是「选一个合法供应商」或「先把在役车辆转走/停用」。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
| 前端提交 | 保存后外部可观察结果 |
|
||||
|----------|--------------------|
|
||||
| `supplierId` 合法值 | 车队关联该供应商,`supplierName` 刷为当前全称快照,状态不变 |
|
||||
| `supplierId=null`,无在役车辆 | 供应商关联与全称快照同时置空,状态变 `DISABLED` |
|
||||
| `supplierId=null`,有在役车辆 | 零写入(名称/排序/备注等也不保存) |
|
||||
|
||||
本次无表结构变更、无 Flyway 迁移,存量脏数据不做批量刷状态(下次编辑保存时自然收口)。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 未登录 → 401(网关拦截)。
|
||||
- 车队不存在 → 601100。
|
||||
- 供应商资格依赖降级 → 601110 失败关闭,不静默放行。
|
||||
- 存量无供应商且仍启用的车队:不会被后台任务批量改状态,只在下次编辑保存时收口。
|
||||
- 已绑供应商的车队保存行为与之前一致,无新增拦截。
|
||||
- 供应商改名后快照陈旧:仍需重新编辑车队刷新(行为未变)。
|
||||
|
||||
---
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
### 字段级对比
|
||||
|
||||
| 字段 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| 请求体字段 | 无变化 | 无变化(路径、字段名、类型、必填性全部不变) |
|
||||
| 响应 `data.status` | 仅当本次清除已绑供应商时可能返回 `DISABLED` | **只要保存结果无供应商且原为启用,就返回 `DISABLED`** |
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 行为 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| 存量无供应商车队(`null → null`)原样保存 | 保持「启用」 | 无在役车辆 → 自动「停用」;有在役车辆 → 601112 |
|
||||
| 清除已绑供应商(`X → null`) | 启用→停用(不查在役车辆) | 有在役车辆时改为 601112 拒绝,不再产出「停用车队挂在役车辆」脏态 |
|
||||
| 绑定/换绑合法供应商 | 200,状态不变 | 不变 |
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **是否破坏向后兼容**:否(请求/响应结构不变,新增一个错误码与一个状态副作用)。
|
||||
- **前端是否必须同步上线**:否。旧前端不报错,但若保存后不刷新列表,页面会短暂显示陈旧的「启用」。
|
||||
- **前端 workaround 清理点**:若前端曾为「无供应商仍显示启用」做过本地推断或提示,可改为直读后端 `status`。
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- **仅影响**:管理后台车队编辑保存(`PUT /admin/fleet/teams/{fleetTeamId}`)。
|
||||
- **零影响**:
|
||||
- 新建车队 `POST /admin/fleet/teams`(无供应商本来就落停用)
|
||||
- 启用/停用 `POST /admin/fleet/teams/{id}/enable`、`/disable`
|
||||
- 车队列表、详情、options 读接口字段
|
||||
- 车辆、司机、派单、看板、对账等其他车务接口
|
||||
- 存量数据(不批量刷状态)
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
部署:`dev-v3` 合入 PR #6827(merge `740fdb834`)后部署 hl-fleet-service(8087/8187 滚动,两实例健康)。
|
||||
|
||||
```
|
||||
PUT /admin/fleet/teams/348397854705979392 (无供应商 + 启用 + activeVehicleCount=0)
|
||||
→ 200 成功,保存后 GET 详情 status=DISABLED, supplierId=null ✓
|
||||
|
||||
PUT /admin/fleet/teams/348398422098841600 (无供应商 + 启用 + activeVehicleCount=1)
|
||||
→ 601112 「车队未关联供应商需自动停用,但仍有在役车辆…」
|
||||
保存后 GET 详情 status=ACTIVE(零写入) ✓
|
||||
|
||||
PUT /admin/fleet/teams/2026072200010000001 (已绑供应商 + 有订单, 清除供应商)
|
||||
→ 601111 既有订单围栏优先,行为未变 ✓
|
||||
```
|
||||
|
||||
验证用车队:`348397854705979392`(测试车队P4-1787131721)、`348398422098841600`(测试车队P4-1787131856,验证用临时车辆已删除并回到 0 车)。
|
||||
|
||||
单测与构建:`mvn -pl hl-fleet-service -am verify` → Tests run 3887 / Failures 0 / Errors 0(Skipped 5 为既有 Release-E 跳过项);`mvn -pl hl-fleet-service spotless:check` → 799 文件 0 违规。
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 前置契约:`changelogs-v2/2026-08/30_6717_车队关联供应商并展示供应商全名-修改接口-管理后台.md`
|
||||
- 本次 PR:[#6827](https://git.1814.love:8443/wx/HL/pulls/6827)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
- Issue:[#6811](https://git.1814.love:8443/wx/HL/issues/6811)
|
||||
- 后端联系人:@wx
|
||||
@@ -0,0 +1,488 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "6834"
|
||||
title: "供应商余额支付类型与草稿编辑项"
|
||||
consumer: "admin"
|
||||
author: "lc(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "verified"
|
||||
frontend_owner: "hl-admin"
|
||||
frontend_ref: "848cca7b"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-09-01"
|
||||
status_note: "PR #6860 已合并 dev-v3,合并提交 6ed8e24c0f04051c9c4385c79de67f255abb4952 已部署 TEST。真实 Gateway 已验证支付类型字典、新增必填与失败零写入、余额和支付类型保存回显、草稿省略 changeReason 更新及验收数据清理。前端 hl-admin v2.1 提交 848cca7b 已交付:SupplierEditModal 新增余额 NInputNumber(数值允许负数,提交 number)+支付类型 NSelect(接 supplier_payment_type 字典存 dictValue 显 dictLabel,字典未就绪禁用提交不硬编码),balance 必填用 type:'number' 修 async-validator 误判,编辑按快照增量仅改动携带,DetailModal 回显两字段;spec 12 例绿。当前状态:后端已就绪,前端已交付。"
|
||||
updated_at: "2026-09-01"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 供应商余额支付类型与草稿编辑项
|
||||
|
||||
供应商新增、提交、编辑和详情增加 `balance`、`paymentType`。新增与提交时两项必填;编辑时可按增量提交,详情用于回显。支付类型从 `supplier_payment_type` 字典读取。
|
||||
|
||||
供应商详情 `status=DRAFT` 时,编辑页隐藏“变更原因”;其他状态继续显示并必填。
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---:|---|---|---|---|---|
|
||||
| 1 | 新增供应商草稿 | POST | `/admin/supplier/items/add` | 请求字段 | `balance`、`paymentType` 必填 |
|
||||
| 2 | 提交供应商审批 | POST | `/admin/supplier/items/{supplierId}/submit` | 请求字段 | 完整表单中的两个字段必填 |
|
||||
| 3 | 编辑供应商 | PUT | `/admin/supplier/items/{supplierId}/update` | 请求字段与状态规则 | 可更新两个字段;草稿可省略 `changeReason` |
|
||||
| 4 | 查询供应商基本信息 | GET | `/admin/supplier/items/{supplierId}/basic-info/view` | 响应字段 | 返回 `balance`、`paymentType`、`status` |
|
||||
| 5 | 查询支付类型字典类型 | GET | `/admin/dict/type` | 新增字典数据 | 可按 `supplier_payment_type` 查询字典类型 |
|
||||
| 6 | 查询支付类型字典项 | GET | `/admin/dict/data/supplier_payment_type` | 新增字典数据 | 返回现付、签单、月付 |
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 新增供应商草稿 `POST /admin/supplier/items/add`
|
||||
|
||||
**VO**: `SupplierDraftSaveReqVO / SupplierWriteRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
新增供应商时录入初始余额并选择支付类型。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---:|---|---|
|
||||
| `balance` | Body | Number | 是 | 最多 16 位整数、2 位小数,可为正数、0 或负数 | 供应商当前余额 |
|
||||
| `paymentType` | Body | String | 是 | 必须命中当前生效的 `supplier_payment_type` 字典项 | 支付类型值,传 `1`、`2` 或 `3` |
|
||||
| `fullName` / `taxNo` / `mainCooperation` | Body | String | 是 | 沿用既有规则 | 其他新增必填字段 |
|
||||
|
||||
#### 出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `data.supplierId` | String | 新供应商 ID |
|
||||
| `data.status` | String | 新增草稿为 `DRAFT` |
|
||||
| `data.updateTime` | String | 后续编辑使用的并发版本 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{
|
||||
"fullName": "示例供应商有限公司",
|
||||
"taxNo": "91350211M000100Y46",
|
||||
"mainCooperation": "旅游资源合作",
|
||||
"balance": 1200.50,
|
||||
"paymentType": "1"
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"supplierId": "2094278854020399106",
|
||||
"status": "DRAFT",
|
||||
"updateTime": "2026-08-31 12:19:23"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
`balance` 或 `paymentType` 省略、为 `null` 或支付类型为空白时返回业务码 `400`,不创建草稿。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{"code":400,"message":"余额不能为空","success":false,"data":null}
|
||||
```
|
||||
|
||||
```json
|
||||
{"code":400,"message":"供应商支付类型不合法或已停用","success":false,"data":null}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 前端必须传数值型 `balance`,不要传格式化金额字符串。
|
||||
- `paymentType` 传字典 `dictValue`,不要传“现付”等展示文本。
|
||||
- 校验失败时不产生供应商记录。
|
||||
|
||||
### 2. 提交供应商审批 `POST /admin/supplier/items/{supplierId}/submit`
|
||||
|
||||
**VO**: `SupplierSubmitReqVO / SupplierApprovalCommandRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
把完整供应商表单提交审批时,一并提交余额和支付类型。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---:|---|---|
|
||||
| `supplierId` | Path | String | 是 | 正整数 ID 字符串 | 草稿供应商 |
|
||||
| `balance` | Body | Number | 是 | 最多 16 位整数、2 位小数 | 当前余额 |
|
||||
| `paymentType` | Body | String | 是 | 当前生效字典值 | 支付类型 |
|
||||
| `expectedUpdateTime` | Body | String | 是 | `yyyy-MM-dd HH:mm:ss` | 当前供应商并发版本 |
|
||||
| 其他完整表单字段 | Body | 对应类型 | 按既有规则 | 沿用现有提交契约 | 不得只提交本次新增字段 |
|
||||
|
||||
#### 出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `data.approvalLogId` | String | 审批记录 ID |
|
||||
| `data.approvalStatus` | String | 审批状态 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{
|
||||
"fullName": "示例供应商有限公司",
|
||||
"taxNo": "91350211M000100Y46",
|
||||
"mainCooperation": "旅游资源合作",
|
||||
"balance": 1200.50,
|
||||
"paymentType": "1",
|
||||
"expectedUpdateTime": "2026-08-31 12:19:23"
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{"code":200,"message":"成功","success":true,"data":{"approvalLogId":"2094279000000000001","approvalStatus":"PENDING"}}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
两个新增字段任一为空即拒绝提交,审批状态不推进。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{"code":400,"message":"支付类型不能为空","success":false,"data":null}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 提交仍是完整表单命令,并继续执行既有状态、并发、审批和幂等门禁。
|
||||
- 支付类型失效或不存在时失败关闭,不使用本地默认值代替。
|
||||
|
||||
### 3. 编辑供应商 `PUT /admin/supplier/items/{supplierId}/update`
|
||||
|
||||
**VO**: `SupplierUpdateReqVO / SupplierWriteRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
编辑页修改余额或支付类型;根据详情的服务端状态决定是否显示变更原因。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---:|---|---|
|
||||
| `supplierId` | Path | String | 是 | 正整数 ID 字符串 | 目标供应商 |
|
||||
| `balance` | Body | Number | 否 | 最多 16 位整数、2 位小数 | 省略表示不修改 |
|
||||
| `paymentType` | Body | String | 否 | 非空时必须命中当前生效字典项 | 省略表示不修改 |
|
||||
| `changeReason` | Body | String | 条件必填 | 最长 500 | `DRAFT` 可省略;其他状态去空白后必须非空 |
|
||||
| `expectedUpdateTime` | Body | String | 是 | `yyyy-MM-dd HH:mm:ss` | 详情最新并发版本 |
|
||||
|
||||
#### 出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `data.supplierId` | String | 供应商 ID |
|
||||
| `data.status` | String | 保存后的服务端状态 |
|
||||
| `data.updateTime` | String | 保存后的新并发版本 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
`DRAFT` 草稿完全省略 `changeReason`:
|
||||
|
||||
```json
|
||||
{
|
||||
"balance": -12.34,
|
||||
"paymentType": "3",
|
||||
"expectedUpdateTime": "2026-08-31 12:19:23"
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{"code":200,"message":"成功","success":true,"data":{"supplierId":"2094278854020399106","status":"DRAFT","updateTime":"2026-08-31 12:19:33"}}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
省略 `balance` 或 `paymentType` 表示该字段不修改;历史供应商尚未补录时,详情可返回 `null`。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
非草稿缺少变更原因:
|
||||
|
||||
```json
|
||||
{"code":400,"message":"变更原因不能为空","success":false,"data":null}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 是否为草稿只取服务端锁定后的当前状态,不接收客户端自报状态。
|
||||
- 草稿隐藏“变更原因”并省略 `changeReason`;非草稿继续显示且必填。
|
||||
- 更新成功后使用新 `updateTime` 重新查询详情;并发失败返回既有 `395014`。
|
||||
|
||||
### 4. 查询供应商基本信息 `GET /admin/supplier/items/{supplierId}/basic-info/view`
|
||||
|
||||
**VO**: `SupplierBasicInfoRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
新增后或进入编辑页时回显余额、支付类型和当前状态。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---:|---|---|
|
||||
| `supplierId` | Path | String | 是 | 正整数 ID 字符串 | 目标供应商 |
|
||||
|
||||
#### 出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `data.balance` | Number/null | 当前余额;历史未补录可为 `null` |
|
||||
| `data.paymentType` | String/null | `supplier_payment_type` 字典值;历史未补录可为 `null` |
|
||||
| `data.status` | String | 用于控制变更原因框 |
|
||||
| `data.updateTime` | String | 编辑请求的并发版本 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /admin/supplier/items/2094278854020399106/basic-info/view
|
||||
Authorization: Bearer <admin-token>
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"supplierId": "2094278854020399106",
|
||||
"balance": -12.34,
|
||||
"paymentType": "3",
|
||||
"status": "DRAFT",
|
||||
"updateTime": "2026-08-31 12:19:33"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
历史供应商没有新字段时返回 `null`;前端显示未补录,不自行写入默认值。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{"code":395001,"message":"供应商不存在","success":false,"data":null}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- `paymentType` 原样返回字典值,中文标签由字典项映射。
|
||||
- 业务失败可能仍是 HTTP 200,必须同时判断 `code` 和 `success`。
|
||||
|
||||
### 5. 查询支付类型字典类型 `GET /admin/dict/type`
|
||||
|
||||
**VO**: `PageResult<SysDictTypeRespVO>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
字典管理页或联调时确认 `supplier_payment_type` 类型已经存在。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---:|---|---|
|
||||
| `page` | Query | Number | 否 | 默认 1 | 页码 |
|
||||
| `pageSize` | Query | Number | 否 | 默认 20 | 每页条数 |
|
||||
| `keyword` | Query | String | 否 | 传 `supplier_payment_type` | 按编码或名称搜索 |
|
||||
|
||||
#### 出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `data.records[].dictType` | String | `supplier_payment_type` |
|
||||
| `data.records[].dictName` | String | `供应商支付类型` |
|
||||
| `data.records[].status` | String | `ACTIVE` |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /admin/dict/type?page=1&pageSize=20&keyword=supplier_payment_type
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{"code":200,"message":"成功","success":true,"data":{"records":[{"dictType":"supplier_payment_type","dictName":"供应商支付类型","status":"ACTIVE"}],"total":1,"page":1,"pageSize":20}}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
未命中时 `records=[]`;这表示字典未就绪,前端不要回退到其他结算字典。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{"code":401,"message":"Token无效或已过期","success":false,"data":null}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 该接口只查询字典类型,不直接提供下拉项。
|
||||
- 下拉选项必须继续调用下一接口。
|
||||
|
||||
### 6. 查询支付类型字典项 `GET /admin/dict/data/supplier_payment_type`
|
||||
|
||||
**VO**: `List<SysDictDataRespVO>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
新增、编辑供应商页面加载支付类型下拉选项。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---:|---|---|
|
||||
| 无 | - | - | 否 | 字典编码固定在路径中 | 无请求体、查询参数或动态路径参数 |
|
||||
|
||||
#### 出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `data[].dictValue` | String | 请求保存的值 |
|
||||
| `data[].dictLabel` | String | 下拉展示文案 |
|
||||
| `data[].sortOrder` | Number | 排序号 |
|
||||
| `data[].status` | String | 当前均为 `ACTIVE` |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /admin/dict/data/supplier_payment_type
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": [
|
||||
{"dictValue":"1","dictLabel":"现付","sortOrder":10,"status":"ACTIVE"},
|
||||
{"dictValue":"2","dictLabel":"签单","sortOrder":20,"status":"ACTIVE"},
|
||||
{"dictValue":"3","dictLabel":"月付","sortOrder":30,"status":"ACTIVE"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
返回空数组或调用失败时禁用提交并提示字典加载失败,不硬编码替代值。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{"code":401,"message":"Token无效或已过期","success":false,"data":null}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 请求保存 `dictValue`,界面展示 `dictLabel`。
|
||||
- 后端每次写入都动态校验当前生效项;已停用或未知值返回业务码 `400`。
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
1. 页面初始化先查询详情与支付类型字典项,用 `paymentType` 匹配 `dictValue`。
|
||||
2. 新增和提交时 `balance`、`paymentType` 都必填;编辑时只发送实际修改字段和最新 `expectedUpdateTime`。
|
||||
3. `status=DRAFT` 隐藏“变更原因”并可省略 `changeReason`;其他状态必须收集非空原因。
|
||||
4. 金额输入保留最多两位小数,允许负数;不要把千分位格式化文本发给后端。
|
||||
|
||||
| 场景 | payload | 结果 |
|
||||
|---|---|---|
|
||||
| 新增完整 | `{ "balance": 100.00, "paymentType": "1", ... }` | 成功 |
|
||||
| 新增缺余额 | `{ "paymentType": "1", ... }` | `400`,余额不能为空 |
|
||||
| 新增非法支付类型 | `{ "balance": 100.00, "paymentType": "9", ... }` | `400`,支付类型不合法或已停用 |
|
||||
| 草稿编辑 | `{ "balance": -12.34, "paymentType": "3", "expectedUpdateTime": "..." }` | 成功,可省略 `changeReason` |
|
||||
| 非草稿编辑缺原因 | `{ "balance": 0, "expectedUpdateTime": "..." }` | `400`,变更原因不能为空 |
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
- 新增或更新成功后,详情回读相同的 `balance`、`paymentType`。
|
||||
- 历史供应商不强制回填,新字段可返回 `null`;创建和提交的新数据仍由接口强制必填。
|
||||
- 审批与非草稿变更继续保留两个字段的业务快照;失败时不产生部分写入。
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- `balance` 最多 16 位整数和 2 位小数,正数、0、负数均有效。
|
||||
- `paymentType` 必须是当前生效字典值;未知、空白或停用值不保存。
|
||||
- 未登录返回业务码 `401`;不存在返回 `395001`;并发版本过期返回 `395014`。
|
||||
- 不新增权限、Gateway 路由、Redis、MQ 或配置行为。
|
||||
|
||||
## 六.5、枚举 / 数据字典
|
||||
|
||||
### `paymentType`(字典 `supplier_payment_type`)
|
||||
|
||||
**所属字段**: `SupplierDraftSaveReqVO.paymentType / SupplierUpdateReqVO.paymentType / SupplierBasicInfoRespVO.paymentType` | **类型**: `String`
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|---|---|---|
|
||||
| `1` | 现付 | 按现付方式处理 |
|
||||
| `2` | 签单 | 按签单方式处理 |
|
||||
| `3` | 月付 | 按月付方式处理 |
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
| 项目 | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| 新增与提交 | 无余额、支付类型字段 | 两字段必填并校验 |
|
||||
| 编辑与详情 | 无法录入和回显两字段 | 支持增量修改并原值回显 |
|
||||
| 支付类型选项 | 无专用字典 | 使用 `supplier_payment_type` 三个生效项 |
|
||||
| 草稿变更原因 | 页面仍可能显示 | `DRAFT` 隐藏;非草稿继续必填 |
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **是否破坏向后兼容**:新增、提交请求增加必填字段,前端必须同步;编辑与详情为兼容扩展。
|
||||
- **前端是否必须同步上线**:是,需要新增两个控件、接入字典并按状态调整变更原因框。
|
||||
- **前端 workaround 清理点**:删除支付类型硬编码和草稿 `changeReason` 必填/展示逻辑;非草稿校验必须保留。
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- 不修改供应商权限、状态机、审批流程、并发、软删除和既有错误码。
|
||||
- 不复用 `resource_settle_type`,也不影响供应商账户、合同和资源关系接口。
|
||||
- 本次仅交付后端接口 Changelog,不修改任何前端源码或资源。
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
- TEST Gateway 查询到字典类型及精确字典项 `1-现付`、`2-签单`、`3-月付`。
|
||||
- 缺余额、缺支付类型、非法支付类型均返回 `400`,验证前后列表为空。
|
||||
- 有效新增返回 `DRAFT`;详情回显 `balance=12.34`、`paymentType=1`。
|
||||
- 草稿省略 `changeReason` 更新成功;详情回显 `balance=-12.34`、`paymentType=3`。
|
||||
- 验收草稿已通过删除接口清理,列表为空且详情返回“供应商不存在”。
|
||||
|
||||
## 当前状态
|
||||
|
||||
- 后端:已部署并已验证。
|
||||
- 前端:待处理。
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- Issue:[#6834](https://git.1814.love:8443/wx/HL/issues/6834)
|
||||
- 后端 PR:[#6860](https://git.1814.love:8443/wx/HL/pulls/6860)
|
||||
- 合并提交:[6ed8e24c](https://git.1814.love:8443/wx/HL/commit/6ed8e24c0f04051c9c4385c79de67f255abb4952)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
- **Issue**: [#6834](https://git.1814.love:8443/wx/HL/issues/6834)
|
||||
- **PR**: [#6860](https://git.1814.love:8443/wx/HL/pulls/6860)
|
||||
- **后端负责人**: @lc
|
||||
- **当前状态**: 后端已就绪,前端待处理。
|
||||
@@ -0,0 +1,373 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "6842"
|
||||
title: "供应商补充合同字段与附件"
|
||||
consumer: "admin"
|
||||
author: "lc(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "verified"
|
||||
frontend_owner: "hl-admin"
|
||||
frontend_ref: "f5414b12"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-09-01"
|
||||
status_note: "PR #6858 已合并 dev-v3,hl-resource-service 已部署提交 5546d7907c6eda9b372b4639094f84e568de6496。TEST Gateway 已验证完整字段与全空业务字段合同的新增、更新、详情回读,以及单个 PDF 上传、预览、下载和清理。前端 hl-admin v2.1 提交 f5414b12 已交付:ContractEditModal 增 businessLine/relatedMainContract/autoRenew(三态,false 不按空过滤)+SIGNED 选项,scanFileUrl 改 FileUpload 文件中心单附件(upload/token→confirm 写 ossUrl),隐藏 contractType/amount/pricingMode/settleCycle/remark 但编辑原样回传防整份替换误清;ManageModal 列重排契约固定集、SIGNED→已签约、有效期直拼上海日历日、附件预览带登录态 preview-by-url/下载直开永久地址;api/file.js 加 getContractPreviewBlobUrl。spec 编辑 16 例+管理 6 例绿。当前状态:后端已就绪,前端已交付。"
|
||||
updated_at: "2026-09-01"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 供应商补充合同字段与附件
|
||||
|
||||
供应商补充合同新增 `businessLine`、`relatedMainContract`、`autoRenew`,并允许写入 `SIGNED`(已签约)。所有合同业务字段均可为空,只有 `changeReason` 继续必填。
|
||||
|
||||
管理端登记页仅展示补合同编号、状态、合同名称、业务线、有效期、签署日期、关联主合同、自动续约和单个 Word/PDF 附件;其他兼容字段隐藏。
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---:|---|---|---|---|---|
|
||||
| 1 | 独立登记供应商合同 | POST | `/admin/supplier/items/{supplierId}/contracts/add` | 扩展请求与响应 | 增加三个字段,状态支持 `SIGNED` |
|
||||
| 2 | 独立更新供应商合同 | PUT | `/admin/supplier/items/{supplierId}/contracts/{contractId}/update` | 扩展请求与响应 | 新字段支持补录、清空和 `false` |
|
||||
| 3 | 查询供应商基本信息 | GET | `/admin/supplier/items/{supplierId}/basic-info/view` | 扩展响应 | `contracts[]` 完整回读补充合同字段 |
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 独立登记供应商合同 `POST /admin/supplier/items/{supplierId}/contracts/add`
|
||||
|
||||
**VO**: `SupplierContractCreateReqVO / SupplierContractRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
供应商已创建后登记一份补充合同;业务资料未齐时也可只提交审计原因,稍后再补录。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---:|---|---|
|
||||
| `supplierId` | Path | String | 是 | 正整数 ID 字符串 | 目标供应商 |
|
||||
| `contractNo` | Body | String/null | 否 | 最长 100 | 补合同编号 |
|
||||
| `status` | Body | String/null | 否 | `DRAFT` / `ACTIVE` / `SIGNED` / `EXPIRED` | `SIGNED` 显示为“已签约” |
|
||||
| `contractName` | Body | String/null | 否 | 最长 500 | 合同名称 |
|
||||
| `businessLine` | Body | String/null | 否 | 最长 100 | 业务线,自由文本 |
|
||||
| `startDate` | Body | String/null | 否 | `yyyy-MM-dd` | 有效期开始日 |
|
||||
| `endDate` | Body | String/null | 否 | `yyyy-MM-dd` | 有效期结束日;两端都有值时不得早于开始日 |
|
||||
| `signDate` | Body | String/null | 否 | `yyyy-MM-dd` | 合同签署日期 |
|
||||
| `relatedMainContract` | Body | String/null | 否 | 最长 100 | 关联主合同外部引用 |
|
||||
| `autoRenew` | Body | Boolean/null | 否 | `true` / `false` / `null` | 是 / 否 / 未登记 |
|
||||
| `scanFileUrl` | Body | String/null | 否 | 最长 1000 | 文件中心确认上传后返回的永久 `ossUrl` |
|
||||
| `changeReason` | Body | String | 是 | 去空白后非空,最长 500 | 登记原因,写入审计 |
|
||||
|
||||
`contractType`、`amount`、`pricingMode`、`settleCycle`、`remark` 仍兼容但本页面隐藏,也均可为空。
|
||||
|
||||
#### 出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `data.contractId` | String | 合同 ID |
|
||||
| `data.contractNo` | String/null | 补合同编号 |
|
||||
| `data.status` | String/null | 包含新增可写值 `SIGNED` |
|
||||
| `data.contractName` | String/null | 合同名称 |
|
||||
| `data.businessLine` | String/null | 业务线 |
|
||||
| `data.startDate` / `data.endDate` | String/null | 有效期日期 |
|
||||
| `data.signDate` | String/null | 签署日期 |
|
||||
| `data.relatedMainContract` | String/null | 关联主合同 |
|
||||
| `data.autoRenew` | Boolean/null | 自动续约三态值 |
|
||||
| `data.scanFileUrl` | String/null | 合同附件永久地址 |
|
||||
| `data.updateTime` | String | 当前合同版本,格式 `yyyy-MM-dd HH:mm:ss` |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{
|
||||
"contractNo": "SHCT2025070102599446",
|
||||
"status": "SIGNED",
|
||||
"contractName": "内蒙古牵手草原国际旅行社有限公司额外后返",
|
||||
"businessLine": null,
|
||||
"startDate": "2010-01-01",
|
||||
"endDate": "2099-12-31",
|
||||
"signDate": "2025-07-01",
|
||||
"relatedMainContract": "2185933",
|
||||
"autoRenew": true,
|
||||
"scanFileUrl": "https://files.example.com/supplier-contract/example.pdf",
|
||||
"changeReason": "补录已签约合同"
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"contractId": "2095000000000000001",
|
||||
"contractNo": "SHCT2025070102599446",
|
||||
"status": "SIGNED",
|
||||
"contractName": "内蒙古牵手草原国际旅行社有限公司额外后返",
|
||||
"businessLine": null,
|
||||
"startDate": "2010-01-01",
|
||||
"endDate": "2099-12-31",
|
||||
"signDate": "2025-07-01",
|
||||
"relatedMainContract": "2185933",
|
||||
"autoRenew": true,
|
||||
"scanFileUrl": "https://files.example.com/supplier-contract/example.pdf",
|
||||
"updateTime": "2026-08-31 12:00:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
仅提交 `changeReason` 也可成功;所有合同业务字段返回 `null`,前端不得补默认状态或日期。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 400,
|
||||
"message": "变更原因不能为空",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 所有合同业务字段可空;`changeReason` 不属于业务展示字段,继续必填。
|
||||
- `startDate`、`endDate` 同时有值时才校验日期先后。
|
||||
- `scanFileUrl` 只保存一个附件永久地址;上传、预览、下载复用文件中心。
|
||||
|
||||
### 2. 独立更新供应商合同 `PUT /admin/supplier/items/{supplierId}/contracts/{contractId}/update`
|
||||
|
||||
**VO**: `SupplierContractUpdateReqVO / SupplierContractRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
补齐、修改或清空已登记合同。该接口为完整替换,省略或传 `null` 会清空对应字段。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---:|---|---|
|
||||
| `supplierId` | Path | String | 是 | 正整数 ID 字符串 | 目标供应商 |
|
||||
| `contractId` | Path | String | 是 | 正整数 ID 字符串 | 目标合同 |
|
||||
| 新增接口全部业务字段 | Body | 对应类型/null | 否 | 与新增一致 | 完整替换;`autoRenew=false` 会保留为否 |
|
||||
| `expectedUpdateTime` | Body | String/null | 否 | `yyyy-MM-dd HH:mm:ss` | 提供时必须匹配当前版本 |
|
||||
| `changeReason` | Body | String | 是 | 去空白后非空,最长 500 | 更新原因 |
|
||||
|
||||
#### 出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `data.contractId` | String | 原合同 ID |
|
||||
| `data.businessLine` | String/null | 更新后的业务线,空白规范为 `null` |
|
||||
| `data.relatedMainContract` | String/null | 更新后的关联主合同 |
|
||||
| `data.autoRenew` | Boolean/null | 精确保留 `true`、`false` 或 `null` |
|
||||
| `data.status` | String/null | 可返回 `SIGNED` |
|
||||
| 其余合同业务字段 | 对应类型/null | 完整替换后的值 |
|
||||
| `data.updateTime` | String | 更新后的合同版本 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{
|
||||
"contractNo": "SHCT2025070102599446",
|
||||
"status": "SIGNED",
|
||||
"contractName": "内蒙古牵手草原国际旅行社有限公司额外后返",
|
||||
"businessLine": null,
|
||||
"startDate": "2010-01-01",
|
||||
"endDate": "2099-12-31",
|
||||
"signDate": "2025-07-01",
|
||||
"relatedMainContract": null,
|
||||
"autoRenew": false,
|
||||
"scanFileUrl": "https://files.example.com/supplier-contract/example.pdf",
|
||||
"changeReason": "调整自动续约信息"
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"contractId": "2095000000000000001",
|
||||
"contractNo": "SHCT2025070102599446",
|
||||
"status": "SIGNED",
|
||||
"businessLine": null,
|
||||
"relatedMainContract": null,
|
||||
"autoRenew": false,
|
||||
"updateTime": "2026-08-31 12:00:01"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
仅提交 `changeReason` 可把全部业务字段清空;与当前内容完全相同时拒绝空更新,不生成新版本。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 395054,
|
||||
"message": "合同有效期开始日期不能晚于结束日期",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 页面隐藏兼容字段时,编辑既有合同应从详情原样回传其 `contractType`、`amount`、`pricingMode`、`settleCycle`、`remark`,避免完整替换误清历史值。
|
||||
- `autoRenew=false` 是有效业务值,不能按空值过滤。
|
||||
- `expectedUpdateTime` 可省略;提供过期值时返回 `395014`,合同保持不变。
|
||||
|
||||
### 3. 查询供应商基本信息 `GET /admin/supplier/items/{supplierId}/basic-info/view`
|
||||
|
||||
**VO**: `SupplierBasicInfoRespVO / SupplierContractRespVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
进入供应商编辑或详情页时,从 `data.contracts[]` 回显合同表格和附件操作。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---:|---|---|
|
||||
| `supplierId` | Path | String | 是 | 正整数 ID 字符串 | 目标供应商 |
|
||||
|
||||
#### 出参
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `data.contracts` | Array | 当前有效合同,按合同 ID 升序 |
|
||||
| `data.contracts[].businessLine` | String/null | 业务线 |
|
||||
| `data.contracts[].relatedMainContract` | String/null | 关联主合同 |
|
||||
| `data.contracts[].autoRenew` | Boolean/null | 自动续约三态值 |
|
||||
| `data.contracts[].status` | String/null | `SIGNED` 显示为“已签约” |
|
||||
| `data.contracts[].scanFileUrl` | String/null | 单个合同附件永久地址 |
|
||||
| 其余合同字段 | 对应类型/null | 与新增、更新响应一致 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /admin/supplier/items/2095000000000000000/basic-info/view
|
||||
Authorization: Bearer <admin-token>
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"supplierId": "2095000000000000000",
|
||||
"contracts": [
|
||||
{
|
||||
"contractId": "2095000000000000001",
|
||||
"contractNo": "SHCT2025070102599446",
|
||||
"status": "SIGNED",
|
||||
"contractName": "内蒙古牵手草原国际旅行社有限公司额外后返",
|
||||
"businessLine": null,
|
||||
"startDate": "2010-01-01",
|
||||
"endDate": "2099-12-31",
|
||||
"signDate": "2025-07-01",
|
||||
"relatedMainContract": "2185933",
|
||||
"autoRenew": true,
|
||||
"scanFileUrl": "https://files.example.com/supplier-contract/example.pdf"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
没有合同记录时 `contracts=[]`;合同存在但某项未登记时,对应字段为 `null`。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 395001,
|
||||
"message": "供应商不存在",
|
||||
"success": false,
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 日期均为上海业务日字符串,前端按 `yyyy-MM-dd` 原样展示,不做 UTC 日期转换。
|
||||
- 查询只返回未删除合同;附件为空时隐藏预览、下载操作。
|
||||
- 未登录请求由 Gateway 返回业务码 `401`。
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
1. 表格列固定映射为 `contractNo`、`status`、`contractName`、`businessLine`、`startDate/endDate`、`signDate`、`relatedMainContract`、`autoRenew`、`scanFileUrl`;`SIGNED` 展示“已签约”。
|
||||
2. `startDate/endDate` 直接按上海日历日拼成有效期;`autoRenew=true` 可显示“到期自动续约”,`false` 显示“否”,`null` 显示未登记占位。
|
||||
3. 附件复用文件中心:申请上传凭证 `POST /admin/file/upload/token`,按返回模式上传后调用 `POST /admin/file/upload/confirm`,把确认响应的 `data.ossUrl` 写入 `scanFileUrl`。组件仅保留一个 `.doc`、`.docx` 或 `.pdf` 文件。
|
||||
4. 预览使用带登录态的 `GET /admin/file/preview-by-url?url={encodeURIComponent(scanFileUrl)}`;下载直接访问 `scanFileUrl`。
|
||||
5. 本页面隐藏 `contractType`、`amount`、`pricingMode`、`settleCycle`、`remark`;编辑历史合同时仍从详情原样回传,避免完整替换清空旧值。
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
这里只约定外部可观察结果:新增或更新成功后,再次请求基本信息详情可读到相同字段;未填写值返回 `null`,`autoRenew=false` 不会丢失。失败响应不产生部分合同变更。
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 所有合同业务字段可空,只有新增、更新、删除操作的 `changeReason` 必填。
|
||||
- `SIGNED` 仅是新增可写状态,不改变既有 `DRAFT`、`ACTIVE`、`EXPIRED` 和历史 `TERMINATED` 的兼容读取。
|
||||
- 日期两端都有值且开始日晚于结束日时返回 `395054`,整次更新零写入。
|
||||
- 附件字段为单值;前端负责限制为一个 Word/PDF,并在替换附件后提交新的永久地址。
|
||||
- 写接口沿用既有角色、权限、幂等、锁和审计规则,无新增错误码。
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
| 项目 | 修改前 | 修改后 |
|
||||
|---|---|---|
|
||||
| 业务线、关联主合同、自动续约 | 无字段 | 新增 `businessLine`、`relatedMainContract`、`autoRenew`,均可空 |
|
||||
| 合同状态 | 可写 `DRAFT` / `ACTIVE` / `EXPIRED` | 追加可写 `SIGNED`(已签约) |
|
||||
| 详情回读 | 无新增三字段 | `contracts[]` 完整回读新增三字段 |
|
||||
| 其他合同业务字段 | 可空 | 继续可空,语义不变 |
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **是否破坏向后兼容**:否,均为可选字段和状态枚举扩展。
|
||||
- **前端是否必须同步上线**:是,需要新增列、附件控件和 `SIGNED` 中文映射,并隐藏非目标字段。
|
||||
- **前端 workaround 清理点**:不要在本地拼装缺失字段;以详情 `contracts[]` 为回显真相。
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- 不修改供应商主体、联系人、资质、结算账户、审批和归档接口。
|
||||
- 不把合同并回供应商注册聚合;仍先取得 `supplierId`,再调用独立合同接口。
|
||||
- 不新增 Gateway 路由、权限点、配置、Redis 或 MQ 契约。
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
- 已在 TEST Gateway 验证完整字段和仅含 `changeReason` 的合同均可新增并跨请求回读;`SIGNED`、`null`、`false`、日期错误零写入均符合本契约。
|
||||
- 已验证一个 PDF 经文件中心上传后可预览、下载,验收产生的合同、附件和会话均已清理。
|
||||
|
||||
## 当前状态
|
||||
|
||||
- 后端:已部署并已验证。
|
||||
- 前端:待处理。
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- Issue:[#6842](https://git.1814.love:8443/wx/HL/issues/6842)
|
||||
- 后端 PR:[#6858](https://git.1814.love:8443/wx/HL/pulls/6858)
|
||||
- 合并提交:[5546d790](https://git.1814.love:8443/wx/HL/commit/5546d7907c6eda9b372b4639094f84e568de6496)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
- **Issue**: [#6842](https://git.1814.love:8443/wx/HL/issues/6842)
|
||||
- **PR**: [#6858](https://git.1814.love:8443/wx/HL/pulls/6858)
|
||||
- **后端负责人**: @lc
|
||||
@@ -0,0 +1,877 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "6843"
|
||||
title: "供应商暂停合作联动停用车队与派单拦截"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "verified"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "f1b861c6"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-08-31"
|
||||
status_note: "PR #6868 已合并 dev-v3(merge commit df78cce5)已部署测试服。前端已消费:useAssignFlow 补 605074 专用分支「车队已停用不可派新单,请先启用车队或换车」;precheck 前端无调用方白名单属 no-op;司机候选静默排除无适配量。"
|
||||
updated_at: "2026-08-31"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 车务派单:供应商暂停合作联动停用车队,派单全链路拦截停用车队资源
|
||||
|
||||
> **存放目录**:
|
||||
> - 二期(v3,`order-v3` 标签的工单)→ `changelogs-v2/2026-08/`
|
||||
>
|
||||
> **服务**: hl-fleet-service(端口 8087/8187,双实例滚动)
|
||||
> **PR**: #6868
|
||||
> **Issue**: #6843
|
||||
> **作者**: wx
|
||||
> **更新时间**: 2026-08-31
|
||||
> **影响范围**: 管理后台「车务 - 派单」候选查询 / 预校验 / 创建(批量)/ 改派;新增服务间 internal 联动端点(前端不直接调用)
|
||||
|
||||
供应商暂停合作(SUSPENDED)/拉黑(BLACKLIST)后,其名下在启车队被批量联动停用;停用车队的车辆与常驻司机不再可派新单。**已派订单不受影响**(存量派单的最终确认、撤销取消不挂新校验)。
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
- **precheck 新增 warning type `fleet_team_disabled`**:`POST /admin/fleet/assignments/precheck` 的 `warnings[]` 新增该类型(文案「车队已停用不可派新单,提交派单将被拦截,请先启用车队或换车」)。前端若按 type 白名单渲染提示,需把新 type 加入渲染映射,否则该提示会被静默丢弃。
|
||||
- **新错误码 605074**:「车队已停用不可派新单(请先启用车队或换车)」。派单创建(批量,单派同路径)与改派在写库前硬拦截;前端错误码字典需补 605074 文案映射,建议引导动作 = 启用车队或更换车辆/司机。
|
||||
- **司机候选静默排除**:`POST /admin/fleet/assignments/candidates` 的司机候选不再返回「常驻车所属车队已停用」的司机(无新增/删除字段,结果集合缩小;车辆侧自 #6717 起已排除)。
|
||||
- **新增 internal 端点** `POST /internal/fleet-teams/supplier-status-sync`:仅供供应商域(resource 侧,工单 #6844)Feign 调用,**前端不直接调用、网关不路由**;本文按模板写全契约供服务间对齐。
|
||||
|
||||
---
|
||||
|
||||
## 一、背景
|
||||
|
||||
工单 #6843:供应商生命周期进入「停止合作」态(SUSPENDED=暂停合作 / BLACKLIST=拉黑)时,其名下关联车队应联动停用,且停用车队的司机/车辆不得再派新订单。
|
||||
|
||||
前置条件已由 #6717 建好:车队主数据持有 `supplierId`/`supplierName` 快照,车队停用会合并车辆可派状态(车辆侧候选过滤与 605037 拦截已存在)。本单补齐两环:
|
||||
|
||||
1. **联动入口(fleet 侧接收端)**:新增 internal 端点接收供应商域的状态同步事件,CAS 批量停用该供应商名下 `status=ACTIVE` 的车队。与人工停用的差异:供应商侧停用是**强制联动**,不做「在役车辆」守卫(车队不能再接新单与名下车辆是否仍在役无关)。
|
||||
2. **派单链路拦截**:候选查询排除(司机侧新增)+ precheck 预警(新增 type)+ 创建/改派锁内硬校验(新错误码 605074,先于车辆/司机自身状态校验 605037/605038 执行)。
|
||||
|
||||
供应商侧通知发出端由工单 #6844 实现(本 PR 合并后指派),届时按本文 §三.1 契约 Feign 投递。车队域暂无独立操作日志表,停用结果经响应回执 + 服务端日志留痕。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 供应商状态同步(内部) | POST | `/internal/fleet-teams/supplier-status-sync` | 新增接口 | SUSPENDED/BLACKLIST 批量停用名下在启车队;同 eventId 重投返 duplicate=true 成功回执 |
|
||||
| 2 | 查询派单候选资源 | POST | `/admin/fleet/assignments/candidates` | 行为变化 | 司机候选排除常驻车所属车队已停用的司机(无字段变化,集合缩小) |
|
||||
| 3 | 派单预校验冲突 | POST | `/admin/fleet/assignments/precheck` | 响应新增枚举值 | `warnings[]` 新增 type `fleet_team_disabled`(只提示不阻断) |
|
||||
| 4 | 批量创建派单 | POST | `/admin/fleet/assignments/batch` | 新增错误码 | 车队停用硬校验 605074,先于 605037/605038;单派/批量同路径 |
|
||||
| 5 | 修改派单(改派) | POST | `/admin/fleet/assignments/{assignmentId}/change` | 新增错误码 | 换车/换司机到停用车队资源被 605074 拦截;纯费用调整司机侧不查 |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 供应商状态同步(内部) `POST /internal/fleet-teams/supplier-status-sync`
|
||||
|
||||
**VO**: `SupplierStatusSyncReqVO` / `Result<SupplierStatusSyncRespVO>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
供应商域(resource 侧,工单 #6844)在供应商生命周期变更为 SUSPENDED/BLACKLIST 后,经 Feign LB 直连投递本端点;fleet 侧批量停用该供应商名下 `status=ACTIVE` 的车队,让派单链路(候选排除 + 创建/改派硬校验 605074)不再放行其资源。
|
||||
|
||||
`/internal` 前缀**不经网关路由**(网关实测返回业务码 404「接口不存在」),`X-Internal-Token` 由 InternalAuthFilter 统一校验。前端不直接调用本端点。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| `supplierId` | Body | String(Long) | 是 | `@Positive` | 供应商 ID(雪花,建议按字符串传输,禁止转 JavaScript Number) |
|
||||
| `targetStatus` | Body | String | 是 | ≤32,大小写不敏感 | 供应商目标状态;仅 `SUSPENDED`/`BLACKLIST` 触发联动停用,其余状态忽略 |
|
||||
| `eventId` | Body | String | 是 | ≤64 | 事件 ID(幂等键,同一事件重复投递不产生二次变更) |
|
||||
| `occurredAt` | Body | String/null | 否 | ISO 日期时间 | 事件发生时间(观测/日志用;本端不落供应商状态镜像,不做乱序围栏) |
|
||||
|
||||
#### 出参 `Result<SupplierStatusSyncRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `supplierId` | String | 供应商 ID(回显,雪花序列化为字符串) |
|
||||
| `targetStatus` | String | 供应商目标状态(回显调用方传值) |
|
||||
| `ignored` | Boolean | 是否忽略(目标状态非 SUSPENDED/BLACKLIST,未做联动) |
|
||||
| `duplicate` | Boolean | 是否重复投递(true=同 eventId 幂等窗口内已消费,不产生二次变更;重复回执 `disabledCount` 恒 0、清单恒空) |
|
||||
| `disabledCount` | Integer | 本次实际停用的车队数(CAS 实际影响行数;并发人工停用时可能小于快照大小;重复投递为 0) |
|
||||
| `disabledTeamIds` | String[] | 本次停用的车队 ID 列表(停用前快照,雪花字符串,供调用方核对) |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{
|
||||
"supplierId": "8996843000000000001",
|
||||
"targetStatus": "SUSPENDED",
|
||||
"eventId": "supplier-status-8996843000000000001-SUSPENDED-20260831142000",
|
||||
"occurredAt": "2026-08-31T14:20:00"
|
||||
}
|
||||
```
|
||||
|
||||
(传输头:`POST /internal/fleet-teams/supplier-status-sync`,`Content-Type: application/json`,`X-Internal-Token: <服务间令牌>`,Feign LB 直连不经网关。)
|
||||
|
||||
#### 响应示例
|
||||
|
||||
首次投递(TEST 实测:该供应商名下 1 个 ACTIVE 车队被停用,名下有在役车辆仍强制停用——供应商联动不做在役车辆守卫):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"supplierId": "8996843000000000001",
|
||||
"targetStatus": "SUSPENDED",
|
||||
"ignored": false,
|
||||
"duplicate": false,
|
||||
"disabledCount": 1,
|
||||
"disabledTeamIds": ["352696323818000384"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
- 目标状态非停止合作类(如恢复 ACTIVE):不联动、零写入,正常返回 `ignored=true`、`disabledCount=0`、`disabledTeamIds=[]`(恢复合作不自动启用是本单边界,车队需人工启用,安全侧)。
|
||||
- 供应商名下无 ACTIVE 车队(含窗口外重投、车队已被人工停用):CAS 守卫自然命中 0 行,返回 `ignored=false`、`disabledCount=0`、`disabledTeamIds=[]`,仍属成功回执,调用方不应告警。
|
||||
- 同 `eventId` 幂等窗口(24h)内重复投递:返回 `duplicate=true` 的成功回执(「已消费」语义),`disabledCount=0`、`disabledTeamIds=[]`(首次投递的实际停用明细不随幂等键留存,重复回执只承诺「已处理」);供应商侧「失败即重试告警」不应被触发。
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"supplierId": "8996843000000000001",
|
||||
"targetStatus": "SUSPENDED",
|
||||
"ignored": false,
|
||||
"duplicate": true,
|
||||
"disabledCount": 0,
|
||||
"disabledTeamIds": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
缺少必填字段(Bean Validation,TEST 实测缺 `eventId`):
|
||||
|
||||
```json
|
||||
{ "code": 400, "message": "事件ID不能为空", "success": false, "data": null }
|
||||
```
|
||||
|
||||
`X-Internal-Token` 缺失或错误(InternalAuthFilter 拦截,原始响应为过滤器直出、字段名为 `msg`,TEST 实测):
|
||||
|
||||
```json
|
||||
{ "code": 403, "msg": "内部接口禁止外部访问" }
|
||||
```
|
||||
|
||||
经网关误调(`/internal/**` 无网关路由,TEST 实测 HTTP 200 传输层 + 业务码 404):
|
||||
|
||||
```json
|
||||
{ "code": 404, "message": "接口不存在: /internal/fleet-teams/supplier-status-sync", "success": false, "data": null }
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 幂等双保险:① `@Idempotent` 按 `eventId` 开窗 24h(覆盖 MQ/Feign 延迟重投),窗口内重投由 Controller 转 `duplicate=true` 成功回执;② 窗口外重投由 Mapper CAS 守卫(仅停用 `status=ACTIVE` 行)兜底,自然命中 0 行。
|
||||
- 状态匹配大小写不敏感(防调用方传小写被静默忽略);回执原样回显调用方传值。
|
||||
- 与人工停用的差异:供应商联动是强制停用,**不做在役车辆守卫**;存量派单/槽位/占用不在联动范围。
|
||||
- 本端不落供应商状态镜像、不做乱序围栏——迟到旧事件依赖调用方按事件流顺序投递。
|
||||
- `supplierId`/`disabledTeamIds[]` 均按字符串序列化(`@JsonSerialize(ToStringSerializer)`),禁止转 JavaScript Number。
|
||||
|
||||
---
|
||||
|
||||
### 2. 查询派单候选资源 `POST /admin/fleet/assignments/candidates`
|
||||
|
||||
**VO**: `AssignmentCandidateReqVO` / `Result<AssignmentCandidateRespVO>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
派单 Step2 选车/选司机时查询候选资源。**本单行为变化**:司机候选排除「常驻车所属车队已停用」的司机(TEST 实测:司机斯琴常驻车挂到停用车队后,候选从 1 条变为 0 条;重新启用车队后恢复 1 条)。车辆侧排除自 #6717 起已存在(车队停用合并车辆可派状态),本单未改。
|
||||
|
||||
请求/响应**结构零字段变化**——排除是静默的,不返回被排除司机及其原因。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| `startDate` | Body | String(日期) | 是 | `yyyy-MM-dd` | 用车开始日期 |
|
||||
| `endDate` | Body | String(日期) | 是 | `yyyy-MM-dd`,≥startDate | 用车结束日期 |
|
||||
| `orderId` | Body | String(Long)/null | 否 | - | 当前订单 ID;改派排除自身时必填 |
|
||||
| `requirementId` | Body | String(Long)/null | 否 | - | 当前用车需求 ID;改派排除自身时必填 |
|
||||
| `fleetItemIndex` | Body | Integer/null | 否 | ≥0 | 需求车型项序号 |
|
||||
| `vehicleKeyword` | Body | String/null | 否 | - | 车辆关键词(车牌/车型/常驻司机姓名或完整手机号) |
|
||||
| `driverKeyword` | Body | String/null | 否 | - | 司机关键词(姓名/完整手机号/常驻车牌) |
|
||||
| `fleetTeamId` | Body | String(Long)/null | 否 | - | 按车队过滤车辆候选(雪花字符串) |
|
||||
| `vehicleTypeId` | Body | String(Long)/null | 否 | - | 按车型大类过滤 |
|
||||
| `driverAvailability` | Body | String | 否 | `AVAILABLE`/`ALL`,默认 `ALL` | 司机可用性筛选 |
|
||||
| `selectedVehicleId` | Body | String(Long)/null | 否 | - | 已选车辆 ID(先选车后选司机场景) |
|
||||
| `selectedDriverId` | Body | String(Long)/null | 否 | - | 已选司机 ID(先选司机后选车场景) |
|
||||
| `excludeAssignmentId` | Body | String(Long)/null | 否 | 须属当前订单与需求 | 改派时排除的当前派单 ID |
|
||||
|
||||
#### 出参 `Result<AssignmentCandidateRespVO>`
|
||||
|
||||
顶层字段(与改前完全一致,无新增/删除):
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `vehicles` | Object | 车辆候选分页(`records[]` + `total`);停用车队名下车辆继续被排除(#6717 既有口径) |
|
||||
| `drivers` | Object | 司机候选分页(`records[]` + `total`);**本单起追加排除常驻车所属车队已停用的司机** |
|
||||
| `fleetTeamFacets` | Array | 车队聚合筛选项 |
|
||||
| `vehicleTypeFacets` | Array | 车型大类聚合筛选项 |
|
||||
| `selectedDriverResidentVehicle` | Object/null | 已选司机的常驻车信息 |
|
||||
| `selectedDriverResidentVehicles` | Array | 已选司机的全部常驻车 |
|
||||
| `selectedVehicleResidentDriver` | Object/null | 已选车辆的常驻司机 |
|
||||
| `selectedRelation` | Object/null | 已选车+司机的常驻关系 |
|
||||
| `suggestedDriverId` | String(Long)/null | 自动代入司机 ID(雪花字符串) |
|
||||
| `suggestedDriverReason` | String | 自动代入原因码 |
|
||||
| `suggestedDriverMessage` | String | 自动代入原因展示文案 |
|
||||
| `canonicalSnapshot` | Object | Step2 canonical 快照(代际/版本/槽位集合) |
|
||||
|
||||
`drivers.records[]` 项关键字段(结构未变,列与联调相关者):
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `driverId` | String | 司机 ID(雪花字符串,禁止转 Number) |
|
||||
| `name` | String | 司机姓名 |
|
||||
| `maskedPhone` | String | 司机手机(脱敏,如 `135****5001`) |
|
||||
| `driverStatus` | String | 占用态(`idle`/`busy`/`rest`/`pending`) |
|
||||
| `season` | String | 赛季态(候选恒 `active`,其余赛季已过滤) |
|
||||
| `residentVehicleId` | String/null | 常驻车辆 ID(常驻车所属车队停用时,该司机整体不出现在候选) |
|
||||
| `residentVehiclePlate` | String/null | 常驻车辆车牌 |
|
||||
| `available` | Boolean | 是否覆盖整个请求日期范围可用 |
|
||||
| `selectable` | Boolean | 本次选车场景下是否可被选中 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
POST /admin/fleet/assignments/candidates
|
||||
Authorization: Bearer <admin-token>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"startDate": "2026-09-10",
|
||||
"endDate": "2026-09-12",
|
||||
"driverKeyword": "斯琴"
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
司机常驻车所属车队**启用**时(TEST 实测基线,斯琴在候选内):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"drivers": {
|
||||
"total": 1,
|
||||
"records": [
|
||||
{
|
||||
"driverId": "2065272145289658370",
|
||||
"name": "斯琴",
|
||||
"maskedPhone": "135****5001",
|
||||
"years": 17,
|
||||
"driverStatus": "idle",
|
||||
"season": "active",
|
||||
"licenseType": "A3",
|
||||
"licenseExpired": false,
|
||||
"available": true,
|
||||
"selectable": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
车队停用后同一查询(TEST 实测:`records=[]`、`total=0`、`code=200`)——排除是静默的,响应结构不变、不附带被排除原因;前端按「无匹配司机」渲染即可,无需特殊处理。车辆候选同理(车队停用后名下车辆不再返回)。
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"drivers": { "total": 0, "records": [] }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
缺少必填日期(Bean Validation):
|
||||
|
||||
```json
|
||||
{ "code": 400, "message": "用车开始日期不能为空", "success": false, "data": null }
|
||||
```
|
||||
|
||||
未登录(网关拦截):
|
||||
|
||||
```json
|
||||
{ "code": 401, "message": "未登录", "success": false, "data": null }
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 司机侧排除口径 = 常驻关系(`fleet_vehicle.primary_driver_id`)反查:司机任一常驻车所属车队为 DISABLED 即被排除,与司机列表「所属车队」筛选同源(同一反查端口,防口径漂移)。
|
||||
- **无常驻车的司机不受车队联动约束**(司机档案无车队列,本期不加列)。
|
||||
- 排除只读过滤,不写任何表;被排除司机在司机档案/列表页不受影响,仅派单候选场景不可见。
|
||||
- 车队长停用后重新启用,候选即时恢复(无缓存延迟,TEST 实测启用后同查询恢复 1 条)。
|
||||
|
||||
---
|
||||
|
||||
### 3. 派单预校验冲突 `POST /admin/fleet/assignments/precheck`
|
||||
|
||||
**VO**: `PrecheckReqVO` / `Result<PrecheckRespVO>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
Step2 选定车辆 + 司机后、保存前预探冲突。**本单变化**:`warnings[]` 新增 type `fleet_team_disabled`——车辆所属车队停用、或司机常驻车所属车队停用时给出预警(对齐创建/改派锁内 605074 硬校验,precheck 只提示不抛)。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| `vehicleId` | Body | String(Long) | 是 | 雪花字符串 | 车辆 ID |
|
||||
| `driverId` | Body | String(Long) | 是 | 雪花字符串 | 司机 ID |
|
||||
| `startDate` | Body | String(日期) | 是 | `yyyy-MM-dd` | 用车开始日期(闭区间起点) |
|
||||
| `endDate` | Body | String(日期) | 是 | `yyyy-MM-dd` | 用车结束日期(闭区间终点) |
|
||||
| `orderId` | Body | String(Long)/null | 否 | - | 订单 ID(仅日志/上下文) |
|
||||
| `pickupAt` | Body | String/null | 否 | - | 接客地(城市衔接判定用) |
|
||||
| `dropoffAt` | Body | String/null | 否 | - | 送客地(城市衔接判定用) |
|
||||
| `headcount` | Body | Integer/null | 否 | ≥0 | 人数(座位不足 warning 判定用) |
|
||||
| `excludeAssignmentId` | Body | String(Long)/null | 否 | - | 改派预校验排除自身 |
|
||||
|
||||
#### 出参 `Result<PrecheckRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `conflict` | Boolean | 是否存在阻断性冲突;资源不可用(含车队停用)时为 true,此时阻断原因在 `warnings[]` 体现、`conflicts[]` 可为空 |
|
||||
| `conflicts` | Array | 冲突明细(`type`/`conflictAssignmentId`/`conflictOrderNo`/`conflictDateRange`/`cityJunctionShareCandidate`/`msg`),无冲突为空数组 |
|
||||
| `warnings` | Array | 非阻断提示(`type` + `msg`);**本单新增 type 值 `fleet_team_disabled`** |
|
||||
|
||||
`warnings[]` 项字段:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `type` | String | 提示类型;新增 `fleet_team_disabled`=车队已停用(全集见 §六.5) |
|
||||
| `msg` | String | 提示文案,新 type 恒为「车队已停用不可派新单,提交派单将被拦截,请先启用车队或换车」 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
POST /admin/fleet/assignments/precheck
|
||||
Authorization: Bearer <admin-token>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"vehicleId": "2094307514693648385",
|
||||
"driverId": "2065272145289658370",
|
||||
"startDate": "2026-09-10",
|
||||
"endDate": "2026-09-12"
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
车队停用后(TEST 实测:车辆侧 `vehicle_unavailable` 与新增 `fleet_team_disabled` 同时出现,资源不可用使 `conflict=true` 但 `conflicts=[]`):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"conflict": true,
|
||||
"conflicts": [],
|
||||
"warnings": [
|
||||
{ "type": "vehicle_unavailable", "msg": "车辆处于维保或停用状态" },
|
||||
{ "type": "fleet_team_disabled", "msg": "车队已停用不可派新单,提交派单将被拦截,请先启用车队或换车" }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
无冲突无提示(车队启用基线,TEST 实测):`conflict=false`、`conflicts=[]`、`warnings=[]`、`code=200`。precheck 为只读咨询,恒 code=200 不抛业务异常;车辆/司机不存在时也不抛错,而是落 `vehicle_unavailable`/`driver_unavailable` warning + `vehicle_not_found`/`driver_not_found` conflict 明细(既有 #5629 口径)。
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": { "conflict": false, "conflicts": [], "warnings": [] }
|
||||
}
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
缺少必填车辆 ID(Bean Validation):
|
||||
|
||||
```json
|
||||
{ "code": 400, "message": "车辆 ID 不能为空", "success": false, "data": null }
|
||||
```
|
||||
|
||||
未登录(网关拦截):
|
||||
|
||||
```json
|
||||
{ "code": 401, "message": "未登录", "success": false, "data": null }
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- precheck 只提示不阻断提交动作本身;最终一致以 create/change 锁内重校验(605074)为准——车务忽略预警强行提交会被 605074 拦截。
|
||||
- 车辆所属车队行缺失(历史数据无车队)时 `fleet_team_disabled` 不触发,仍走 `vehicle_unavailable` 既有口径。
|
||||
- 司机侧判定 = 常驻车所属车队停用;无常驻车司机不产生本 warning。
|
||||
- 前端若按 type 白名单渲染 warning,需把 `fleet_team_disabled` 加入映射(建议样式与 `vehicle_unavailable` 同级:醒目提示 + 引导换车/启用车队)。Swagger 注解中的 type 枚举列举未同步新值,以本文 §六.5 全集为准。
|
||||
|
||||
---
|
||||
|
||||
### 4. 批量创建派单 `POST /admin/fleet/assignments/batch`
|
||||
|
||||
**VO**: `BatchCreateAssignmentReqVO` / `Result<BatchAssignmentWriteRespVO>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
派单 Step2 提交(单派/批量同路径,经同一锁内创建逻辑)。**本单变化**:新增车队状态硬校验——所选车辆所属车队、或所选司机常驻车所属车队非 ACTIVE 时,写库前抛 **605074**,先于车辆/司机自身状态校验(605037/605038)执行,避免车队停用被误报成「车辆维保/停用」。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| `orderId` | Body | String(Long) | 是 | 雪花字符串 | 订单 ID |
|
||||
| `requirementId` | Body | String(Long) | 是 | 雪花字符串 | 用车需求 ID |
|
||||
| `startDate` | Body | String(日期) | 是 | `yyyy-MM-dd` | 用车开始日期 |
|
||||
| `endDate` | Body | String(日期) | 是 | `yyyy-MM-dd` | 用车结束日期 |
|
||||
| `requestId` | Body | String | 是 | 非空,幂等键 | 批次幂等请求标识(重复提交同 requestId + 同载荷幂等放行) |
|
||||
| `items` | Body | Array | 是 | 每项含 `fleetItemIndex`/`vehicleId`/`driverId`(均必填) | 派车明细;`vehicleId`/`driverId` 为雪花字符串 |
|
||||
| `pickupAt` | Body | String/null | 否 | - | 接客地 |
|
||||
| `dropoffAt` | Body | String/null | 否 | - | 送客地 |
|
||||
| `headcount` | Body | Integer/null | 否 | ≥0 | 乘客人数(不含司机) |
|
||||
| `chargeableServiceDates` | Body | Array/null | 否 | - | 收取车费的服务日期 |
|
||||
| `confirmCrossResident` | Body | Boolean/null | 否 | - | 跨常驻车派单显式确认 |
|
||||
|
||||
#### 出参 `Result<BatchAssignmentWriteRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `assignments` | Array | 创建结果列表(`fleetItemIndex` + `assignment` 写入结果,含 `assignmentId` 雪花字符串) |
|
||||
| `failedFleetItemIndex` | Integer/null | 失败的需求项序号(整批事务回滚,任一失败全部不生效) |
|
||||
| `dailyDifferences` | Array/null | 逐日差异提示 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
POST /admin/fleet/assignments/batch
|
||||
Authorization: Bearer <admin-token>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"orderId": "2087157006417657857",
|
||||
"requirementId": "2087157006845476865",
|
||||
"startDate": "2026-09-10",
|
||||
"endDate": "2026-09-12",
|
||||
"headcount": 4,
|
||||
"requestId": "web-step2-<uuid>",
|
||||
"items": [
|
||||
{ "fleetItemIndex": 0, "vehicleId": "2094307514693648385", "driverId": "2065272145289658370" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
成功(结构示例;本次 TEST 未跑通正向创建,原因见 §八):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"assignments": [
|
||||
{
|
||||
"fleetItemIndex": 0,
|
||||
"assignment": { "assignmentId": "2094310000000000001", "assignmentStatus": "assigned" }
|
||||
}
|
||||
],
|
||||
"failedFleetItemIndex": null,
|
||||
"dailyDifferences": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
本接口为写接口,无空数据分支;整批任一 item 失败即同事务回滚,不产生半批状态。幂等重试(同 `requestId` + 同载荷)返回首个成功结果,不重复建单。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
车队已停用(新增;车辆所属车队或司机常驻车所属车队非 ACTIVE,写库前拦截、零写入):
|
||||
|
||||
```json
|
||||
{ "code": 605074, "message": "车队已停用不可派新单(请先启用车队或换车)", "success": false, "data": null }
|
||||
```
|
||||
|
||||
行程已结束的需求不可再派(既有口径,TEST 实测优先级高于 605074——过期需求先撞本码):
|
||||
|
||||
```json
|
||||
{ "code": 605047, "message": "行程已结束,派车信息只读,不能修改或改派", "success": false, "data": null }
|
||||
```
|
||||
|
||||
车辆自身维保/停用(既有;车队校验通过后才轮到本码):
|
||||
|
||||
```json
|
||||
{ "code": 605037, "message": "车辆处于维保或停用状态,不能派车", "success": false, "data": null }
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 校验顺序(锁内):需求项占用守卫 → **车队状态(605074)** → 车辆状态(605037)→ 司机状态(605038)→ 司机赛季(605006/605013)→ 跨常驻确认 → 档期重叠。
|
||||
- 只拦「产生新占用」的创建入口;存量派单的最终确认/撤销取消不挂本校验——供应商事后停用不卡死在途单。
|
||||
- 车队行缺失(历史车辆无车队)不归 605074,仍由合并后车辆状态走 605037 老口径。
|
||||
- 幂等:同 `requestId` 重放返回首个结果;分布式锁串行化同需求写入。
|
||||
- 行程已结束(605047)、需求版本过期(605905)等前置守卫先于车队校验命中。
|
||||
|
||||
---
|
||||
|
||||
### 5. 修改派单(改派) `POST /admin/fleet/assignments/{assignmentId}/change`
|
||||
|
||||
**VO**: `ChangeAssignmentReqVO` / `Result<ChangeAssignmentRespVO>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
车务对在途派单换车/换司机/调整费用。**本单变化**:改派目标为停用车队资源时写库前抛 **605074**(TEST 实测:把在途派单改到停用车队的车辆+司机,返回 605074 且原派单零副作用)。纯费用调整(车人未换)不查司机常驻车队——在途单不因司机另一辆常驻车挂的车队被供应商事后停用而卡死费用修订。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| `assignmentId` | Path | String(Long) | 是 | 雪花字符串 | 目标派单 ID |
|
||||
| `effectiveDate` | Body | String(日期) | 是 | 须在派单服务日期范围内 | 改派生效日期 |
|
||||
| `newVehicleId` | Body | String(Long)/null | 否 | 与 `newDriverId` 至少其一 | 新车辆 ID(雪花字符串) |
|
||||
| `newDriverId` | Body | String(Long)/null | 否 | 与 `newVehicleId` 至少其一 | 新司机 ID(雪花字符串) |
|
||||
| `reason` | Body | String | 是 | 非空 | 修改原因 |
|
||||
| `requestId` | Body | String | 是 | 非空,幂等键 | 请求幂等 ID |
|
||||
| `serviceDates` | Body | Array/null | 否 | - | 指定改派的服务日期子集 |
|
||||
| `vehicleFeeTotal` | Body | Number/null | 否 | ≥0 | 手工总车费(需搭配调整原因) |
|
||||
| `confirmCrossResident` | Body | Boolean/null | 否 | - | 跨常驻车派单显式确认 |
|
||||
|
||||
#### 出参 `Result<ChangeAssignmentRespVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `assignmentId` | String | 新派单 ID(雪花字符串) |
|
||||
| `assignmentSlotId` | String | 槽位 ID(雪花字符串) |
|
||||
| `previousAssignmentGroupId` | String/null | 原派车组 ID |
|
||||
| `newAssignmentGroupId` | String/null | 新派车组 ID |
|
||||
| `assignmentStatus` | String | 新派单状态 |
|
||||
| `effectiveDate` | String(日期) | 改派生效日期 |
|
||||
| `affectedDays` | Integer | 影响天数 |
|
||||
| `vehicleFeeTotal` | String(Number)/null | 最终总车费 |
|
||||
| `dailyVehicleFees` | Array/null | 逐日车费 |
|
||||
| `warningCode` | String/null | 预警码 |
|
||||
| `warningMessage` | String/null | 预警文案 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
POST /admin/fleet/assignments/2091068072742756354/change
|
||||
Authorization: Bearer <admin-token>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"effectiveDate": "2026-09-02",
|
||||
"newVehicleId": "2094307514693648385",
|
||||
"newDriverId": "2065272145289658370",
|
||||
"reason": "车队停用前换车",
|
||||
"requestId": "web-change-<uuid>"
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
成功(结构示例;本次 TEST 的改派请求按预期被 605074 拦截,未产生成功样本):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"success": true,
|
||||
"data": {
|
||||
"assignmentId": "2094310000000000002",
|
||||
"assignmentSlotId": "2094310000000000003",
|
||||
"assignmentStatus": "assigned",
|
||||
"effectiveDate": "2026-09-02",
|
||||
"affectedDays": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
本接口为写接口,无空数据分支;幂等重放(同 `requestId` + 同载荷)返回首个成功回执,不重复改派。改派目标派单自身已取消/已完成时不归本单变化范围,走既有 605066「派单已取消,无法修改,请重新派车」。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
改派目标资源所属车队已停用(新增,TEST 实测原文;写库前拦截、原派单零副作用):
|
||||
|
||||
```json
|
||||
{ "code": 605074, "message": "车队已停用不可派新单(请先启用车队或换车)", "success": false, "data": null }
|
||||
```
|
||||
|
||||
生效日期不在派单服务日期范围内(既有):
|
||||
|
||||
```json
|
||||
{ "code": 605028, "message": "生效日期不在派单服务日期范围内", "success": false, "data": null }
|
||||
```
|
||||
|
||||
派单已取消(既有):
|
||||
|
||||
```json
|
||||
{ "code": 605066, "message": "派单已取消,无法修改,请重新派车", "success": false, "data": null }
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 车队校验先于 605037/605038 执行(与创建同口径);车辆侧车队停用必拦(含纯费用调整场景——该场景 #6717 起本就被合并状态 605037 拦截,此处只是把报错换成更准的 605074)。
|
||||
- 纯费用调整(`identityUnchanged`,车人未换)时**司机侧不查常驻车队**:在途单司机的另一辆常驻车被联动停用,不影响本单费用修订(「已派订单不受影响」边界)。
|
||||
- 换车/换司机(非纯费用调整)时司机常驻车队停用 → 605074。
|
||||
- 幂等:同 `requestId` 重放不重复改派;车锁/司机锁串行化。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
### ✅ 正确 / ❌ 错误 payload 对照
|
||||
|
||||
| 场景 | 调用 / 结果 |
|
||||
|---|---|
|
||||
| ✅ 供应商域投递停止合作事件 | `{"supplierId":"...","targetStatus":"SUSPENDED","eventId":"<全局唯一>","occurredAt":"..."}` + `X-Internal-Token` 头,Feign LB 直连 |
|
||||
| ✅ `targetStatus` 传小写 `suspended` | 大小写不敏感,正常触发联动停用 |
|
||||
| ✅ 同 `eventId` 网络重试重投 | 返 `duplicate=true` 成功回执,按已消费处理,**不要告警重试** |
|
||||
| ✅ 非停止合作态(如 ACTIVE 恢复) | 返 `ignored=true` 成功,不联动(恢复合作需人工启用车队) |
|
||||
| ✅ 前端收到 605074 | 按文案引导:启用车队(车队管理页)或更换车辆/司机后重试 |
|
||||
| ✅ precheck 渲染 `fleet_team_disabled` | 与 `vehicle_unavailable` 同级醒目提示;加入 type 白名单映射 |
|
||||
| ❌ 前端直接调 `/internal/fleet-teams/supplier-status-sync` | 网关不路由(业务码 404);该端点仅供服务间 Feign 调用 |
|
||||
| ❌ 调用方对 `duplicate=true` 回执告警重试 | 重复投递是「已消费」而非失败,告警重试会造成误报风暴 |
|
||||
| ❌ 复用 `eventId` 投递不同事件 | 幂等键冲突会让新事件被当成已消费吞掉;`eventId` 必须全局唯一(建议含 supplierId+状态+时间戳) |
|
||||
| ❌ 雪花 ID 转 JavaScript Number | `supplierId`/`disabledTeamIds[]`/`driverId`/`vehicleId`/`assignmentId` 均为雪花字符串,`Number()`/`parseInt()`/一元 `+` 会精度丢失 |
|
||||
| ❌ 车务忽略 precheck 预警强行提交 | 创建/改派锁内 605074 硬拦截,零写入 |
|
||||
|
||||
### 关键提示(当前 TEST 构建)
|
||||
|
||||
- 605074 与 605037 的分工:605074 专指「车队级」停用(多因供应商 SUSPENDED/BLACKLIST 联动);605037 指车辆自身维保/停用。前端错误码字典两个都要保留。
|
||||
- 候选排除是静默的:前端不需要也不可能在候选接口里展示「被排除的司机」;如业务需要解释「为什么某司机不可派」,引导至司机档案查看常驻车所属车队状态。
|
||||
- 车队重新启用后(车队管理 → 启用,前提是已绑供应商),候选/预校验/创建/改派即时恢复,无缓存延迟。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
- **internal 联动端点只写 `fleet_team` 行**:CAS 批量 update(`status` ACTIVE→DISABLED,`where` 带 `status=ACTIVE` 守卫),重复/并发自然命中 0 行;不跨服务写 `supplier_main`/`supplier_resource_rel`。
|
||||
- **幂等键在 Redis**:`fleet:team:supplier-status-sync:{eventId}`,TTL 24h(@Idempotent 开窗),窗口内重投不再触库。
|
||||
- **派单侧零表结构变更**:候选排除 = 查询过滤(只读);创建/改派 605074 = 锁内读校验(只读),不写任何额外表。
|
||||
- **本工单无 Flyway 迁移**:不加表、不加列(车队-供应商关联列由 #6717 迁移建好)。
|
||||
- 联动停用不影响存量 `fleet_assignment` 行(本单边界:不动在途派单/槽位/占用)。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 未登录/登录失效:业务码 `401`(网关拦截)。
|
||||
- internal 端点 `X-Internal-Token` 缺失/错误:`403`(过滤器直出,字段名 `msg`)。
|
||||
- 经网关误调 `/internal/**`:业务码 `404`「接口不存在」(无路由,符合预期)。
|
||||
- Bean Validation 失败:`400`(如缺 `eventId`/`vehicleId`/`startDate`)。
|
||||
- 车队已停用不可派新单:`605074`(新增;创建/改派写库前硬拦截)。
|
||||
- 车辆维保/停用:`605037`(既有;车队校验通过后才会命中)。
|
||||
- 司机休假/待激活:`605038`(既有;同上)。
|
||||
- 行程已结束只读:`605047`(既有;过期需求/派单先于 605074 命中,TEST 实测)。
|
||||
- 派单已取消不可改派:`605066`(既有)。
|
||||
- 生效日期越出派单服务日期:`605028`(既有)。
|
||||
- 车队已停用仍选该车(车队管理写口径):`601102`(既有,本单不改)。
|
||||
- 跨服务 Feign 不可用:本单 fleet 侧链路不新增 Feign 调用(联动方向是 resource → fleet);候选/预校验/创建/改派均为库内读写,不受 resource 侧可用性影响。
|
||||
|
||||
---
|
||||
|
||||
## 六.5、枚举 / 数据字典
|
||||
|
||||
### `targetStatus`(供应商目标状态,internal 端点入参)
|
||||
|
||||
**所属字段**: `SupplierStatusSyncReqVO.targetStatus` | **类型**: `String`(大小写不敏感)
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `SUSPENDED` | 暂停合作 | **触发**联动停用名下在启车队 |
|
||||
| `BLACKLIST` | 拉黑 | **触发**联动停用名下在启车队 |
|
||||
| `DRAFT`/`VETTING`/`ACTIVE`/`FROZEN`/`ARCHIVED` | 草稿/审核中/合作中/冻结/归档 | 忽略(`ignored=true`),不联动 |
|
||||
|
||||
### `warnings[].type`(precheck 非阻断提示类型)
|
||||
|
||||
**所属字段**: `PrecheckRespVO.warnings[].type` | **类型**: `String`
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `seats_short` | 座位不足 | 既有 |
|
||||
| `vehicle_unavailable` | 车不可用 | 既有(车辆不存在/维保/停用;车队停用合并车辆状态后同现) |
|
||||
| `driver_unavailable` | 司机不可用 | 既有 |
|
||||
| `driver_blacklisted` | 司机已黑名单 | 既有(提交将被 605006 拦截) |
|
||||
| `driver_season_not_registered` | 司机非在册赛季 | 既有(提交将被 605013 拦截) |
|
||||
| **`fleet_team_disabled`** | **车队已停用** | **本单新增**(提交将被 605074 拦截,引导启用车队或换车) |
|
||||
| `cross_resident` | 跨常驻 | 既有 |
|
||||
| `license_expired` | 驾照过期 | 既有 |
|
||||
| `veh_inspect_expired` | 车辆年检过期 | 既有 |
|
||||
| `veh_insure_expired` | 车辆保险过期 | 既有 |
|
||||
|
||||
### 车队状态(`fleet_team.status`)
|
||||
|
||||
**所属字段**: 车队管理相关响应 `status` | **类型**: `String`
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `ACTIVE` | 启用 | 名下资源可派;前提:已关联供应商(#6717/#6811 不变量) |
|
||||
| `DISABLED` | 停用 | 名下车辆/常驻司机不可派新单(本单贯通到派单全链路) |
|
||||
|
||||
---
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
### 字段/枚举级对比
|
||||
|
||||
| 项 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| `POST /internal/fleet-teams/supplier-status-sync` | 不存在 | 新增 internal 端点(ReqVO 4 字段 / RespVO 6 字段) |
|
||||
| `PrecheckRespVO.warnings[].type` 值域 | 9 种既有 type | 新增 `fleet_team_disabled` |
|
||||
| 派单错误码 | 无车队级码(车队停用被合并状态误报 605037) | 新增 605074「车队已停用不可派新单(请先启用车队或换车)」 |
|
||||
| `candidates` 请求/响应字段 | 现有结构 | 零字段变化(行为变化:司机候选集合缩小) |
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 行为 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| 供应商暂停/拉黑 | 车队无感知,名下资源照常可派 | 联动停用名下在启车队(强制,不做在役车辆守卫) |
|
||||
| 司机候选(常驻车挂停用车队) | 照常返回 | 静默排除 |
|
||||
| precheck 车队停用提示 | 仅 `vehicle_unavailable`(车辆侧) | 追加 `fleet_team_disabled`(车+司机两侧) |
|
||||
| 创建/改派选到停用车队资源 | 报 605037「车辆维保/停用」(语义不准);司机侧可漏过 | 统一报 605074(先于 605037/605038) |
|
||||
| 在途单纯费用调整(司机另一常驻车车队被停用) | 司机侧无校验 | 仍不校验(刻意豁免,不卡死在途单) |
|
||||
| 供应商恢复合作 | - | 不自动启用车队(人工启用,安全侧) |
|
||||
|
||||
---
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **是否破坏向后兼容**: 否。响应结构无字段删改;候选集合缩小属业务收口;新增 warning type 与错误码对旧前端为未知值——旧前端按通用兜底渲染即可,不崩。
|
||||
- **前端是否必须同步上线**: 建议同批但不强阻断。两项适配:① precheck warning type 白名单加 `fleet_team_disabled`(不加则该预警被静默丢弃,车务要到提交时才知道被拦);② 错误码字典加 605074 文案与引导动作(不加则按通用错误提示展示)。
|
||||
- **前端 workaround 清理点**: 无(此前无对应前端绕行逻辑)。
|
||||
- **服务间依赖**: resource 侧 #6844 未上线前,internal 端点无生产流量,派单拦截仅对人工停用/无供应商自动停用的车队生效——同样符合预期。
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- **仅影响**: 派单候选查询 / 派单预校验 / 派单创建(批量,单派同路径)/ 派单改派 4 个管理后台端点 + 1 个 internal 端点(fleet 侧)。
|
||||
- **零影响**:
|
||||
- 团批组派(GroupDispatch 独立体系,不写 `fleet_assignment`,PR 明示不在拦截范围)。
|
||||
- 存量派单的最终确认 / 撤销取消 / 行程短信 / 软清等推进类写口(不挂车队状态新校验,供应商事后停用不卡死在途单)。
|
||||
- 车队管理 CRUD 端点(`/admin/fleet/teams/**` 本单未改;启停用/供应商守卫口径仍属 #6717/#6811)。
|
||||
- 司机档案 / 车辆档案 / 司机列表「所属车队」筛选(排除逻辑复用同一反查端口,档案域零改动感知)。
|
||||
- 供应商域九大资源模块与供应商管理端点(联动发出端 #6844 另行落地)。
|
||||
- 小程序端(派单链路为管理后台车务功能)。
|
||||
- Gateway 路由(`/admin/fleet/assignments/**` 通配已覆盖;`/internal/**` 本就不经网关,无需新增配置)。
|
||||
- Redis/MQ(仅新增幂等键 `fleet:team:supplier-status-sync:{eventId}`,无新消息)。
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
真实 TEST 环境实测(2026-08-31,网关 `https://api.test.1814.love:9443`,admin token 走 `/admin/auth/login`;internal 端点经 SSH 在测试服本机 curl `127.0.0.1:8087` 直连,模拟 Feign 调用方):
|
||||
|
||||
```
|
||||
[internal 链路]
|
||||
POST /internal/fleet-teams/supplier-status-sync SUSPENDED 首投
|
||||
→ 200, ignored=false, duplicate=false, disabledCount=1, disabledTeamIds=["352696323818000384"] ✓
|
||||
POST /internal/fleet-teams/supplier-status-sync 同 eventId 重投(幂等)
|
||||
→ 200, duplicate=true, disabledCount=0, disabledTeamIds=[] ✓
|
||||
POST /internal/fleet-teams/supplier-status-sync targetStatus=ACTIVE(非停合作态)
|
||||
→ 200, ignored=true, disabledCount=0 ✓
|
||||
POST /internal/fleet-teams/supplier-status-sync 缺 eventId
|
||||
→ 400「事件ID不能为空」 ✓
|
||||
POST /internal/fleet-teams/supplier-status-sync 错误 X-Internal-Token
|
||||
→ 403「内部接口禁止外部访问」 ✓
|
||||
POST 网关 /internal/fleet-teams/supplier-status-sync
|
||||
→ 业务码 404「接口不存在」(/internal 不经网关路由,符合预期) ✓
|
||||
|
||||
[管理后台链路(admin token)]
|
||||
GET /admin/fleet/teams/352696323818000384
|
||||
→ status=DISABLED(联动生效,全栈贯通) ✓
|
||||
POST /admin/fleet/assignments/candidates driverKeyword=斯琴
|
||||
→ 停用前 records=1 条 → 联动停用后 records=[] total=0(新增排除生效) ✓
|
||||
POST /admin/fleet/assignments/candidates vehicleKeyword=T6843
|
||||
→ records=[](车辆侧 #6717 既有排除仍生效) ✓
|
||||
POST /admin/fleet/assignments/precheck 停用车队车辆+常驻司机
|
||||
→ warnings 含 {"type":"fleet_team_disabled","msg":"车队已停用不可派新单,提交派单将被拦截,请先启用车队或换车"} ✓
|
||||
POST /admin/fleet/assignments/2091068072742756354/change 改派到停用车队车+司机
|
||||
→ 605074「车队已停用不可派新单(请先启用车队或换车)」,原派单零副作用(事后核 update_time 未变) ✓
|
||||
POST /admin/fleet/assignments/batch 停用车队车+司机(过期行程占位)
|
||||
→ 605047「行程已结束」优先命中(前置守卫先于 605074,符合校验顺序) ✓
|
||||
POST /admin/fleet/teams/352696323818000384/enable 重新启用
|
||||
→ 200;同条件 candidates 斯琴恢复 1 条(恢复路径即时生效) ✓
|
||||
```
|
||||
|
||||
- 部署:Deploy Panel 任务 `a7300401`(hl-fleet-service,2026-08-31 14:02 success,8087/8187 双实例滚动均 UP),部署 dev-v3 HEAD `2543febee`(含本 PR merge commit `df78cce550b4f4ea6a9f40352c1dd176b8d8fba6`,2026-08-31 13:50 合入)。
|
||||
- **batch 创建 605074 正向链未能在 TEST 实跑**:测试服全部待派(unassigned)占位的行程均已结束,创建请求先撞 605047;构造可派需求需完整订单+需求展开链,超出本次验证范围。该路径由单测覆盖(`AssignmentServiceTest#create_fleetTeamDisabledVehicle_throws605074` / `create_driverOnDisabledTeam_throws605074`,与已实测的改派路径 `change_fleetTeamDisabledVehicle_throws605074` 共用同一锁内校验方法),建议 mmg 联调时在真实新订单上补验一次。
|
||||
- 前置态构造说明:「停用车队 + 在役车辆/常驻司机」状态被人工停用守卫(601103)与车队选择守卫(601102)封锁,只有供应商联动能合法产生——实测经 internal 端点真实产生(正是本端点设计语义);测试车队的「ACTIVE + 已绑供应商」前置态由 DB 直改构造(测试服 fixture)。
|
||||
- 本地自动化(PR 自报):目标单测 730 个全绿(AssignmentServiceTest 522 / FleetTeamServiceTest 36 / DriverServiceTest 157 / 新增 Mapper+Controller 测试);全量 `mvn -pl hl-fleet-service test` 3913 tests 0 失败(含 IT);`FleetRedLineArchTest` 13/13 绿;spotless:check 通过。
|
||||
- 数据清理:测试车队(352696323818000384)、测试车辆(2094307514693648385,蒙A-T6843)均已删除(DB 复核无残留);测试用模拟供应商 ID(8996843000000000001)未在 `supplier_main` 落库,随车队删除清除;司机斯琴档案未改动(常驻绑定随车辆删除解除);被改派实测的真实派单(2091068072742756354)经核零副作用;admin token 已登出。
|
||||
|
||||
---
|
||||
|
||||
## 九、相关历史 PR
|
||||
|
||||
| PR | Issue | 说明 | 是否仍有效 |
|
||||
|----|-------|------|------------|
|
||||
| #6868 | #6843 | 供应商暂停合作联动停用车队 + 派单拦截停用车队司机车辆(本次) | ✅ 最新 |
|
||||
| #6753 | #6717 | 车队关联供应商并展示供应商全名(车队-供应商关联与车辆侧排除的前置) | ✅ 已被本单扩展 |
|
||||
| #6827 | #6811 | 车队保存无供应商自动停用 + 601112 在役车辆守卫(车队停用来源之二) | ✅ 并存 |
|
||||
| - | #6844 | 供应商侧通知发出端(resource → fleet 联动调用方),待指派实现 | ⏳ 未开始 |
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 关联 Issue: [wx/HL#6843](https://git.1814.love:8443/wx/HL/issues/6843)
|
||||
- 关联 PR: [wx/HL#6868](https://git.1814.love:8443/wx/HL/pulls/6868)
|
||||
- 前置契约: `changelogs-v2/2026-08/30_6717_车队关联供应商并展示供应商全名-修改接口-管理后台.md`
|
||||
- 前置契约: `changelogs-v2/2026-08/31_6811_车队保存无供应商时自动停用与在役车辆守卫-修改接口-管理后台.md`
|
||||
|
||||
## 撤回
|
||||
|
||||
1. 前端先摘除对 605074 与 `fleet_team_disabled` 的特化渲染(按通用兜底展示),保持线上可用。
|
||||
2. 从最新 `dev-v3` 建独立回退分支,回退 PR #6868 的合并 commit(`df78cce5`),验证后经独立 PR 合入。
|
||||
3. ⚠️ 已被联动停用的车队**不会**因回退自动恢复:回退代码只撤联动与拦截逻辑,已落 DISABLED 的车队需在车队管理人工启用(启用前置 = 已绑供应商)。
|
||||
4. 仅需止血时也可不回退整单:internal 端点无生产流量前(#6844 未上线)本单对线上唯一可感知影响是人工停用/无供应商车队在派单侧的拦截收口,属预期行为。
|
||||
5. 使用 Deploy Panel 滚动部署 `hl-fleet-service`;本单无数据库迁移,无需回滚脚本。
|
||||
6. 撤回后经 Gateway 验证:候选恢复返回停用车队常驻司机、precheck 不再出现 `fleet_team_disabled`、改派停用车队资源回报 605037 老口径。
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#6843](https://git.1814.love:8443/wx/HL/issues/6843)
|
||||
- **PR**: [#6868](https://git.1814.love:8443/wx/HL/pulls/6868)
|
||||
- **Merge commit**: `df78cce550b4f4ea6a9f40352c1dd176b8d8fba6`
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
- **前端联动**: @mmg(① precheck `warnings[]` type 白名单加 `fleet_team_disabled`;② 错误码字典加 605074「车队已停用不可派新单(请先启用车队或换车)」+ 引导动作;③ 司机候选静默排除无前端适配量)
|
||||
- **服务间联动**: resource 侧 #6844 实现方按本文 §三.1 契约投递(supplierId/targetStatus/eventId/occurredAt + `X-Internal-Token`,重复投递认 `duplicate=true` 成功回执)
|
||||
@@ -0,0 +1,74 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "frontend-fleet-team-sort-validation"
|
||||
title: "车队编辑弹窗「排序」已填值仍提示「请输入排序」,无法保存"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "前端缺陷"
|
||||
backend_status: "not_required"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "verified"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "e2b2482e"
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-08-31"
|
||||
status_note: "纯前端校验缺陷,前端已修复。rules.sortOrder 的 required 规则补 type:'number',number 值(含 0)不再误判为空,空值仍正确提示必填。"
|
||||
updated_at: "2026-08-31"
|
||||
base: "dev-v3"
|
||||
generated: "2026-08-31T01:43:00+08:00"
|
||||
---
|
||||
|
||||
# 车队编辑弹窗「排序」已填值仍提示「请输入排序」,无法保存
|
||||
|
||||
> 前端缺陷,待前端修复。后端契约无变化。
|
||||
|
||||
## 现象(TEST 实测)
|
||||
|
||||
车务 → 车队管理 → 编辑车队(如「合作车队A」):
|
||||
|
||||
- 「排序」输入框中已显示值 `1`;
|
||||
- 点击「保存」时表单项仍报红色校验错误「请输入排序」,保存被拦截,无法提交。
|
||||
|
||||
## 只读前端定位(hl-ui 未修改)
|
||||
|
||||
`src/views/fleet/teams/index.vue`:
|
||||
|
||||
- 表单绑定:`NInputNumber v-model:value="formData.sortOrder"`,编辑时 `fillForm` 正确回填 `row?.sortOrder ?? null`,输入框显示的 `1` 说明 model 已有值;
|
||||
- 校验规则(约 391 行):
|
||||
|
||||
```js
|
||||
sortOrder: [{ required: true, message: '请输入排序', trigger: ['blur', 'change'] }],
|
||||
```
|
||||
|
||||
规则缺少 `type: 'number'`。Naive UI 底层 async-validator 的必填规则默认按 string 类型校验,对 number 类型的值会误判为空,导致「值已填但 required 校验不通过」。同页面其他字段均为字符串/枚举值所以未暴露此问题。
|
||||
|
||||
## 修复建议
|
||||
|
||||
规则补充 `type: 'number'`,例如:
|
||||
|
||||
```js
|
||||
sortOrder: [{ required: true, type: 'number', message: '请输入排序', trigger: ['blur', 'change'] }],
|
||||
```
|
||||
|
||||
同时建议回归以下场景:
|
||||
|
||||
1. 编辑已有车队(详情回填 sortOrder)直接保存,不再误报;
|
||||
2. 新建车队填写排序后保存正常;
|
||||
3. 排序为空(清空输入框)时仍正确提示「请输入排序」;
|
||||
4. 排序为 `0` 时可通过校验(`min: 0`,0 是合法值,不得被误判为空)。
|
||||
|
||||
## 后端契约(无变化,供联调核对)
|
||||
|
||||
- 详情:`GET /fleet/teams/{fleetTeamId}`,响应含 `sortOrder`(整数);
|
||||
- 更新:`PUT /fleet/teams/{fleetTeamId}`,请求体 `sortOrder` 为整数;
|
||||
- 新增:`POST /fleet/teams`,请求体 `sortOrder` 为整数。
|
||||
|
||||
本条目不引入后端代码、接口、数据库、配置、Redis 或 MQ 变更。
|
||||
|
||||
## 复现路径
|
||||
|
||||
车务(fleet 管理后台)→ 车队管理 → 任一有排序值的车队 → 编辑 → 直接点「保存」。
|
||||
|
||||
## 撤回
|
||||
|
||||
如需撤回本联调通知,只需回退本 Changelog 文件;后端契约和运行数据不受影响。
|
||||
@@ -0,0 +1,748 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "6903"
|
||||
title: "团期看板六芯片逐户明细 GB-ADM-090~095"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "新增接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "pending"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: "2026-09-01"
|
||||
status_note: "2026-09-01 部署测试服 dev-v3@1159c8949,6 端点网关实测 200 全通过,团期不存在 589500 / 参数错误 400 已核验。前端评估(2026-09-01):团期看板 src/views/order-v2/batch 当前为纯本地 mock 阶段(period 用 mock id,全仓无真实 group-batch 调用),chips 端点需真实雪花 groupBatchId,下钻暂无真实落脚页。决策:先接真团期看板(需 GB-ADM-001 看板列表契约+真实 groupBatchId,不在本单)再接六芯片下钻,下钻形态已定=弹层明细。待看板列表契约后接入,跟踪见 hl-admin 任务 #104。"
|
||||
updated_at: "2026-09-01"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 团期看板:六芯片逐户明细(GB-ADM-090~095,房/车/导/摄/约/保)
|
||||
|
||||
> **存放目录**: 二期(order-v3)→ `changelogs-v2/2026-09/`
|
||||
>
|
||||
> **服务**: hl-order-service-v3
|
||||
> **Issue**: [#6903](https://git.1814.love:8443/wx/HL/issues/6903)
|
||||
> **日期**: 2026-09-01
|
||||
> **影响范围**: 管理后台团期看板行右侧六芯片(配房/配车/配导游/配摄影/合同/保险)点击后的逐户下钻
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
**新增能力,无破坏性变化**:先前看板芯片只有整团聚合色块(GB-ADM-001 `chips.X`)、没有逐户下钻;本次把「点芯片看每户到哪一步」补成可调用接口。数据(order_main 六态列)早已落库并被房务/车队/地接等 Service 消费,本组接口只读透出,不改变任何业务状态。
|
||||
|
||||
---
|
||||
|
||||
## 一、背景(选填)
|
||||
|
||||
团期看板行右侧有「房/车/导/摄/约/保」六个芯片,点击后按需展开该项的**逐户明细**。逐户口径与整团聚合、看板芯片(GB-ADM-001 `chips.X`)同源同算法(共享地基 `GroupBatchChipResolver`,#6902/#6916)。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | GB-ADM-090 配房逐户明细 | GET | `/v3/admin/order/group-batch/{groupBatchId}/chips/hotel` | 新增接口 | 房芯片逐户下钻 |
|
||||
| 2 | GB-ADM-091 配车逐户明细 | GET | `/v3/admin/order/group-batch/{groupBatchId}/chips/vehicle` | 新增接口 | 车芯片逐户下钻 |
|
||||
| 3 | GB-ADM-092 配导游逐户明细 | GET | `/v3/admin/order/group-batch/{groupBatchId}/chips/guide` | 新增接口 | 导芯片逐户下钻 |
|
||||
| 4 | GB-ADM-093 配摄影逐户明细 | GET | `/v3/admin/order/group-batch/{groupBatchId}/chips/photo` | 新增接口 | 摄芯片逐户下钻 |
|
||||
| 5 | GB-ADM-094 合同逐户明细 | GET | `/v3/admin/order/group-batch/{groupBatchId}/chips/contract` | 新增接口 | 约芯片逐户下钻 |
|
||||
| 6 | GB-ADM-095 保险逐户明细 | GET | `/v3/admin/order/group-batch/{groupBatchId}/chips/insurance` | 新增接口 | 保芯片逐户下钻 |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
六个接口共用 `GroupBatchChipDetailVO` / `GroupBatchChipItemRespVO`,正文各自自包含。
|
||||
|
||||
### 1. GB-ADM-090 配房逐户明细 `GET /v3/admin/order/group-batch/{groupBatchId}/chips/hotel`
|
||||
|
||||
**VO**: `GroupBatchChipDetailVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
团期看板行右侧「房」芯片点击展开:返回该团期每户的配房进度(哪一户到哪一步),前端展开列表展示。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| Authorization | Header | String | ✅ | - | 管理端登录令牌 |
|
||||
| X-Admin-Id | Header | Long | ✅ | 网关注入 | 受信管理员 ID;客户端传值一律忽略(不校验客户端值) |
|
||||
| groupBatchId | Path | String | ✅ | 团期 ID(Snowflake) | 不存在/非团期/已软删 → 589500 |
|
||||
|
||||
无查询参数、无请求体。
|
||||
|
||||
#### 出参 `Result<GroupBatchChipDetailVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| batchId | String | 团期 ID |
|
||||
| chipLabel | String | 固定「配房」 |
|
||||
| aggregateStatus | String | 整团聚合态 四态取值与看板 GB-ADM-001 `chips.X` 的 `aggregateStatus` 完全一致(整团待办/进行中/已完成/异常),与 GB-ADM-001 `chips.hotel` 同源同算法 |
|
||||
| totalCount | Integer | 计入统计的子订单数(免闸户不计入分母) |
|
||||
| doneCount | Integer | 已完成户数(`status==DONE`) |
|
||||
| items[].orderId | String | 子订单 ID(JSON String 化) |
|
||||
| items[].orderNo | String | 子订单编号 |
|
||||
| items[].contactName | String | 联系人/客户姓名 |
|
||||
| items[].peopleCount | Integer | 本户人数 = adult+child+youngChild+baby |
|
||||
| items[].status | String | 本户配房状态,`RequirementStatus` 6 值,见「六.5」;无需/未开始为 null |
|
||||
| items[].statusText | String | 状态中文名(服务端给出,见「六.5」) |
|
||||
| items[].needsIt | Boolean | 本户是否需要配房(`order_main.needs_hotel`);false=免闸户,status=null、置灰、不计入计数 |
|
||||
| items[].updateTime | String | 配房最后变更时间;六状态列无独立时间戳时为 null |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/group-batch/90211/chips/hotel
|
||||
Authorization: Bearer ****
|
||||
X-Admin-Id: 3301
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"batchId": "90211",
|
||||
"chipLabel": "配房",
|
||||
"aggregateStatus": "DOING",
|
||||
"totalCount": 7,
|
||||
"doneCount": 5,
|
||||
"items": [
|
||||
{ "orderId": "770152", "orderNo": "GT-26-0096", "contactName": "陈昊",
|
||||
"peopleCount": 2, "status": "DONE", "statusText": "配房完成",
|
||||
"needsIt": true, "updateTime": null },
|
||||
{ "orderId": "770153", "orderNo": "GT-26-0097", "contactName": "林婉清",
|
||||
"peopleCount": 3, "status": null, "statusText": "无需",
|
||||
"needsIt": false, "updateTime": null }
|
||||
]
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
```json
|
||||
{ "code": 200, "data": { "batchId": "90211", "chipLabel": "配房",
|
||||
"aggregateStatus": "整团待办", "totalCount": 0, "doneCount": 0, "items": [] },
|
||||
"success": true }
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{ "code": 589500, "message": "团期不存在", "success": false, "data": null }
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 授权码 `group-batch:view`;未配置权限 → 589507。
|
||||
- 只读(READ_ONLY 事务),无锁、无幂等、不改变任何状态。
|
||||
- 免闸户(`needsIt=false`):status=null、statusText=「无需」、前端置灰、不计入 totalCount/doneCount 分子分母。
|
||||
- 已取消子订单不计入(活跃口径与共享地基一致)。
|
||||
- 团期已流团(CANCELLED)→ aggregateStatus 恒整团待办(wire 值同看板 chips.X),但逐户明细仍如实返回(不造假计数)。
|
||||
|
||||
---
|
||||
|
||||
### 2. GB-ADM-091 配车逐户明细 `GET /v3/admin/order/group-batch/{groupBatchId}/chips/vehicle`
|
||||
|
||||
**VO**: `GroupBatchChipDetailVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
团期看板行右侧「车」芯片点击展开:返回每户配车进度。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| Authorization | Header | String | ✅ | - | 管理端登录令牌 |
|
||||
| X-Admin-Id | Header | Long | ✅ | 网关注入 | 受信管理员 ID;客户端传值忽略 |
|
||||
| groupBatchId | Path | String | ✅ | 团期 ID(Snowflake) | 不存在/非团期/已软删 → 589500 |
|
||||
|
||||
无查询参数、无请求体。
|
||||
|
||||
#### 出参 `Result<GroupBatchChipDetailVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| batchId | String | 团期 ID |
|
||||
| chipLabel | String | 固定「配车」 |
|
||||
| aggregateStatus | String | 整团聚合态 四态取值与看板 GB-ADM-001 `chips.X` 的 `aggregateStatus` 完全一致(整团待办/进行中/已完成/异常),与 GB-ADM-001 `chips.vehicle` 同源 |
|
||||
| totalCount | Integer | 计入统计的子订单数(免闸户不计入) |
|
||||
| doneCount | Integer | 已完成户数(`status==DONE`) |
|
||||
| items[].orderId | String | 子订单 ID(JSON String 化) |
|
||||
| items[].orderNo | String | 子订单编号 |
|
||||
| items[].contactName | String | 联系人/客户姓名 |
|
||||
| items[].peopleCount | Integer | 本户人数 = adult+child+youngChild+baby |
|
||||
| items[].status | String | 本户配车状态,`RequirementStatus` 6 值,见「六.5」;无需/未开始为 null |
|
||||
| items[].statusText | String | 状态中文名(服务端给出,与房同文案,见「六.5」) |
|
||||
| items[].needsIt | Boolean | 本户是否需要配车(`order_main.needs_vehicle`);false=免闸户置灰不计入 |
|
||||
| items[].updateTime | String | 配车最后变更时间;无独立时间戳时为 null |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/group-batch/90211/chips/vehicle
|
||||
Authorization: Bearer ****
|
||||
X-Admin-Id: 3301
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"batchId": "90211",
|
||||
"chipLabel": "配车",
|
||||
"aggregateStatus": "DONE",
|
||||
"totalCount": 3,
|
||||
"doneCount": 3,
|
||||
"items": [
|
||||
{ "orderId": "770160", "orderNo": "GT-26-0101", "contactName": "王强",
|
||||
"peopleCount": 2, "status": "DONE", "statusText": "配房完成",
|
||||
"needsIt": true, "updateTime": null }
|
||||
]
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
```json
|
||||
{ "code": 200, "data": { "batchId": "90211", "chipLabel": "配车",
|
||||
"aggregateStatus": "整团待办", "totalCount": 0, "doneCount": 0, "items": [] },
|
||||
"success": true }
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{ "code": 589500, "message": "团期不存在", "success": false, "data": null }
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 授权码 `group-batch:view`;未配置权限 → 589507。
|
||||
- 只读(READ_ONLY 事务),无锁、无幂等、不改变任何状态。
|
||||
- 免闸户(`needsIt=false`):status=null、statusText=「无需」、前端置灰、不计入计数。
|
||||
- 已取消子订单不计入;流团团期聚合恒 整团待办、明细如实返回。
|
||||
|
||||
---
|
||||
|
||||
### 3. GB-ADM-092 配导游逐户明细 `GET /v3/admin/order/group-batch/{groupBatchId}/chips/guide`
|
||||
|
||||
**VO**: `GroupBatchChipDetailVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
团期看板行右侧「导」芯片点击展开:返回每户配导游进度。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| Authorization | Header | String | ✅ | - | 管理端登录令牌 |
|
||||
| X-Admin-Id | Header | Long | ✅ | 网关注入 | 受信管理员 ID;客户端传值忽略 |
|
||||
| groupBatchId | Path | String | ✅ | 团期 ID(Snowflake) | 不存在/非团期/已软删 → 589500 |
|
||||
|
||||
无查询参数、无请求体。
|
||||
|
||||
#### 出参 `Result<GroupBatchChipDetailVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| batchId | String | 团期 ID |
|
||||
| chipLabel | String | 固定「配导游」 |
|
||||
| aggregateStatus | String | 整团聚合态 四态取值与看板 GB-ADM-001 `chips.X` 的 `aggregateStatus` 完全一致(整团待办/进行中/已完成/异常) |
|
||||
| totalCount | Integer | 计入统计的子订单数(免闸户不计入) |
|
||||
| doneCount | Integer | 已完成户数(`status==DONE`) |
|
||||
| items[].orderId | String | 子订单 ID(JSON String 化) |
|
||||
| items[].orderNo | String | 子订单编号 |
|
||||
| items[].contactName | String | 联系人/客户姓名 |
|
||||
| items[].peopleCount | Integer | 本户人数 = adult+child+youngChild+baby |
|
||||
| items[].status | String | 本户配导游状态,3 值:`NONE` 无需/未开始 / `PENDING` 待指派 / `DONE` 已指派,见「六.5」 |
|
||||
| items[].statusText | String | 状态中文名(服务端给出,见「六.5」) |
|
||||
| items[].needsIt | Boolean | 本户是否需要配导游(`order_main.needs_guide`);false=免闸户 status 恒 `NONE`、不计入计数 |
|
||||
| items[].updateTime | String | 配导游最后变更时间;无独立时间戳时为 null |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/group-batch/90211/chips/guide
|
||||
Authorization: Bearer ****
|
||||
X-Admin-Id: 3301
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"batchId": "90211",
|
||||
"chipLabel": "配导游",
|
||||
"aggregateStatus": "DOING",
|
||||
"totalCount": 4,
|
||||
"doneCount": 2,
|
||||
"items": [
|
||||
{ "orderId": "770170", "orderNo": "GT-26-0102", "contactName": "周磊",
|
||||
"peopleCount": 2, "status": "DONE", "statusText": "已指派", "needsIt": true, "updateTime": null },
|
||||
{ "orderId": "770171", "orderNo": "GT-26-0103", "contactName": "吴芳",
|
||||
"peopleCount": 1, "status": "NONE", "statusText": "无需", "needsIt": false, "updateTime": null }
|
||||
]
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
```json
|
||||
{ "code": 200, "data": { "batchId": "90211", "chipLabel": "配导游",
|
||||
"aggregateStatus": "整团待办", "totalCount": 0, "doneCount": 0, "items": [] },
|
||||
"success": true }
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{ "code": 589500, "message": "团期不存在", "success": false, "data": null }
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 授权码 `group-batch:view`;未配置权限 → 589507。
|
||||
- 只读(READ_ONLY 事务),无锁、无幂等、不改变任何状态。
|
||||
- **免闸户(needsIt=false)status 为 `NONE`(非 null)**,与房/车(null)区分;前端按 NONE 置灰。
|
||||
- 读侧只认 `DONE`,其余非免闸状态一律 `PENDING`;已取消子订单不计入;流团团期聚合恒 整团待办。
|
||||
|
||||
---
|
||||
|
||||
### 4. GB-ADM-093 配摄影逐户明细 `GET /v3/admin/order/group-batch/{groupBatchId}/chips/photo`
|
||||
|
||||
**VO**: `GroupBatchChipDetailVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
团期看板行右侧「摄」芯片点击展开:返回每户配摄影进度。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| Authorization | Header | String | ✅ | - | 管理端登录令牌 |
|
||||
| X-Admin-Id | Header | Long | ✅ | 网关注入 | 受信管理员 ID;客户端传值忽略 |
|
||||
| groupBatchId | Path | String | ✅ | 团期 ID(Snowflake) | 不存在/非团期/已软删 → 589500 |
|
||||
|
||||
无查询参数、无请求体。
|
||||
|
||||
#### 出参 `Result<GroupBatchChipDetailVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| batchId | String | 团期 ID |
|
||||
| chipLabel | String | 固定「配摄影」 |
|
||||
| aggregateStatus | String | 整团聚合态 四态取值与看板 GB-ADM-001 `chips.X` 的 `aggregateStatus` 完全一致(整团待办/进行中/已完成/异常) |
|
||||
| totalCount | Integer | 计入统计的子订单数(免闸户不计入) |
|
||||
| doneCount | Integer | 已完成户数(`status==DONE`) |
|
||||
| items[].orderId | String | 子订单 ID(JSON String 化) |
|
||||
| items[].orderNo | String | 子订单编号 |
|
||||
| items[].contactName | String | 联系人/客户姓名 |
|
||||
| items[].peopleCount | Integer | 本户人数 = adult+child+youngChild+baby |
|
||||
| items[].status | String | 本户配摄影状态,3 值:`NONE` 无需/未开始 / `PENDING` 待指派 / `DONE` 已指派,见「六.5」 |
|
||||
| items[].statusText | String | 状态中文名(服务端给出,见「六.5」) |
|
||||
| items[].needsIt | Boolean | 本户是否需要配摄影(`order_main.needs_photographer`);false=免闸户 status 恒 `NONE`、不计入计数 |
|
||||
| items[].updateTime | String | 配摄影最后变更时间;无独立时间戳时为 null |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/group-batch/90211/chips/photo
|
||||
Authorization: Bearer ****
|
||||
X-Admin-Id: 3301
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"batchId": "90211",
|
||||
"chipLabel": "配摄影",
|
||||
"aggregateStatus": "整团待办",
|
||||
"totalCount": 1,
|
||||
"doneCount": 0,
|
||||
"items": [
|
||||
{ "orderId": "770180", "orderNo": "GT-26-0104", "contactName": "郑浩",
|
||||
"peopleCount": 2, "status": "PENDING", "statusText": "待指派", "needsIt": true, "updateTime": null }
|
||||
]
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
```json
|
||||
{ "code": 200, "data": { "batchId": "90211", "chipLabel": "配摄影",
|
||||
"aggregateStatus": "整团待办", "totalCount": 0, "doneCount": 0, "items": [] },
|
||||
"success": true }
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{ "code": 589500, "message": "团期不存在", "success": false, "data": null }
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 授权码 `group-batch:view`;未配置权限 → 589507。
|
||||
- 只读(READ_ONLY 事务),无锁、无幂等、不改变任何状态。
|
||||
- 免闸户(needsIt=false)status 为 `NONE`(非 null),与导芯片一致。
|
||||
- 读侧只认 `DONE`,其余非免闸状态一律 `PENDING`;已取消子订单不计入;流团团期聚合恒 整团待办。
|
||||
|
||||
---
|
||||
|
||||
### 5. GB-ADM-094 合同逐户明细 `GET /v3/admin/order/group-batch/{groupBatchId}/chips/contract`
|
||||
|
||||
**VO**: `GroupBatchChipDetailVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
团期看板行右侧「约」芯片点击展开:返回每户合同(签约)进度。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| Authorization | Header | String | ✅ | - | 管理端登录令牌 |
|
||||
| X-Admin-Id | Header | Long | ✅ | 网关注入 | 受信管理员 ID;客户端传值忽略 |
|
||||
| groupBatchId | Path | String | ✅ | 团期 ID(Snowflake) | 不存在/非团期/已软删 → 589500 |
|
||||
|
||||
无查询参数、无请求体。
|
||||
|
||||
#### 出参 `Result<GroupBatchChipDetailVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| batchId | String | 团期 ID |
|
||||
| chipLabel | String | 固定「合同」 |
|
||||
| aggregateStatus | String | 整团聚合态 四态取值与看板 GB-ADM-001 `chips.X` 的 `aggregateStatus` 完全一致(整团待办/进行中/已完成/异常);房车导摄四项未全 DONE 前恒整团待办(硬规则;wire 值同看板 chips.X) |
|
||||
| totalCount | Integer | 计全部活跃子订单(约/保无免闸户) |
|
||||
| doneCount | Integer | 已完成户数(`status==SIGNED`) |
|
||||
| items[].orderId | String | 子订单 ID(JSON String 化) |
|
||||
| items[].orderNo | String | 子订单编号 |
|
||||
| items[].contactName | String | 联系人/客户姓名 |
|
||||
| items[].peopleCount | Integer | 本户人数 = adult+child+youngChild+baby |
|
||||
| items[].status | String | 本户合同状态,`ContractStatus` 8 值,见「六.5」;无合同为 null |
|
||||
| items[].statusText | String | 状态中文名(服务端给出,见「六.5」) |
|
||||
| items[].needsIt | Boolean | 恒 true(约/保无免闸) |
|
||||
| items[].updateTime | String | 合同最后变更时间;无独立时间戳时为 null |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/group-batch/90211/chips/contract
|
||||
Authorization: Bearer ****
|
||||
X-Admin-Id: 3301
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"batchId": "90211",
|
||||
"chipLabel": "合同",
|
||||
"aggregateStatus": "DOING",
|
||||
"totalCount": 2,
|
||||
"doneCount": 1,
|
||||
"items": [
|
||||
{ "orderId": "770190", "orderNo": "GT-26-0105", "contactName": "钱进",
|
||||
"peopleCount": 2, "status": "SIGNED", "statusText": "已签署", "needsIt": true, "updateTime": null },
|
||||
{ "orderId": "770191", "orderNo": "GT-26-0106", "contactName": "孙丽",
|
||||
"peopleCount": 2, "status": "PENDING", "statusText": "待出具", "needsIt": true, "updateTime": null }
|
||||
]
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
```json
|
||||
{ "code": 200, "data": { "batchId": "90211", "chipLabel": "合同",
|
||||
"aggregateStatus": "整团待办", "totalCount": 0, "doneCount": 0, "items": [] },
|
||||
"success": true }
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{ "code": 589500, "message": "团期不存在", "success": false, "data": null }
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 授权码 `group-batch:view`;未配置权限 → 589507。
|
||||
- 只读(READ_ONLY 事务),无锁、无幂等、不改变任何状态。
|
||||
- needsIt 恒 true(约/保无免闸户);doneCount 只按 `SIGNED`;作废中/已作废计入整团 ERROR。
|
||||
- 约/保整团聚合在房车导摄四芯片未全部 DONE 前恒 整团待办(硬规则);已取消子订单不计入;流团团期聚合恒 整团待办。
|
||||
|
||||
---
|
||||
|
||||
### 6. GB-ADM-095 保险逐户明细 `GET /v3/admin/order/group-batch/{groupBatchId}/chips/insurance`
|
||||
|
||||
**VO**: `GroupBatchChipDetailVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
团期看板行右侧「保」芯片点击展开:返回每户保险进度。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| Authorization | Header | String | ✅ | - | 管理端登录令牌 |
|
||||
| X-Admin-Id | Header | Long | ✅ | 网关注入 | 受信管理员 ID;客户端传值忽略 |
|
||||
| groupBatchId | Path | String | ✅ | 团期 ID(Snowflake) | 不存在/非团期/已软删 → 589500 |
|
||||
|
||||
无查询参数、无请求体。
|
||||
|
||||
#### 出参 `Result<GroupBatchChipDetailVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| batchId | String | 团期 ID |
|
||||
| chipLabel | String | 固定「保险」 |
|
||||
| aggregateStatus | String | 整团聚合态 四态取值与看板 GB-ADM-001 `chips.X` 的 `aggregateStatus` 完全一致(整团待办/进行中/已完成/异常);房车导摄未全 DONE 前恒整团待办(硬规则;wire 值同看板 chips.X) |
|
||||
| totalCount | Integer | 计全部活跃子订单(约/保无免闸户) |
|
||||
| doneCount | Integer | 已完成户数(`status==INSURED`) |
|
||||
| items[].orderId | String | 子订单 ID(JSON String 化) |
|
||||
| items[].orderNo | String | 子订单编号 |
|
||||
| items[].contactName | String | 联系人/客户姓名 |
|
||||
| items[].peopleCount | Integer | 本户人数 = adult+child+youngChild+baby |
|
||||
| items[].status | String | 本户保险状态,4 值:`INSURING` 投保中 / `INSURED` 已出单 / `CANCELLED` 已取消 / `FAILED` 出单失败;无保险为 null |
|
||||
| items[].statusText | String | 状态中文名(服务端给出,见「六.5」) |
|
||||
| items[].needsIt | Boolean | 恒 true(约/保无免闸) |
|
||||
| items[].updateTime | String | 保险最后变更时间;无独立时间戳时为 null |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/group-batch/90211/chips/insurance
|
||||
Authorization: Bearer ****
|
||||
X-Admin-Id: 3301
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"batchId": "90211",
|
||||
"chipLabel": "保险",
|
||||
"aggregateStatus": "DOING",
|
||||
"totalCount": 2,
|
||||
"doneCount": 1,
|
||||
"items": [
|
||||
{ "orderId": "770200", "orderNo": "GT-26-0107", "contactName": "李娜",
|
||||
"peopleCount": 2, "status": "INSURED", "statusText": "已出单", "needsIt": true, "updateTime": null },
|
||||
{ "orderId": "770201", "orderNo": "GT-26-0108", "contactName": "赵敏",
|
||||
"peopleCount": 3, "status": "INSURING", "statusText": "出单中", "needsIt": true, "updateTime": null }
|
||||
]
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
```json
|
||||
{ "code": 200, "data": { "batchId": "90211", "chipLabel": "保险",
|
||||
"aggregateStatus": "整团待办", "totalCount": 0, "doneCount": 0, "items": [] },
|
||||
"success": true }
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{ "code": 589500, "message": "团期不存在", "success": false, "data": null }
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 授权码 `group-batch:view`;未配置权限 → 589507。
|
||||
- 只读(READ_ONLY 事务),无锁、无幂等、不改变任何状态。
|
||||
- needsIt 恒 true;doneCount 只按 `INSURED`;已取消/出单失败计入整团 ERROR。
|
||||
- 约/保整团聚合在房车导摄未全 DONE 前恒 整团待办(硬规则);已取消子订单不计入;流团团期聚合恒 整团待办。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式(接口类必写)
|
||||
|
||||
> 本节只写后端接受/拒绝请求的规则,不写 UI 渲染建议。
|
||||
|
||||
### ✅ 正确 / ❌ 错误调用对照
|
||||
|
||||
| 场景 | 说明 |
|
||||
|------|------|
|
||||
| ✅ 网关已注入 X-Admin-Id,调任一芯片端点 | 返回 `Result<GroupBatchChipDetailVO>`,code=200 |
|
||||
| ✅ 免闸户(needsIt=false) | 房/车 status=null;导/摄 status=NONE;不计入 totalCount/doneCount |
|
||||
| ❌ 未配置 `group-batch:view` 权限 | 589507 无操作权限 |
|
||||
| ❌ groupBatchId 不存在/非团期/已软删 | 589500 团期不存在 |
|
||||
| ❌ 客户端自行传 X-Admin-Id | 以网关注入值为准,客户端值一律忽略(不校验客户端值) |
|
||||
|
||||
### 切换状态时的必要动作
|
||||
|
||||
无状态切换——六个端点均为只读 GET、无请求体;前端点击芯片即查询,请求方不需要携带任何业务状态字段,也不修改任何状态。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为(涉及写操作时必写)
|
||||
|
||||
本组接口**无写操作**(READ_ONLY 事务),无表变更、无字段变更、无数据迁移。数据来源即为现有 order_main 六态列(needs_hotel/needs_vehicle/needs_guide/needs_photographer/合同状态/保险状态),由共享地基读取投影。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 未登录/无有效 token → 401(网关拦截,本组接口不允许匿名访问)。
|
||||
- 未配置 `group-batch:view` 权限 → 589507 无操作权限。
|
||||
- 团期不存在/非团期/已软删 → 589500 团期不存在。
|
||||
- 任意芯片端点输入非法(groupBatchId 非数字)→ 400 参数错误(框架级)。
|
||||
- 空团期/无活跃子订单 → data 正常返回:totalCount=0、doneCount=0、items=[],不 500 不降级。
|
||||
- 下游数据缺失(老数据无对应需求/合同/保险记录)→ 对应 status 为 null(房/车/约/保)或 NONE(导/摄),不异常。
|
||||
- 团期流团(CANCELLED)→ aggregateStatus 恒整团待办(wire 值同看板 chips.X),明细仍如实返回。
|
||||
- 已返团(审核/结算)→ 房车导摄恒 DONE 聚合。
|
||||
- 列表查询与整团伙计数一次性内存聚合(防 N+1),单次请求最多一次 `listByProductBatchId` 查询。
|
||||
|
||||
---
|
||||
|
||||
## 六.5、枚举 / 数据字典(接口出现枚举时必写)
|
||||
|
||||
### items[].status(配房/配车,`com.hulalv.order.core.enums.RequirementStatus`)
|
||||
|
||||
**所属字段**: `GroupBatchChipItemRespVO.status` | **类型**: `String`
|
||||
|
||||
| 值 | 中文(statusText) | 说明 |
|
||||
|----|------|------|
|
||||
| `PENDING` | 待房务配 | 待房务/车队配 |
|
||||
| `PROCESSING` | 配房中 | 配置进行中 |
|
||||
| `DONE` | 配房完成 | 已完成(房/车同文案) |
|
||||
| `PENDING_REVIEW` | 待审核 | 待复核 |
|
||||
| `REJECTED_TO_CONSULTANT` | 驳回给定制师 | 失败态(计入整团 ERROR) |
|
||||
| `REJECTED_TO_ADMIN` | 驳回给管理员 | 失败态(计入整团 ERROR) |
|
||||
|
||||
### items[].status(配导游/配摄影)
|
||||
|
||||
**所属字段**: `GroupBatchChipItemRespVO.status` | **类型**: `String`
|
||||
|
||||
| 值 | 中文(statusText) | 说明 |
|
||||
|----|------|------|
|
||||
| `NONE` | 无需 | 免闸户/未开始(免闸户恒 NONE) |
|
||||
| `PENDING` | 待指派 | 读侧非免闸非 DONE 一律 PENDING |
|
||||
| `DONE` | 已指派 | 已完成 |
|
||||
|
||||
### items[].status(合同,`com.hulalv.order.*.ContractStatus`)
|
||||
|
||||
**所属字段**: `GroupBatchChipItemRespVO.status` | **类型**: `String`
|
||||
|
||||
| 值 | 中文(statusText) | 说明 |
|
||||
|----|------|------|
|
||||
| `PENDING` | 待出具 | 待生成 |
|
||||
| `GENERATED` | 已生成 | - |
|
||||
| `REPORTED` | 已报备 | - |
|
||||
| `UPLOADED` | 已上传 | - |
|
||||
| `SIGNING` | 签署中 | - |
|
||||
| `SIGNED` | 已签署 | doneCount 判定值 |
|
||||
| `VOIDING` | 作废中 | 失败态(计入整团 ERROR) |
|
||||
| `VOIDED` | 已作废 | 失败态(计入整团 ERROR) |
|
||||
|
||||
### items[].status(保险)
|
||||
|
||||
**所属字段**: `GroupBatchChipItemRespVO.status` | **类型**: `String`
|
||||
|
||||
| 值 | 中文(statusText) | 说明 |
|
||||
|----|------|------|
|
||||
| `INSURING` | 出单中 | 投保中 |
|
||||
| `INSURED` | 已出单 | doneCount 判定值 |
|
||||
| `CANCELLED` | 已取消 | 失败态(计入整团 ERROR) |
|
||||
| `FAILED` | 出单失败 | 失败态(计入整团 ERROR) |
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围(显式声明, 帮前端/QA 缩小排查面)
|
||||
|
||||
- **仅影响**: 管理后台团期看板六芯片的逐户明细展示层接口(GB-ADM-090~095)。
|
||||
- **零影响**:
|
||||
- 看板整团聚合接口(GB-ADM-001 `chips.X` 结构不变)
|
||||
- 房务/车队/地接/合同/保险的任何写接口与业务流程(本组只读)
|
||||
- 一期(v2)所有接口
|
||||
- 数据库结构、网关路由、权限点(复用既有 `group-batch:view`)
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
**JUnit 定向测试**: `GroupBatchChipServiceTest`(12 用例)+ `GroupBatchChipControllerTest`(7 用例)+ 共享地基 `GroupBatchChipResolverTest`(18 用例)全绿,覆盖六端点、聚合态、免闸户、错误码 589500/589507、权限校验。
|
||||
|
||||
真实接口网关验证(2026-09-01 部署 dev-v3 @1159c8949 后实测):
|
||||
|
||||
测试团期: `groupBatchId=2089713777065832450`(batch Q202610312089667212070612994,RESOURCE_PREPARING,含 1 活跃子订单)
|
||||
|
||||
> 聚合态 `aggregateStatus` wire 英文四态值与看板 GB-ADM-001 `chips.X` 完全一致(读侧同源同算法);下表按中文态名展示实测结果。
|
||||
|
||||
```
|
||||
GET /v3/admin/order/group-batch/2089713777065832450/chips/hotel → 200 code=200 aggregateStatus=整团待办 total=1 done=0 items=1 ✓
|
||||
GET /v3/admin/order/group-batch/2089713777065832450/chips/vehicle → 200 code=200 aggregateStatus=整团待办 total=1 done=0 items=1 ✓
|
||||
GET /v3/admin/order/group-batch/2089713777065832450/chips/guide → 200 code=200 aggregateStatus=整团待办 total=0 items=1(needsIt=false 免闸不计 total,"无需")✓
|
||||
GET /v3/admin/order/group-batch/2089713777065832450/chips/photo → 200 code=200 aggregateStatus=整团待办 total=0 items=1(免闸,"无需")✓
|
||||
GET /v3/admin/order/group-batch/2089713777065832450/chips/contract → 200 code=200 aggregateStatus=整团待办 total=1 items=1(无合同 → statusText="无合同")✓
|
||||
GET /v3/admin/order/group-batch/2089713777065832450/chips/insurance→ 200 code=200 aggregateStatus=整团待办 total=1 items=1(无保险 → statusText="无保险")✓
|
||||
GET /v3/admin/order/group-batch/999999999999999999/chips/hotel → 200 code=589500 message="团期不存在" ✓
|
||||
GET /v3/admin/order/group-batch/abc/chips/hotel → 200 code=400 message="参数 groupBatchId 格式错误,请检查后重试" ✓
|
||||
```
|
||||
|
||||
验证通道: 统一网关 `https://api.test.1814.love:9443`,管理员登录 token + 网关注入 `X-Admin-Id`。
|
||||
|
||||
---
|
||||
|
||||
## 九、相关历史 PR(纠错 / 功能演进时必写)
|
||||
|
||||
| PR | Issue | 说明 | 是否仍有效 |
|
||||
|----|-------|------|------------|
|
||||
| #6916 | #6915 | 共享地基 statusText 契约文案修正 + 测试补齐(本单契约基础) | ✅ 有效 |
|
||||
| 本 PR(#6903 分支合并) | #6903 | 六芯片逐户明细接口交付 | ✅ 最新 |
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 关联 Issue: [wx/HL#6903](https://git.1814.love:8443/wx/HL/issues/6903)
|
||||
- 关联 PR: [wx/HL#6918](https://git.1814.love:8443/wx/HL/pulls/6918)(squash 合并至 dev-v3 @1159c8949)
|
||||
- 共享地基: #6902(Resolver/看板聚合)、#6916(statusText 契约修正)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#6903](https://git.1814.love:8443/wx/HL/issues/6903)
|
||||
- **PR**: 合并后回填
|
||||
- **Merge commit**: 合并后回填
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx(GIT)
|
||||
@@ -0,0 +1,330 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "6904"
|
||||
title: "团期看板统计条 GB-ADM-009 + 团期导出 GB-ADM-008"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "新增接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "pending"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: "2026-09-01"
|
||||
status_note: "2026-09-01 部署测试服 dev-v3@9f689354e,/summary 与 /export 经测试网关实测 200 全通过;CSV BOM/CRLF/10 列、7 桶口径、589517 上限均核验。前端评估(2026-09-01):团期看板 src/views/order-v2/batch 当前为纯本地 mock 阶段(无真实 group-batch 调用),统计条/导出依赖真实看板页;且看板列表契约 #6905/GB-ADM-000~003 未推送进 changelog 仓(仅被本单与 #6903 引用,无契约本体)。决策:等后端推送看板列表契约后,统一接真看板再一并接统计条+导出+六芯片下钻(#6903)。跟踪见 hl-admin 任务 #104。"
|
||||
updated_at: "2026-09-01"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 团期看板:统计条(GB-ADM-009)+ 团期导出(GB-ADM-008)
|
||||
|
||||
> **存放目录**: 二期(order-v3)→ `changelogs-v2/2026-09/`
|
||||
>
|
||||
> **服务**: hl-order-service-v3
|
||||
> **PR**: #6917
|
||||
> **Issue**: [#6904](https://git.1814.love:8443/wx/HL/issues/6904)
|
||||
> **日期**: 2026-09-01
|
||||
> **影响范围**: 管理后台团期看板顶部统计条(7 桶计数 + 活跃子订单数)与整表 CSV 导出
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
**新增能力,无破坏性变化**:团期看板新增两个只读端点——统计条(`/summary`)返回七大状态桶与跨团期活跃子订单数;导出(`/export`)按当前筛选整表导出 CSV(UTF-8 BOM、CRLF、固定 10 列)。与 #6905 交付的列表/详情(GB-ADM-000~003)同地基(#6902),本单不触碰 #6905 任何接口与文件。
|
||||
|
||||
---
|
||||
|
||||
## 一、背景(选填)
|
||||
|
||||
管理后台团期看板页顶部需要一条统计条(七桶计数 + 活跃子订单数),并把当前筛选下的团期整表导出为 CSV。口径与看板列表(GB-ADM-000/001/002/003)完全一致:同筛选(productId 精确、month 落出团月、keyword 模糊 OR)、同状态源(`order_group_batch.batch_status` 八态)、同聚合(跨命中团期统计活跃子订单)。FORMED 为 RESOURCE_PREPARING+MATERIAL_PREPARING 复合桶;AUDITING/CHECKED 在接口层保持独立桶,前端展示可折叠合并。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | GB-ADM-009 团期看板统计条 | GET | `/v3/admin/order/group-batch/summary` | 新增接口 | 七桶计数 + total + subOrderCount |
|
||||
| 2 | GB-ADM-008 团期导出 | GET | `/v3/admin/order/group-batch/export` | 新增接口 | 看板整表 CSV 导出(10 列) |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
两个接口共用筛选口径与状态映射(#6902 `GroupBatchStageBuckets` 七桶 / `GroupBatchStatus` 八态),正文各自自包含。
|
||||
|
||||
### 1. GB-ADM-009 团期看板统计条 `GET /v3/admin/order/group-batch/summary`
|
||||
|
||||
**VO**: `GroupBatchSummaryVO`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
团期看板页顶部统计条:返回当前筛选口径下七大状态桶计数、桶合计与跨团期活跃子订单数(任一桶非零即有数据)。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| Authorization | Header | String | ✅ | - | 管理端登录令牌 |
|
||||
| X-Admin-Id | Header | Long | ✅ | 网关注入 | 受信管理员 ID;客户端传值一律忽略(不校验客户端值) |
|
||||
| productId | Query | Long | ❌ | - | 精确匹配团期所属产品 |
|
||||
| month | Query | String | ❌ | `yyyy-MM` | 出团月筛选:`depart_date >= 月初 && < 次月`;空/缺省 = 全部 |
|
||||
| keyword | Query | String | ❌ | - | 团期号/团期名模糊 OR(trim 后;`%` `_` `\` 转义,与 GB-ADM-001 同义) |
|
||||
|
||||
无请求体。
|
||||
|
||||
#### 出参 `Result<GroupBatchSummaryVO>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| total | Integer | 七桶计数之和(接口自校验桶和,恒等于各桶值加总) |
|
||||
| buckets | Map<String,Integer> | 七键恒全:RECRUIT/FORMED/PENDING_TRIP/TRAVELLING/AUDITING/CHECKED/DISBANDED;FORMED=RESOURCE_PREPARING+MATERIAL_PREPARING 复合 |
|
||||
| subOrderCount | Integer | 跨全部命中团期的活跃子订单数(order_status != CANCELLED 且未软删;1 团期 1 房) |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/group-batch/summary?month=2026-06&keyword=%E6%B5%8B%E8%AF%95
|
||||
Authorization: Bearer ****
|
||||
X-Admin-Id: 3301
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"total": 14,
|
||||
"buckets": {
|
||||
"RECRUIT": 0, "FORMED": 13, "PENDING_TRIP": 0, "TRAVELLING": 0,
|
||||
"AUDITING": 0, "CHECKED": 0, "DISBANDED": 1
|
||||
},
|
||||
"subOrderCount": 1
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
```json
|
||||
{ "code": 200, "data": { "total": 0,
|
||||
"buckets": { "RECRUIT": 0, "FORMED": 0, "PENDING_TRIP": 0, "TRAVELLING": 0,
|
||||
"AUDITING": 0, "CHECKED": 0, "DISBANDED": 0 },
|
||||
"subOrderCount": 0 }, "success": true }
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{ "code": 589507, "message": "无操作权限", "success": false, "data": null }
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 授权码 `group-batch:list`;未配置权限 → 589507。
|
||||
- 只读(READ_ONLY 事务),无锁、无幂等、不改变任何状态。
|
||||
- 非法 month(非 `yyyy-MM`)→ 400 参数错误(框架级)。
|
||||
- 空数据时七键仍全返回(各桶 0),total=0、subOrderCount=0,不 500 不降级。
|
||||
|
||||
---
|
||||
|
||||
### 2. GB-ADM-008 团期导出 `GET /v3/admin/order/group-batch/export`
|
||||
|
||||
**VO**: `text/csv`(流式附件,非 JSON 信封)
|
||||
|
||||
#### 使用场景
|
||||
|
||||
团期看板页「导出」按钮:按当前筛选口径整表导出 CSV 文件(浏览器附件下载)。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| Authorization | Header | String | ✅ | - | 管理端登录令牌 |
|
||||
| X-Admin-Id | Header | Long | ✅ | 网关注入 | 受信管理员 ID;客户端传值忽略 |
|
||||
| productId | Query | Long | ❌ | - | 同 summary |
|
||||
| month | Query | String | ❌ | `yyyy-MM` | 同 summary;文件名 `group-batch-{month}.csv` |
|
||||
| keyword | Query | String | ❌ | - | 同 summary |
|
||||
|
||||
无请求体。
|
||||
|
||||
#### 出参 `text/csv`(附件流)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| Content-Type | String | `text/csv; charset=utf-8` |
|
||||
| Content-Disposition | String | `attachment; filename=group-batch-{month|all}.csv` |
|
||||
| body | String | UTF-8 BOM 开头的 CSV 文本:CRLF 换行、固定 10 列(表头见下) |
|
||||
|
||||
**表头 10 列**: 团期号、期号、日期、出团日、满团名额、已售、剩余、状态、子订单数、整团应收。
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
GET /v3/admin/order/group-batch/export?month=2026-06
|
||||
Authorization: Bearer ****
|
||||
X-Admin-Id: 3301
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
(响应体为原生 CSV 文本,非 JSON 信封;下表以 JSON 字符串形式呈现字节内容示例)
|
||||
|
||||
```json
|
||||
"团期号,期号,日期,出团日,满团名额,已售,剩余,状态,子订单数,整团应收\r\nQ2026...,测试期,2026-06-10,2026-06-10,10,7,3,资源准备,1,15900.00\r\n"
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
0 命中仅输出表头 10 列(仍带 UTF-8 BOM 与 CRLF),HTTP 200。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{ "code": 589517, "message": "导出数据超限", "success": false, "data": null }
|
||||
```
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 授权码 `group-batch:export`;未配置权限 → 589507。
|
||||
- 命中行数 > 2000 → 589517(上限 2000 行),前端提示收窄筛选。
|
||||
- 每次成功导出写入 `group_batch_status_log` 导出留痕(BATCH_EXPORT,data 类变更,记录操作管理员与导出档头内容)。
|
||||
- 只读(READ_ONLY 事务);0 命中仅表头(BOM/CRLF 保持)。
|
||||
- 非法 month → 400 参数错误;未登录 → 401(网关拦截)。
|
||||
- 期号含逗号/引号/换行 → CSV 引号转义(内部引号翻倍);金额两位小数不加千分位;剩余=max_rooms-已售且下限 0。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式(接口类必写)
|
||||
|
||||
> 本节只写后端接受/拒绝请求的规则,不写 UI 渲染建议。
|
||||
|
||||
### ✅ 正确 / ❌ 错误调用对照
|
||||
|
||||
| 场景 | 说明 |
|
||||
|------|------|
|
||||
| ✅ 仅带 Authorization 调 summary/export | 全量口径:7 桶恒全、export 全表(受 2000 行上限约束) |
|
||||
| ✅ productId+month+keyword 组合筛选 | 与 GB-ADM-001 同义:productId 精确 / month 落出团月 / keyword trim 后模糊 OR(`%` `_` `\` 转义) |
|
||||
| ❌ 未配置 `group-batch:list` / `group-batch:export` | 589507 无操作权限(两个授权点独立) |
|
||||
| ❌ 命中 > 2000 行导出 | 589517 导出数据超限 |
|
||||
| ❌ 客户端自行传 X-Admin-Id | 以网关注入值为准,客户端值一律忽略 |
|
||||
|
||||
### 切换状态时的必要动作
|
||||
|
||||
无状态切换——两端点均只读 GET、无请求体;导出需前端先具备 `group-batch:export` 授权点(看板查看仅需 `group-batch:list`)。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为(涉及写操作时必写)
|
||||
|
||||
两端点主体为只读(READ_ONLY 事务):聚合 `countActiveByProductBatchIds`(活跃子订单)、`sumBatchAmountsByProductBatchIds`(整团应收,共享 #6902 地基),无表变更、无字段变更。唯一写操作:成功导出后向 `group_batch_status_log` 追加一条 BATCH_EXPORT 留痕记录(`REQUIRES_NEW` 独立事务,导出失败不写留痕)。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 未登录/无有效 token → 401(网关拦截,两端点不允许匿名访问)。
|
||||
- 未配置对应授权点 → 589507。(`/summary` 用 `group-batch:list`,`/export` 用 `group-batch:export`)
|
||||
- 导出命中 > 2000 行 → 589517;前端应引导收窄 month/productId/keyword。
|
||||
- 非法 month 格式 → 400 参数错误(框架级)。
|
||||
- 空数据:summary 七键全 0;export 仅表头 10 列(BOM/CRLF 保持),不 500 不降级。
|
||||
- 状态未知/历史脏数据:`safeStatus` 原样透出(导出的状态列不报错)。
|
||||
- 剩余列恒 `max_rooms - 已售` 且下限 0;金额列两位小数、不加千分位。
|
||||
- 期号含 `,` `"` `\r` `\n` → CSV 引号转义(内部引号翻倍)。
|
||||
|
||||
---
|
||||
|
||||
## 六.5、枚举 / 数据字典(接口类必写)
|
||||
|
||||
### buckets 键(7 桶)
|
||||
|
||||
**所属字段**: `GroupBatchSummaryVO.buckets` | **类型**: `Map<String,Integer>`
|
||||
|
||||
| 桶键 | 来源 batch_status | 说明 |
|
||||
|------|------------------|------|
|
||||
| RECRUIT | RECRUITING | 招募中 |
|
||||
| FORMED | RESOURCE_PREPARING + MATERIAL_PREPARING | 复合桶(成团准备) |
|
||||
| PENDING_TRIP | PENDING_DEPARTURE | 待出发 |
|
||||
| TRAVELLING | TRAVELLING | 出游中 |
|
||||
| AUDITING | REVIEWING | 审核 |
|
||||
| CHECKED | SETTLED | 结算 |
|
||||
| DISBANDED | CANCELLED | 流团/取消 |
|
||||
|
||||
> 说明:FORMED 复合桶、AUDITING/CHECKED 独立桶为接口层口径;前端展示可将 AUDITING+CHECKED 折叠为「返团核账」类目(display 层合并,接口不并)。
|
||||
|
||||
### 导出「状态」列中文名
|
||||
|
||||
**所属字段**: CSV 第 8 列 | **类型**: `String`
|
||||
|
||||
| batch_status | 中文 |
|
||||
|--------------|------|
|
||||
| RECRUITING | 招募中 |
|
||||
| RESOURCE_PREPARING | 资源准备 |
|
||||
| MATERIAL_PREPARING | 物料准备 |
|
||||
| PENDING_DEPARTURE | 待出发 |
|
||||
| TRAVELLING | 出游中 |
|
||||
| REVIEWING | 审核中 |
|
||||
| SETTLED | 已结算 |
|
||||
| CANCELLED | 已流团 |
|
||||
|
||||
---
|
||||
|
||||
## 七、不影响范围(显式声明, 帮前端/QA 缩小排查面)
|
||||
|
||||
- **仅影响**: 团期看板统计条(GB-ADM-009)与导出(GB-ADM-008)两个新增端点。
|
||||
- **零影响**:
|
||||
- #6905 交付的看板列表/详情/条件接口(GB-ADM-000~003)与文件(本单未改动)
|
||||
- 共享地基 #6902 的 `OrderService`/`GroupBatchStageBuckets`(本单仅新增导出留痕方法与本枚举项,未改既有方法与接口)
|
||||
- 一期(v2)所有接口;数据库结构、网关路由、权限点(复用既有 `group-batch:list`/`group-batch:export`)
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
**JUnit 定向测试**: `GroupBatchBoardStatsServiceTest`(11 用例)+ `GroupBatchBoardStatsControllerTest`(6 用例)+ `GroupBatchMapperEscapeLikeTest`(3 用例)全绿;覆盖七桶折叠、total 自校验、subOrderCount、筛选转义、CSV 格式、589507/589517、留痕写入。
|
||||
|
||||
**全模块** `mvn -pl hl-order-service-v3 -am test`(2026-09-01,分支合并至 dev-v3 后):Tests run 7872 / Failures 3 / Errors 0 / Skipped 7——3 个失败均为 refund 模块**既有**切片用例(旧契约期望 200+code401 信封,当前拦截器直返 401),已在独立基线 worktree(dev-v3 不含本次改动)复现,与本次 9 个文件变更无关。
|
||||
|
||||
真实接口网关验证(2026-09-01 部署 dev-v3@9f689354e 后实测):
|
||||
|
||||
```
|
||||
GET /v3/admin/order/group-batch/summary → 200 code=200 buckets={RECRUIT:0,FORMED:13,PENDING_TRIP:0,TRAVELLING:0,AUDITING:0,CHECKED:0,DISBANDED:1} total=14 subOrderCount=1 ✓
|
||||
GET /v3/admin/order/group-batch/summary?month=2026-06&keyword=测试 → 200 code=200 ✓
|
||||
GET /v3/admin/order/group-batch/export → 200 size=1739 BOM ✓ CRLF ✓ 10 列 ✓
|
||||
GET /v3/admin/order/group-batch/export?month=2026-06 → 200 size=98(0 命中仅表头) ✓
|
||||
```
|
||||
|
||||
验证通道: 统一网关 `https://api.test.1814.love:9443`,管理员登录 token + 网关注入 `X-Admin-Id`。
|
||||
|
||||
---
|
||||
|
||||
## 九、相关历史 PR(纠错 / 功能演进时必写)
|
||||
|
||||
| PR | Issue | 说明 | 是否仍有效 |
|
||||
|----|-------|------|------------|
|
||||
| #6917 | #6904 | 团期看板统计条 + 导出交付(本单) | ✅ 最新 |
|
||||
| #6914 | #6902 | 共享地基(七桶枚举/聚合/权限/错误码) | ✅ 有效 |
|
||||
| #6916 | #6915 | 共享地基 statusText 契约修正 | ✅ 有效 |
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 关联 Issue: [wx/HL#6904](https://git.1814.love:8443/wx/HL/issues/6904)
|
||||
- 关联 PR: [wx/HL#6917](https://git.1814.love:8443/wx/HL/pulls/6917)(合并至 dev-v3 @9f689354e)
|
||||
- 共享地基: #6902(七桶/聚合/权限)、#6915/#6916(契约修正)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#6904](https://git.1814.love:8443/wx/HL/issues/6904)
|
||||
- **PR**: #6917
|
||||
- **Merge commit**: 9f689354e
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx(GIT)
|
||||
在新工单中引用
屏蔽一个用户