This commit is contained in:
2026-10-19 17:56:33 +08:00
parent c116bcfe8a
commit 94bbdea386
15 changed files with 1865 additions and 4 deletions
@@ -0,0 +1,526 @@
# 排车列表接口与数据设计
> 2026-10-19 已按[正式契约](排车列表统一作业改造契约.md)第八至十节进入首批实现,替代仅准备门禁。四项业务选择已确认;公共Java契约骨架见第0节。运行端点、持久化主流程和集成尚未完成,骨架不表示已部署。仅在独占范围实现及离线验证,不提交、部署、执行环境SQL或真实HTTP;共同SQL/OpenAPI/权限由总控集成。其余尚未实现的设计文字不能覆盖正式契约。
唯一写者:排车后端 `01a1524f-4f11-70c2-8a46-8e06e73a2d86`。总控 `01a15230-a9d6-7503-a8a9-d70691df49e5`;TEST 项目、host、cwd 按协作登记 2026-10-19 节。派送设计由 `01a1524f-56a9-7320-a5fd-8ff76fa4356b` 独占[另一设计文件](排车列表派送费用接口设计.md),双方不并写。本文创建前该文件及本文均不存在,未发现本文其他写者;源代码工作区已有其他未提交改动,未覆盖。
## 0 公共Java契约骨架交接
以下文件已经实际新增,均位于 `baiyue-firstleg/lastleg-service/src/main/java/com/baiyue/lastleg/dispatch/port/`,仅依赖JDK17,无业务Entity或对方实现类。所有下游以实际record组件/端口方法为准,不以旧准备稿伪造同义DTO。Execution与Completion已进入实际实现,草稿由总控分配的唯一写者实现;当前代码/验证交接以10.3为准,公共骨架不表示整链上线。
| 文件 | SHA-256 |
| --- | --- |
| DispatchTypes.java | ecd710d123747fa28f1c39c6fbafcd58f7bbe8656717135b81c80ff0736177d2 |
| DispatchContracts.java | 1c96bc5d4e471714a5f5829a5dd264aedd78d08deaa376ad5e8a11d59e2cac5e |
| DispatchCommands.java | f487b7bf7c21aac194b3525aa706da4fa6ab8ba0852de7eed3c68282d5a8497c |
| DispatchResults.java | 2355f33a878c8aba599122b4d94fb97f88db1b27622f541f3082dadc27dc1e94 |
| DispatchViews.java | f5f8a273e693496f27075fb8d2e839ee33f6b35317e107048025dbcb96664b85 |
| DispatchDeliveryModels.java | a8452477225b71400996992896f0a8a3ed385118cf988d5c4ed7b014c4e85896 |
| DispatchExecutionPort.java | 06ab14af3879ba90a7f4e5d6b2c56a8142bbdc3068911c8eaa9336807c8c8457 |
| DispatchPlanningModels.java | 33e3512544ec49aa6d9dd332cfc2e271303519058f8aac978125d91fa3c72f8d |
| DispatchPlanningPort.java | 2ecc408b6d5adb31fab23fa677916608e4434013716bade9a88dbd2655f64b88 |
| DispatchDeliveryPort.java | 1963618f71d831482a1a8527da68467773721db980c84b5c970832b1c3dafbd6 |
| DispatchCompletionPort.java | 4a222f0d3b3a34267f7469aa0242cf62486f70496c15481838ac889b5e64a76d |
| DispatchSignalPort.java | 85b26c63d98f87abbc9ddbbcd5b18961ac32c4bb60e5cb74832997a4ff233c07 |
| DispatchApi.java | 584a973627365c4987e4fad05bb26ca8dbea538c779472f0df83b9ee81302c25 |
实际端口精确签名(类型均为同包对应容器内record):
- `DispatchExecutionPort`:`Context preview(Actor,String)`;`LoadingPreview loading(Actor,String)`;`Context lockForArrangement(Actor,String)`;`Optional<CommandResult> replay(Context,Actor,CommandIdentity)`;`void requireFreshArrangement(Context,long recordVersion)`;`CommandResult recordArrangement(Context,Actor,CommandIdentity,ArrangementFact,boolean alreadyArranged)`。lock方法只取得顺序锁及身份上下文,原命令恢复先于新命令资格;新命令必须调用fresh方法,不能以拿到Context视为已允许。
- `DispatchDeliveryPort`(派送实现):`ArrangementPreview preview(Context)`;`CommandResult arrange(Actor,String,ArrangeDelivery)`;`DepartureFacts departureFacts(Context,boolean lock)`;`DepartureResult markDeparted(Context,Actor,Instant,String commandKey)`;`NormalDeliveryFacts normalDeliveryFacts(Context)`。markDeparted仅改客户单据事实,不执行库存SHIP。
- `DispatchCompletionPort`(主流程实现):`CompletionResult reconcile(Signal)`,新事务内主阶段CAS与signal确认同提交。
- `DispatchSignalPort`(派送实现):`List<Signal> due(Instant,int)`;`void acknowledge(Signal,Instant)`(MANDATORY,同汇总事务);`void failed(Signal,Instant nextRetryAt,String errorCode)`(父事务回滚后REQUIRES_NEW);`void enqueue(String dispatchNo,String deliveryNo,long deliveryVersion,Instant occurredAt)`(原签收事务内)。due不持信号锁进入父事务;主流程唯一负责调度/退避/重扫。
字段/端点对应:所有端点常量在DispatchApi。平台POST根→DispatchCommands.Create;PUT单号→Update;POST items→AddCargo;remove/cancel→ReasonCommand;submit-review→SubmitReview;dispatch-review/loading-review→ReviewCommand;return-to-review→ReturnToReview;双端loading-complete→LoadingComplete(新增真实measurements和manifestHash,不含补录packages);双端depart→Depart;PDA items/pick→Pick;平台transfer-delivery→DispatchDeliveryModels.ArrangeDelivery。所有命令响应统一DispatchContracts.CommandResult,严格区分replayed与alreadyArranged;arrangement数组在result中,不保留单outboundNo。GET列表/详情分别为DispatchViews.Page/Detail,PDA为PdaTask,组合预览为ArrangementPreview。公开参数均recordVersion,0合法;Actor和CommandIdentity均服务端内部类型,不绑定HTTP请求体。
实际列表统计record字段采用lowerCamelCase:ReviewCounts为notSubmitted/pending/approved/rejected,Stage键仍是五项枚举。实际详情VehicleInfo为vehicle/driver两个显式mode快照,DispatchInfo/Documents/OrderDetails保持四对象;以这些实际字段取代准备稿中的展示性伪代码。数值ID沿项目Long序列化约定输出,内部ID不作业务号回退。
已运行JDK17 `javac --release 17` 编译上述全部生产骨架及 `src/test/java/com/baiyue/lastleg/dispatch/port/DispatchPortCompileContract.java`,随后离线合同输出 `PASS`,不启动Spring/HTTP/数据库。此结果只证明共享类型编译、五阶段/版本域/整车客户边界/完整单据数组及端口方法可静态引用,不证明持久化、鉴权或事务行为。Maven暂未运行,待派送写者稳定后由本任务统一执行。总控已提供路由合同5/5和Lastleg依赖合同既存9通过3失败(pickup→transfer未审阅边)的基线;本任务不修改pickup或豁免,后续分别报告新增结果。
### 0.1 骨架修订:缺版本、既有预约、实载与草稿
公开已有单/草稿写请求的recordVersion均改为Long并在record紧凑构造器拒绝null/负数,显式0合法;创建主单/草稿没有既有版本。不是仅依赖Controller @Valid或Jackson对primitive的默认0。Update.vehicleAppointment已收敛为`DispatchCommands.AppointmentReference(referenceNo,Long recordVersion)`,没有status/appointmentAt写字段,保存时必须读取权威已有预约并复验版本;读模型仍可显示实际确认状态/时间,本批不新建预约流程。没有关联提供者解锁时该写接线待总控分配,不能拼一个本地确认状态替代。
新增实际种子及端口见 `DispatchPlanningModels.java/DispatchPlanningPort.java`,源数据生产者已沿原方法核对,前端按下表接入;当前尚无新Controller映射或可调用运行端点,不以端点常量冒充实现。
| 方法/路径 | 实际请求/响应类型 | 权威来源及边界 |
| --- | --- | --- |
| GET D/candidates | CandidateQuery→CandidateList(含Candidate.sourceVersion、selection、真实客户/起运仓/来源/目的地/参考测量/批次) | 原DispatchCandidateMapper/ReturnDispatchCandidateMapper及专用换标来源;sourceVersion将由服务端以稳定sourceKey、客户/仓/目的地、当前业务资格、真实来源库存组成版本及箱数、测量依据组成规范化SHA256,选择/转换锁内重算;不存在可靠摘要时不得填0 |
| GET D/drafts;POST D/drafts | List<Draft>;CreateDraft→Draft | 当前操作者拥有的持久草稿;服务端生成token,主单和草稿版本分开、均不信任布局赋权 |
| POST D/drafts/containers;POST D/drafts/snapshot | CreateContainer/DraftSnapshot→Draft | 带draft recordVersion及原请求键,保存类型化containers/sources;不继续使用raw containersJson作另一写协议 |
| POST D/draft-locks/lock、unlock、release | DraftSelection或DraftRelease→Draft | 选择携sourceVersion,先复验仓、真实来源、实托排他,再保存;不释放其他拥有者占用 |
| DELETE D/drafts/{draftToken}/containers/{containerId} | ReasonCommand(含草稿版本及键)→Draft | 原容器/拥有者/版本下释放,禁止无版本覆盖新选择 |
| POST D/drafts/pre-arrange | ConvertDraft→ConvertedDraft | groups每项DraftVehicle(draftContainerId,Create dispatch),每组一个真实整车;全部组一个事务,失败全回滚。vehicles每项为容器ID及完整CommandResult;不按客户拆单、不保留原客户PreArrange DTO |
| GET D/{dispatchNo}/loading;GET P/{dispatchNo}/loading | LoadingPreview | 双端分别检查平台list权限或PDA角色/绑定仓;返回当前排车recordVersion、manifestHash、逐实托scheduled/picked、真实实测值/证据、缺项、confirmedLoad及loadingComplete资格。只读、不新增页签 |
| GET详情 | Detail.dispatchInfo.appointmentBatches | 读取现有AppointmentWorkDispatchPort.references的真实batchKey/batchNo/customer/sources,不新增日历预约权威模型 |
实载数据必须区分证据:`FleetTripExecutionService.loading/confirmLoad`已有load_json(manifest匹配时)是CONFIRMED_LOAD;`RelabelPalletMeasurementService.snapshots`有库存组成stockVersion的真实测量是RELABEL_MEASUREMENT,组成过期必须缺项。普通候选grossWeightKg来自订单/入仓快照按托分摊、volumeCbm来自allocated_volume_cbm,是ORDER_SNAPSHOT_ALLOCATION参考,MeasurementEvidence.actual=false,只显示suggested值,不自动填真实weightKg/volumeCbm;未知托位/卸货段也明确missingFields。LoadingComplete仅接受用户核对的measurements,不接受PDA箱数补录。候选摘要计算及这些真实服务适配尚待主流程实现,不声称GET已可调用。
`dispatch/service/DispatchCommandCodec.java`已提供按Action封闭解码:Saved/Picked/Loaded/Departed/ArrangementResult具体类型由动作决定,拒绝缺结果字段或错误动作数据;复用正式时间/Long配置,树解析使用BigDecimal保留原精度,避免实测或费用经过double。其SHA256为`0e61307658495fb05e6dfed3b7029e89d1f92ab100bae777be8f4f7ef35871f4`。`DispatchPublicJsonContract`已实际通过15类请求missing/null/negative拒绝及显式0、预约伪造字段拒绝、全部Action持久JSON跨Codec/ObjectMapper实例恢复和原键重放;`DispatchPublicContractTest`供后续Maven统一执行同一组真实合同。此处是跨实例JSON恢复证据,未声称数据库事务/实际进程重启全链验收。
本轮依赖合同重新运行仍为9通过3失败,仅输出总控已登记的3条pickup→transfer.entity及fleet出向变化,无本批新增类型边违规;不扩大到pickup修复或豁免。编译最初受到Windows命令行长度限制,已改用target内javac/java参数文件;随后发现并修复中间JsonNode导致实测BigDecimal精度/scale变化,最终合同通过。测试产物仅在target,不纳入源码。
### 0.2 严格摘要及命令账本首片实现
独占新增 `dispatch/service/DispatchCommandHasher.java`、`DispatchCommandLedger.java`、`dispatch/entity/DispatchCommandRecord.java`、`dispatch/repository/DispatchCommandRepository.java`、`dispatch/mapper/DispatchCommandMapper.java`。摘要绑定actor/action/原版本/排车/规范载荷;业务摘要排除actor/版本/键,无序货物/费用/组排序而路线stops保持顺序。账本全局查询原键后先拒绝异主体,再判断动作/版本/摘要,结果恢复用真实Codec;保存只insert,不更新旧结果,唯一键冲突抛失败让外层回滚。方法MANDATORY,Repository/Mapper分层,不接入旧按customer分片幂等。
总控SQL精确需求:`ll_dispatch_command`字段为`id BIGINT PK`、`dispatch_id BIGINT`、`dispatch_no VARCHAR(64)`、`action VARCHAR(40)`、`operator_id VARCHAR(128)`、`idempotency_key VARCHAR(255) NOT NULL UNIQUE`、`record_version BIGINT NOT NULL`(原请求版本)、`command_hash CHAR(64)`、`result_json LONGTEXT`、`created_at DATETIME(3)`(UTC);各成功记录业务字段实际均应NOT NULL,创建前不落半成品行。dispatch_id/dispatch_no在创建成功结果产生后一起落账本。索引建议dispatch_id+created_at辅助审计,不以客户/操作者拆全局唯一键;中文备注由总控与基线/统一SQL一起集成。本任务未写共享SQL,也未执行环境SQL。
新增真实 `dispatch/service/DispatchCommandLedgerPersistenceTest.java`:H2内存表+实际MyBatis Mapper/Repository保存、提交,关闭Session并换Codec/ObjectMapper实例后读取原结果;异主体/换版本/换单拒绝,以及严格摘要和安排业务摘要区别、无序组与有序路线差异均已独立编译运行PASS。测试仅覆盖此首片持久/业务身份,不表示Spring事务代理、并发主单CAS、所有客户费用回滚或完整HTTP通过;完整Maven仍待另一后端稳定后统一运行。公共13个port文件保持本次哈希,后续主单/审核/装车/发车及Controller集成尚未完成。
## 1 已核对的现状及必须收敛点
以下 Java 路径相对 `baiyue-firstleg/lastleg-service/src/main/java/com/baiyue/lastleg/`。
| 实际入口 | 当前事实 | 本批影响 |
| --- | --- | --- |
| `dispatch/service/DispatchService.java` 的 `preArrangeContainer/preArrangeDraft/requireSingleCustomerScope` | 主表 `DispatchOrder.customerId` 单客户;草稿每组也强制单客户;一组一张客户单 | 真实改为整车主单和跨客户明细,删除单客户头部推导,不能生成若干客户排车再仅在 UI 合并 |
| `fleet/trip/FleetTripPreparationService.java`、`FleetTripService.bindDispatch` | 先建客户排车,再建/选共享车次,循环绑定多张排车;调度审核落在绑定记录 | 新建排车一次完成内部车次关联,审核归整车;不让用户先去车次页 |
| `dispatch/dto/DispatchRequests.Stage` | 装车前对应 `PENDING_DEPARTURE`;装车中包含 `READY_TO_DEPART`;装车后实际映射到已发车 | 替换为五阶段真实状态,不能继续用旧映射表示装车后 |
| `FleetTripExecutionService.reviewLoading/confirmLoad` | 理货通过要求实际装载 hash;实际装载又读取拣货就绪 manifest | 理货审核改到装车前,不能保留“先拣货才允许审核”的循环;实载事实核对独立在装车完成门禁 |
| `dispatch/pda/service/DispatchPdaWorkflowService.pick` | 普通仓位依次 allocate/pick/ship;扫码成功是当前库存扣减点;逐入仓归属落在 `ll_dispatch_pda_pick_order` | 保留扣减时点和真实来源,装车完成、实际发车均不得再扣一次库存 |
| 同类 `pick/completePicking` | 最后一条扫码自动将 task 设为 `COMPLETED`、排车设为 `READY_TO_DEPART`;`complete` 无版本且仅 PDA | 拣货核平事实与用户确认装车完成分开;最后扫码只开放确认资格,后台/PDA调用同一确认服务 |
| `delivery/service/DeliveryTransferService.transferResolved` | 从主单 customer 生成一张客户出库单及按目的地拆分派送单,可在待发车/拣货中提前转派送 | 仅装车后安排,全车一次命令按客户再按目的地拆分;由派送后端实现 |
| `dispatch/fleet/DispatchFleetAdapter.completion` | 签收或真实退回核平都可满足旧车次结案 | 保留旧附加结案能力;排车五阶段的“派送完成”单独只认全部正常签收,不能复用“退回也完成”的布尔值 |
| `inboundplan/work/AppointmentWorkDispatchBinding/Mapper` | 分配事实带真实客户;但 bind 内按 dispatch 全量删除重建,部分 SQL 以 `d.customer_id` Join | 不可逐客户循环旧 bind,否则后一个客户覆盖前一个;须一次保存整车完整分配且以明细客户关联 |
上一批修复保留:取消历史与活动关联分开;已拣货/发车不得取消;普通和退货换标按整申请全部应排托盘;同托多来源按拣货归属核算外接承诺和费用;退货箱不伪造普通库存仓位。工作区 `exceptionorder/service/ExceptionOrderDispatchReturnSourceTest.java` 有其他未提交修改,本任务只读。
## 2 推荐模型:排车是真主单,Fleet 保留资源与附加能力
### 2.1 主单与资源的唯一关系
推荐继续使用 `ll_dispatch_order`,将其改为平台整车作业主单,删除 `customer_id`;不填写 0、假客户或所谓共享客户。每张整车主单自动且唯一关联一条 `ll_fleet_trip`,后者继续承载车辆司机占用、逐段容量、外接预订、成本、交接、资源结束及其历史。两个编号是不同职责的业务编号,排车列表只要求用户使用排车号;不能由前端按客户创建多张子排车后再隐藏。
保留 `ll_fleet_trip_dispatch` 作为排车与资源记录的一对一关联,删除其中 `customer_id/review_json`,保留 `dispatch_id/dispatch_no/trip_id/trip_no/package_count/bound_by/bound_at`。增加 `trip_id` 唯一约束、原 `dispatch_id` 主键及 `dispatch_no` 唯一约束;本批绑定对象只有整车主单。`tripPurpose=PICKUP` 的接货流程不是本批排车流程,冻结前核实其直接调用,不能因收敛配送车次误改接货能力。
整车新建事务内同时建立上述两条记录及关系;空单允许 Fleet 的未分配资源、未确定卸货点和未知计划时间,不通过虚构站点、计划时间或容量去调用旧 Create DTO。起运仓必须真实存在;空单没有 PDA 拣货事实、实际装载、出库单或派送单。Fleet 的 `PLANNED/IN_TRANSIT/COMPLETED` 继续只表示资源运输生命周期;排车 `stage` 是五阶段的唯一事实,排车 `status` 仅表达 ACTIVE/CANCELLED 生命周期,详见第3节。正常签收汇总不自动执行 Fleet 结案或资源释放。
### 2.2 主单字段
| 字段 | 语义与约束 |
| --- | --- |
| `id/dispatchNo` | 内部关联键/全局唯一排车业务号;不将 id 当展示编号 |
| `overseasWarehouseId/Code/Name` | 唯一起运仓及目录快照;有货物后禁止换仓;空单换仓如开放需同事务更新 Fleet,不得单改展示 |
| `status`、`stage` | status仅ACTIVE/CANCELLED;stage仅BEFORE_LOADING/LOADING/AFTER_LOADING/DELIVERING/DELIVERED。取消保留实际停止阶段,不再推进;无第六阶段、无两字段同义状态映射 |
| `recordVersion` | 主流程公共版本,所有前台可写命令必须提交并 CAS 递增;不再让 PDA 用 tripRecordVersion 代替排车版本 |
| `cargoRevision/cargoHash` | 货物集合、真实来源/箱数、目的地与有效批次分配的修订号及稳定摘要;用于审核/装载/派送快照有效性,不与每次扫码计数混用 |
| `reviewRound/reviewBasisHash` | 审核轮次与当轮关键事实摘要;摘要包括 cargo、资源身份/能力、计划/目的地及批次版本;无关备注不改变审核依据 |
| `dispatchReview/loadingReview` | 当前轮状态、决定、原因、操作者、时间、依据摘要;历史追加保存,不能覆盖旧驳回记录 |
| `vehicle/driver/carrier` | 详情读取内部 Fleet 权威分配及临时资源快照;不再允许排车、派送请求分别覆盖出两套司机车牌 |
| `plannedDepartureAt/estimatedArrivalAt` | 可空计划时间,推荐 API 统一 ISO-8601 Instant、数据库 UTC DATETIME(3);不把计划发车保存为实际发车 |
| `loadingCompletedAt/loadingCompletionHash/departedAt/deliveryCompletedAt` | 服务端实际动作时间及装车事实摘要;摘要绑定cargo修订、真实pick/归属集合和实载快照,确认命令不接受客户端伪填实际时间 |
| `itemCount/palletCount/packageCount/pickedPackageCount/shippedPackageCount` | 全车计数;托数按实物托盘去重;逐订单展示不可重复计算同一托盘 |
| `remark/createdAt/updatedAt` | 详情所需备注及时间;updatedAt 当前 DTO 缺失需补齐 |
`departAt` 旧混合语义在 API 中直接拆为 `plannedDepartureAt/departedAt`,同步消费者,不保留双字段别名。路线从起运仓和真实目的地按稳定顺序生成;请求不允许任意改路线字符串。
### 2.3 货物、客户及来源
`ll_dispatch_item` 保留真实 `customer_id>0`,保留计划托盘、实物托盘、退回入仓、换标申请及目的地字段。一次货物选择以来源键表达,客户端不提供权威客户。服务端批量解析选中来源,复验客户、仓、暂扣、可排箱数、实物及有效占用后写入;允许同车不同客户/目的地,不允许跨起运仓。
同托多来源不能用 `orderNo/orderRefs/inboundCode` 展示快照作为分摊依据。复用 `ll_dispatch_pda_pick_order` 的逐入仓/退回来源和真实箱数,不另造第二套逐箱库存模型;拣货前的容量/预约/候选读取真实库存组成,拣货后派送和费用读取不可变拣货来源。每条 pick 的客户必须等于对应 item 和真实来源客户。普通、退回入仓、`RELABEL`、`RETURN_RELABEL` 四类实际分支均保留;来源阶段不是客户端随意传入的业务状态。
原来源锁 `ll_dispatch_pallet_lock` 保留真实客户与来源;实物托盘的全局唯一占用防止两个来源别名被分配两车,PLAN 与 RECEIVED 不能成为同一实物的两个有效锁。现有 `(customer_id,active_key)` 不足以单独证明实物全局排他,建议增加可空 `active_warehouse_pallet_id` 唯一索引,仍保留来源占用键;取消只释放属于当前主单的活动锁,不碰其他新单占用。
PDA 每车一个平台作业 task,删除 task 的 customer 头部;pick 与 pick_order 继续每条真实客户。task 业务号沿正式编号生成器生成,不能继续 `DPDA + 内部ID` 展示。主流程操作/幂等归平台整车,不借客户表承载。
### 2.4 主档与临时外配
推荐车辆、司机分别使用显式联合类型,支持一个选档案、另一个临时填写:
- `vehicle: { mode: MASTER, vehicleId }` 或 `{ mode: EXTERNAL, plate, carrierName, capacity: { palletSpaces, maxLoadKg, maxVolumeCbm } }`。
- `driver: { mode: MASTER, driverId }` 或 `{ mode: EXTERNAL, name, phone }`。
MASTER 的状态、区域、能力、证照及占用由服务端主档复验;请求不能覆盖主档快照。EXTERNAL对应ID必须为空,不生成主档、不造资源ID,不进入主档ID占用锁。按已确认规则由建单人补齐本趟真实容量、证照证据及预计任务结束,调度审核核实;空单可先保存、审核前补齐,快照进入审核依据及发车历史。临时车牌与既有主档占用的交集仍须核对正式资源规则,不以mode跳过必要校验。
## 3 五阶段与审核/动作资格
| 阶段 | 当前事实及允许的正常主操作 | 推进条件 |
| --- | --- | --- |
| 装车前 `BEFORE_LOADING` | 新建/加货/移除未拣货货物、修改资源与计划、提交调度审核、调度决定、理货决定 | 有货且资源等必需事实齐备才可提交;调度通过后才可理货;理货通过原子进入装车中 |
| 装车中 `LOADING` | PDA逐托/来源拣货,后台/PDA确认装车完成 | 两审核当前轮有效;每条明细 PDA 真实箱数等于应装数量、正数、有完整来源且未超量;确认完成后进入装车后 |
| 装车后 `AFTER_LOADING` | 后台安排派送,后台/PDA实际发车 | 派送/客户出库/费用核对完整且对应当前 cargoHash;资源、审核、批次接收资格再次有效;发车一次性进入派送中 |
| 派送中 `DELIVERING` | 派送侧真实执行与正常签收、读取异常提示 | 所有当前有效派送均正常签收且逐客户逐来源数量覆盖本车实发后,自动汇总派送完成 |
| 派送完成 `DELIVERED` | 只读本次作业;附加结案/资源结束保留原权限和入口 | 不自动释放车辆/司机,不把财务入账或成本结算当成完成条件 |
审核流程字段为 `reviewState=DRAFT|DISPATCH_PENDING|DISPATCH_REJECTED|LOADING_PENDING|LOADING_REJECTED|APPROVED`,每张审核记录自己的 `status=NOT_SUBMITTED|PENDING|APPROVED|REJECTED`。正常为DRAFT→DISPATCH_PENDING→LOADING_PENDING→APPROVED;调度/理货驳回分别进入对应REJECTED状态,两种拒绝不改变五阶段,仍停装车前。驳回原因必填。修改后显式重新提交,从调度开始新轮次,不复用旧理货通过。审核变更不自动补 PDA 或装车事实。
公开主单语义只保留一套:`status=ACTIVE|CANCELLED` 表示作业单是否有效,`stage` 表示唯一实际作业阶段,`reviewState` 表示审核子流程;不能再把Fleet状态、PDA task.status或单张delivery.status写到主单status。取消令status变CANCELLED,stage保留最后真实阶段供历史查询,所有主动作关闭;不生成CANCELLED阶段。主单新建为ACTIVE+BEFORE_LOADING,正常完成为ACTIVE+DELIVERED。数据库和DTO同步替换旧PENDING_DEPARTURE/READY_TO_DEPART/DEPARTED等主单状态,不保留旧字段别名。车辆状态、审核状态等嵌套字段均使用明确对象命名。
审核有效性不以 `review.recordVersion == 当前版本` 判断:审核命令及扫码本身都会加版本,应以 `reviewRound + reviewBasisHash` 判断。货物集合/来源数量/目的地、关键资源或资格/能力、关键计划及批次变化使相关审核和装载/派送快照失效。装车前编辑直接回草稿;已进入装车后的变更策略见第 10 节,不以后台编辑偷偷回滚状态或重新扣库存。
最后一次 PDA 扫码可记录 `pickingReconciled=true`,主单仍为 LOADING;后台/PDA再调用 `loading-complete`。后台没有数量补录字段,不允许用“全车总数相等”代替逐 item/source 核平。确认装车不要求已生成派送单,避免与“装车后才能安排派送”形成循环。
服务端统一返回 `actions`,每个动作为 `{ allowed, blockCode, blockReason }`,覆盖 create/add/remove/edit/cancel/submit/dispatchReview/loadingReview/pick/loadingComplete/arrangeDelivery/depart。列表批量计算阶段/审核/派送摘要,不逐行查详情;详情返回同一资格函数。资格包括当前用户权限和仓范围,按钮隐藏不替代写入时复验。
## 4 提议 API 与 DTO(待冻结)
统一 Gateway 前缀仍是 `/api`;Admin 客户端一般调用 `/platform/v1/...`,由 http base 拼接 `/api`;PDA同理。以下列完整公网路径。全部业务拒绝沿统一 ApiResponse 约定,不把代码内 ResponseStatusException 推定为最终 HTTP 响应。
### 4.1 平台接口
令 `D=/api/platform/v1/last-leg/dispatches`。
| 方法与路径 | 请求/响应提案 | 调整 |
| --- | --- | --- |
| `GET D`、`GET D/{dispatchNo}` | Query及统计见4.5;公开status/stage按第3节唯一定义,ListRow/Detail均带recordVersion | 保留路径,替换旧三阶段;平台customerId仅以真实item筛车,不改变主单归属 |
| `GET D/candidates` | 按仓、来源类型、订单/目的仓过滤,返回真实 customer/sourceKey/availablePackages/sourceVersion/blockReasons | 保留路径,移除多客户 planIds 的单客户推断;有界批量读取 |
| `POST D` | `Create { idempotencyKey, warehouseId, vehicle?, driver?, plannedDepartureAt?, estimatedArrivalAt?, remark?, cargo[], batchAllocations[] }` → CommandResult | 新入口;cargo为空建空单,非空同事务选货建车;没有customerId/tripNo字段;未取得服务端生成号时通过原POST同键恢复 |
| `PUT D/{dispatchNo}` | `Update { recordVersion,idempotencyKey,vehicle?,driver?,plannedDepartureAt?,estimatedArrivalAt?,vehicleAppointment?,remark? }` | 保留路径;关键字段修改触发审核失效;不接收 status 或实际时间 |
| `POST D/{dispatchNo}/items` | `AddCargo { recordVersion,idempotencyKey,cargo[],batchAllocations[] }` | 新入口;可继续在空单加货;批次分配提交最终完整快照,来源锁和保存原子 |
| `POST D/{dispatchNo}/items/{itemId}/remove` | `{ recordVersion,idempotencyKey,reason? }` | 替换旧无版本 DELETE,保留删除历史与占用证据;不物理删除已执行事实 |
| `POST D/{dispatchNo}/cancel` | `{ recordVersion,idempotencyKey,reason }` | 替换旧无版本 DELETE;取消前真实未拣货、无活动派送/出库;保留原取消历史及资金作废语义 |
| `POST D/{dispatchNo}/submit-review` | `{ recordVersion,idempotencyKey,remark? }` | 新入口,建立审核轮次及当前依据 |
| `POST D/{dispatchNo}/dispatch-review` | `{ recordVersion,idempotencyKey,decision: APPROVED或REJECTED,reason? }` | 调度审核归整车;不再按客户子单调度 |
| `POST D/{dispatchNo}/loading-review` | 同上 | 理货审核在拣货前;不要求派送或实际拣货 |
| `POST D/{dispatchNo}/loading-complete` | `{ recordVersion,idempotencyKey,remark? }` → CommandResult | 后台/PDA共享服务,后台没有拣货数量字段 |
| `POST D/{dispatchNo}/depart` | 同上 → CommandResult | 新增后台入口,与PDA同一版本/命令持久化语义 |
| `GET D/{dispatchNo}/prints/bol` | 全车真实来源、单据与客户分组的PDF | 保留路径,SQL去除主单 customer 假设;只读list权限 |
| `GET D/{dispatchNo}/external-freight`、`POST D/{dispatchNo}/transfer-delivery` | 由派送设计冻结,主流程建议保留已有路径,POST带整车版本/请求键及逐客户目的地费用数据 | 只在 AFTER_LOADING 开放写;由派送后端独占,本文不定义费用具体字段 |
`CargoSelection` 建议为判别联合类型:`{ sourceType: INBOUND_PLAN,planPalletId,sourceVersion }`、`{ sourceType: RETURN_RECEIPT,returnReceiptId,sourceVersion }`、`{ sourceType: RELABEL,relabelNo,warehousePalletId,sourceVersion }`;服务端再识别 RELABEL/RETURN_RELABEL,不信任客户传入阶段。sourceVersion 用于识别选择后事实变化;若当前来源没有可靠单行版本,应定义其真实组成摘要,不能常量填0。
`batchAllocations` 保持 `planPalletId/batchKey/customerId/sourceKey/destinationKey/packageCount` 当前语义,customerId 是待复验的分配断言,不是写入客户权威。混合客户一次校验完整集合再保存。换标全申请选择门禁保留,不自动开放分批。
### 4.2 智能草稿与旧车次操作
草稿能力保留:`GET /drafts`、`POST /drafts/snapshot`、`/drafts/containers`、`/draft-locks/lock|unlock|release`。草稿及每个装车容器不再带单客户归属,锁保留真实客户。每个容器最终生成一张整车主单,跨客户是同一 cargo[],不是先按客户拆子单。正式转换建议由现有 `POST D/drafts/pre-arrange` 接收每组完整 Create 内容和持久请求键,组装车元数据可空;沿同一 create 服务实现。直接 `POST D/pre-arrange` 的生产消费者全部改用 `POST D` 后删除旧入口,不保留双建单协议。
`fleet/trips/prepare`、`/{tripNo}/dispatches/bind|unbind`、客户单调度审核旧路径不再用于配送排车主流程,直接调用者同步收敛。旧“多张排车共车”入口必须替换为给同一主单加货;不能把旧操作留为绕过新状态门禁的写入通道。车次容量/外接/成本/交接/资源结束等非该旧协议能力继续保留,进入 DELIVERY 关联主单时遵守共同锁和审核失效规则。PICKUP及独立附加能力的精确接线由总控裁定,不能笼统删整个 Controller。
### 4.3 PDA 与恢复
令 `P=/api/pda/v1/last-leg/dispatches`。`GET P`、`GET P/{dispatchNo}` 保留,返回本绑定仓整车 task,加入 `recordVersion/stage/reviews/actions/pickingReconciled`;items 保留真实来源、业务号、应装/已拣箱数。移除要求用户操作旧子单的 `tripDispatchCount`,内部 tripNo 可在附加追溯中读取,不能作为主动作请求身份。
- `POST P/{dispatchNo}/items/{itemId}/pick`:现有 positionId/packages/productQuantity/cartonMark/remark/idempotencyKey 加 `recordVersion`;客户、起运仓均从服务端明细和Token取得。
- `POST P/{dispatchNo}/loading-complete`:与平台同 `LoadingComplete` DTO;替换旧 `/complete` 的仅PDA完成协议,两端调用同服务。返回装车后阶段及动作结果。
- `POST P/{dispatchNo}/depart`:保留路径,改用整车排车 recordVersion;与平台同 Depart DTO、结果和持久命令。
- 两端均提供 `GET D或P/{dispatchNo}/commands/{idempotencyKey}`;按当前身份/动作权限/仓绑定及第6.2节授权表读取原结果。未知键返回业务错误 `DISPATCH_COMMAND_NOT_FOUND`(data为空,非成功结果),不把未找到解释成动作未执行;原POST同键同载荷可安全恢复。数据库提交中途尚不可见也可能暂未找到,客户端继续保留原键及payload,不能立即换键。跨主体键不返回原结果。
统一公开 `CommandResult { dispatchNo,action,idempotencyKey,recordVersion,status,stage,occurredAt,effect,counts,result,replayed }`:recordVersion是该命令完成时排车版本(0有效),status/stage是该时刻事实;effect为APPLIED或EXISTING;result为动作判别联合类型(CREATE/EDIT返回已保存身份及变化,PICK返回item/source核平事实,LOADING_COMPLETE返回loadingCompletionHash,DEPART返回departedAt/逐客户实发单据,ARRANGE_DELIVERY返回完整outbounds[]/deliveries[]/arrangementKey)。不得只返回布尔成功或单个outboundNo。所有平台/PDA命令及GET命令结果使用同一schema;原结果除replayed标记外不可变,禁止将后来阶段塞进旧动作快照。前端校验同一dispatchNo/action/idempotencyKey后,再GET当前详情恢复UI;旧结果证明原动作,不覆盖新详情。响应不再要求用Fleet IN_TRANSIT加客户子排车数组猜当前排车成功。
### 4.4 详情字段供四页签使用
ListRow 返回业务号、五阶段、审核和装车/派送摘要、车辆司机、起运仓、计划/实际发车、数量、版本及动作资格;不装载完整订单/附件集合。Detail补充:
- `vehicleInfo { vehicleMode,vehicleId?,plate,driverMode,driverId?,driverName,driverPhone,carrierName,vehicleStatus? }`;临时车辆没有主档状态时显示“—”,不虚构启用状态。
- `dispatchInfo { route,plannedDepartureAt,departedAt,estimatedArrivalAt,fbaAppointments[],vehicleAppointment }`。多目的仓不能只返回一个 FBA 约号;预约从有效批次读,保留未知与待复核,不用头部可编辑约号覆盖正式预约。
- `documents { dispatchNo,deliveries:[{customerId,deliveryNo,destinationKey,status}],createdAt,updatedAt,remark }`;完整返回业务单号,列表摘要批量读取。
- `orderDetails[]`:真实客户、订单号/入仓码、重量kg、托数、目的FBA仓、体积m³、集装箱号及来源键。按真实订单组成批量组装,不把同托全量重量/体积复制到每个订单;没有可信分摊则对应字段null。`containerNo`、updatedAt、remark、多派送单当前排车DTO缺失需补齐。
### 4.5 列表审核分区、分页和统计
完整Query提案:`{ page>=1,size=1..200,dispatchNo?,vehiclePlate?,warehouseId?,customerId?,fbaAppointmentNo?,status=ACTIVE|CANCELLED|ALL,stage?,beforeLoadingSection?,reviewStatus? }`。status缺省ACTIVE;ALL仅为查询选项,不是保存值/第六阶段。stage严格五项;beforeLoadingSection为`VEHICLE_INFO|DISPATCH_REVIEW|LOADING_REVIEW`,仅在status=ACTIVE且stage=BEFORE_LOADING时合法,其他组合拒绝参数错误,不能静默忽略。reviewStatus仅在某个审核分区存在时合法。
三个装车前分区保留同一整车集合和不同列投影,不能擅自将“车辆信息”解释成未提交队列并隐藏已审核单。`VEHICLE_INFO`不接reviewStatus;`DISPATCH_REVIEW`按本轮dispatchReview.status过滤;`LOADING_REVIEW`按本轮loadingReview.status过滤,均支持NOT_SUBMITTED/PENDING/APPROVED/REJECTED,未传reviewStatus时不过滤该状态。尚未提交/尚未轮到的审核是NOT_SUBMITTED,不伪造PENDING;旧轮失效通过history/invalidatedReason展示,当前轮重新待提交,不将旧APPROVED算入当前。
返回 `DispatchPage { records,total,page,size,pages,statistics,snapshotAt }`;statistics结构固定为:
```text
statistics {
matchedTotal, activeTotal, cancelledTotal,
stageCounts: { BEFORE_LOADING, LOADING, AFTER_LOADING, DELIVERING, DELIVERED },
beforeLoadingReviewCounts: {
vehicleInfoTotal,
dispatchReview: { NOT_SUBMITTED, PENDING, APPROVED, REJECTED },
loadingReview: { NOT_SUBMITTED, PENDING, APPROVED, REJECTED }
}
}
```
`total`是当前status+stage+审核筛选后的整车总数,与分页records一致;客户筛选通过EXISTS item.customerId选车,每车只计一次。statistics的共同基集只套当前用户范围及dispatchNo/vehiclePlate/warehouse/customer/正式预约号等搜索条件,排除分页、status/stage/分区/reviewStatus;matchedTotal=activeTotal+cancelledTotal,stageCounts仅ACTIVE且五项之和=activeTotal。beforeLoadingReviewCounts只统计ACTIVE+BEFORE_LOADING,vehicleInfoTotal=该阶段数;两审核状态各四项之和同样等于该阶段数,三个分区不是三份互斥货物。零项明确为0,不返回当前页长度冒充总数。
页面、数量及动作投影在同一只读一致快照事务读取,statistics不逐行查详情。stage筛选影响records/total,不影响顶部各阶段计数;审核筛选不影响同基集的审核分布。用户选择客户筛选仍是平台整车查询,行数量为全车数量;若需要仅本客户数量,必须另名customerMatchedPackages,不能把全车总量悄悄改成筛选客户量。本节是后端可实现的查询提案,分区是否默认只看待审属界面默认值,不新增业务阶段。
## 5 派送后端可读取事实和共同端口提案
端口不共享持久化Entity,不让派送方从 `DispatchOrder.customerId` 推导归属。建议在本任务 `dispatch/port/` 新增只包含 DTO/接口的契约,由总控冻结后移交派送方实现其端口;实现仍遵守各自唯一写权。所有端口同 Lastleg 本地事务,不新建HTTP内部调用。
| 端口 | 签名/事实语义 | 实现者 |
| --- | --- | --- |
| `DispatchExecutionPort.lockForArrangement(actor,dispatchNo)` | 返回Context:dispatchId/no、tripId/no、warehouse、recordVersion、cargoRevision/hash、stage、审核/装车事实、当前资源及cargo;先锁定和核对身份供命令恢复,首次写入另调requireFreshArrangement | 主流程后端;先统一锁,不能派送方先锁delivery再回头锁主单;不得在查原结果前拒绝旧版本 |
| `Context.cargo()` | 返回CargoLine:dispatchItemId、真实customer、来源与pick/attribution、退货/换标/实托、Destination、packages及批次;精确字段见实际Java | 主流程提供稳定身份;逐来源查询可由派送方沿现有DispatchCargoSourceQuery/DispatchFeeCargoMapper适配,只保留一份来源权威 |
| `DispatchExecutionPort.recordArrangement(context,actor,identity,fact,alreadyArranged)` | 首次安排CAS递增整车版本并记录公共命令,阶段仍AFTER_LOADING;已存在同业务仅记当前新键EXISTING结果,不重建单据 | 主流程后端;派送服务全部客户成功后同事务调用,字段/签名以第0节为准 |
| `DispatchDeliveryPort.departureFacts(locked,lock)` | 返回 arrangementRevision/hash、客户出库单和逐客户目的地派送单、覆盖箱数、费用核对有效性及 blockReasons;逐来源精确覆盖,不能只比总数 | 派送后端 |
| `DispatchDeliveryPort.markDeparted(locked,departedAt,commandKey)` | 所有客户出库/派送推进,返回逐客户实发事实;不扣已扫码库存、不更新排车阶段 | 派送后端,与主流程发车同事务,任一失败全车回滚 |
| `DispatchDeliveryPort.normalDeliveryFacts(context)` | 批量普通读NormalDeliveryFacts,返回全部本车派送原实发/签收/退回/异常、expectedDeliveryNos、allNormallySigned、factsHash及正常签收最大时间;不加历史来源订单锁 | 派送后端;空集合不是真,拒签/退回不得正常完成 |
| `DeliveryFactsChanged(dispatchNo,deliveryNo,deliveryVersion)` | 签收事务内记录唯一持久待核对事实,签收提交后消费/重试 | 派送后端保存事实,主流程在新事务汇总;不在原签收子锁下申请父锁 |
| `DispatchCompletionPort.reconcile(signal)` | 主流程新事务先取得trip/dispatch锁,再普通读全部已提交签收事实,CAS stage DELIVERING→DELIVERED并递增recordVersion,记录唯一汇总依据和完成时间,signal确认同事务 | 主流程后端;重复提醒无副作用,失败回滚后记录退避;写权按6.5 |
`allNormallySigned` 至少要求:当前有效派送单非空、没有缺项/取消/异常,全部有真实正常签收事实且逐客户/目的地/来源覆盖本车实发;分批签收仍未完成。原 `FleetCompletionModels` 中“正常签收或真实退回可结案”继续只服务附加结案,不能替代此端口。
本稿完成后已只读核对派送稿第6节:采纳其“签收保持本派送→来源订单锁、提交后持久提醒再汇总”的建议,以保留已有跨多趟来源完成证明并避免扩大签收锁范围。正常签收后另发退货不重开本趟完成,完成时间取本趟最后真实签收;不能只因当前派送状态后来出现退货就否认已完成的原运输。汇总不二次发布来源完成事件、回访或费用。提醒待处理记录归派送域,主流程消费/重扫归dispatch域,确切存储和调度入口仍由总控冻结。
发车事务按6.4精确链:trip根锁→原命令恢复或新命令资源锁→dispatch→全部客户outbound/delivery→task/批次→真实来源订单和业务行→单据及订单统计推进→写唯一DEPART历史、stage=DELIVERING及不可变命令结果;status保持ACTIVE。库存扣减已在PDA扫码,不在此事务再ship;资料核验可普通读,不允许适配器在锁齐派送前提前取得来源订单锁。
派送服务安排事务按6.4先取得父级及安排行、outbound/delivery锁(首次为先完成子单插入),再取需要的来源/费用锁和写Outbox,最后recordArrangement及共同命令结果同事务提交;首次命令只允许ACTIVE+AFTER_LOADING。相同目的仓不同客户不合单;逐客户目的地费用具体由派送设计定义,不由本任务猜测。
## 6 事务、版本、幂等与锁顺序
### 6.1 共同命令与两种摘要
公开写命令统一 `recordVersion`(创建无既有单版本除外),不存在公开expectedVersion。新增 `ll_dispatch_command { id,dispatch_id,dispatch_no,action,operator_id,idempotency_key,command_hash,result_json,created_at }`,`idempotency_key`平台全局唯一,operator作为不可改的所有者字段,不能用`(operator,key)`分片后让另一个操作者重建同键动作。Admin/PDA共享表、schema及当前身份权限规则;渠道不进入命令身份,同一账号从另一端恢复仍先核验该端权限/绑定仓。
- **commandHash**:绑定可信主体operatorId、动作、目标业务号、原recordVersion及规范化完整业务payload(含真实证据版本/hash);排除随机键本身和传输字段。Create无目标号时使用CREATE作用域及完整创建内容,结果绑定服务端生成业务号。对象属性排序、集合按稳定业务键排序、金额精度固定,不能因显示顺序不同误判;集合只有确认为无序时才排序。原键不能改变主体/动作/单号/原版本/任一业务字段。
- **arrangementBusinessHash**:仅安排派送有此业务去重摘要,包含dispatch身份、cargoHash、loadingCompletionHash、逐客户目的地完整组、资源快照、费用/报价/覆盖证据及全部业务字段;排除operatorId、recordVersion、idempotencyKey、客户端显示顺序等纯命令身份。另一操作者/另一键也不能创建第二套安排或费用。它不用于吞掉两次数量相同但实际不同的PDA扫码,不代替commandHash。
- `ll_dispatch_delivery_arrangement`以dispatchId唯一保存业务摘要、单据集合和原事实状态;派送域独占写,本地command与安排事实、子单、费用及Outbox同事务。首条Arrange命令的不可变原结果保存在共同命令表;不能在delivery再建立一套不同授权的命令键协议。
既有命令查验在版本/阶段门禁之前;当前权限和仓绑定校验始终在结果披露之前。新命令使用CAS及当前recordVersion;版本冲突整体回滚,不将换版本当成同一未决命令重试。原结果恢复不递增版本、不重复扣库存/发布费用。没有持久成功命令的未知结果继续以原payload重试;失败或超时不自动改键。
### 6.2 授权、恢复与不同请求键判定表
| 场景 | 权限和判断顺序 | 结果/副作用 |
| --- | --- | --- |
| 原键、原主体、原完整payload;当前阶段/版本已推进 | 先核验当前端身份、原动作权限及PDA绑定仓;再校验commandHash和业务号;先于旧版本拒绝 | 原不可变CommandResult,replayed=true;不重新执行;再GET当前详情 |
| 原键、原主体但改recordVersion/动作/单号/数量/费用等 | 当前权限后检查严格commandHash | `DISPATCH_COMMAND_CONFLICT`,不覆盖原记录、不返回“按新版本成功” |
| 原键、不同主体,即使有同等权限或同样安排业务 | 当前身份后比较命令所有者,不披露原payload/结果 | `DISPATCH_COMMAND_FORBIDDEN`;必须用自己的新键走当前事实授权流程;不复用他人命令身份 |
| 原主体同键,但权限已撤销、角色不符或PDA已换仓 | 先检查当前授权范围 | 拒绝读取/重放;历史成功不授予当前访问权 |
| 新键、原主体或另一有权限主体、同一有效安排业务 | 先核验平台安排写权限和全车范围,再校验新命令recordVersion;锁内比较全车唯一安排的businessHash及有效状态 | 不新建单据/费用;保存属于新主体/新键的严格命令及EXISTING结果,返回原完整单据集合,replayed=false;同一新键再次请求则replayed=true |
| 新键同安排业务但recordVersion过期 | 新命令不享受旧命令重放豁免 | `DISPATCH_VERSION_CONFLICT`;GET当前事实后另起明确命令,不静默替换未决payload |
| 新键同车但安排业务不同,或原安排已取消/失效 | 复验当前唯一安排及businessHash/状态 | `DISPATCH_ARRANGEMENT_CONFLICT/INACTIVE`;返回可公开的阻断原因,不重建、不重复发布;原键仍只能恢复其历史结果 |
| 新键重复装车完成/发车 | 即使载荷看似相同,也无第二次动作;有权限者可读当前事实 | `DISPATCH_ALREADY_LOADING_COMPLETED/ALREADY_DEPARTED`;不插第二执行事实。需恢复成功证明时用原键查询 |
| GET原键不存在/尚未提交可见 | 当前身份/权限/仓后查询 | `DISPATCH_COMMAND_NOT_FOUND`且无成功data;保留原payload同键POST核对,不把“未找到”等同“可重新生成” |
| GET他人键/仅有list权限但没有原写动作权限 | list仅允许读当前业务事实,不允许冒充原命令所有者 | 原结果拒绝;另有已定义审计端口时走独立审计权限,不在本次泛化开放 |
以上错误码是设计提案,由总控统一映射ApiResponse。业务错误沿项目HTTP200+body.code规范;真实认证/基础设施拒绝按现有401/403等规则,不把表内业务冲突强制解释成HTTP409。命令查询与当前详情都不会将内部ID作为业务编号。
### 6.3 本轮沿实际Java核对的锁点
以下是当前源码事实,不是新设计已证明无死锁;`→`表示同一事务中先取得/执行,括号“读”不表示行锁。
| 操作与实际方法 | 已核对顺序和边界 |
| --- | --- |
| `FleetTripService.assign` | `planned→repository.lockTrip` → `vehicles.lockById` → `DriverService.requireDispatchable→drivers.lockById` → resource reserve/release DML → `DispatchFleetAdapter.lockForAssignment`锁dispatch → 锁outbound → 锁task → assign再锁相关deliveries。资源占用`FleetResourceMapper.find`只是SELECT,不能当成FOR UPDATE;新增/删除占用由唯一键及DML串行化 |
| `FleetTripExecutionService.depart` | trip行锁 → vehicle档案锁 → occupancy普通读 → driver档案锁 → `manifests(true)`逐dispatch行锁、outbound锁、task锁,manifest末尾`DeliveryExecutionGuard.departureBlockReason(...,true)`锁deliveries → `AppointmentWorkDispatchBinding.requireDeparture`批次锁 → 真发车子操作。不可改写成“先来源再派送” |
| `DispatchPdaWorkflowService.depart`(MANDATORY,外层已持Fleet父锁) | 重入dispatch锁 → binding锁 → outbound锁 → `DeliveryExecutionGuard.requireDeparture`派送锁 → task锁 → `DispatchPickOrderAttributionService.depart`按订单数字ID锁来源订单并更新订单计数 → 转运/换标业务更新 → 来源占用释放/DML。此处没有inventory.ship;订单计数移动不等于库存移动 |
| `DispatchPdaWorkflowService.pick` | dispatch行锁 → task行锁 → binding锁 → `preparePicking`按orderId锁真实来源订单/已有退回收据 → inventory的allocate/pick/ship;每个`InventoryCommandService.execute`先查/插幂等operation(唯一键DML),再`locked`取得position行锁→balance行锁。随后写pick/归属;没有outbound/delivery行锁。RETURN_RELABEL先`prepareNativeReturn`锁来源订单,再`ReturnRelabelDispatchStock.pick`锁真实退货箱;不伪造position |
| `DeliveryTransferService.platformTransfer/transferResolved` | dispatch行锁 → 活动outbound锁/原键查询 → binding锁 → bindReceivedPallets更新item(DML锁)→ freight/coverage及费用证据校验 → 插入outbound/子单及费用/来源更新。不是天然trip父锁入口,必须接新上下文端口;安排流程不得照搬旧提前转换资格 |
| `DeliveryTransferService.cancelOutbound` | 非锁读定位 → dispatch锁(有关联时)/独立relabel锁(对应类型)→ outbound锁 → binding锁及未执行资格 → delivery集合锁 → 按业务分支的退货/转运/任务/来源更新;普通dispatch分支后续按订单ID锁tailOrders。不能从outbound锁后才请求trip锁 |
| `DispatchService.cancel` | repository.lockOrder锁dispatch → binding锁且当前requireUnbound → outbound锁 → 来源换标/退货释放及活动锁DML。新一对一主单恒有关联,须改为父级协调取消,不能保留“必须unbound”造成永远不能取消;不等于授权已拣/已装车撤销 |
| `DeliveryDocumentService.save`及`SourceDeliveryCompletionQuery.proveAll` | 显式READ_COMMITTED,先本delivery行锁;outbound以普通读判断已出库,避免反向outbound锁;随后proveAll按orderId锁相关真实来源订单,普通读历史发运证明,不锁历史delivery/dispatch/exception/inventory。不可持这些子锁再调trip/dispatch汇总 |
源码证据还表明已有逐来源订单记账方法 `LastlegOrderMapper.transferPickedToShipped` 是尾程订单picked/shipped统计移动,普通扫码调用的 `InventoryCommandService.ship` 才是仓位/库存实际扣减。新设计分别命名 `finalizeSourceOrderShipmentCounters` 与“库存SHIP事实”,不再写含糊的picked→shipped。装车完成、安排、发车只核对并消费已存在pick/attribution/SHIP流水;发车仍要按真实来源更新订单/出库/派送计数,不能因不再ship就漏掉订单统计。
### 6.4 新实现逐操作锁协议(待冻结,替换相反提案)
共同根为trip行锁,沿现有Fleet顺序固定为 **trip→必要vehicle档案→必要driver档案/占用DML→dispatch**。不能本任务保留trip→dispatch→资源而另一任务保留资源→dispatch。身份只读解析不加锁,锁trip后重读一对一关系;所有会改主单的原Fleet/后台/PDA/派送入口必须走同一根,旧直锁dispatch入口不得遗留。原键命令可在trip锁后直接授权重放,不能先要求当前司机仍可派车才允许恢复过去结果;新命令才按下列顺序取得资源及子锁并复验。
| 新操作 | 根以后实际需要的链 | 不能做的事 |
| --- | --- | --- |
| 装车前编辑/审核/资源变更 | 根→资源(涉及才取)→dispatch→binding;涉及批次/来源变更再按batchKey及真实来源ID锁,触及预建出库时先取出库/派送再取来源 | 不将审核前置查询直接解释为来源已锁;不在库存锁后改批次或派送 |
| PDA扫码 | 根→必要资源校验→dispatch→binding→task/item→真实来源订单升序→退回来源行(对应分支)→position→balance,库存命令和pick归属同事务 | 本链不请求outbound/delivery行锁,不写整车派送完成;否则会与签收的delivery→source相反 |
| 装车完成 | 根→必要资源→dispatch→binding→task/item;批次/装载快照按当前事实复验,读取既有pick/归属;若需要来源锁则按ID升序且不再申请delivery锁 | 不先造派送以满足装车门禁;不执行allocate/pick/ship |
| 安排/发车/关联出库取消 | 根→资源→dispatch→binding→整车安排行/命令检查→全部客户outbound(客户/ID排序)→全部delivery(客户/ID排序)→task/item(需锁才取)→相关预约批次→真实来源订单/转运/换标等来源行→费用来源/Outbox及DML | 所有需取得的delivery行必须在第一个来源订单锁之前取得;不在持来源锁后补锁另一张派送,不再扣库存;取消只适用已有未执行合法分支 |
| 签收资料/来源完成证明 | 本delivery→来源订单升序(保持既有显式READ_COMMITTED链)→业务完成/Outbox/待汇总信号提交 | 不取得trip/dispatch/outbound行锁;来源证明只读历史证据,不以父锁替代跨趟来源序列化 |
| 主流程汇总 | 新事务trip→dispatch(不请求资源锁)→普通读全部已提交本趟签收/异常事实→CAS主stage→acknowledge本信号→同事务提交 | 不调用SourceDeliveryCompletionQuery.proveAll、不锁来源/历史派送;资源不因汇总释放;失败阶段与ack同时回滚 |
关键交集论证:发车和签收都将需要的delivery置于来源订单之前;扫码只持来源/库存而不再申请delivery,持有来源锁的扫码最终可提交,不等待签收持有的delivery;汇总不持子锁申请父锁。仅trip共同根不能解决跨trip共享来源的反向等待,以上限定正为此而设。安排内若费用/报价/来源适配器提前锁来源,必须后移至全部delivery锁已取得(首次新建子单也先完成插入),不把当前旧调用顺序原样当新协议。
多客户统一排序:客户用已验证客户ID的数值顺序(现有API字符串只做传输),内部订单/出库/派送/仓位用Long数值顺序,batchKey按稳定字符串排序。不是把`"10"`放在`"2"`前的随意字典序。跨车资源替换应预先按资源类型/数值ID锁定本次全部旧/新主档,再执行占用变化,避免两车交换资源相互等锁;额外主档/交接入口仍需精确写权。
未证实的锁边界保留为实施门禁:预约编辑/取消/替换与来源/费用的外部入口是否会在持batch/source/fee锁后回头申请本车父锁,尚未遍历验证;对这些已列直接消费者须由总控分配后逐一核对,不允许靠一句“顺序统一”宣称全局无死锁。新增测试要覆盖跨车同来源签收/发车、两车资源交换及预约变更竞争;本轮只静态核对,未运行并发验证。
出站费用与业务事件只写本地Outbox并随事务提交,远程成功不等于本地事务原子性;资金/文件/其他服务继续正式端口协作,不跨Schema Join。
### 6.5 正常签收持久信号的唯一写者
派送任务独占 `delivery/` 下拟新增 `DeliveryFactsChangedSignal`、Repository/Mapper及 `DeliveryReconcileSignalPort` 实现:签收提交前在同事务插入 `(dispatchId,deliveryId,deliveryVersion)` 唯一信号,保存PENDING、attempts、nextRetryAt、lastError;相关结构由总控统一SQL集成。纯内存afterCommit可作唤醒加速,不能代替持久信号。
主流程任务独占 `dispatch/service/DispatchDeliveryCompletionWorker.java`、`DispatchCompletionService.java`及直接重试/并发测试;使用服务已有调度能力有界扫描due信号,逐车调用新事务reconcile。端口返回信号时不持delivery/source/signal行锁跨入父锁事务;多实例可重复处理,父锁/CAS及唯一汇总依据保证幂等。按正式契约第九节,阶段CAS和本次signal acknowledge在同一汇总事务提交,禁止旧稿“先提交阶段再另事务markProcessed”;任一失败两者均回滚。失败后才独立短事务记attempts/nextRetryAt/错误;未核齐的合法部分签收已完成本次核对,可在同汇总事务确认该信号,后续签收会发新信号。
增加有界修复性重扫只读定位“DELIVERING且存在未处理签收信号/已具备全签候选”的本车,仍调用相同reconcile,不写造签收事实;它是本功能可靠消费重试,不是测试存量回填。信号记录/markProcessed端口由派送写,消费/调度/重试决策/重扫由本主流程写,Maven仍本任务唯一执行。普通最终一致窗口内页面读真实stage=DELIVERING,不在签收POST成功后由前端直接置DELIVERED。
## 7 权限与隔离提案
平台入口限定 PLATFORM 且 tenantId=0;列表详情及只读候选使用正式list权限,写权限分别检查。真实客户只来自来源,平台头部不是一位客户。客户已有派送/出库读取继续Token客户隔离:按客户明细/单据读投影,不向客户暴露整车客户清单、其他客户费用、附件或完整整车详情。按用户已确认规则,由派送任务移除旧merchantTransfer创建此类派送的写入口,客户继续合法读取本客户单据/资料,不保留客户改整车旁路。
建议具体权限如下,由总控统一维护 `OverseasPermissions`、IAM/SQL及前端,不由本任务写共享文件:
| 动作 | 权限提案 |
| --- | --- |
| 整车列表/详情/BOL | 既有 `platform:overseas-warehouse:dispatch:list`,不因可读详情而可调用写命令 |
| 命令结果GET及同键POST恢复 | 当前原动作写权限+原主体+业务范围/PDA绑定仓,按6.2;list不授予恢复他人命令的权利 |
| 新建/选货建单 | 新增 `platform:overseas-warehouse:dispatch:create`;旧智能排车权限消费者由总控映射/收敛 |
| 修改/加货/移除/提交审核 | 既有 `platform:overseas-warehouse:dispatch:edit`;提交是申请,不是审核通过 |
| 取消 | 既有 `platform:overseas-warehouse:dispatch:cancel` |
| 调度/理货决定 | 推荐沿用既有 `platform:fleet:trip:dispatch-review/loading-review`,总控同步排车操作资源,不新增一套同义审核权限 |
| 后台装车完成/实际发车 | 建议新增 `platform:overseas-warehouse:dispatch:loading-complete/depart`,不借edit或PDA角色授权后台 |
| 安排派送 | 既有 `platform:overseas-warehouse:dispatch:transfer-delivery`,费用确认权限另按派送设计 |
| PDA列表/扫码/装车完成/发车 | `lastleg:pda:operate` 加海外PDA角色和Token绑定仓;不接收customerId/warehouseId改变范围 |
已有车辆主档/司机资格/资源占用仍由服务端验证,不能因为排车详情不显示附加能力而省略。主单、task、每条货物、出库派送所属起运仓一致;PDA跨仓读/写和同键恢复都拒绝。后台目前没有PDA的绑定仓事实,不从客户端PDA token或伪造身份调用PDA Controller;后台使用平台权限并复用领域服务。
## 8 精确实施清单与直接消费者
### 8.1 本任务后续独占生产触点
| 路径 | 计划变更 |
| --- | --- |
| `dispatch/controller/DispatchController.java`、`DispatchQueryController.java`、`SmartDispatchController.java` | 上述平台API、五阶段过滤、整车跨客户建单、删除单客户推断 |
| `dispatch/dto/DispatchRequests.java`、`DispatchResponse.java`、`DispatchDraftGroupResult.java` | Create/Add/版本命令、资源联合类型、五阶段、四页签事实、全车结果;禁止别名兼容 |
| `dispatch/entity/DispatchOrder.java`、`DispatchItem.java`、`DispatchDraft.java`、`DispatchPalletLock.java` | 主单/草稿去客户头部、主单版本/审核/实际节点、明细真实客户与实物排他 |
| `dispatch/mapper/DispatchOrderMapper.java`、`DispatchItemMapper.java`、`DispatchCandidateMapper.java`、`ReturnDispatchCandidateMapper.java`、`DispatchPalletLockMapper.java`、`DispatchDraftMapper.java` | 全车查锁、按明细客户过滤、来源解析批量化、保留真实客户库存条件和退回来源 |
| `dispatch/repository/DispatchMutationRepository.java`;拟新增 `DispatchRepository.java`、`DispatchCommandRepository.java` 与 `dispatch/mapper/DispatchCommandMapper.java` | 接管触及服务直调Mapper、主单和命令持久化;不把SQL搬进Service |
| `dispatch/service/DispatchService.java`;拟新增 `DispatchExecutionService.java`、`DispatchReviewService.java`、`DispatchCompletionService.java`、`DispatchDeliveryCompletionWorker.java` | 创建/加货/审核/五阶段状态机;双端共同装车与发车;主流程唯一消费持久签收信号、重试与重扫,不两端各写一套 |
| `dispatch/pda/controller/DispatchPdaController.java`、`dto/DispatchPdaRequests.java`、`DispatchPdaResponses.java` | 整车身份/版本、仓绑定、替换complete协议、恢复结果 |
| `dispatch/pda/service/DispatchPdaWorkflowService.java`、`DispatchPickOrderAttributionService.java`,以及 `dispatch/service/ReturnRelabelDispatchStock.java` | item真实客户、全车核平、扣减一次、归属/换标退货事实;不改变已修库存规则 |
| `dispatch/pda/entity/DispatchPdaTask.java`、`DispatchPdaOperation.java` 及对应Task/Operation/Pick/PickOrder Mapper | task去客户头部、命令集中;pick继续真实客户,查询不能再按单一头部customer过滤全task |
| 拟新增 `dispatch/pda/repository/DispatchPdaRepository.java` | PDA读写经Repository;逐来源账本与库存命令保留其领域入口 |
| `dispatch/fleet/DispatchFleetAdapter.java`、`FleetDispatchPlanningAdapter.java`、`FleetDispatchPlanningMapper.java` | 快照不再单客户;manifest分解为拣货核平/实载/派送三个事实,保留sourceCargo/交接/成本直接消费者 |
| `dispatch/service/DispatchBolPrintService.java`、`dispatch/mapper/DispatchBolMapper.java` | 全车BOL、按明细真实客户取来源;补Repository边界 |
| `fleet/trip/FleetTripEntity.java`、`FleetTripDispatch.java`、`FleetTripModels.java`、`FleetExecutionModels.java`、`FleetTripReview.java` | 一对一关联、去客户绑定审核、临时资源快照、内部和外部版本区分 |
| `fleet/trip/FleetTripService.java`、`FleetTripExecutionService.java`、`FleetTripPreparationService.java`、`FleetDispatchBindingGuard.java`、`FleetDispatchGateway.java`、`FleetDispatchWorkflowQuery.java` | 自动资源记录、统一阶段/审核资格、消除多客户子单绑定流程;保留附加能力及其服务端验证 |
| `fleet/trip/FleetTripRepository.java`、`FleetTripMapper.java`、`FleetTripDispatchMapper.java`、`PlatformFleetTripController.java` | 一对一关系和明确旧接口删除、附加API仍可用;新建资源不伪填旧必填值 |
| `fleet/trip/FleetTripCompletionService.java`、`FleetTripCostService.java`、`FleetTripCostBasis.java`、`FleetTripHistory.java`、`FleetTripHistoryMapper.java` | 按真实客户货物读取,原结案/成本历史与资源结束保持;不将资源COMPLETED当五阶段完成 |
| `fleet/trip/VehicleFulfillmentMapper.java`、`FleetTripCalendarService.java`、`fleet/trip/work/AppointmentWorkTripMapper.java` | 已发现b.customer_id连接需改按派送/货物真实客户;日历保留真实时间和空单缺项,不再假定计划时间全非空 |
新增文件名为推荐落点,冻结时可按既有层次合并;不能为少建Repository继续跨层SQL。`FleetBookingFreight.java` 归派送任务,本任务只提出 SourceCargo/绑定关系变化,不修改。
### 8.2 必须由总控分配或集成的外部触点
| 精确触点 | 必须处理的影响/写者 |
| --- | --- |
| `delivery/service/DeliveryTransferService.java`、`DeliveryFeeCoverageService.java`、`DispatchCargoSourceQuery.java`、`delivery/repository/DispatchCargoSourceRepository.java`、`delivery/mapper/DispatchFeeCargoMapper.java` | 派送后端;所有 dispatch.getCustomerId 消费改为真实分组,签名不能留旧头部参数 |
| `delivery/service/DeliveryDocumentService.java`、`DeliveryExecutionGuard.java` 及客户Controller/DTO、Outbound/Delivery Mapper | 派送后端;共同锁在单据锁之前;正常签收汇总端口、跨客户出库覆盖与唯一键 |
| `inboundplan/work/AppointmentWorkDispatchBinding.java`、`AppointmentWorkDispatchMapper.java`、`appointmentwork/AppointmentWorkDispatchPort.java` | 超出本任务当前生产写权,总控分配唯一写者;必须一次校验/保存全车各客户分配,移除d.customer_id Join;保留稳定batch/source/customer/目的地和已接收门禁 |
| `exceptionorder/service/ExceptionOrderService.java` | 已发现DispatchOrder直接消费者;改用派送/拣货真实客户归属,保留 RETURN_RELABEL 原箱回溯;总控分配,不能覆盖在途测试修改 |
| `fleet/exception/FleetHandoverPdaQuery.java` 及直接交接事实调用 | 客户从派送事实而非整车头部获取;保留未完成派送资源和完成时历史快照,总控分配 |
| `customervisit/service/CustomerVisitService.java`、`delivery/model/DeliveryStatusPolicy.java` | 已修状态门禁继续有效,签收/状态写入需共同锁入口;总控确认派送后端接管范围 |
| `src/main/resources/db/lastleg.sql`、`deploy/sql/头程数据库统一增量-MySQL8.sql`、IAM权限资源 | 总控唯一集成;按新基线收敛,不迁移/回填测试存量,不执行SQL |
| OpenAPI `overseas-business-v1.yaml`、`fleet-trips-v1.yaml`、`fleet-preparation-v1.yaml`、`appointment-work-v1.yaml`、必要新增 dispatch 文档 | 总控唯一集成;删除旧协议与消费者同批完成;统一Gateway既有last-leg通配应离线验证,不能凭路径推定运行可达 |
| `file` 附件提供者 | 共享整车BOL/附件若改变授权归属,先列出正式接口由总控分配;不把客户附件改为平台公共附件,本轮不改file代码 |
前端直接消费者已定位:Admin `overseas-warehouse/api/dispatches.ts`、`pages/DispatchesView.vue`、`pages/DispatchDetailView.vue`、`components/SmartDispatchDialog.vue`,以及 `overseas-master-data/api/trips.ts`、`components/TripPreparationDialog.vue`、`components/TripBookingPanel.vue`;PDA `src/api/fulfillment.js`、`src/stores/fulfillment.js`、`src/pages/dispatch/dispatch.vue`。归前端任务。
预约在途消费者只读列明:Admin `appointment-calendar/WorkDispatchForm.vue` 当前调用 drafts/pre-arrange 后弹车次准备和同仓共车,`WorkSharedTripForm.vue`要求两张客户排车,`WorkExistingDispatchForm.vue`仍以旧PENDING_DEPARTURE/workflow判断“继续安排本批车辆”。三者必须接入同一整车创建/加货及actions,保留事项弹窗关闭、原单跳转和已操作批次事实;总控分配接线,不由本任务覆盖。
脚本直接消费者已发现 `scripts/admin-test-env-modules/{grouped-dispatch-acceptance,dispatch-repair-acceptance,fleet-cross-customer-acceptance,fleet-cross-customer-fixture,fleet-external-dispatch-acceptance,fleet-fee-coverage-acceptance,fleet-booking-reservation-acceptance,fleet-return-redispatch-acceptance,fleet-handover-acceptance,fleet-trip-cost-acceptance,fleet-trip-fulfillment,phase3-outbound-dispatch,phase4-overseas-dispatch,phase5-overseas-fulfillment}.mjs` 等及其合同。这里只登记实际路径命中,不读取历史报告或执行真实HTTP;协议冻结后总控定向分配旧请求/状态更新,不能让旧fixture继续构造客户子排车。
### 8.3 结构变更给总控的清单
1. `ll_dispatch_order` 去customer,新增stage五阶段列、status收敛ACTIVE/CANCELLED、主版本、cargo修订/摘要、当前审核轮/依据、装车/发车/正常派送完成实际时间、备注;索引改仓/status/stage/计划时间及业务号,客户检索走item索引。审核详情可JSON但历史不可覆盖。
2. `ll_fleet_trip_dispatch` 去customer/review;一对一唯一约束;`ll_fleet_trip`新增临时资源快照/模式,允许新建草稿时计划、资源、卸货点未知,检查直接DTO/Mapper均不伪造默认值。
3. `ll_dispatch_item`保留customer,新增或明确稳定来源键/摘要,补全详情真实来源字段;索引按dispatch+customer+目的地及来源核对。物理托数与来源明细数分开统计。
4. `ll_dispatch_pda_task`去customer且dispatch唯一;pick/pick_order继续真实客户与来源;新增公共command(idempotencyKey全局唯一且operator不可改)及主单历史表,废除重复PDA整车命令记录协议。派送域持久待汇总信号表按6.5由派送任务设计、总控集成。
5. `ll_dispatch_draft`去客户头部,锁明细保留客户,补实物唯一活动锁;转换请求原子创建对应整车及Fleet。
6. `ll_dispatch_appointment_allocation`保留客户与逐来源箱数,不改成整车客户;全量替换时原子校验全车,必要索引加dispatch+customer+item。
7. 派送方同步将出库活动唯一键确定为 `(dispatch_order_id,customer_id)`,派送按客户+目的地;取消历史保持可追溯,不能以删除历史满足唯一键。费用和单据结构具体由另一文档给总控。
8. 全部中文表/字段备注、主外关联和索引同时进模块最新基线及统一结构脚本。无迁移、双写、别名、回填或测试存量修复;本清单不是执行数据库授权。
## 9 实施后的最小验证包(本轮未执行)
已有测试精确触点(相对 `lastleg-service/src/test/java/com/baiyue/lastleg/`):
| 测试 | 必须覆盖 |
| --- | --- |
| `dispatch/service/DispatchServiceTest.java`、`DispatchRepairWorkflowTest.java` | 同仓多客户多目的地一张真主单、空单再加货、来源不可伪造、跨仓拒绝、取消只释放本单、上一批换标全托/退货/合托修复 |
| `dispatch/controller/SmartDispatchControllerTest.java`、`DispatchQueryControllerTest.java`、`dispatch/DispatchSchemaContractTest.java` | 新API/五阶段/权限和去客户头部结构;草稿每容器一整车、不同客户同组 |
| `dispatch/controller/DispatchQueryControllerTest.java`及新增真实Mapper分页/统计用例 | status/stage正交,取消保留最后阶段;审核分区+状态SQL过滤、NOT_SUBMITTED不算PENDING、多客户一车只计一次、分页total和全基集统计/零值/一致快照 |
| `dispatch/DispatchCandidateMapperContractTest.java`、`DispatchPalletLockPersistenceContractTest.java`、`DispatchPhysicalWeightTest.java` | 实物别名排他、真实来源客户、重量未知不填零或材积重 |
| `dispatch/pda/service/DispatchPdaWorkflowServiceTest.java`、`DispatchPickOrderAttributionServiceTest.java`、`ReturnedDispatchAllowancePersistenceTest.java` | 未审核禁扫、后台不能补录、逐项核平、只扣一次库存、最后扫码不自动装车完成、跨客户来源、错仓/超量/同键异内容 |
| `fleet/trip/FleetTripPersistenceTest.java`、`FleetTripAuthorizationTest.java`、`FleetTripApiContractTest.java`、`FleetDispatchWorkflowQueryTest.java`、`FleetTripMatchedStopsTest.java` | 自动唯一关系、审核重提/关键修改失效、主档占用与资格、保留站点顺序/外接容量、临时ID为空及审核依据 |
| `dispatch/service/DispatchAppointmentCancellationPersistenceTest.java`、`inboundplan/work/AppointmentWorkDispatchBindingTest.java` | 跨客户batch不互删,旧取消后重排不占用失效批次,改批次版本拒绝,已接收门禁;后者写权需总控分配 |
| `fleet/trip/FleetTripCostPersistenceTest.java`、`FleetTripCostContractTest.java`、`FleetTripCalendarServiceTest.java`、`FleetTripCalendarMapperTest.java` | 附加能力保留;空计划不伪时间;成本实发事实及资源结束仍独立 |
| 拟新增 `dispatch/service/DispatchExecutionConcurrencyTest.java`、`DispatchReviewServiceTest.java`、`DispatchCommandPersistenceTest.java` | 双端同版本竞争只有一次装车/发车;同键原快照恢复;先派送后发车原子性;任一客户失败全车回滚;审核前后及取消/发车竞争 |
| 拟新增 `dispatch/service/DispatchDeliveryCompletionWorkerTest.java` | 信号提交后崩溃恢复、阶段CAS后ack前故障两者均回滚、重复信号/多实例、部分签收、重试/重扫、最后全签只CAS一次;签收持子锁期间不得回调申请父锁 |
派送方测试由其独占:`DeliveryTransferServiceTest`、费用覆盖/来源持久化、正常/部分/异常签收和汇总回调。共同集成应证明:未安排不可发车;部分/拒签/退回不自动DELIVERED;全部正常签收只推进一次且不释放资源;多客户同仓费用、来源和客户只读不串。
实现全部集成后,由本任务唯一串行执行后端 Maven `mvn -pl lastleg-service -am test-compile` 和对应行为测试(最终 `mvn -pl lastleg-service -am test`);先核对本批测试/基线稳定。总控集成后运行 Gateway 路由合同,新增Mapper运行 mapper-scan,跨包端口变化运行 lastleg-dependencies 检查。输入未变不重复跑。代码检查、真实HTTP、人工截图/PDA真机分别记录;本稿仅做源码只读分析,全部这些验证尚未执行。
## 10 需要总控冻结或裁定的具体问题
### 10.1 四项业务选择已确认,不再待问
正式契约第八节用户确认“四项都按建议执行”,替代前两轮选项表。本任务执行以下唯一基线,不再请用户重复决定。
| 场景 | 当前确定行为 | 本任务影响 |
| --- | --- | --- |
| 临时外配取证 | 建单人保存本趟真实容量/证照资料及预计任务结束,调度核实;空单可先保存,审核前补齐 | MASTER/EXTERNAL明确分离,不造主档;证据提供者变更交总控逐文件分配 |
| 实际装载核对 | 确认装车弹框接入既有实载,有依据数据带出、缺项按真实事实确认 | LoadingComplete携manifestHash和measurements;必须先核平PDA,不新增第五页签、不代填拣货 |
| 零扫码变更 | 保留原业务号,显式退回装车前,修改后两审重提 | ReturnToReview命令及历史;已有任何真实扫码/库存SHIP则不开放本批退装/回库 |
| 客户旧创建入口 | 整车安排仅平台执行,移除旧客户创建此类派送入口,客户保持自己单据读取 | 派送任务和前端任务按各自写权处理;不实现客户意图第二协议 |
新增预约明确属于日历,不在本任务设计、矩阵或实现范围;只保留整车改造影响的既有预约读取/关联,提供者未解锁前不修改。
### 10.2 总控工程冻结清单(无需用户重复裁定技术细节)
- 本稿已依据共同契约统一公开recordVersion、平台全局唯一请求键绑定主体、严格commandHash与arrangementBusinessHash及6.2授权恢复表;派送稿若尚保留expectedVersion或“同键跨主体也返回原结果”,由总控同步其唯一写者收敛,不让前端拼第三套协议。
- status/stage按第3节正交唯一语义、审核查询统计按4.5、命令GET及结果按4.3确定一套共同OpenAPI,前端移除旧状态和trip版本协议;这是数据/接口选择,不要求用户再选技术字段名。
- 锁协议采用6.3源码事实及6.4逐操作新顺序;其中库存position→balance、发车delivery→来源、签收delivery→来源已核对。预约/费用/其他主档入口反向加锁尚未逐一验证,须分配后验,不能标注“已无死锁”。
- 签收信号记录归派送,主流程worker/消费/重试/重扫及直接测试归本任务(6.5);接口包名选一套消费域端口,禁止双向具体实现依赖或为了编译加循环豁免。
- `AppointmentWorkDispatchBinding/Mapper/Port`及`WorkSharedTripForm`等预约触点、Client、File、脚本和共享SQL/OpenAPI/权限仍需总控精确分配唯一写者;保留预约保存/事项关闭/原单跳转及上一批真实来源修复。
- 旧多单Fleet prepare/bind/unbind配送协议收敛,PICKUP和外接/容量/成本/交接/资源结束保留原能力;原独立取消“必须unbound”改成父级协调,不新增已扫码撤销路径。本稿已只读比对派送稿,不修改另一文档、不跨对话发消息。
下游按第0节已落地骨架接入,不再以准备稿猜字段;实现范围以正式契约第九节为准,额外文件仍需总控精确分配。公共类型后续变更先更新交接点和哈希,再同步已发现直接消费者。
### 10.3 真实主单切片与精确集成依赖(2026-10-19)
2026-10-19补充作业公开DTO:新增`dispatch/port/DispatchWorkModels.java`,SHA256=`1e91da656e38aebbc554b4b4275b080d387e6bdb2eefaa14d5ae1ebd5b1aec64`。原13文件未改;新文件仅JDK依赖,确切record以实际源码为准:`Items(dispatchNo,recordVersion,status,stage,List<CargoItem>)`,CargoItem含真实dispatchItemId、真实客户、sourceKey/sourceVersion/sourceType、planPalletId/returnReceiptId/relabelNo、warehousePalletId/palletNo、orderNo/inboundCode、destination、scheduledPackages/pickedPackages/remove Gate;不能从orderDetails.sourceKey或warehousePalletId替代删除身份。`Route(dispatchNo,recordVersion,List<Stop(index,name,address,destinationKey)>,edit Gate)`使用真实Fleet持久顺序,0为起运仓,其余index可直接对应LoadingMeasurement.deliveryIndex;`RouteCommand(recordVersion,idempotencyKey,List<String> destinationKeys,reason)`只重排完整既有目的地,服务端保留真实名称/地址、起点及外接卸货点,采用既有Action.EDIT严格幂等。
新增已接线平台端点(D=`/api/platform/v1/last-leg/dispatches`):`GET D/{dispatchNo}/items`返回Items,`GET D/{dispatchNo}/route`返回Route,`PUT D/{dispatchNo}/route`接收RouteCommand返回CommandResult;读权限dispatch:list、改路线dispatch:edit。四详情对象不加第五页签。`GET D/{dispatchNo}/loading`及`POST D/{dispatchNo}/loading-complete`使用已冻结LoadingPreview/LoadingComplete/CommandResult;后者权限dispatch:loading-complete,显式确认前保持LOADING。
附件正式入口已接线:`POST D/{dispatchNo}/evidence/{kind}`,kind=`VEHICLE|DRIVER`,multipart字段`file`,权限dispatch:edit,返回`{fileId,fileName,contentType,sizeBytes}`;上传不自动替换主单引用,后续使用当前recordVersion的Update保存evidenceFileIds/evidence.fileIds。`GET D/{dispatchNo}/evidence/{kind}/{fileId}`返回同一元数据,`GET .../{fileId}/content`返回鉴权代理字节、inline Content-Disposition及no-store,读权限dispatch:list;不能使用匿名URL。内部File按平台0+新类别+真实dispatchId+fileId核验。File类别由总控新分配的派送后端补齐,本任务不修改File源码;本域新入口仍待离线/真实验收分别核对。
主单新增`loading_confirmation_json`精确字段,已纳入DispatchAggregate和saveState Mapper;内容是已冻结ConfirmedLoad完整快照。DispatchExecutionPort现由DispatchAggregateService实际实现,preview/lock/replay/fresh/recordArrangement使用同一主单、逐项真实扫码和不可变归属;新的DispatchExecutionReadMapper/Repository批量读取,不用计划数量凑核平。装车完成沿原Fleet容量策略并核对外接预订,保存实载但不调用库存SHIP。以上新输入测试正在运行,不用前一轮通过结果替代。
已经实施`DispatchAggregate`及其Mapper/Repository、`DispatchAggregateService/Controller`、`DispatchReadMapper`、`DispatchSourceMapper/Repository`,并沿允许的Fleet原生应用入口建立真实一对一资源记录。主单没有customer字段;同仓多客户只在真实明细保留归属。空单、选货建单、加货、分页和四对象详情、命令持久恢复已接线。候选返回共同`CandidateList`,按真实起运仓在SQL取有界多客户集合,来源摘要由当前库存版本、仓、收货组成、目的地及换标轮次生成。两级审核、驳回重提、资源编辑与零真实扫码退回已进入本域实现,新增验证仍进行中。
共享SQL精确输入:主单字段以`DispatchAggregate`实际属性为准,移除customer_id,trip_id唯一;Fleet计划、资源允许空值,`ll_fleet_trip_dispatch`移除customer_id/review_json并唯一trip_id;明细新增customer_name/source_key/source_version/source_snapshot_json;来源锁新增nullable且唯一active_warehouse_pallet_id,与customer_id/active_key唯一键共同防别名重复占托;不可变`ll_dispatch_history`字段id/dispatch_id/record_version/action/operator_id/reason/snapshot_json/occurred_at;命令表见0.2。路线须容纳多目的地真实地址,不能沿用短VARCHAR截断。未写共享DDL、未执行SQL。
本域新增临时资源证据适配`dispatch/gateway/DispatchEvidenceGateway`及`HttpDispatchEvidenceGateway`,只读写File现有通用内部业务文件路径。File提供者精确触点是`file/src/main/java/com/baiyue/file/service/BusinessFileCategory.java`:需要两个平台固定0、businessId为真实dispatchId、PDF/JPG/PNG、10MiB、POSITIVE_LONG类别`OVERSEAS_DISPATCH_VEHICLE_EVIDENCE`与`OVERSEAS_DISPATCH_DRIVER_EVIDENCE`,以及直接类别/归属测试和必要OpenAPI。该提供者未分配给本任务,未修改;由总控分配唯一写者。Lastleg按平台0+类别+本车ID+文件ID复验,不复用司机主档证照类别,不为临时输入自动建主档。公开上传/读取入口尚待接线;File类别和真实调用未验收,不把测试夹具的文件提供者模拟视为真实文件验证。
预约精确触点仍是`AppointmentWorkDispatchPort.bind/Binding/Mapper`:现接口按单客户调用且会删除整车所有分配,查询仍以头客户联接;不能跨客户循环调用或静默丢掉已有批次。本域暂明确拒绝有既有批次的建单/加货和车辆预约编辑;需要总控分配提供者最小适配后解除,不表示业务取消预约能力。候选appointmentBatches尚未填充,后续读取须同一权威提供者接通。
离线证据:首轮生产与全部测试源码编译通过,主单/公共合同/账本/查询及派送交付的19个直接类共144项中143项通过;唯一Merchant OpenAPI残留由总控处理,之后复测推进到第133行旧POST残留。主单3项实际MyBatis/H2事务测试通过,证明空单一车一记录、同仓多客户多目的地一头、过滤不缩减整车、原结果恢复及后项冲突全事务回滚;选货 authority 提供者当时为受控输入,不证明完整来源链。补跑旧回归66项有3项旧夹具缺新依赖,修夹具后Fleet57项全部通过,DispatchRepair还需补真实dispatch_no关联继续验证。Gateway路由合同5/5;Mapper扫描389个/10模块。新增审核和候选改动待下一次串行Maven,不把既往结果覆盖新输入。依赖分析旧fleet/pickup三项基线未改。
2026-10-19后续实现:`DispatchAggregateService`实际实现Execution端口,`DispatchCompletionService`实际实现Completion端口;`DispatchDeliveryCompletionWorker`有界读取到期信号、逐车新事务汇总、失败回滚后独立退避、按真实已签收版本重扫缺失信号。主阶段CAS、唯一完成历史、指定信号ack同事务;合法部分签收只ack本次通知,后续版本独立产生新信号;全部正常签收才DELIVERED,不释放资源、不二次发布来源/费用。
PDA实际Controller已切到共同DTO:GET根与单号返回DispatchViews.PdaTask;列表query使用stage;POST items/{itemId}/pick接收DispatchCommands.Pick;新增GET loading、POST loading-complete、GET commands/{idempotencyKey};POST depart接收DispatchCommands.Depart并返回CommandResult;旧/complete不再注册。身份由DispatchActorFactory.pda→PdaWarehouseIdentity取操作者及唯一绑定仓,请求不接受客户/仓。DispatchPdaQueryService+DispatchWholePdaReadMapper批量装配多客户明细/仓位/原生退货箱/真实归属,列表不逐车查详情。整体taskNo明确以排车业务号识别;底层任务现按dispatch唯一且不含customerId;taskNo直接使用dispatchNo,真实客户继续留在pick/pick_order及库存命令。
新增DispatchPickingService与DispatchDepartureService。扫码沿原InventoryCommandService、DispatchPickOrderAttributionService和原生退货生产者执行;实际来源订单先于库存锁,最后一箱只核平、阶段仍LOADING。版本化发车先根锁,再收齐所有出库/派送锁,再锁全部实际来源订单;调用原来源计数及转仓/换标/FleetPool生产者,不再扣库存;原命令恢复先于阶段资格。旧DELETE移货/取消已退出Controller,使用POST items/{itemId}/remove和POST cancel+ReasonCommand,释放精确原占用,旧键重放不能释放新车占用。实际扫码、发车及原提供者整链正在验证,不能将编译视为这些生产者已证明。
草稿唯一写权已移交原派送后端:Draft/Container实体及Mapper、SmartDispatchController、Draft专用Service/Repository/占用Mapper及直接测试;本任务停止写入。按派送设计第12节已补最小桥接:`DispatchService.lockDraftSources(String,List<CargoSelection>): List<WholeSource>`,MANDATORY,允许草稿逐托暂存换标但正式create仍要求完整申请;`DispatchAggregateService.lockDraftCreationResources(Actor,List<Create>): void`,MANDATORY,锁齐全批车辆升序→司机升序→车辆占用升序→司机占用升序,再取全批来源。转换里的新trip是当前事务创建且外部不可见的身份,正式create只重入已持资源身份锁,不在第二组取新资源。已移除旧DispatchService草稿读写/占用/分组转换公开方法及对应旧测试,不恢复Draft.customerId;旧单客户建单/Fleet适配尚待整体退出。
新增离线证据:最新完整Lastleg Maven在固定输入下生产及442个测试源码编译通过,53项中52项通过,包含主单/审核/真实明细与路线/显式实载/失败归属8项SQL测试、Schema中文注释3项;唯一Merchant第133行由原写者修正路径块提取,保留其他业务合法POST,后续新候选待复验。JDT曾把错误字节码写入target/classes,javap确认含Unresolved compilation problems;只对Lastleg target运行一次mvn clean后恢复编译,没有修改转仓/本土卡车源码或停止IDE。
随后在草稿在途输入之外,使用明确19个生产文件的javac独立输出及JUnit 1.10.3实际启动器运行本域23项直接测试全部通过:9项根/版本/审核/路线/实载/移货取消SQL、2项完成CAS与ack共同回滚及重试/部分签收、1项worker退避顺序、2项真实来源订单/库存/实收SQL与锁序、2项持久账本、5项证据归属/模拟HTTP下载MIME长度签名及业务错误、2项公共合同(16类请求缺/null/负/零版本、全部Action重启恢复)。这是固定本域快照,不是新增PDA/草稿完整Maven,也不把受控库存/归属/文件提供者当真实环境证明。后续新增PDA查询与扫码测试正在进行。
共享验证:IAM四类24项全部通过,输入未变不重复;File新证据测试在145/146/171/175行未处理IOException导致测试源码编译失败,已给原写者精确定位,本任务不改File。Gateway路由5/5、Mapper390个/10模块通过;依赖分析仍为原fleet/pickup三项失败,未加豁免,后续新Mapper输入需重新核对。
主要未闭环:既有预约/车辆预约权威写提供者仍按总控保护,禁止循环旧bind或静默丢批次;FleetTripDispatch旧customerId/reviewJson及旧单客户适配/直接消费者尚未完全收敛;草稿新候选及全域测试编译、实际PDA扫码/发车与全部正常签收联合验证尚未完成。所有结果均不表示已部署、真实HTTP、人工页面或PDA真机验收通过。
2026-10-19 16:58本域继续收口:`DispatchPdaTask`已移除customerId,TaskMapper只按dispatch读取,PickingService按根锁创建唯一任务并以dispatchNo作taskNo。新增双客户实扫用例验证一张任务、两条真实客户扫码、各自库存命令及最后扫码仍LOADING;H2用例有dispatch唯一约束。`FleetTripDispatch`已移除customerId/reviewJson,DispatchBinding/PreviousDispatch去客户头,新增FleetDispatchGateway.workflows批量从本域真实主单读取两级审核;Fleet详情/历史投影、拣货门禁不再读取绑定级审核。已撤下PlatformFleetTripController旧调度审核、理货审核及独立实载确认写入口;原公开Fleet资源/计划入口对已关联主单拒绝写,必须走共同主单版本与审核。公共原13文件和WorkModels未修改。余下旧手工绑定/prepare、外接来货入车及旧DispatchOrder适配正继续调整,不视为本批完成。
稳定草稿输入上的Maven:1056个生产源码及450个测试源码test-compile通过;31个直接类共178项,176通过,两项草稿失败为SmartDispatchControllerTest:65把非路由方法计入权限遍历,以及DispatchDraftPersistenceTest:151转换重启恢复的抽象Payload反序列化失败;失败报告交原写者,不修改其文件。主单双端发车竞争及源生产者失败全事务回滚2项均通过,明确源发布生产者在该用例受控模拟。File七类串行命令再次停于DispatchEvidenceFileTest:145/146/171/175四个IOException测试编译错误,未执行测试,输入未变不重复。
上述模型收口后PDA Maven34项出现13项非法DispatchPdaTask.class字节码VerifyError(本机JDT写target产生),不能视为业务失败或通过。随后在固定本域显式源码列表的独立javac输出中重编译受影响实体/Mapper/Service,运行JUnit真实启动器36/36通过:根SQL/审核/资源/路线/实载/取消移货9,完成事务/worker3,真实来源SQL2,共同账本2,模拟HTTP证据5,公共JSON/依赖2,PDA真实读1,PDA扫码3,批量资源锁2,PDA路由/DTO6,实际发车事务2。此轮Fleet历史审核已通过真实DispatchFleetAdapter.workflows→AggregateRepository→MyBatis/H2读取,不再模拟绑定级旧审核。仍需稳定后的全域Maven、最新正式SQL合同与附加能力集成验证。
保护范围精确触点:`inboundplan/work/FbaAppointmentExecutionAdapter.java:78`仍用TerminalEvidence.customerId决定整车归属且状态仍使用旧DEPARTED/PENDING_DEPARTURE/PDA_PENDING;`FbaAppointmentTransportAdapterTest.java:48`旧DispatchBinding构造器含客户参数,需原提供者在唯一写权下同步。TerminalEvidence本身尚未改,不能设置0、首客户或空值当整车客户完成收敛。实际客户仍必须从逐条TerminalDelivery/扫码/归属取值。
2026-10-19 17:08 File冻结修复验证:`DispatchEvidenceFileTest.java`当前SHA-256=bba87946457fb1290960218127087552de95b59f443eb6d5dacf354084d04a19,与总控候选匹配。唯一串行命令`mvn -pl file -am '-Dtest=DispatchEvidenceFileTest,BusinessFileStorageTest,InternalBusinessFileServiceTest,InternalBusinessFileControllerTest,DriverLicenseFileTest,TripOperationEvidenceFileTest,VehicleCarePhotoTest' '-Dsurefire.failIfNoSpecifiedTests=false' test`实际49/49通过,其中证据新类别12/12。输出`.logs/dispatch-file-fixed-tests.log`,JUnit报告`file/target/surefire-reports/`,输入未变不重跑。草稿当前稳定摘要b0db1bb5191fa3578dacfa0f07c7c2a6a30346f0198047e34fc1fe3a99b0e554及17项待下一Lastleg编译窗口,不将写好当通过。
36项固定输出证据位于`lastleg-service/target/dispatch-owned-compile/model-tests.log`(SHA-256 325ccd6c326aee5465639e5b017c46ba5f641b8f9268e81b6b7e9238e3046084)、`model-36-evidence.json`(当时仍未变化的10份PDA/测试源码指纹及范围说明)、`verified-36-classes.jar`(SHA-256 43d626ac44389dbbe9ee522a832b980e0a6b85dbedb9b6cb7f2c20d68dcd7ff9)。后续Fleet适配/附加能力接线不由该结果覆盖。
Fleet现继续按真主单收敛:`FleetDispatchGateway.Snapshot(dispatchId,dispatchNo,warehouseId,packageCount)`与`TerminalEvidence(dispatchNo,warehouseId,shippedPackages,deliveries)`、`FleetTransitEvidence(dispatchNo,warehouseId,shippedPackages,deliveries)`均去客户头;逐行TerminalDelivery/Delivery.cargo保留真实客户。SourceCargo结构未变,读取改为`sourceCargo(dispatchNo,customerId)`,客户参数仅由服务端真实货物/预订归属传入;`customers(dispatchNo)`返回本车真实明细客户。DispatchFleetAdapter不再读旧DispatchOrder或旧客户PDA任务,交接/结束读取所有客户出库及所有派送并逐客户核平,先全部outbound后全部delivery锁。正常全签后退回仍保留原已履约,不从主单客户裁切全车。在途更换只更新对应未终结客户派送。
旧Fleet主流程写协议已撤下:POST fleet/trips、POST /prepare、GET /{tripNo}/dispatch-candidates、POST /{tripNo}/dispatches/bind、POST /{tripNo}/dispatches/unbind、POST /{tripNo}/dispatches/{dispatchNo}/review、POST /{tripNo}/loading-review、POST /{tripNo}/loading。旧PreparationService及旧PDA list/task/pick/complete/depart应用方法退出,PDA SourceWorkflow仅保留真实来源发车计数/状态生产者。外接预订bind/arrange保留,但只核对/加入该Fleet已唯一关联主单,不创建或挂入第二张客户排车;普通取货createPickup保留。`PlatformFleetCapacityOfferController`arrange-dispatch已把旧smart-dispatch改为当前dispatch:create,并继续要求原capacity-offer manage、booking-edit、trip-edit。共同OpenAPI/保护中的预约提供者/外部前端入口由总控协调,当前尚未全域编译/回归完成。
受保护生产直接消费者新增精确交集:`FbaAppointmentTransportAdapter.java:95/102`调用已退役FleetTripService.unbindDispatch/bindDispatch;`FbaAppointmentExecutionAdapter.java:87`调用已移除TerminalEvidence.customerId。不得恢复旧头字段、循环旧bind或加成功空实现。旧手工H2与Fleet/预约直接测试必须按新模型继续调整,当前36项不能替代这些回归。外配任务结束现在不对不存在的主档ID释放占用或修改主档状态,仍保存真实结束时间/地点与状态;待直接测试。
2026-10-19 既有预约接线取得用户授权后的公共合同:`DispatchContracts.BatchAllocation(planPalletId,batchKey,customerId,sourceKey,destinationKey,packageCount,Long recordVersion)`及`BatchReference(batchKey,batchNo,customerId,sources,Long recordVersion)`末尾增加权威记录版本,compact constructor拒绝缺失/null/负数、0合法,不提供旧签名重载。FBA引用取原预约版本;自动DISPATCH引用取实际对应整车recordVersion,不伪造FBA版本,也不接受客户端以自动引用替代FBA分配。DispatchContracts.java SHA-256=73b5b83fcd0155bf09b18c18b1bd6a5bbb0175221102a13101248f92ca84f641。其余原公开DTO当前未改,原13文件“全部不变”指纹表从本次字段变更起只作历史基线,不再代表当前。
新增JDK内部端口`dispatch/port/DispatchAppointmentAmendmentPort.java`,SHA-256=45aca62fa8a52f3b97a3e666c87b85d2ae0ca5372ca46e1c29ef76f27887b238。精确方法为`void lockExistingAppointmentDispatches(Actor,List<String> dispatchNos)`和`CommandResult applyExistingAppointmentChange(Actor,String dispatchNo,ReasonCommand identity,List<PhysicalItem> touchedItems,List<BatchAllocation> expectedAllocations,List<BatchAllocation> nextAllocations,boolean removePhysicalItems)`;PhysicalItem为`(Long itemId,String customerId,Long planPalletId,String sourceStage,Integer packages)`。两方法MANDATORY内部协作、不注册HTTP;先全范围trip→全部资源→全部dispatch,再全部必要子单→批次→来源→协调guard。ReasonCommand为本车公共版本+原父预约命令键,内部按父键/真实操作者/本车派生稳定子键,共用公共账本并优先恢复原结果。expected/next只接原权威scope,必须逐项核DB真实明细/原分配及完整实物,BEFORE_LOADING/零真实扫码/无下游执行;移客户货物不解绑或取消整车。端口源码先交付,实际实现和主流程接入正继续,不能据接口存在推定已完成。
获得最小写权的ExceptionOrderService.java:548已将任务查找改为findByDispatch(dispatchId),后续本客户pick/item/归属过滤保持;直接ExceptionOrderDispatchReturnSourceTest仅移除两个任务头客户setter并收敛两个mock调用,保留该文件此前未提交业务修改。异常原箱Mapper、客户进度、库存发运证明及派送Mapper中旧头客户SQL仍需总控独占分派,不顺手扩写。
本次公共版本字段变更前的独立1052份源码构建(明确排除原预约ExecutionAdapter/TransportAdapter及当时未获写权的ExceptionOrderService,不是全服务Maven)运行62项,57通过,仅Fleet权限类5项缺新增Gateway测试Bean;补真实方法安全上下文依赖后该5项单独5/5通过。草稿新17项、主单/PDA/发车、SQL实体合同3项及Fleet审核只读1项在57通过之内;输出`target/dispatch-owned-compile/fleet-draft-tests.log`、`fleet-auth-tests.log`、输入`draft-input-evidence.json`(实际13/13及聚合b0db...554匹配)。后续公共预约版本字段、Provider及Fleet在途适配修正未由该结果覆盖,最终Maven仍须串行重跑。
2026-10-19 17:39 IAM权限新输入串行验证:`mvn -pl iam -am '-Dtest=Phase5DeliveryPermissionSqlContractTest,Phase5DeliveryPermissionSqlExecutionTest,AuthorizationSnapshotServiceTest,IamOpenApiContractTest,OverseasPhase4PermissionSqlContractTest' '-Dsurefire.failIfNoSpecifiedTests=false' test`实际29/29(1+5+7+5+11)通过。源码与target/classes/db/lastleg-permissions.sql SHA-256均为9deef80d2bcf383918b5ad48a63bea893ced2d9a32528e33268c02e73bbd4f67。输出`.logs/dispatch-iam-retired-smart-tests.log`;报告`iam/target/surefire-reports/`。File49当前输入未变,本轮未重跑。
预约接线正在实施:已新增`DispatchAppointmentAllocations`按Provider实际record字段顺序逐字段映射(Provider版本在第3字段,公共DTO在末尾),一次合并当前保留分配+新请求版本预锁,缺失/负数/同批混版本拒绝;create/add与草稿全groups预锁位于来源锁前。仅将已保存DispatchItem的真实客户传给bindItems,touched增量保留他人。源码已增加Fleet全范围父级/全部资源预锁及主单排序锁、内部预约改量端口实现:原主版本/零真实扫码/BEFORE_LOADING、完整实物及原分配集合复验、稳定父键子命令恢复,移出不解绑/取消整车,主单/来源/审核/历史随父事务回滚。新测试与整体验证尚在补齐;候选真实批读仍待Provider实际薄签名后接入,不能将这些源码视为已编译通过或链路完成。旧DispatchService.preArrange及两个私有单客户创建实现已移除,不恢复Provider旧bind/requireReplay;BOL仍待从旧platformDetail读取切到整车投影,旧测试和残留DTO同步继续。