13 KiB
【前端对接·管理后台】车务派单可靠通知、取消后重派与发送状态契约
Issue: wx/HL#4933
PR: wx/HL#5073、wx/HL#5084
服务:
hl-fleet-service/hl-user-service/hl-order-service-v3/hl-gateway日期: 2026-07-19
影响范围: 派单/改派弹窗、派单详情操作记录、通知发送日志、订单详情推送记录、取消后重新派车
一、前端结论
holdMode=1的创建派单和改派现在会冻结本次通知模板与正文,并由后端异步执行可靠短信发送。- 创建 HOLD 成功只表示派单和通知意图已落库;首次响应中的
holdSentAt固定为null。只有供应商真实受理后,派单详情的currentAssignment.holdSentAt才会回显发送时间。 - 通知日志
status已从旧的少量状态扩展为0~6。前端必须展示“投递中、结果不确定、授权撤销”,不得把它们归并成发送成功或失败。 - 取消派单成功后,后端会可靠地把当前生效用车需求重新打开,允许再次派车;该过程为最终一致。前端刷新看板和详情,并以最新
canAssign/当前需求状态决定是否开放重派,不调用内部重开接口。 - 订单详情推送记录的归一化状态枚举已调整,前端需要同步新枚举。
/internal/**、/v3/internal/**均为服务间接口,经网关调用返回业务码403;任何 Web/小程序代码都不得调用。
二、前端可调用接口
| 接口 | 方法 | 路径 | 本轮变化 |
|---|---|---|---|
| 创建派单 | POST | /admin/fleet/assignments |
新增 messageTemplateId/customBody;明确 holdSentAt 语义 |
| 修改派单 | POST | /admin/fleet/assignments/{assignmentId}/change |
HOLD 改派新增 messageTemplateId/customBody |
| 派单看板详情 | GET | /admin/fleet/board/orders/{orderId} |
回显真实 holdSentAt;操作记录补齐取消/退保完整时间线 |
| 派单看板汇总 | GET | /admin/fleet/board/summary |
取消后刷新当前状态与能力字段 |
| 派单看板列表 | GET | /admin/fleet/board/orders |
取消后刷新当前状态与能力字段 |
| 通知发送日志 | GET | /admin/notification/logs |
状态扩展为 0~6,新增可靠投递审计字段 |
| 通知发送统计 | GET | /admin/notification/logs/stats |
新增跳过、投递中、不确定、撤销等统计 |
| 人工核对可靠短信 | PUT | /admin/notification/logs/{id}/resolve-reliable |
新增,仅专用权限可用 |
| 订单详情推送记录 | GET | /v3/admin/order/{id}/push-records |
归一化状态枚举调整 |
三、创建/修改 HOLD 派单
3.1 请求字段
两个写接口新增相同的可选字段:
| 字段 | 类型 | 规则 |
|---|---|---|
messageTemplateId |
string | HOLD 通知模板 ID;可空,空时使用 hold_notify 默认模板;holdMode=0 时忽略 |
customBody |
string | 本次通知自定义正文;可空,最大 4000 字符;只冻结本次内容,不回写模板 |
所有雪花 ID 继续按字符串传递和保存,禁止转为 JavaScript Number。
创建 HOLD 请求示例:
POST /admin/fleet/assignments
Content-Type: application/json
Authorization: Bearer <fleet-admin-token>
{
"orderId": "2074746808742928386",
"requirementId": "2075001000000000001",
"vehicleId": "2076001000000000001",
"driverId": "2077001000000000001",
"startDate": "2026-07-20",
"endDate": "2026-07-22",
"headcount": 4,
"holdMode": 1,
"messageTemplateId": "20260706000101",
"customBody": "王师傅您好,26-7218 团 7 月 20 日待确认。",
"fromEntry": "from-board",
"requestId": "hold-2074746808742928386-001"
}
修改为 HOLD 请求示例:
POST /admin/fleet/assignments/2078001000000000001/change
Content-Type: application/json
Authorization: Bearer <fleet-admin-token>
{
"effectiveDate": "2026-07-21",
"newVehicleId": "2076001000000000002",
"newDriverId": "2077001000000000002",
"holdMode": 1,
"messageTemplateId": "20260706000101",
"customBody": "李师傅您好,本团 7 月 21 日起调整由您服务,请确认。",
"reason": "原司机临时无法执行",
"requestId": "change-2078001000000000001-001"
}
3.2 创建响应与 holdSentAt
HOLD 创建成功响应示例:
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"id": "2078001000000000001",
"assignmentGroupId": "2078001000000000001",
"assignmentSlotId": "2078001000000000001",
"assignmentStatus": "holding",
"stageCode": "holding_wait_driver",
"stageLabel": "排车中·等待司机确认",
"currentStep": 3,
"skippedStepCodes": [],
"protocolPrice": "1300.00",
"holdSentAt": null,
"confirmedAt": null,
"sideEffects": null,
"dailyDifferences": []
}
}
前端处理规则:
code=200且assignmentStatus=holding后立即关闭重复提交入口,并刷新详情。holdSentAt=null不是接口失败,也不能显示“短信已发送”;应显示“通知处理中/等待发送结果”。- 后续读取
GET /admin/fleet/board/orders/{orderId},仅当currentAssignment.holdSentAt非空时显示真实发送时间。 - 模板缺失、供应商失败或结果不确定时,派单仍保持
holding,前端通过通知日志查看真实状态,不自行改派单状态。
四、通知发送日志状态
4.1 状态枚举
GET /admin/notification/logs 的请求筛选参数和响应字段 status 统一使用:
| status | 含义 | 前端展示建议 |
|---|---|---|
| 0 | 发送成功,供应商明确受理 | 成功 |
| 1 | 明确失败 | 失败 |
| 2 | 无收件人 | 已跳过·无收件人 |
| 3 | 无模板 | 已跳过·无模板 |
| 4 | 投递中 | 投递中 |
| 5 | 结果不确定 | 待核对 |
| 6 | 授权撤销 | 已撤销 |
前端不得把 4/5/6 计入成功或失败。状态 5 也不能自动重发,避免供应商实际已发送时重复通知司机。
单条日志新增字段:
{
"id": 2080001000000000001,
"eventCode": "FLEET_DISPATCH_CREATED",
"channel": "SMS",
"bizId": "2078001000000000001",
"bizType": "FLEET_ASSIGNMENT_HOLD",
"status": 5,
"latestProviderAttemptAt": "2026-06-19T10:00:00",
"providerSentAt": null,
"resultTime": null,
"manualResolvedAt": null,
"manualResolvedBy": null,
"manualResolutionReason": null
}
新增统计字段:
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"totalToday": 20,
"successToday": 12,
"failToday": 2,
"skippedToday": 3,
"dispatchingToday": 1,
"unknownToday": 1,
"canceledToday": 1,
"terminalAttemptToday": 14,
"successRate": 85.71,
"channelStats": []
}
}
successRate 的分母是 terminalAttemptToday = successToday + failToday,前端不要再用 totalToday 自行计算。
五、人工核对结果不确定短信
该入口只处理超过供应商 29 天查询窗口、仍为 status=5 的车务可靠短信,并要求 NOTIFICATION_RELIABLE_RESOLVE 专用权限。当前后端只授予 SUPER_ADMIN;普通管理员即使手工构造请求也会被拒绝。
确认已发送:
PUT /admin/notification/logs/2080001000000000001/resolve-reliable
Content-Type: application/json
Authorization: Bearer <super-admin-token>
{
"resolution": "SUCCESS",
"reason": "阿里云控制台发送记录核对,工单 SMS-20260719-001",
"externalMessageId": "SMS-20260719-001",
"providerSentAt": "2026-06-19T10:00:30"
}
确认未发送:
{
"resolution": "NOT_SENT",
"reason": "阿里云控制台未查到对应发送记录",
"externalMessageId": null,
"providerSentAt": null
}
成功响应:
{
"code": 200,
"message": "成功",
"success": true,
"data": null
}
处理规则:
SUCCESS必须传externalMessageId和providerSentAt;事实时间必须位于最近一次供应商尝试时间前后 5 分钟内。NOT_SENT不得传providerSentAt。- 请求返回
100001表示参数或证据时间不合法;返回100003表示无权限、日志不符合人工核对条件或状态已变化。 - 操作成功后刷新当前日志行和统计;不要在前端直接篡改状态。
六、订单详情推送记录状态
GET /v3/admin/order/{id}/push-records 的 records[].status 改为:
| status | 含义 |
|---|---|
SENT |
供应商明确受理 |
FAILED |
明确失败 |
SKIPPED_NO_RECIPIENT |
无收件人 |
SKIPPED_NO_TEMPLATE |
无模板 |
DISPATCHING |
投递中 |
UNKNOWN |
结果不确定 |
CANCELED |
授权已撤销 |
UNRECOGNIZED |
未识别的存量状态 |
响应示例:
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"total": 1,
"records": [
{
"id": 2080001000000000001,
"eventCode": "FLEET_DISPATCH_CREATED",
"channel": "SMS",
"channelName": "短信",
"kind": "sms",
"target": "王师傅",
"status": "UNKNOWN",
"statusName": "结果不确定",
"rawStatus": 5,
"failReason": null,
"bizId": "2078001000000000001",
"bizType": "FLEET_ASSIGNMENT_HOLD",
"sentAt": "2026-07-19T10:00:00"
}
],
"summary": {
"all": 1,
"sms": 1,
"miniapp": 0,
"officialAccount": 0,
"inapp": 0,
"internal": 0,
"wework": 0,
"other": 0,
"failed": 0
}
}
}
summary.failed 只统计 rawStatus=1,不包含 UNKNOWN/DISPATCHING/CANCELED。
七、取消后重新派车
前端仍调用既有接口取消:
DELETE /admin/fleet/assignments/{assignmentId}
成功后的正确流程:
- 接受取消响应中的
assignmentStatus=canceled。 - 重新请求
/admin/fleet/board/summary、/admin/fleet/board/orders和/admin/fleet/board/orders/{orderId}。 - 后端完成需求重开后,当前订单重新出现可派状态;按钮只看最新响应的
canAssign,不要本地强制改为可派。 - 如果首次刷新仍未开放重派,保持处理中并短暂重试刷新;不要调用
/v3/internal/order/**,也不要让用户重复取消。 - 重新派车成功后再次刷新服务端状态,不能沿用已取消派单的
assignmentId。
派单详情 operationLog.records[] 会保留不可变取消时间线,新增/强化的 opType 包括:
cancel_requesteddriver_notification_recordedcancel_evidence_recordedinsurance_refund_pendinginsurance_refund_succeededinsurance_refund_failedcancel_completedcancel_restoredcancel_failed
前端优先展示后端返回的 opTypeLabel、operationStatusLabel 和 summary,不要另维护中文文案。operationStatus 允许 pending/succeeded/failed。
八、网关 internal 边界
下列路径全部禁止客户端调用:
/internal
/internal/**
/v3/internal
/v3/internal/**
网关按项目协议返回 HTTP 200,但响应体为:
{
"code": 403,
"message": "接口不可访问",
"success": false,
"data": null
}
请前端全仓检查是否仍有 /v3/internal/mp/** 等历史调用;如存在,不要自行改成另一个 internal 地址,应反馈后端补正式 BFF/admin 契约。
九、前端待处理清单
- 派单/改派弹窗在 HOLD 模式支持
messageTemplateId/customBody,DIRECT 模式不提交或忽略这两个字段。 - HOLD 创建成功时把
holdSentAt=null展示为处理中,不显示“已发送”。 - 通知日志筛选、标签和统计适配
0~6状态及新增字段。 - 仅对具备专用权限的账号展示“人工核对可靠短信”入口,并实现
SUCCESS/NOT_SENT两种表单校验。 - 订单详情推送记录适配新的归一化状态枚举。
- 取消派单后刷新服务端状态,以
canAssign控制重新派车入口。 - 确认前端不存在任何
/internal/**或/v3/internal/**调用。 - 所有雪花 ID 保持字符串。
十、后端交付与测试环境状态
- 后端 PR #5073、#5084 已合并到
dev-v3。 hl-order-service-v3、hl-fleet-service、hl-gateway已按顺序部署 TEST,双实例健康;当前 OpenAPI 已公开本文全部管理端接口。- 已用真实测试订单完成 DIRECT、取消、需求重开、再次 DIRECT、司机同步和退保时间线验收。
- TEST 当前
hold_notify短信模板仍是占位配置,真实 HOLD 短信会失败关闭,holdSentAt保持null;这是环境配置阻塞,不应由前端伪造成发送成功。 - 本文件只做契约交接,不修改
hl-ui。