hl-api-changelog/changelogs-v2/2026-07/26_4760_车务司机险自动投退保闭环与保险菜单契约-管理后台.md
2026-07-06 12:16:45 +08:00

328 行
12 KiB
Markdown

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

# 【前端对接·管理后台】车务司机险自动投退保闭环与保险菜单契约
> **Issue**: [wx/HL#4760](https://git.1814.love:8443/wx/HL/issues/4760)
> **服务**: hl-fleet-service + hl-order-service-v3 insurance Feign
> **日期**: 2026-07-06
> **影响范围**: 车务管理 / 保险菜单 / 派单确认 / 取消 / 提前完结 / 车队对账
---
## 1. 结论
- 车务保险菜单只展示司机险任务,不展示游客保险、订单保险、客户退款保险明细。
- `insurance.type=perTrip` 的司机在派单确认进入 `assigned` 后,后端按 `driverId + serviceDate` 自动一日一保投保。
- `insurance.type=annual` 的司机不会自动按单买险,但派单确认时后端会逐服务日校验年保覆盖;年保到期或覆盖不足会生成车务保险待办。
- 同一司机同一服务日只能有一份司机险;前后订单相接同日不重复买。
- 取消/提前完结后,只退该司机该服务日已无其它 active 派单占用的单日险。
- 司机险只进入车队/司机成本,不进入客户退款、订单收款、游客保险或合同保险。
- 自动投退保失败不阻断派单主流程,但必须进入车务保险任务待处理。
---
## 2. 角色与菜单
| 角色 | 行为 |
|------|------|
| `VEHICLE_MANAGER` / 车务角色 | 可访问 `/admin/fleet/insurance/**`,可查看任务、重试、标记线下完成、忽略。测试服独立账号:`fleet_mgr_4760` |
| `CUSTOMIZER` / 定制师 | 不应展示车务保险菜单;若访问 `/admin/fleet/**` 按角色守卫返回 `403`。测试服独立账号:`designer_4760` |
前端不要复用订单保险页面的数据源。车务保险菜单只调本文件接口。
---
## 3. 接口一:司机险任务/流水分页
```http
GET /admin/fleet/insurance/tasks
```
### 3.1 Query 参数
| 参数 | 类型 | 必填 | 示例 | 说明 |
|------|------|------|------|------|
| `page` | number | 否 | `1` | 页码 |
| `pageSize` | number | 否 | `20` | 每页条数 |
| `pendingOnly` | boolean | 否 | `true` | `true` 只查 `PENDING/PROCESSING`;不传查全部 |
| `taskType` | string | 否 | `PURCHASE` | `PURCHASE` 投保 / `REFUND` 退保 |
| `taskStatus` | string | 否 | `PENDING` | `PENDING/PROCESSING/SUCCESS/RESOLVED/IGNORED` |
| `driverId` | string | 否 | `188800000000000001` | 司机 ID |
| `assignmentId` | string | 否 | `194000000000000001` | 派单 ID |
| `orderId` | string | 否 | `193900000000000001` | 订单 ID |
| `serviceDateFrom` | string | 否 | `2026-07-01` | 服务日起,`yyyy-MM-dd` |
| `serviceDateTo` | string | 否 | `2026-07-31` | 服务日止,不能早于起始日 |
### 3.2 请求例子
```http
GET /admin/fleet/insurance/tasks?pendingOnly=true&taskType=PURCHASE&serviceDateFrom=2026-07-01&serviceDateTo=2026-07-31&page=1&pageSize=20
Authorization: Bearer <admin-token>
```
### 3.3 响应例子
```json
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"page": 1,
"pageSize": 20,
"total": 2,
"records": [
{
"taskId": "194100000000000001",
"bizKey": "PURCHASE:188800000000000001:2026-07-06",
"taskType": "PURCHASE",
"taskTypeLabel": "投保",
"taskStatus": "PENDING",
"taskStatusLabel": "待处理",
"driverId": "188800000000000001",
"driverName": "王师傅",
"assignmentId": "194000000000000001",
"orderId": "193900000000000001",
"orderNo": "HL20260705001",
"insuranceOrderId": null,
"policyNo": "",
"serviceDate": "2026-07-06",
"coverageStartDate": "2026-07-06",
"coverageEndDate": "2026-07-06",
"premiumAmount": "0.00",
"source": "BAOYOU",
"sourceLabel": "保游网",
"errorCode": "540007",
"errorMessage": "保险计划没有匹配该保障天数的费率",
"suggestedAction": "保险计划没有匹配该保障天数的费率,请维护费率后重试",
"retryCount": 1,
"proofUrls": [],
"handledBy": null,
"handledAt": null,
"handleRemark": "",
"createTime": "2026-07-06T09:10:00",
"updateTime": "2026-07-06T09:12:00"
},
{
"taskId": "194100000000000002",
"bizKey": "PURCHASE:188800000000000001:2026-07-07",
"taskType": "PURCHASE",
"taskTypeLabel": "投保",
"taskStatus": "SUCCESS",
"taskStatusLabel": "已成功",
"driverId": "188800000000000001",
"driverName": "王师傅",
"assignmentId": "194000000000000002",
"orderId": "193900000000000002",
"orderNo": "HL20260705002",
"insuranceOrderId": "194200000000000001",
"policyNo": "BY-20260707001",
"serviceDate": "2026-07-07",
"coverageStartDate": "2026-07-07",
"coverageEndDate": "2026-07-07",
"premiumAmount": "18.00",
"source": "BAOYOU",
"sourceLabel": "保游网",
"errorCode": "",
"errorMessage": "",
"suggestedAction": "",
"retryCount": 0,
"proofUrls": [],
"handledBy": "0",
"handledAt": "2026-07-06T09:10:05",
"handleRemark": "",
"createTime": "2026-07-06T09:10:05",
"updateTime": "2026-07-06T09:10:05"
}
]
}
}
```
### 3.4 字段说明
| 字段 | 说明 |
|------|------|
| `bizKey` | 后端幂等键,前端只展示/排查,不要拼业务逻辑 |
| `taskType` | `PURCHASE` 投保,`REFUND` 退保 |
| `taskStatus` | `PENDING` 待处理,`PROCESSING` 处理中,`SUCCESS` 线上成功,`RESOLVED` 线下解决,`IGNORED` 已忽略 |
| `premiumAmount` | 真实保费/退回金额,字符串;前端不要转 number 做精度运算 |
| `source` | `BAOYOU` 保游网,`OFFLINE` 线下处理 |
| `proofUrls` | 线下处理凭证 URL;线上成功通常为空数组 |
| `handledBy=0` | 后端自动成功流水,不是人工账号 |
---
## 4. 接口二:重试任务
```http
POST /admin/fleet/insurance/tasks/{taskId}/retry
```
### 4.1 请求例子
```http
POST /admin/fleet/insurance/tasks/194100000000000001/retry
Authorization: Bearer <admin-token>
```
无 body。
### 4.2 响应例子
```json
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"taskId": "194100000000000001",
"taskStatus": "SUCCESS",
"taskStatusLabel": "已成功"
}
}
```
### 4.3 前端处理
- 点击后按钮加 loading,接口有幂等保护,不要连续提交。
- 成功后刷新分页列表。
- 如果仍返回 `PENDING`,说明重试失败但已更新错误信息和 `retryCount`,前端展示最新 `errorMessage/suggestedAction`
---
## 5. 接口三:标记线下完成
```http
POST /admin/fleet/insurance/tasks/{taskId}/offline-done
```
### 5.1 请求参数
| 字段 | 类型 | 必填 | 示例 | 说明 |
|------|------|------|------|------|
| `policyNo` | string | 是 | `OFFLINE-20260701001` | 真实保单号 / 退保凭证号 |
| `premiumAmount` | decimal string | 是 | `"18.00"` | 真实保费金额;退保填实际退回/冲减金额 |
| `proofUrls` | string[] | 是 | `["https://...jpg"]` | 至少 1 个凭证 URL |
| `remark` | string | 否 | `保司后台线下补投` | 处理备注 |
### 5.2 请求例子
```json
{
"policyNo": "OFFLINE-20260701001",
"premiumAmount": "18.00",
"proofUrls": [
"https://oss.test.1814.love/fleet/insurance/OFFLINE-20260701001.jpg"
],
"remark": "保司后台线下补投,截图已上传"
}
```
### 5.3 响应例子
```json
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"taskId": "194100000000000001",
"taskStatus": "RESOLVED",
"taskStatusLabel": "已解决"
}
}
```
### 5.4 校验
- `policyNo` 为空:参数校验失败。
- `premiumAmount` 为空或负数:参数校验失败。
- `proofUrls` 为空数组或不传:返回 `code=400``message=线下凭证不能为空`
---
## 6. 接口四:忽略任务
```http
POST /admin/fleet/insurance/tasks/{taskId}/ignore
```
### 6.1 请求例子
```json
{
"remark": "重复人工记录,保单已由另一任务处理"
}
```
### 6.2 响应例子
```json
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"taskId": "194100000000000001",
"taskStatus": "IGNORED",
"taskStatusLabel": "已忽略"
}
}
```
---
## 7. 自动触发场景
| 场景 | 后端行为 | 前端关注 |
|------|----------|----------|
| 派单确认 `holding -> assigned` | perTrip 司机按服务日逐日投保 | 确认派单成功不代表保险一定成功,需看保险任务 |
| 派单确认时司机配置年保且服务日全部被 `annualStart~annualEnd` 覆盖 | 不买按单险,不生成待办 | 保险成本按年保摊销进车队成本 |
| 派单确认时司机配置年保但部分服务日不被覆盖 | 对未覆盖服务日生成 `PENDING` 投保待办,错误码 `540033` | 提示车务续保/修正年保起止,或改按行程险后重试 |
| 司机年保为手工/线下来源且与线上保单不一致 | 生成 `PENDING` 核对待办,错误码 `540034` | 提示上传线下凭证或修正保单信息 |
| 已有线上保单覆盖同司机同日 | 不重复购买,写 `SUCCESS` 幂等流水 | 不要提示重复投保 |
| 已有手工/年保覆盖同司机同日 | 不买按单险,写 `PENDING` 提醒核对 | 展示 `suggestedAction` |
| 派单取消 | 只退该司机该服务日无其它 active 派单占用的单日险 | 同日仍有其它订单时不会退 |
| 提前完结 | 退新 `endDate` 之后不再使用的单日险 | 定时完结通常无动作 |
| 撤销取消恢复为 assigned | 如果之前已退险,恢复后补买 | 前端刷新任务列表 |
---
## 8. 前端不要做的事
- 不要用大交通日期推导司机险保障日期;司机险保障日期以派单服务日为准。
- 不要把司机险退保结果参与客户退款计算。
- 不要把 `premiumAmount` 转成 JS number 再计算。
- 不要在前端拼重试业务参数,重试只传 `taskId`
- 不要在车务保险菜单展示订单游客险。
- 不要把 `SUCCESS` 自动流水当成待办提示;待办只看 `PENDING/PROCESSING`
- 不要只看司机保险类型是 `annual` 就认为无需处理;必须以后端任务状态为准。年保到期、行程增加一天、换司机后都会重新按服务日校验。
---
## 9. 错误码提示
| code | 场景 | 前端文案建议 |
|------|------|--------------|
| `100001` | 参数非法、任务缺上下文、服务日区间反了 | 展示后端 message |
| `100502` | 幂等保护,重复提交 | “正在处理,请勿重复提交” |
| `540007` | 保险计划缺费率 | “请维护保险计划费率后重试” |
| `540032` | 同司机服务日已有保单覆盖 | “已有保单覆盖,请核对后处理” |
| `540033` | 司机配置年保但服务日不在年保起止内 | “司机年保未覆盖当前服务日,请续保/修正年保后重试,或改按行程险处理” |
| `540034` | 手工年保/线下保单与线上覆盖信息不一致 | “请核对线下保单并上传凭证,确认后标记线下完成” |
| `605601` | 保险服务不可用 | “保险服务暂不可用,请稍后重试或线下处理” |
---
## 10. 测试要求
前端联调不要只测接口 200。至少覆盖
- 新建订单 -> 补出行人 -> 补大交通 -> 提用车需求 -> 车务派单确认 -> 保险任务生成。
- 同一司机同一服务日多订单,不重复买保险。
- 取消派单时,同司机同日还有其它 active 派单则不退保。
- 年保司机在服务期内不买按单险;服务日超过年保到期日时生成 `540033` 待办。
- 年保续保后重试 `540033` 任务,应变为 `SUCCESS`,且不能产生重复按单险。
- 投保失败后重试成功。
- 线下完成必须上传凭证。
- 定制师角色不可见车务保险菜单。