hl-api-changelog/changelogs-v2/2026-07/68_4933_车务派单可靠通知取消重派与发送状态-修改接口-管理后台.md

13 KiB

【前端对接·管理后台】车务派单可靠通知、取消后重派与发送状态契约

Issue: wx/HL#4933

PR: wx/HL#5073wx/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": []
  }
}

前端处理规则:

  1. code=200assignmentStatus=holding 后立即关闭重复提交入口,并刷新详情。
  2. holdSentAt=null 不是接口失败,也不能显示“短信已发送”;应显示“通知处理中/等待发送结果”。
  3. 后续读取 GET /admin/fleet/board/orders/{orderId},仅当 currentAssignment.holdSentAt 非空时显示真实发送时间。
  4. 模板缺失、供应商失败或结果不确定时,派单仍保持 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 必须传 externalMessageIdproviderSentAt;事实时间必须位于最近一次供应商尝试时间前后 5 分钟内。
  • NOT_SENT 不得传 providerSentAt
  • 请求返回 100001 表示参数或证据时间不合法;返回 100003 表示无权限、日志不符合人工核对条件或状态已变化。
  • 操作成功后刷新当前日志行和统计;不要在前端直接篡改状态。

六、订单详情推送记录状态

GET /v3/admin/order/{id}/push-recordsrecords[].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}

成功后的正确流程:

  1. 接受取消响应中的 assignmentStatus=canceled
  2. 重新请求 /admin/fleet/board/summary/admin/fleet/board/orders/admin/fleet/board/orders/{orderId}
  3. 后端完成需求重开后,当前订单重新出现可派状态;按钮只看最新响应的 canAssign,不要本地强制改为可派。
  4. 如果首次刷新仍未开放重派,保持处理中并短暂重试刷新;不要调用 /v3/internal/order/**,也不要让用户重复取消。
  5. 重新派车成功后再次刷新服务端状态,不能沿用已取消派单的 assignmentId

派单详情 operationLog.records[] 会保留不可变取消时间线,新增/强化的 opType 包括:

  • cancel_requested
  • driver_notification_recorded
  • cancel_evidence_recorded
  • insurance_refund_pending
  • insurance_refund_succeeded
  • insurance_refund_failed
  • cancel_completed
  • cancel_restored
  • cancel_failed

前端优先展示后端返回的 opTypeLabeloperationStatusLabelsummary,不要另维护中文文案。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-v3hl-fleet-servicehl-gateway 已按顺序部署 TEST,双实例健康;当前 OpenAPI 已公开本文全部管理端接口。
  • 已用真实测试订单完成 DIRECT、取消、需求重开、再次 DIRECT、司机同步和退保时间线验收。
  • TEST 当前 hold_notify 短信模板仍是占位配置,真实 HOLD 短信会失败关闭,holdSentAt 保持 null;这是环境配置阻塞,不应由前端伪造成发送成功。
  • 本文件只做契约交接,不修改 hl-ui