- 26_8371:DELETE /admin/fleet/assignments/{assignmentId} 命中改期残留行只取消该行、豁免 605027/605047/605028;
CancelRespVO.evidenceFileIds 改为按组累计(本次未传凭证时为空);取窗失败 605710 失败关闭。
- 26_8372:GET 团期派车总览 days[].vehicles[] 新增 groupCode,可原样回提 reconfigure 的 assignments[].groupId。
- 20_5935:旧件各【需确认】/错误说法处加 #8371 订正指针,指向新件。
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
34 KiB
schema, ticket, title, consumer, author, change_type, backend_status, gateway_status, frontend_status, frontend_owner, frontend_ref, target_release, verified_at, status_note, updated_at, base
| schema | ticket | title | consumer | author | change_type | backend_status | gateway_status | frontend_status | frontend_owner | frontend_ref | target_release | verified_at | status_note | updated_at | base |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| hl-changelog/v2 | 5935 | 改期残留清理用现有读写口即可,无需新增字段/接口 | admin | wx(GIT) | 前端缺陷 | deployed | not_required | verified | mmg | 28d4482c636dbfeba1803c64ef263f09c3656934 | hl-ui@28d4482c | 2026-09-21 | 前端 2026-09-20 反馈清单第4项引用了一个不存在的写口 POST .../slots/{slotId}/clear-residue-dates(该端点已随 #7067 去槽位化整体删除,origin/dev-v3 全仓零命中),并要求后端在读侧新增 residueServiceDates 字段。核实:读侧 cells[].rescheduleResidue 已能推导出该日期子集,写侧 DELETE /{assignmentId} 与 POST /batch 两条既有端点已覆盖单日/整批清理,本次零后端代码改动。POST /batch 对已过去的残留日期有硬门禁会拦截,DELETE 是否放行未取证,本文档已如实标注【需确认】。前端 2026-09-21 已交付:Step2 逐日栅格恢复残留格「取消残留」入口(canDelete!==false 且 assignmentId 时渲染,只读行禁用,deleteBlockReason 透传),逐日清理走 DELETE /admin/fleet/assignments/{assignmentId}(cancelReason/driverNotified/requestId 按契约显式传,requestId=createRequestId('residue-clear'));整批 POST /batch 100001「已过去或已完结」识别后提示改走逐日清理。DELETE 对过去日期是否放行(605028)仍未实证,由拦截器统一透后端 message 兜底。2026-09-26 订正(#8371):DELETE 命中改期残留行已改为单行取消,并豁免 605027/605047/605028 三道日期门,见 26_8371_改期残留行单行取消与累计凭证回显-修改接口-管理后台.md。 | 2026-09-26 | dev-v3 |
车务派单:改期残留清理用现有读写口即可(用法说明,零代码改动)
存放目录: 二期(v3) →
changelogs-v2/2026-09/服务: hl-fleet-service (端口 8082) PR: 无(零代码改动,未产生新 PR) Issue: #5935(原始功能单,2026-08-16 已上线并随 #7067 部分退役;本文档是第三次说明) 日期: 2026-09-20 影响范围: 管理后台车务排车 Step2 弹窗「改期残留清理」交互的前端实现方式;不改后端代码、不改网关路由
给前端的一句话结论
你要的能力今天就已经有:可勾选的残留日期数组不需要后端新增字段,从现有 candidates 响应的 cells[] 里按 rescheduleResidue === true 过滤即可拿到;清理动作也不需要等新接口,逐日清理用 DELETE /admin/fleet/assignments/{assignmentId}、整批清理用 POST /admin/fleet/assignments/batch(不提交残留日期项即视为清理)。但你在清单里引用的那个写口 POST .../slots/{slotId}/clear-residue-dates 不存在——它已经随 #7067 去槽位化被整体删除,请改用下面两条现成的写口。
⚠️ 关键变化
- 本次没有新增、修改或删除任何接口,后端零代码改动。
- 前端诉求引用的写口
POST .../slots/{slotId}/clear-residue-dates在origin/dev-v3全仓零命中(clear-residue-dates/clearResidueDates/cancel-residue均零命中)。AssignmentControllerTest.java:938留有明确注释:「#7067:cancel-residue 端点已随槽位概念退役删除」——「槽位(slot)」这个概念本身已经退役,基于slotId的写口不会再出现。 - 前端要求新增的读侧字段
residueServiceDates不需要新增:AssignmentCandidateRespVO.CanonicalSnapshotVO.cells[](AssignmentCandidateRespVO.java:108-150)里每个 cell 已经逐日携带rescheduleResidue(布尔)+serviceDate+assignmentId,前端自己过滤即可得到这个数组。 - ⚠️
POST /batch整批清理对"已过去的残留日期"有硬门禁会拦截(详见「四、契约约束」),前端做整批清理时必须预期这种场景下的 400。逐日清理DELETE /{assignmentId}是否放行过去日期,本轮未取证,标记为【需确认】。
2026-09-26 订正(#8371):
DELETE /{assignmentId}逐日清理残留行现已单行取消(不再整组取消),并豁免 605027/605047/605028 三道日期门。- 改期残留行判定、单行处理、错误码豁免的细节,见 26_8371_改期残留行单行取消与累计凭证回显-修改接口-管理后台.md。
一、背景
前端诉求(原文摘要,仅作背景,不作为下文技术结论的依据)
前端 2026-09-20 反馈清单第4项:「写口 POST .../slots/{slotId}/clear-residue-dates 已就绪,但前端只有 rescheduleResidue 布尔徽标,无可勾日期子集,无法实现『选槽位+勾日期』交互」,要求后端在读侧补一个 residueServiceDates 日期数组。
「前端以为的」vs「实际是」
| 维度 | 前端以为的 | 实际是(文件:行 @origin/dev-v3) |
|---|---|---|
| 写口 | POST .../slots/{slotId}/clear-residue-dates 已就绪可直接调 |
该端点不存在:clear-residue-dates/clearResidueDates/cancel-residue 全仓零命中;AssignmentControllerTest.java:938「#7067:cancel-residue 端点已随槽位概念退役删除」。「槽位」概念本身已随 #7067 去槽位化退役,请改用 DELETE /admin/fleet/assignments/{assignmentId}(逐日)或 POST /admin/fleet/assignments/batch(整批) |
| 读侧字段 | 需要后端新增 residueServiceDates: string[] 字段才能拿到可勾选日期子集 |
不需要新增:cells[] 已逐日携带 rescheduleResidue(AssignmentCandidateRespVO.java:146-147)+ serviceDate,前端 cells.filter(c => c.rescheduleResidue).map(c => c.serviceDate) 即得;editableServiceDates(AssignmentCandidateRespVO.java:90)也已把残留日期并入集合(Step2CanonicalSnapshotService.java:218-227) |
该问题的历史脉络(同一张单 #5935)
- 2026-08-16:
changelogs-v2/2026-08/16_5935_排车按日期清理改期残留-新增接口-管理后台.md记录了 当时新增 的端点POST /admin/fleet/assignments/slots/{slotId}/clear-residue-dates(commit1fe44677a,PR #5985)。 - #7067 去槽位化整体重构派单契约,
slotId概念被删除,该端点随之整体删除(该 changelog 的status_note已自我标注:"本端点...已被 #7067 去槽位化整体删除(404),无前端落地对象...残留清理语义由POST /batch整批 diff 覆盖")。 - 2026-09-20:前端仍然引用了这个已退役的端点、并额外要求新增字段——本文档是第三次澄清:不用等新接口,现有两条写口 + 现有读侧字段已完全覆盖这个场景。
二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|---|---|---|---|---|
| 1 | 查询派单候选资源(含残留标记) | POST | /admin/fleet/assignments/candidates |
复用不改 | canonicalSnapshot.cells[].rescheduleResidue 已可推导残留日期子集 |
| 2 | 取消派单(逐日清理残留) | DELETE | /admin/fleet/assignments/{assignmentId} |
复用不改 | 按 cell 的 assignmentId 单日取消,条件释放占用 |
| 3 | 批量创建派单(整批清理残留) | POST | /admin/fleet/assignments/batch |
复用不改 | 提交时不带残留日期项,后端按 diff 精确取消 |
2026-09-26 订正(#8371):上表第 2 行「按 cell 的
assignmentId单日取消」在写作当时并不成立——彼时DELETE命中改期残留行仍按整组取消(还会连带取消同组窗内的其它日期行,这正是 #8371 要修的缺陷)。#8371 已实测确认:DELETE现在对「改期残留行」做真正的单行取消,窗内其它行不受影响;非残留行仍按原整组逻辑处理。细节与实测证据见26_8371_改期残留行单行取消与累计凭证回显-修改接口-管理后台.md。
三、接口详情
1. 查询派单候选资源 POST /admin/fleet/assignments/candidates
VO: AssignmentCandidateReqVO → AssignmentCandidateRespVO
使用场景
车务打开排车/派单 Step2 弹窗时调用本端点获取 canonicalSnapshot;改期后旧日期的逐日派车切片会作为「改期残留」继续出现在 cells[] 里(Step2CanonicalSnapshotService.java:199-227:完整可编辑服务日 = 需求 serviceDates 权威集合 ∪ 在途旧行覆盖的越窗旧日期,注释原文「并入改期残留旧日期:在途行覆盖日期中越出当前需求窗的部分(含全程行展开)」)。前端从返回的 cells[] 中筛出 rescheduleResidue === true 的项,即得到「待清理的改期残留」候选日期集合,供「选槽位+勾日期」交互勾选。
本节仅列出与「识别/清理改期残留」直接相关的字段;候选资源查询的完整契约(车辆/司机分页、筛选、常驻关系、自动代入司机等)不在本文档范围。
入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| orderId | Body | String(Long) | 否(改派排除自身时必填) | - | 当前订单ID |
| requirementId | Body | String(Long) | 否(改派排除自身时必填) | - | 当前用车需求ID;requirementId 缺失或需求上下文不可用时 canonicalSnapshot 为 null(AssignmentCandidateRespVO.java:65-68) |
| startDate | Body | LocalDate | 是 | @NotNull |
查询窗口开始日 |
| endDate | Body | LocalDate | 是 | @NotNull |
查询窗口结束日 |
出参字段表
| 字段 | 类型 | 说明 |
|---|---|---|
| data.canonicalSnapshot | Object | 无 requirementId 或上下文不可用时为 null |
| data.canonicalSnapshot.editableServiceDates | LocalDate[] | 完整有序可编辑服务日集合;已并入改期残留旧日期(AssignmentCandidateRespVO.java:90;并入逻辑 Step2CanonicalSnapshotService.java:218-227) |
| data.canonicalSnapshot.cells[] | Array | 每个 RetainedGroupSet × editableServiceDates 笛卡尔积位置的唯一 cell |
| data.canonicalSnapshot.cells[].groupId | String(Long) | 稳定派车组ID(历史行无 assignmentGroupId 时回退 assignmentId) |
| data.canonicalSnapshot.cells[].serviceDate | LocalDate | 服务日期 |
| data.canonicalSnapshot.cells[].assignmentId | String(Long) | 当天逐日派车行ID;全程行/无行/不用车为 null;「取消残留派车按此ID单日取消」(AssignmentCandidateRespVO.java:124-126原文注释) |
| data.canonicalSnapshot.cells[].used | String | USED(实际用车) / UNUSED(显式不用车) / null(无切片行) |
| data.canonicalSnapshot.cells[].rescheduleResidue | Boolean | 前端要的「可勾选残留日期」判据就是这个字段:true=该 cell 服务日期越出当前需求日期窗,属改期后待清理的旧实派车,前端据此挂「改期残留/待清理」徽标(AssignmentCandidateRespVO.java:146-147) |
| data.canonicalSnapshot.cells[].canDelete | Boolean | 是否可删除/取消:改期残留旧行恒 true,只能改派或取消释放占用;受只读窗口约束时为 false(AssignmentCandidateRespVO.java:149-150原文注释) |
| data.canonicalSnapshot.cells[].readOnly | Boolean | 该 cell 是否只读;只读 cell 不可被批量操作改写 |
| data.canonicalSnapshot.cells[].readOnlyReason | String | 只读原因;可编辑时为 null |
请求示例
以下示例按 VO 字段与类型构造,非测试服抓包实测。
{
"orderId": "2096412454643802114",
"requirementId": "2096412454643812345",
"startDate": "2026-09-10",
"endDate": "2026-09-15"
}
响应示例
按
AssignmentCandidateRespVO/SnapshotCellVO字段契约与仓库内测试夹具(AssignmentControllerTest.java:143-199)构造,非测试服抓包实测;cells[1]展示一个rescheduleResidue=true的残留日期。
{
"code": 200,
"message": "成功",
"data": {
"canonicalSnapshot": {
"planGeneration": "9007199254741001",
"snapshotVersion": "1",
"retainedGroupIds": ["9007199254741002"],
"editableServiceDates": ["2026-09-10", "2026-09-11", "2026-09-08"],
"cells": [
{
"groupId": "9007199254741002",
"serviceDate": "2026-09-10",
"assignmentId": "2096412454488612900",
"used": "USED",
"rescheduleResidue": false,
"canDelete": true,
"readOnly": false,
"readOnlyReason": null
},
{
"groupId": "9007199254741002",
"serviceDate": "2026-09-08",
"assignmentId": "2096412454488612866",
"used": "USED",
"rescheduleResidue": true,
"canDelete": true,
"readOnly": false,
"readOnlyReason": null
}
]
}
},
"success": true
}
空数据 / 降级响应
requirementId 缺失或需求上下文不可用时 canonicalSnapshot 整体为 null(不是空对象),前端应据此区分「无残留」和「查不到需求上下文」两种态;没有任何改期残留时 cells[] 内所有项 rescheduleResidue 恒为 false,不会单独返回空数组表示"无残留"。
{ "code": 200, "message": "成功", "data": { "canonicalSnapshot": null }, "success": true }
错误响应
{
"code": 400,
"message": "参数非法: 用车开始日期不能为空",
"data": null,
"success": false
}
业务边界
cells[]是 RetainedGroupSet × editableServiceDates 的笛卡尔积,并非只含残留日期——必须用rescheduleResidue === true过滤,不能假设整个数组都是残留。canDelete字段的设计意图是「改期残留旧行恒 true」,但受「只读窗口」约束时会翻转为false;前端在渲染可勾选状态时应优先读canDelete,而不是只看rescheduleResidue。- 改派场景(排除自身占用)需另传
excludeAssignmentId(本文档聚焦残留清理场景,未展开该分支)。
2. 取消派单(逐日清理残留) DELETE /admin/fleet/assignments/{assignmentId}
VO: CancelReqVO → CancelRespVO
2026-09-26 订正(#8371):本小节标题「逐日清理残留」写作时是前端诉求的期望描述,当时源码并不支持——
DELETE命中改期残留行时仍按整组取消。#8371 已实测确认标题所述行为现已成立:目标行若判定为「改期残留行」,走单行取消分支,操作日志operationLog[].detailJson会带scope="RESIDUE_ROW"可供核验;非残留行不受影响,仍是整组取消。细节见26_8371_改期残留行单行取消与累计凭证回显-修改接口-管理后台.md。
使用场景
车务在 Step2 栅格里勾选一个或多个 rescheduleResidue=true 的日期后,对每个勾选日期取其 cell 的 assignmentId,逐日调用本端点单独取消,条件释放该行占用的车辆/司机。适合「只清理某几天」「部分勾选」场景。
入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| assignmentId | Path | Long | 是 | - | 取自 cells[].assignmentId(残留清理场景下即待取消的那一天) |
| cancelReason | Body | String | 是 | @NotBlank |
取消原因 |
| driverNotified | Body | Boolean | 是 | @NotNull |
是否已告知司机(人工存证;true/false 均可取消) |
| notifyNote | Body | String | 否 | - | 告知备注 |
| cutoffDate | Body | LocalDate | 否 | - | 取消生效日;服务端仅接受当天,空值按当天处理,不支持预约未来取消(CancelReqVO.java:35-37原文注释) |
| evidenceFileIds | Body | List | 否 | 最多20个 | 取消凭证文件ID列表 |
| confirmWithoutEvidence | Body | Boolean | 否 | - | 无凭证时的二次确认标记 |
| requestId | Body | String | 是 | @NotBlank,最长64 |
请求幂等ID |
出参字段表
| 字段 | 类型 | 说明 |
|---|---|---|
| data.assignmentStatus | String | canceled=已取消 |
| data.assignmentGroupId | String(Long) | 派车组ID;历史行无该字段时回退 assignmentId |
| data.assignmentSlotId | String(Long) | 稳定车辆槽位ID(历史字段,随 #7067 去槽位化不再是新契约主键) |
| data.effectiveDate | LocalDate | 实际取消生效日 |
| data.evidenceFileIds | String(Long)[] | 取消凭证文件ID列表 |
| data.confirmedWithoutEvidence | Boolean | 无凭证时是否已二次确认 |
| data.sideEffects.vehicleStatusUpdated | String | 车辆状态更新结果(idle/busy/maint;不涉及车为 null) |
| data.sideEffects.driverStatusUpdated | String | 司机状态更新结果(busy/idle;不涉及司机为 null) |
| data.sideEffects.reconPrepRowsCreated | Integer | 对账预备单创建条数(M1 降级恒 0) |
| data.sideEffects.reconPrepMarkedCanceled | Integer | 对账预备单标记取消条数(M1 降级恒 0) |
请求示例
{
"cancelReason": "改期残留清理",
"driverNotified": true,
"notifyNote": "已电话通知师傅",
"requestId": "residue-clear-2096412454488612866"
}
响应示例
按
CancelRespVO字段契约构造,非测试服抓包实测。
{
"code": 200,
"message": "成功",
"data": {
"assignmentStatus": "canceled",
"assignmentGroupId": "9007199254741002",
"assignmentSlotId": null,
"effectiveDate": "2026-09-20",
"evidenceFileIds": [],
"confirmedWithoutEvidence": null,
"sideEffects": {
"vehicleStatusUpdated": "idle",
"driverStatusUpdated": "idle",
"reconPrepRowsCreated": 0,
"reconPrepMarkedCanceled": 0
}
},
"success": true
}
空数据 / 降级响应
本端点是单条操作,无「空数据」态;命中不存在的 assignmentId 走错误响应(见下)。
{ "code": 605009, "message": "派单不存在", "data": null, "success": false }
错误响应
{
"code": 605028,
"message": "取消生效日不在派单服务日期范围内",
"data": null,
"success": false
}
其它错误码(AssignmentController.java:469-471):400(cancelReason/driverNotified/requestId 请求校验)/ 605009(派单不存在)/ 605020(当前状态不允许取消)/ 605026(无凭证未二次确认)/ 605027(已出发禁止整组取消)。
2026-09-26 订正(#8371):目标行判定为「改期残留行」时,605027(连同 605047/605028)不再触发,改走单行取消;新增 605710「订单服务不可用:{0}」——残留行判定依赖 order-v3 查询窗口数据,查询失败时整笔 fail-closed,不取消任何行。非残留行的错误码集合不变。见
26_8371_改期残留行单行取消与累计凭证回显-修改接口-管理后台.md。
业务边界
-
【需确认】本端点对"服务日期已过去的改期残留行"是否放行取消,本轮未取证:
CancelReqVO.cutoffDate注释写明「服务端仅接受当天,空值按当天处理,不支持预约未来取消」,而 605028 的语义是「取消生效日不在派单服务日期范围内」——若某残留行serviceDate是 3 天前、cutoffDate按当天处理,"当天"是否落在该行的服务日期范围内、会不会触发 605028,未经实测确认。读侧canDelete字段的设计意图是「改期残留旧行恒true,受只读窗口约束时false」(AssignmentCandidateRespVO.java:149-150),但这是响应快照上的展示态,不等于运行时 DELETE 调用本身在过去日期上必然放行。若实测发现本端点对已过去日期同样拦截,那才是真正需要后端开一张单的地方(放开残留行对过去日期的取消)。2026-09-26 订正(#8371):已实测坐实——修复前,改期残留行(服务日期已过去)确实被拦截,实测复现 605027;#8371 修复后,同一残留行
DELETE直接取消成功,不再命中 605027/605047/605028。取消后该行状态落exception(不是canceled,因取消前处于assigned/holding,与通用取消 #6152 同口径)。判定逻辑与全链路证据见26_8371_改期残留行单行取消与累计凭证回显-修改接口-管理后台.md。 -
assignmentId必须取自对应 cell(残留清理场景下即rescheduleResidue=true的那一项),传错会命中另一天的派车行。 -
driverNotified传false也允许取消,只是前端需要在未告知司机时给出强提示(后端只做存证,不代为通知)。
3. 批量创建派单(整批清理残留) POST /admin/fleet/assignments/batch
VO: BatchCreateAssignmentReqVO → BatchAssignmentWriteRespVO
使用场景
车务在 Step2 栅格里编辑好完整的新逐日配车方案后一次性提交。若不希望保留某些残留日期,只需在 dailyPlan[] 里不包含那些日期,后端按(服务日期×车辆)diff 原子重写:新方案未覆盖的现行日期行自动取消(含改期残留行)。适合"整批一次性对齐新方案"场景。
入参字段表
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| orderId | Body | String(Long) | 是 | @NotNull |
订单ID |
| requirementId | Body | String(Long) | 是 | @NotNull |
当前生效用车需求ID |
| kind | Body | String | 否 | TRAVEL/TRANSFER |
需求类别,不传按 TRAVEL |
| startDate | Body | LocalDate | 是 | @NotNull |
用车开始日期 |
| endDate | Body | LocalDate | 是 | @NotNull |
用车结束日期 |
| confirmNoVehicleServiceDates | Body | Boolean | 否 | - | 逐日计划未覆盖全部服务日期时的显式二次确认 |
| sendItinerarySms | Body | Boolean | 否 | 不传按 false | 是否向本批各车师傅发送行程短信 |
| requestId | Body | String | 是 | @NotBlank,最长64 |
批次级幂等请求标识 |
| dailyPlan[] | Body | Array | 是 | 最多4000项 | 按行程日的完整配车列表;要清理的残留日期不要出现在这个数组里 |
| dailyPlan[].serviceDate | Body | LocalDate | 是 | @NotNull |
服务日期 |
| dailyPlan[].vehicleId | Body | String(Long) | 是 | @NotNull |
车辆ID |
| dailyPlan[].driverId | Body | String(Long) | 是 | @NotNull |
司机ID |
| dailyPlan[].assignmentPrice | Body | String(BigDecimal) | 否 | ≥0 | 本车当天实际价格,不传按车型价格日历兜底 |
| dailyPlan[].priceAdjustmentReason | Body | String | 否 | 最长256 | 实际价格与参考价不一致时的调整原因 |
| items / chargeableServiceDates / vehicleFeeWaiverReason / confirmAllServiceDatesFree / holdMode | Body | - | - | 禁止携带 | #7067 已移除的旧字段,携带即 400(BatchCreateAssignmentReqVO.java:98-124) |
出参字段表
| 字段 | 类型 | 说明 |
|---|---|---|
| data.assignments[] | Array | 按 fleetItemIndex 升序返回的派单结果(历史字段名,结构见 AssignmentWriteRespVO) |
| data.finalPlanPublished | Boolean | 本次是否已发布最终方案;false 表示排车已落库但接送机未配齐 |
| data.pickupDropoffGate | Object | 接送机门禁状态(要求日与缺口日) |
| data.failedFleetItemIndex | Integer | 直接派定基线失败的车辆槽位序号 |
| data.dailyDifferences[] | Array | 直接派定基线失败的逐日差异 |
请求示例
{
"orderId": "2096412454643802114",
"requirementId": "2096412454643812345",
"startDate": "2026-09-09",
"endDate": "2026-09-15",
"requestId": "batch-residue-clear-20260920-001",
"dailyPlan": [
{ "serviceDate": "2026-09-10", "vehicleId": "2079857983403024385", "driverId": "2079857983403024387" },
{ "serviceDate": "2026-09-11", "vehicleId": "2079857983403024385", "driverId": "2079857983403024387" }
]
}
上例中 2026-09-09(残留日期)未出现在 dailyPlan[] 里,后端 diff 会把该日现行行自动取消。
响应示例
按
BatchAssignmentWriteRespVO字段契约构造,非测试服抓包实测。
{
"code": 200,
"message": "成功",
"data": {
"assignments": [],
"finalPlanPublished": true,
"pickupDropoffGate": null,
"failedFleetItemIndex": null,
"dailyDifferences": null
},
"success": true
}
空数据 / 降级响应
未配置接送机声明的订单 pickupDropoffGate 恒为 null;无接送机声明时提交即完成(finalPlanPublished=true)。
错误响应
本场景最关键的错误——残留日期已过去时,diff 判定要取消它会被"日期已过去"门禁拦下(AssignmentService.java:2775-2778 assertDailyPlanDateMutable),整个批次连同一并提交的其它合法项一起被拒绝,非部分成功:
{
"code": 100001,
"message": "参数非法: 2026-09-09已过去或已完结,不能修改逐日派车配置",
"data": null,
"success": false
}
其它已知错误码:605062(提交项的服务日期越出当前需求日期窗;已有的窗外在途行不受此码约束,改由整批 diff 精确取消,见 AssignmentServiceTest.java:1727 用例)/ 605912(派车快照缺少车型)。
业务边界
- ⚠️ 改期后延的残留日期多半已经过去,本端点无法清理它们:
assertDailyPlanDateMutable(AssignmentService.java:2775-2778)对"新方案未覆盖、因此要被 diff 自动取消"的现行行同样做日期校验,命中过去日期即整批 400。前端做"整批清理"操作前必须预判这种场景,遇到 400 应引导车务改用逐日DELETE /{assignmentId}。 - 旧字段(
items/fleetItemIndex/used/pickupParticipant等)携带即 400,不再兼容。 - 接送机不在本接口配置,走独立的
PUT /pickup-dropoff-config。 - 整批提交是原子操作:校验失败不会部分落库。
四、契约约束与正确调用方式
本节只写后端接受/拒绝 payload 的规则,不写 UI 渲染建议。
✅ 正确 / ❌ 错误 用法对照
| 场景 | 用法 |
|---|---|
| ✅ 获取可勾选残留日期 | GET candidates 响应的 cells.filter(c => c.rescheduleResidue).map(c => c.serviceDate) |
| ✅ 逐日清理(含过去日期,未取证是否放行) | DELETE /admin/fleet/assignments/{cell.assignmentId} |
| ✅ 整批清理(残留日期均未过去) | POST /batch,dailyPlan[] 不包含要清理的日期 |
| ❌ 整批清理(残留日期已过去) | 同上 payload → 400(code=100001)整批被拒,不会"跳过过去日期、只清未来的" |
| ❌ 调用文档外的写口 | POST .../slots/{slotId}/clear-residue-dates → 该路径不存在(#7067 已删除,无 slotId 概念) |
切换清理方式的必要动作
前端在实现"整批清理"按钮时,遇到 code=100001 且消息含"已过去或已完结",应识别为"残留日期已过去,本接口无法清理",引导车务改走逐日 DELETE 路径(而不是当成普通校验失败重试原样提交)。
2026-09-26 订正(#8371):上表「✅ 逐日清理(含过去日期,未取证是否放行)」与本段「引导车务改走逐日
DELETE」在写作时均未经实测;实测结果是当时同样会被拦截(改期残留行过去日期DELETE命中 605027),引导车务改走逐日DELETE在当时并不能真正解决问题。#8371 已修复:残留行现在DELETE直接取消成功,完全跳过整组门禁,上表与本段的方向自此才成立。见26_8371_改期残留行单行取消与累计凭证回显-修改接口-管理后台.md。
五、数据库行为
本次零改动;以下是既有行为,供前端理解结果落库口径:
| 清理方式 | 命中行为 |
|---|---|
DELETE /{assignmentId} |
该派车行状态置 canceled,条件释放车辆/司机占用(sideEffects 回真实反算结果) |
POST /batch(diff 自动取消未覆盖的现行行) |
assignmentMapper.cancelActiveRowsByIds(...) 批量置 canceled,cancel_source 显式留空(AssignmentService.java:2796-2802原文注释「#5924:方案重写不是取消动作,cancel_source不盖章(留NULL)」),取消原因固定写"逐日派车方案调整" |
两条路径都不会物理删除行,只做状态流转;assignmentId 一旦取消不可复用为新方案的匹配键。
2026-09-26 订正(#8371):上表「状态置
canceled」是简化写法。测试服实测:取消前处于assigned/holding的行,落库状态是exception(与通用取消 #6152 同口径),仅unassigned行取消才直接落canceled。改期残留行走单行取消分支时同样遵循这条状态机,不因残留身份而特殊化。见26_8371_改期残留行单行取消与累计凭证回显-修改接口-管理后台.md。
六、边界行为
-
未登录 → 401(网关拦截)
-
assignmentId不存在 →DELETE返 605009 -
requirementId缺失或需求上下文不可用 →candidates的canonicalSnapshot为null,不报错 -
已出发/已完结的派单 → 605027(整组取消禁止)/「该日已完结不可改派」400(
AssignmentService.java:2714)2026-09-26 订正(#8371):上条仅对非改期残留行成立。目标行判定为改期残留行时,605027/605047/605028 三道日期门全部豁免,按单行取消处理;order-v3 查询窗口失败时新增 605710 fail-closed(不取消任何行)。见
26_8371_改期残留行单行取消与累计凭证回显-修改接口-管理后台.md。 -
老数据兼容:历史行无
assignmentGroupId时,cells[].groupId回退返回assignmentId,对任何真实行恒非空
六.5、枚举 / 数据字典
used(AssignmentCandidateRespVO.SnapshotCellVO.used)
所属字段: data.canonicalSnapshot.cells[].used | 类型: String
| 值 | 中文 | 说明 |
|---|---|---|
USED |
实际用车 | 该 cell 当天有实派车辆/司机 |
UNUSED |
显式不用车 | 车务明确标记该日不配车 |
null |
无切片行(未编辑) | 该 group×日尚无任何逐日行 |
六.6、修改前后对比
本文档不涉及接口修改(三条端点均为"复用不改"),无字段级/行为级对比。
六.7、影响评估
- 是否破坏向后兼容: 否(无任何接口变更)
- 前端是否必须同步上线: 否(后端零改动,前端可按自己节奏切换实现方式)
- 前端 workaround 清理点: 若前端此前已按"等待新写口/新字段"的假设搭了占位代码或临时禁用了"整批清理"按钮,可撤——改用本文档给出的两条现成端点与现成字段即可实现完整交互
七、不影响范围
- 仅影响: 管理后台车务排车 Step2 弹窗「改期残留清理」交互的前端实现方式(用哪个端点、用哪个字段)
- 零影响:
- 后端代码(本次零改动)
- 数据库 schema
- 网关路由(三条端点均已在网关既有路由内)
POST /candidates、DELETE /{assignmentId}、POST /batch的既有契约与行为- 其它车务/排车流程(本文档只梳理"改期残留清理"这一个场景)
八、测试环境已验证
本文档不改代码,本轮未做新的测试服 HTTP 实测;以下用源码引用替代实测取证,均为 origin/dev-v3(HEAD 10efbaddf,与本地工作树一致)实际存在的代码:
AssignmentCandidateRespVO.java:90 editableServiceDates 字段存在 ✓
AssignmentCandidateRespVO.java:108-150 SnapshotCellVO 含 rescheduleResidue/canDelete/assignmentId ✓
Step2CanonicalSnapshotService.java:218-227 残留日期并入 editableServiceDates 的逻辑 ✓
AssignmentController.java:75 类级路由 /admin/fleet/assignments ✓
AssignmentController.java:201-234 POST /batch 端点存在 ✓
AssignmentController.java:462-477 DELETE /{assignmentId} 端点存在 ✓
AssignmentControllerTest.java:938 cancel-residue 端点已随 #7067 删除的注释 ✓
AssignmentService.java:2743-2744,2775-2778 diff 自动取消对过去日期的硬门禁 ✓
AssignmentServiceTest.java:1727 窗外在途行由 diff 取消而非 605062 拦整批的用例 ✓
grep clear-residue-dates / clearResidueDates / cancel-residue → 全仓零命中 ✓
【需确认】未取证项:DELETE /{assignmentId} 对服务日期已过去的改期残留行是否放行取消(涉及 605028 与只读窗口判定),需要一次真实测试服调用才能确认,本文档不代为下结论。
2026-09-26 订正(#8371):已取证,不再是【需确认】。改前基线:确实拦截,命中 605027。改后(#8371 测试服全链路 6 步实测):改期残留行判定为真时,
DELETE直接放行取消,不命中 605027/605047/605028;判定失败(order-v3 不可达)时新码 605710 fail-closed。见26_8371_改期残留行单行取消与累计凭证回显-修改接口-管理后台.md。
十、相关文档
- 原始功能单及第一版说明:
changelogs-v2/2026-08/16_5935_排车按日期清理改期残留-新增接口-管理后台.md(已在其status_note里自我标注端点已随 #7067 退役) - 退役背景:#7067 去槽位化重构(
06_7067_派单去槽位化按行程日配车-接送机独立配置-修改接口-管理后台.md) - 后续计划:若【需确认】项实测发现
DELETE对过去日期同样拦截,需另开工单放开残留行的过去日期取消
2026-09-26 订正(#8371):该「另开工单」已完成,即 #8371——已实测坐实过去拦截、已修复并验证放行。见
26_8371_改期残留行单行取消与累计凭证回显-修改接口-管理后台.md。
关联 / 联系人
链接
- Issue: #5935
- PR: 无(零代码改动,未产生新 PR)
联系人
- 后端负责人: @wx