431 行
26 KiB
Markdown
431 行
26 KiB
Markdown
---
|
||
schema: "hl-changelog/v2"
|
||
ticket: "8603"
|
||
title: "派单原子确认响应删除恒为空的 missingPickupDates / missingDropoffDates,缺失日期只由 605914/605915 错误消息承载"
|
||
consumer: "admin"
|
||
author: "wx(GIT)"
|
||
change_type: "修改接口"
|
||
backend_status: "deployed"
|
||
gateway_status: "verified"
|
||
frontend_status: "not_required"
|
||
frontend_owner: ""
|
||
frontend_ref: ""
|
||
target_release: ""
|
||
verified_at: ""
|
||
status_note: "PR #8608 已 squash 合并 dev-v3(b4eb919b47),hl-fleet-service dev-v3 分支已滚测试服。删除的两个字段是 #8579 加的、在本端点上恒为空数组;接送机缺口在本端点是硬门禁,日期写在 605914/605915 的错误消息里。真正带「已落库但还差几天」中间态的是 POST /admin/fleet/assignments/batch 的 pickupDropoffGate 对象,该对象未动。"
|
||
updated_at: "2026-09-30"
|
||
base: "dev-v3"
|
||
---
|
||
|
||
# hl-fleet-service: 派单原子确认响应删除恒为空的接送机缺失日期字段
|
||
|
||
> **存放目录**: `changelogs-v2/2026-09/`
|
||
> **服务**: hl-fleet-service (端口 8089)
|
||
> **PR**: #8608
|
||
> **Issue**: #8603
|
||
> **日期**: 2026-09-30
|
||
> **影响范围**: 车务四步向导第③步「按需求整组原子确认」的响应体
|
||
|
||
---
|
||
|
||
## ⚠️ 关键变化
|
||
|
||
- 🔴 **`ConfirmRequirementRespVO` 删除两个字段**:`missingPickupDates`、`missingDropoffDates`。它们是 #8579 加进来的,在**本端点上恒为空数组**。
|
||
- **为什么恒空**:本端点的接送机门禁是**硬门禁**——有缺口一定在写入之前抛 605914 / 605915,缺哪几天以 `yyyy-MM-dd` 逗号分隔原样写在错误消息里。**能拿到 200 响应,就说明门禁已经通过了**,此时「缺失日期」这个概念在本端点上不存在。
|
||
- 🔴 **这两个字段的存在制造了一个不存在的中间态**:前端若按 `finalPlanPublished=false && missingPickupDates.length>0` 去渲染「确认成功但还差 N 天」,这个分支**永远不会成立**——本端点没有这种中间态。
|
||
- ✅ **「已落库但还差几天」这个中间态确实存在,但它在另一个端点上**:`POST /admin/fleet/assignments/batch`(批量创建派单)的响应里,字段挂在 **`pickupDropoffGate` 对象**下(`arrivalRequiredDates` / `departureRequiredDates` / `missingPickupDates` / `missingDropoffDates` / `declared` / `satisfied`)。**该对象本次未动,全部字段照旧**。要做「还差哪几天」的提示,读那里。
|
||
- **前端要做的事**:把本端点响应里对 `missingPickupDates` / `missingDropoffDates` 的读取删掉,改成**捕获 605914 / 605915 并把错误消息里的日期展示给用户**;若已有「差 N 天」的提示 UI,把它的数据源指向 `POST /batch` 的 `pickupDropoffGate`。
|
||
- **其余字段全部未变**:`requirementId`、`dispatchPlanGeneration`、`confirmed`、`finalPlanPublished`、`finalPlanNotPublishedReason`、`groups` 及其内部结构逐字段不变;请求体完全未变。
|
||
|
||
---
|
||
|
||
## 一、背景
|
||
|
||
车务四步向导第③步是「按当前派车方案代际原子确认全部执行段」。接送机门禁在这条路径上有**两个不同位置**的判定,二者的失败表现完全不同:
|
||
|
||
| 位置 | 时机 | 门禁不满足时 |
|
||
|------|------|--------------|
|
||
| `assertPickupDropoffCoverage` | **确认动作开始之前**(硬门禁) | 抛 605914 / 605915,**整笔不执行**,缺失日期在错误消息里 |
|
||
| 最终方案发布漏斗 | 确认已成功、准备发布 finalPlan 时 | 确认仍算成功,`finalPlanPublished=false` + `finalPlanNotPublishedReason` 给原因 |
|
||
|
||
#8579 把 `missingPickupDates` / `missingDropoffDates` 加进响应,想表达的是第二个位置的「还差几天」。但**第一个位置排在前面且是硬门禁**:门禁开启且真有缺口时,请求在第一个位置就被拦掉了,根本走不到组装响应那一步;门禁关闭时则两处都不判缺口。两条路都不会产出非空的缺失日期列表,于是这两个字段在本端点上**结构性恒为空数组**——它们不是"通常为空",是**没有任何取值路径能让它们非空**。
|
||
|
||
真正存在该中间态的是批量提交端点:那里"写入成功"与"门禁满足"确实是两件独立的事,所以 `BatchAssignmentWriteRespVO.pickupDropoffGate` 里的六个字段有实际取值。
|
||
|
||
---
|
||
|
||
## 二、变更接口清单
|
||
|
||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||
|---|------|------|------|----------|------|
|
||
| 1 | 按当前派车方案代际原子确认全部执行段 | POST | `/admin/fleet/assignments/requirements/{requirementId}/confirm` | 响应删除字段 | 删除恒为空的 `missingPickupDates` / `missingDropoffDates`;缺口由 605914/605915 承载 |
|
||
|
||
---
|
||
|
||
## 三、接口详情
|
||
|
||
### 1. 按当前派车方案代际原子确认全部执行段 `POST /admin/fleet/assignments/requirements/{requirementId}/confirm`
|
||
|
||
**VO**: `ConfirmRequirementReqVO → ConfirmRequirementRespVO`
|
||
|
||
#### 使用场景
|
||
|
||
车务四步向导第③步的整组原子确认入口。请求必须**精确列出**当前最终方案的全部有效派车组及各组是否发行程短信;服务端按 `expectedPlanGeneration` 锁定并重读完整方案,重跑最终确认基线 + 行程短信决策一致性校验 + 接送机门禁,通过后重发最终方案快照。任一组缺失、过期或通知歧义则**整笔回滚**,不产生部分 assigned、不产生部分副作用。
|
||
|
||
#### 入参
|
||
|
||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||
|------|------|------|------|------|------|
|
||
| `requirementId` | Path | Long | ✅ | - | 当前用车需求 ID |
|
||
| `orderId` | Body | Long | ✅ | `@NotNull` | 订单 ID;为空返 400「订单ID不能为空」 |
|
||
| `requestId` | Body | String | ✅ | `@NotBlank`,`@Size(max=64)` | 幂等请求标识;同一 `requestId` 用于**不同**确认内容时返 605059 |
|
||
| `expectedRequirementVersion` | Body | Integer | ✅ | `@NotNull` | 预期当前有效用车需求版本,取自 Board 读口 |
|
||
| `expectedRequirementSha256` | Body | String | ✅ | `@NotBlank`,`@Pattern("^[0-9a-f]{64}$")` | Board 返回的当前用车需求 canonical SHA-256;必须是**小写**十六进制 64 位,否则返 400 |
|
||
| `expectedPlanGeneration` | Body | Long | ✅ | `@NotNull` | 预期当前最终派车方案代际 |
|
||
| `groups` | Body | Array | ✅ | `@NotEmpty`,`@Size(max=50)` | 当前有效执行段的**精确集合**;超 50 个返 400「单次确认执行段不能超过50个」 |
|
||
| `groups[].assignmentGroupId` | Body | Long | ✅ | `@NotNull` | 当前有效派车组 ID |
|
||
| `groups[].sendItinerarySms` | Body | Boolean | ✅ | `@NotNull` | 是否向本执行段司机发送行程短信;为空返 400「请选择是否向本段司机发送行程短信」 |
|
||
|
||
#### 出参
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `requirementId` | String | 用车需求 ID(雪花 ID,JSON 中为字符串) |
|
||
| `dispatchPlanGeneration` | String | 已确认的最终派车方案代际(JSON 中为字符串) |
|
||
| `confirmed` | Boolean | 整组是否原子确认成功;返 200 时恒为 `true` |
|
||
| `finalPlanPublished` | Boolean | 本次是否真的发布了最终方案快照。`false` 表示确认已成功但订单车控仍处理中(方案未派满等) |
|
||
| `finalPlanNotPublishedReason` | String | 最终方案未发布的原因;已发布时为 `null`。取值见「六.5、枚举 / 数据字典」 |
|
||
| ~~`missingPickupDates`~~ | ~~Array~~ | 🔴 **本次删除**(#8579 加入,在本端点恒为空数组)。缺失接机日改由 605914 的错误消息承载 |
|
||
| ~~`missingDropoffDates`~~ | ~~Array~~ | 🔴 **本次删除**(同上)。缺失送机日改由 605915 的错误消息承载 |
|
||
| `groups` | Array | 各执行段确认结果 |
|
||
| `groups[].assignmentId` | String | 代表派单 ID(雪花 ID,JSON 中为字符串) |
|
||
| `groups[].assignmentGroupId` | String | 派车组 ID;历史行无该 ID 时回退下发 `assignmentId`,对任何真实行恒非空 |
|
||
| `groups[].assignmentStatus` | String | 派单状态 |
|
||
| `groups[].confirmedAt` | String | 车务最终确认时间(`yyyy-MM-dd HH:mm:ss`) |
|
||
| `groups[].sendItinerarySms` | Boolean | 本段是否选择了发送行程短信(回显请求中的选择) |
|
||
| `groups[].itinerarySmsEventId` | String | 行程短信 Outbox 事件 ID;**未发送时为 `null`** |
|
||
| `groups[].itinerarySmsStatus` | String | 行程短信状态,取值见「六.5、枚举 / 数据字典」 |
|
||
| `groups[].itineraryUrl` | String | 本段电子行程单 H5 链接;本端点组装时**恒为 `null`**,签发链接请走行程短信状态查询端点 |
|
||
|
||
#### 请求示例
|
||
|
||
```json
|
||
{
|
||
"orderId": "2099459272533323777",
|
||
"requestId": "confirm-2099459272533323777-20260930-01",
|
||
"expectedRequirementVersion": 3,
|
||
"expectedRequirementSha256": "9f2c4e1ab7d05836c41fbe2907a5d4638e1c0b7a53d92f8146ce70bb2d5a3ff4",
|
||
"expectedPlanGeneration": "12",
|
||
"groups": [
|
||
{ "assignmentGroupId": "2099461003812864001", "sendItinerarySms": true },
|
||
{ "assignmentGroupId": "2099461003812864002", "sendItinerarySms": false }
|
||
]
|
||
}
|
||
```
|
||
|
||
#### 响应示例
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "成功",
|
||
"success": true,
|
||
"data": {
|
||
"requirementId": "2099460881234567890",
|
||
"dispatchPlanGeneration": "12",
|
||
"confirmed": true,
|
||
"finalPlanPublished": true,
|
||
"finalPlanNotPublishedReason": null,
|
||
"groups": [
|
||
{
|
||
"assignmentId": "2099461003812864001",
|
||
"assignmentGroupId": "2099461003812864001",
|
||
"assignmentStatus": "assigned",
|
||
"confirmedAt": "2026-09-30 10:12:33",
|
||
"sendItinerarySms": true,
|
||
"itinerarySmsEventId": "2099461099887766554",
|
||
"itinerarySmsStatus": "PENDING",
|
||
"itineraryUrl": null
|
||
},
|
||
{
|
||
"assignmentId": "2099461003812864002",
|
||
"assignmentGroupId": "2099461003812864002",
|
||
"assignmentStatus": "assigned",
|
||
"confirmedAt": "2026-09-30 10:12:33",
|
||
"sendItinerarySms": false,
|
||
"itinerarySmsEventId": null,
|
||
"itinerarySmsStatus": "NOT_SENT",
|
||
"itineraryUrl": null
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
**注意响应里没有 `missingPickupDates` / `missingDropoffDates` 两个键**——不是值为空数组,是**键本身不存在**。
|
||
|
||
#### 空数据 / 降级响应
|
||
|
||
「确认成功但最终方案未发布」是本端点唯一的部分成功形态:`confirmed=true` + `finalPlanPublished=false` + `finalPlanNotPublishedReason` 给出原因。此时**没有缺失日期可读**:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "成功",
|
||
"success": true,
|
||
"data": {
|
||
"requirementId": "2099460881234567890",
|
||
"dispatchPlanGeneration": "12",
|
||
"confirmed": true,
|
||
"finalPlanPublished": false,
|
||
"finalPlanNotPublishedReason": "PLAN_INCOMPLETE",
|
||
"groups": [
|
||
{
|
||
"assignmentId": "2099461003812864001",
|
||
"assignmentGroupId": "2099461003812864001",
|
||
"assignmentStatus": "assigned",
|
||
"confirmedAt": "2026-09-30 10:12:33",
|
||
"sendItinerarySms": false,
|
||
"itinerarySmsEventId": null,
|
||
"itinerarySmsStatus": "NOT_SENT",
|
||
"itineraryUrl": null
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
`groups` 恒非空(请求 `@NotEmpty` 保证至少一段,且每段都要有结果)。幂等重放命中已成功回执时返回**与首次逐字段相同**的结果,含冻结在回执里的 `finalPlanPublished` 与 `finalPlanNotPublishedReason`。
|
||
|
||
#### 错误响应
|
||
|
||
接送机缺口的唯一载体(`{0}` = 缺失日期,`yyyy-MM-dd` 逗号分隔、升序):
|
||
|
||
```json
|
||
{
|
||
"code": 605914,
|
||
"message": "大交通要求接机,以下日期未配置接机车辆:2026-10-08,2026-10-09",
|
||
"success": false,
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
```json
|
||
{
|
||
"code": 605915,
|
||
"message": "大交通要求送机,以下日期未配置送机车辆:2026-10-12",
|
||
"success": false,
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
其余错误码(本次未变):
|
||
|
||
| 码 | 报文 | 触发 / 处置 |
|
||
|----|------|-------------|
|
||
| 605062 | `派车日期 {0} 越出当前{1}日期窗(版本 v{2},窗内服务日 {3}):请先调整或取消这些越窗槽位,或让定制师重新提交{1}换版后再派车` | 存在越窗在途槽位;须先调整或取消 |
|
||
| 605037 | `车辆处于维保或停用状态,不能派车:{0}` | 先改派换车 |
|
||
| 605038 | `司机处于休假或待激活状态,不能派车:{0}` | 先改派换司机 |
|
||
| 605059 | `幂等请求标识已用于不同确认内容` | 同一 `requestId` 配了不同载荷;**换新 `requestId` 重试** |
|
||
| 605063 | `原子确认回执已损坏,无法幂等重放,请联系管理员` | 🔴 **不可自愈终态**,重试同一 `requestId` 永远同码;前端**不得自动重试、不得静默轮询**,须直接提示用户联系管理员 |
|
||
|
||
#### 业务边界
|
||
|
||
- **鉴权**:`AssignmentController` 未挂方法级权限注解,只有网关登录态校验;未登录返 401。
|
||
- 🔴 **缺失日期只存在于错误消息里**:本端点拿到 200 就代表接送机门禁已通过,不要在响应体里找缺口字段。
|
||
- 🔴 **「还差几天」的中间态在 `POST /admin/fleet/assignments/batch`**:读其响应的 `pickupDropoffGate` 对象(含 `arrivalRequiredDates` / `departureRequiredDates` / `missingPickupDates` / `missingDropoffDates` / `declared` / `satisfied`),该对象本次未动。
|
||
- **门禁开关关闭时不判缺口**:接送机门禁受服务端配置开关控制;关闭时硬门禁直接放行、发布漏斗也不判门禁,所以既不会抛 605914/605915,也不会因接送机原因压住发布。这是服务端配置项,**不是请求参数,前端无法也无需感知**。
|
||
- **`GATE_UNSATISFIED` 在本端点上几乎不可达**:门禁开启且有缺口时请求在硬门禁处就被拦成 605914/605915;门禁关闭时不判。它只剩「需求身份不全」的兜底分支,而那条分支按源码注释本来就**没有任何缺失日期可言**。
|
||
- **`groups` 必须是精确集合**:少给一组、多给一组、或组已过期,整笔回滚返错,不会部分生效。
|
||
- **幂等**:以 `requestId` 为键;重放已成功的回执返回同一份结果(含冻结的发布结论),载荷变了返 605059。
|
||
- **`itineraryUrl` 在本响应中恒为 `null`**:行程单链接由行程短信状态查询端点下发。
|
||
- **失败零副作用**:所有门禁与基线校验都排在写入之前,报错时不产生部分 assigned、不产生短信事件。
|
||
|
||
---
|
||
|
||
## 四、契约约束与正确调用方式
|
||
|
||
> 本节只写后端接受 / 拒绝请求的规则与响应字段的正确读法,不写 UI 渲染建议。
|
||
|
||
### ✅ 正确 / ❌ 错误 读法对照
|
||
|
||
| 目的 | ✅ 正确做法 | ❌ 错误做法 |
|
||
|------|-------------|-------------|
|
||
| 判断「接送机缺哪几天」 | 捕获 605914 / 605915,从 `message` 里取日期(`yyyy-MM-dd` 逗号分隔) | 读本端点响应的 `missingPickupDates` / `missingDropoffDates`——**这两个键已不存在** |
|
||
| 渲染「已落库但还差 N 天」 | 读 `POST /admin/fleet/assignments/batch` 响应的 `data.pickupDropoffGate.missingPickupDates` / `.missingDropoffDates` | 在本端点响应上拼这个中间态——本端点没有该中间态 |
|
||
| 判断「确认成功了吗」 | 看 HTTP 层 `code=200` + `data.confirmed` | 看 `finalPlanPublished`——它答的是另一个问题(方案有没有发布) |
|
||
| 判断「最终方案发出去了吗」 | `data.finalPlanPublished`;为 `false` 时读 `finalPlanNotPublishedReason` | 假定 `confirmed=true` 就等于已发布 |
|
||
| 撞到 605063 | 停止重试,提示用户联系管理员 | 自动重试 / 静默轮询——同一 `requestId` 永远返同码 |
|
||
| 撞到 605059 | **换一个新的 `requestId`** 重发 | 用同一个 `requestId` 重试 |
|
||
|
||
### 前端必须做的改动
|
||
|
||
1. **删掉对本端点响应 `missingPickupDates` / `missingDropoffDates` 的一切读取**(含可选链兜底、空数组判断、TS 类型定义)。
|
||
2. **接送机缺口提示改走 605914 / 605915 的错误消息**,日期在 `message` 里逐字给出。
|
||
3. 若页面上有「已落库但还差几天」的提示块,**把它的数据源改指向 `POST /admin/fleet/assignments/batch` 的 `pickupDropoffGate`**。
|
||
|
||
---
|
||
|
||
## 五、数据库行为
|
||
|
||
**本次改动不涉及任何数据库变更**:无建表、无加列、无改列、无数据迁移、无 Flyway 脚本。
|
||
|
||
端点自身的写入行为(本次未变):
|
||
|
||
| 动作 | 写入 |
|
||
|------|------|
|
||
| 整组原子确认 | 各执行段派单行 `assignment_status → assigned`、写 `confirmed_at` |
|
||
| 行程短信 | `sendItinerarySms=true` 的段写一条短信 Outbox 事件,`itinerary_sms_event_id` 回填到派单行 |
|
||
| 幂等回执 | 落一条确认回执,冻结本次结果(含 `finalPlanPublished` 与 `finalPlanNotPublishedReason`)供重放 |
|
||
| 最终方案快照 | 发布判据全部通过时冻结一次 DAILY_V3 finalPlan,由 order-v3 消费后把车控状态推进 |
|
||
|
||
**失败零写入**:接送机硬门禁、越窗门禁、基线校验全部排在写入之前;任一失败整事务回滚。
|
||
|
||
---
|
||
|
||
## 六、边界行为
|
||
|
||
- 未登录 → 401(网关拦截)。
|
||
- 请求体字段缺失 / 格式不符(`expectedRequirementSha256` 不是小写 64 位十六进制、`groups` 为空、超 50 段等)→ 400,消息即上表「约束」列所写的校验文案。
|
||
- 接送机门禁开启且缺接机日 → 605914,日期在消息里,零写入。
|
||
- 接送机门禁开启且缺送机日 → 605915,日期在消息里,零写入(接机缺口先判,两者都缺时先报 605914)。
|
||
- 接送机门禁关闭 → 不判缺口,既不抛 605914/605915,也不因接送机压住发布。
|
||
- 存在越窗在途槽位 → 605062,零写入。
|
||
- 车辆维保/停用、司机休假/待激活 → 605037 / 605038,零写入。
|
||
- 同一 `requestId` 配不同载荷 → 605059;换新 `requestId` 即可。
|
||
- 回执损坏 → 605063,不可自愈终态。
|
||
- 幂等重放命中成功回执 → 200,返回与首次逐字段相同的结果。
|
||
- 确认成功但方案未发布 → 200 + `confirmed=true` + `finalPlanPublished=false` + `finalPlanNotPublishedReason`,**此时无缺失日期可读**。
|
||
|
||
---
|
||
|
||
## 六.5、枚举 / 数据字典
|
||
|
||
### finalPlanNotPublishedReason(最终方案未发布原因)
|
||
|
||
**所属字段**: `data.finalPlanNotPublishedReason` | **类型**: `String` | 已发布时为 `null`
|
||
|
||
判据按固定顺序执行,**只回第一个没通过的原因**:
|
||
|
||
| 顺序 | 值 | 含义 |
|
||
|------|----|------|
|
||
| 1 | `STALE_FINALIZED_PLAN` | 存在按旧需求定稿的陈旧行,需车务对当前需求重新确认 |
|
||
| 2 | `INVALID_PLAN_GENERATION` | 当前生效行的方案代际不一致(部分已定稿、部分未定稿或代际不同) |
|
||
| 3 | `PLAN_INCOMPLETE` | 满派拓扑不完整:有逻辑 key 没派车、缺司机、在途行越窗、同 key 多行等 |
|
||
| 4 | `CAPACITY_INSUFFICIENT` | 未定稿分支上当日载客量不足以覆盖需求人数 |
|
||
| 5 | `GATE_UNSATISFIED` | 大交通要求的接/送机日没有配车。🔴 **在本端点上几乎不可达**(有缺口时硬门禁先抛 605914/605915) |
|
||
|
||
> 另有 `NO_GATE_TRANSITION` 与 `PICKUP_DROPOFF_GATE_DISABLED` 两个值,**只在接送机配置端点出现**,本端点不会返回。
|
||
|
||
### itinerarySmsStatus(行程短信状态)
|
||
|
||
**所属字段**: `data.groups[].itinerarySmsStatus` | **类型**: `String`
|
||
|
||
| 值 | 含义 |
|
||
|----|------|
|
||
| `NOT_SENT` | 本段未选择发送,或历史行没有短信事件 |
|
||
| `PENDING` | 本次已产生短信 Outbox 事件,投递中 |
|
||
| `SENT` | 短信已发出(出现在已确认段的重放回显里) |
|
||
|
||
---
|
||
|
||
## 六.6、修改前后对比
|
||
|
||
### 字段级对比
|
||
|
||
| 字段 | 改前 | 改后 |
|
||
|------|------|------|
|
||
| `data.missingPickupDates` | `Array<String>`,**恒为空数组 `[]`** | 🔴 **键已删除,响应中不存在** |
|
||
| `data.missingDropoffDates` | `Array<String>`,**恒为空数组 `[]`** | 🔴 **键已删除,响应中不存在** |
|
||
| `data.requirementId` | String | 未变 |
|
||
| `data.dispatchPlanGeneration` | String | 未变 |
|
||
| `data.confirmed` | Boolean | 未变 |
|
||
| `data.finalPlanPublished` | Boolean | 未变 |
|
||
| `data.finalPlanNotPublishedReason` | String / null | 未变 |
|
||
| `data.groups[*]` 全部字段 | 8 个字段 | 未变 |
|
||
| 请求体全部字段 | — | 未变 |
|
||
| 错误码集合 | 605062 / 605914 / 605915 / 605037 / 605038 / 605059 / 605063 | 未变 |
|
||
| `BatchAssignmentWriteRespVO.pickupDropoffGate` | 6 个字段 | **未变**(缺失日期的正确来源) |
|
||
|
||
### 行为级对比
|
||
|
||
| 行为 | 改前 | 改后 |
|
||
|------|------|------|
|
||
| 接送机有缺口 + 门禁开启 | 抛 605914/605915(响应根本到不了组装步) | 未变 |
|
||
| 接送机门禁通过、拿到 200 | 响应带两个**恒为空**的日期数组 | 响应**不含**这两个键 |
|
||
| 前端按 `missingPickupDates.length > 0` 判缺口 | 永远为 `false`,分支不可达 | 该字段不存在;改捕获 605914/605915 |
|
||
| 「已落库但还差几天」的读法 | 本端点读不到(恒空),实际在 `POST /batch` | 未变,仍在 `POST /batch` 的 `pickupDropoffGate` |
|
||
|
||
---
|
||
|
||
## 六.7、影响评估
|
||
|
||
- **是否破坏向后兼容**: **是(响应删字段)**。但删的是**在本端点恒为空数组**的两个字段,任何依赖它们做判断的前端分支在改前也永远不成立——即行为上前端看不到差异,看得到差异的是**读取代码本身**(可选链失效 / TS 类型不匹配 / 空数组默认值)。
|
||
- **前端是否必须同步上线**: **建议同步**。JS 里读不存在的键得 `undefined`,若代码写的是 `resp.data.missingPickupDates.length` 会抛 TypeError;写成 `?.length` 或有默认值则不报错。TS 侧需删掉类型声明里的这两个字段。
|
||
- **前端 workaround 清理点**: 如果曾为「这两个字段总是空」做过兜底(写死不展示、或转去读别的来源),可以连同兜底一起清掉,直接按 605914/605915 + `POST /batch` 的 `pickupDropoffGate` 这两条正路走。
|
||
- **联调注意**: 缺口提示的数据源从此分两处——**硬门禁报错**(本端点,错误消息)与**中间态展示**(`POST /batch`,`pickupDropoffGate` 对象),不要把两者混为一处。
|
||
|
||
---
|
||
|
||
## 七、不影响范围
|
||
|
||
- **仅影响**: `POST /admin/fleet/assignments/requirements/{requirementId}/confirm` 的响应体字段集合。
|
||
- **零影响**:
|
||
- `POST /admin/fleet/assignments/batch` 及其 `pickupDropoffGate` 对象(六个字段全部保留,取值逻辑未动)
|
||
- 接送机配置端点 `POST /admin/fleet/assignments/requirements/{requirementId}/pickup-dropoff`(含它专属的 `NO_GATE_TRANSITION` / `PICKUP_DROPOFF_GATE_DISABLED` 两个原因值)
|
||
- 派单创建 / 修改 / 取消 / 软清 / 一键重派推荐 / 候选查询 / 预校验
|
||
- 行程短信状态查询与受控重发
|
||
- 接送机门禁自身的判定逻辑与开关语义(**只删了响应回显,门禁一步没动**)
|
||
- 最终方案发布漏斗与 order-v3 的车控状态推进
|
||
- 数据库:无表结构或数据变更
|
||
|
||
---
|
||
|
||
## 八、测试环境已验证
|
||
|
||
- **代码事实**(对 `origin/dev-v3` 逐一查证):
|
||
- 合并提交 `b4eb919b47`(PR #8608 squash 合并进 `dev-v3`),8 文件 / +119 −55。
|
||
- `ConfirmRequirementRespVO` 当前字段集已逐字段核对:`requirementId` / `dispatchPlanGeneration` / `confirmed` / `finalPlanPublished` / `finalPlanNotPublishedReason` / `groups`,两个日期字段处留有说明注释、字段已删。
|
||
- `AssignmentController` 的 `@ApiOperation(notes=…)` 新增 6 行说明,逐行核对:缺失日期载体是错误码、`yyyy-MM-dd` 逗号分隔、中间态在 `POST /batch` 的 `pickupDropoffGate`。
|
||
- `assertPickupDropoffCoverage` 两条抛错分支(605914 接机、605915 送机,日期以 `,` join)与开关关闭时的早返回逐行核对;确认主流程里该硬门禁排在回执重放与任何写入之前。
|
||
- `BatchAssignmentWriteRespVO.pickupDropoffGate` 与 `PickupDropoffGateVO` 六字段在 `dev-v3` 上原样存在,本提交未触及这两个文件。
|
||
- `FinalPlanNotPublishedReasons` 七个常量与两份 Swagger 说明文本已核对:本端点用的是只含五个取值的通用说明。
|
||
- 回归覆盖:`AssignmentControllerTest` 断言 `$.data.missingPickupDates` / `$.data.missingDropoffDates` **不存在**;`AssignmentServicePickupDropoffTest` 新增「门禁显式开启且有缺口时抛错且日期在消息里」用例;`RequirementConfirmationReceiptServiceTest` 新增回执往返用例。
|
||
- **部署**:`hl-fleet-service` 的 `dev-v3` 分支已滚到测试服,端点走管理端网关 `/admin/fleet/**` 既有路由,无新增路由。
|
||
|
||
```
|
||
POST /admin/fleet/assignments/requirements/{requirementId}/confirm → 200,响应无 missingPickupDates / missingDropoffDates 两键 ✓
|
||
POST /admin/fleet/assignments/requirements/{requirementId}/confirm(缺接机日) → 605914,日期在 message ✓
|
||
POST /admin/fleet/assignments/batch → 200,data.pickupDropoffGate 六字段照旧 ✓
|
||
```
|
||
|
||
---
|
||
|
||
## 九、相关历史 PR
|
||
|
||
| PR | Issue | 说明 | 是否仍有效 |
|
||
|----|-------|------|------------|
|
||
| — | #7067 | 接送机门禁落地:605914/605915 + 最终方案发布漏斗 | ✅ 有效 |
|
||
| — | #8429 | 未发布原因 `finalPlanNotPublishedReason` 进响应 | ✅ 有效 |
|
||
| — | #8579 | 给确认响应加 `missingPickupDates` / `missingDropoffDates` | ❌ **已被本单撤销**(在本端点恒为空) |
|
||
| **本 PR #8608** | **#8603** | 删除上述两个恒空字段,缺口归错误码承载 | ✅ 最新 |
|
||
|
||
---
|
||
|
||
## 十、相关文档
|
||
|
||
- 关联 Issue: [wx/HL#8603](https://git.1814.love:8443/wx/HL/issues/8603)
|
||
- 关联 PR: [wx/HL#8608](https://git.1814.love:8443/wx/HL/pulls/8608)
|
||
|
||
## 关联 / 联系人
|
||
|
||
### 链接
|
||
|
||
- **Issue**: [#8603](https://git.1814.love:8443/wx/HL/issues/8603)
|
||
- **PR**: [#8608](https://git.1814.love:8443/wx/HL/pulls/8608)
|
||
- **Merge commit**: [b4eb919b47](https://git.1814.love:8443/wx/HL/commit/b4eb919b47)
|
||
|
||
### 联系人
|
||
|
||
- **后端负责人**: @wx
|