27 KiB
自有业务后端接口设计
2026-10-20 总控已冻结v1,进入独占包实现;尚未执行Maven或环境验收。业务唯一依据为首版开发契约,已纳入其第11/12条:计划发车日期筛选、四项统计同一完整筛选,以及独立居中的运营登记弹窗。所有编号以 dispatchNo 为用户入口。
用户最新定位已收敛:本页是原生排车列表装车完成后的自有车辆运营视图。不创建第二个任务/车次主单,不保存第二份排车货物、预约、单据或审核/装车/发车/签收状态机;外配后续也消费同一排车事实。
原排车查询能否直接复用
DispatchViews.Query当前没有车辆ownership、计划发车日期范围和目的仓关键词过滤;ListRow没有完整目的地/预约和附加运营信息。因此不能靠浏览器取一页后筛OWNED、求统计或逐行GET补齐,列表需要本包新增只读数据库投影。- 原主单详情在服务端通过公开窄端口读取
dispatch.port.DispatchDetailReader.detail(Actor, dispatchNo)的DispatchViews.Detail,再组合本页附加信息;已核实其真实来源,不重写主单查询协议或状态推算。新详情响应建议保留一个明确的dispatch: DispatchViews.Detail字段;独立三页签由现有事实和附加信息映射展示,不改变排车四页签。 - 新API是有自身list/edit/upload权限的投影视图与附加信息入口,不是第二套排车API;原排车查询/Controller/DTO/Mapper全部保持只读,不要求额外公开主流程写动作。
1. 事实来源与装车归属捕获
| 事实 | 当前权威代码/数据 | 本页处理 |
|---|---|---|
| 主单、三个阶段、计划/实际发车、装车时间 | dispatch/entity/DispatchAggregate.java;ll_dispatch_order |
只读 ACTIVE 且 AFTER_LOADING/DELIVERING/DELIVERED,必须有真实装车确认;不以 DEPART 历史作为入选前置 |
| 实际托数 | DispatchContracts.ConfirmedLoad.pallets;loading_confirmation_json |
按 warehousePalletId 去重数,不取逻辑明细数,不合计 palletSpaces;缺少可信确认时不伪造托数 |
| 车辆、司机、容量 | resources_json 的 DispatchContracts.Resources |
读取本趟快照;VehicleSnapshot 没有 ownership 字段,MASTER 也不等于自有 |
| 当前车辆归属 | masterdata/vehicle/.../VehicleRepository.VehicleSnapshot.ownership;ll_vehicle.ownership |
有真实 OWNED/DISPATCHED,但车辆可在未占用时改归属;当前表不能独自证明历史归属 |
| 历史车辆归属 | 原快照未保存ownership | 已冻结在装车完成同事务捕获真实OWNED/DISPATCHED;根接唯一hook,旧已装车TEST无快照不回填、不按当前主档降级 |
| 多目的仓、货物、各客户 | ll_dispatch_item、真实来源及其 destinationKey |
服务端批量汇总;同车一行,各客户来源保留 |
| 预约号、时间和分配数量 | ll_dispatch_appointment_allocation + ll_inbound_fba_appointment;查询规则见 DispatchReadMapper.batches |
按 destinationKey/实际分配关联返回数组,不取首个预约,也不按客户重复整车 |
| 到达、里程、燃油、运营金额 | 当前主单无此页独立权威登记 | 新增附加记录;不推导到达、不改签收、不写Finance、不改排车版本 |
fleet/reporting/TripOperationsRepository 仅查实际发车且按货源分类,并使用旧 JdbcTemplate;本模块不复用它的业务过滤或持久化实现。新模块严格 Controller → Service → Repository → @Mapper。
2. API、权限与入口
统一前缀 /api/platform/v1/last-leg/fleet/owned-business。全部要求可信平台身份 appCode=PLATFORM, tenantId=0,服务端重复核对真实归属、阶段和仓权限,不接受浏览器声明 ownership/客户归属/托数。
| 方法与相对路径 | 权限 | 作用 |
|---|---|---|
GET / |
platform:fleet:owned-business:list |
分页与完整筛选四项统计同一只读快照返回;浏览器不逐行GET |
GET /{dispatchNo} |
同上 | 独立三页签所需任务车辆、路线货物与凭证、附加信息及版本 |
GET /{dispatchNo}/history?page=1&size=20 |
同上 | 真实操作记录分页;无模拟初始操作 |
PUT /{dispatchNo}/operations |
platform:fleet:owned-business:edit |
运营弹窗保存;整车金额、逐目的地到达、里程与燃油同一附加版本 |
POST /{dispatchNo}/photos/{slot}/uploads |
platform:fleet:owned-business:upload |
multipart file + idempotencyKey,只准备一个真实文件,不假称已经保存照片 |
PUT /{dispatchNo}/photos/{slot} |
同上 | 将已上传的 fileId 绑定/替换到该分类框;独立于运营编辑权限 |
GET /{dispatchNo}/photos/{slot}/files/{fileId}/content?revisionVersion= |
list | 当前或明确历史版本中已绑定文件的授权代理读取,不返回File内部地址 |
GET /{dispatchNo}/commands/{idempotencyKey} |
list | 按当前操作者恢复本页写命令;不允许读取别人的命令载荷 |
没有创建主单、改变运输状态、删除运输或费用发布接口;不公开内部 dispatchId/tripId 作为业务编号。
3. 冻结v1 DTO
以下为已冻结v1结构,源码与OpenAPI按本节同步。所有 Long ID/版本通过现有Jackson策略无损输出;Money/数量测量用规范十进制字符串传输并在服务端BigDecimal校验,金额/单价不交前端重新计算。
type Stage = 'AFTER_LOADING' | 'DELIVERING' | 'DELIVERED'
type PhotoSlot = 'LOADING_SCENE' | 'LOADING_LIST' | 'PRE_DEPARTURE_CHECK'
| 'DRIVER_LICENSE' | 'RECEIPT' | 'FUEL_START' | 'FUEL_END'
type Money = { currency: string; amount: string }
type Arrival = { destinationKey: string; actualArrivedAt: string | null }
type Photo = { slot: PhotoSlot; category: 'LOADING'|'FUEL'; fileId: string;
fileName: string; contentType: 'image/jpeg'|'image/png'; sizeBytes: string;
uploadedAt: string; savedAt: string | null }
type Operations = { money: Money | null; actualDistanceMiles: string | null;
fuelConsumptionLitresPer100Km: string | null; arrivals: Arrival[] }
type Gate = { allowed: boolean; blockReason: string | null }
type Destination = { destinationKey: string; warehouseCode: string | null;
warehouseName: string | null; address: string | null;
appointments: { appointmentNo: string; confirmationNo: string | null;
scheduledAt: string | null; allocatedPackages: string }[];
actualArrivedAt: string | null }
type Row = { dispatchNo: string; stage: Stage; plannedDepartureAt: string;
loadingCompletedAt: string; departedAt: string | null; deliveryCompletedAt: string | null;
origin: { warehouseId: string; warehouseCode: string; warehouseName: string };
vehicle: { vehicleId: string; plate: string; ownership: 'OWNED' };
driver: { name: string | null; phone: string | null };
destinations: Destination[]; actualPalletCount: number | null;
operations: Operations; unitPrice: string | null; recordVersion: string;
dispatchRecordVersion: string;
photoSummary: { loadingCount: number; fuelCount: number };
actions: { edit: Gate; upload: Gate }; sourceIssues: string[] }
type Query = { page?: number; size?: number; plannedDepartureFrom?: string;
plannedDepartureTo?: string; keyword?: string; stage?: Stage; warehouseId?: string }
type ListResult = { records: Row[]; page: number; size: number; total: number;
summary: { dispatchCount: number; afterLoadingCount: number;
deliveringCount: number; deliveredCount: number } }
type Detail = { row: Row; dispatch: DispatchViews.Detail;
cargo: { sourceKey: string; customerId: string; customerName: string | null;
orderNo: string | null; inboundCode: string | null; palletNo: string;
destinationKey: string; packages: string }[]; photos: Photo[] }
type SaveOperations = { recordVersion: string; idempotencyKey: string;
operations: Operations; reason: string | null }
type BindPhoto = { recordVersion: string; idempotencyKey: string; fileId: string }
type Command = { dispatchNo: string; action: 'SAVE_OPERATIONS'|'UPLOAD_PHOTO'|'BIND_PHOTO';
idempotencyKey: string; state: 'SUCCEEDED'|'PENDING'|'UNKNOWN_RESULT'|'REJECTED';
recordVersion: string | null; replayed: boolean; result: Detail | Photo | null;
errorCode: string | null; retryAllowed: boolean }
type History = { eventSource: 'DISPATCH'|'OPERATIONS'; recordVersion: string | null; dispatchRecordVersion: string | null; action: string; operatorName: string | null;
occurredAt: string; reason: string | null; operations: Operations; photos: Photo[] }
- 日期筛选明确对应
planned_departure_at;接口接收带时区的ISO时间边界,[from,to),前端按最终选定显示时区将日期范围转换,不能靠数据库会话时区猜测。 - page默认1、size默认20且1–200,keyword最长128;匹配dispatchNo/车牌/实际目的仓,不匹配内部ID。
- 四项统计与列表使用完全相同过滤条件,按dispatch去重,统计不带LIMIT;stage筛选也同时作用于统计。标题为“排车数”,没有“固定今天”分支。
recordVersion是本页附加版本,未登记时只读返回0,不因GET建记录;第一笔成功绑定/登记返回1。不占用、不回写主单recordVersion。dispatchRecordVersion仅用于原排车事实的只读定位;运营保存和照片绑定提交本页recordVersion,不可混用这两个版本。- 0金额表示明确登记零;
money=null表示未登记。Money存在时currency和amount必须成对完整,金额DECIMAL(18,2)非负;单价amount/actualPalletCount按HALF_UP两位,只读返回;未知/零托数返回null。 - 币种仅CNY/USD,不预选、不换汇;同一dispatch只有一份money,允许同一版本化编辑更正或清空,并留不可变历史。money=null与明确0严格区分。
- 多目的地实到以原车实际destinationKey集合为边界,不额外设置站数上限,禁止重复/不属于该车的key;不要求首站到达等于签收。输入时间必须带时区;不以estimatedArrivalAt或signedAt回填。
- actualDistanceMiles为mile;fuelConsumptionLitresPer100Km为手工登记的L/100km,均可空或明确0,非负、DECIMAL(12,3)。不派生总燃油量,不写默认18.6。
- 7个PhotoSlot来自原型最终函数的5个装车框和2个油耗框;
DRIVER_LICENSE/RECEIPT只表示本页图片位置,不审批司机资格,也不触发派送签收。每框一个当前文件,替换保存不可变历史,不删除File对象。无照片清空入口,避免扩大当前原型操作。
4. 保存、命令恢复与错误
- 编辑/绑定先核对平台、权限、真实自有资格和三个阶段,再读取/创建附加行。并发首次创建依dispatch唯一约束串行;同一附加行内CAS版本、命令成功结果及历史同事务提交。
- 命令唯一键为dispatchId + idempotencyKey,另存operatorId并强制匹配;摘要包含action、原recordVersion和完整规范化载荷。同键同操作者同摘要返回原结果,不同操作者403、同键异载荷409。重放先于新写版本判断,不修改原版本重发。
- 新增独立上传命令意图,文件调用前持久化
PENDING且不持业务行锁跨HTTP;成功后记录原fileId和元数据,不推进附加版本。上传响应未知转UNKNOWN_RESULT且retryAllowed=false,禁止自动换键/重复上传;GET命令返回原已知状态。现有File没有此业务上传请求键的查找接口,因此不承诺把未知上传自动恢复为成功。 - 绑定时复核File的tenantId=0、两个受控businessType之一、businessId=本单内部dispatchId、fileId、JPG/PNG及大小;绑定成功才标记照片已保存。上传失败不创建新的业务主单;已知成功上传的fileId可保留原请求另行明确恢复绑定,未知文件不伪造已上传。
- 不持派送、库存、Finance或车辆占用写锁;只写本页附加行/命令/历史。源读取与CAS的并发保护需要第7节捕获的稳定装车事实;没有新排车状态机。
- 入口沿现有HTTP200+业务code约定;缺版本/无效金额/非法目的地为参数错误,身份与权限拒绝为403/A0403,缺主单为404/NOT_FOUND,附加版本变化/源事实变化/同键异载荷为409/A0409。普通参数/权限/缺失/版本错误沿项目A0400/A0403/A0404/A0409;上传命令另存OWNED_UPLOAD_UNKNOWN_RESULT诊断码,正文不向用户展示内部ID。
5. 新增结构(根维护实际DDL)
全部在Lastleg schema,MySQL8,内部BIGINT主键;表与列按根要求完整中文COMMENT。新包不写初始化或增量SQL文件。
| 表 | 精确主要字段与约束 |
|---|---|
ll_fleet_owned_business_record |
id BIGINT PK;dispatch_id BIGINT NOT NULL UNIQUE;record_version BIGINT NOT NULL(持久行首次成功为1);currency VARCHAR(3) NULL;total_amount DECIMAL(18,2) NULL;actual_distance_miles DECIMAL(12,3) NULL;fuel_consumption_litres_per_100km DECIMAL(12,3) NULL;arrivals_json JSON NOT NULL;photos_json JSON NOT NULL;created_by/updated_by VARCHAR(64)、created_at/updated_at DATETIME(3)。Money成对、非负与JSON形状由业务层及可实现CHECK约束共同保证 |
ll_fleet_owned_business_command |
id BIGINT PK;dispatch_id BIGINT、idempotency_key VARCHAR(128)联合唯一;另存operator_id VARCHAR(64) NOT NULL;action VARCHAR(32);expected_version BIGINT NULL(上传无需编辑版本);request_hash CHAR(64);request_json JSON;status VARCHAR(24);result_json JSON NULL;error_code VARCHAR(64) NULL;created_at/updated_at DATETIME(3)。保留原请求和原结果,不只保存hash |
ll_fleet_owned_business_history |
id BIGINT PK;dispatch_id BIGINT、record_version BIGINT唯一;action VARCHAR(32)、operator_id VARCHAR(64)、reason VARCHAR(1000) NULL、snapshot_json JSON、occurred_at DATETIME(3);索引(dispatch_id,record_version) |
ll_fleet_owned_business_ownership |
dispatch_id BIGINT PK;vehicle_id BIGINT NULL;resource_mode VARCHAR(16) NOT NULL;vehicle_ownership VARCHAR(16) NOT NULL;vehicle_record_version BIGINT NULL;ownership_captured_at DATETIME(3) NOT NULL;loading_completion_hash VARCHAR(128) NOT NULL;loading_completed_at DATETIME(3) NOT NULL。MASTER读真实VehicleRepository;原dispatch EXTERNAL资源原样保存,并记DISPATCHED及null车辆版本;仅保存归属证据,不复制主单/货物/状态 |
金额、arrival、photo可以用有界JSON存附加行,暂不新增逐目的地/逐图片业务表;历史提供文件旧版本授权依据。并发初建使用唯一键+事务锁,不用未保存的随机业务ID假造记录;主单表不更新。
6. 查询与分层实现
新增包内 controller/OwnedBusinessController、model/OwnedBusinessModels、service/OwnedBusinessQueryService、OwnedBusinessRecordService、OwnedBusinessPhotoService、repository/OwnedBusinessRepository、mapper/OwnedBusinessQueryMapper、OwnedBusinessRecordMapper、OwnedBusinessCommandMapper、OwnedBusinessHistoryMapper 及gateway/OwnedBusinessFileGateway。
所有Mapper显式@Mapper,由既有Lastleg根扫描注册。GET为repeatable-read快照;count/四项统计共用同一SQL过滤片段,再做主单分页,页内目的地、预约及货物使用dispatchId集合批量查询,Mapper局部只读复用真实表,不循环调用排车GET或浏览器逐行补数据。细节API直接给三个页签所需数据,不把旧车次八页签整体搬来。
7. 根负责的精确外部触点
- 已冻结唯一捕获hook:根在DispatchAggregateService.loadingComplete最终确认时点同事务调用本包port.OwnedBusinessOwnershipCapture.capture(long dispatchId, Long vehicleId, String resourceMode, String loadingCompletionHash, Instant loadingCompletedAt)。实现经Repository→Mapper持久化,MASTER只普通读VehicleRepository真实归属/版本,不反向加车辆写锁;EXTERNAL记DISPATCHED。相同原事实捕获重放只返回,异事实拒绝;原命令已成功的回放不补写。
- 旧已装车单:无归属快照不回填、不在GET降级使用当前主档;本页不纳入,原排车读取完全不变。新原生样本用装车同事务快照验收。
- 文件分类(根已明确冻结):
OVERSEAS_DISPATCH_LOADING_PHOTO、OVERSEAS_DISPATCH_FUEL_PHOTO,平台tenantId固定0、businessId为原dispatch内部ID,JPG/PNG、每文件10MiB及内容签名校验。根唯一修改File分类、直接测试和契约;本包新增代理只按此消费,并承担原dispatch可见性、类别及当前/历史引用授权。不复用客户POD、司机、运维或车次运营类别授权,也不把dispatchId冒充tripId。内部HTTP沿/internal/file/v1/business-files/0/{type}/{dispatchId},不将该地址或内部业务ID交浏览器。根共享File接线不阻断本包结构实现。 - IAM/导航/Gateway/SQL:根集成3权限及角色授权、Admin路由菜单、Lastleg及统一基线、Gateway合同。路径已落在现有Lastleg域,但仍由根验证实际路由注册,不推定可达。
- 照片保存语义已冻结:逐槽上传后绑定立即保存;绑定失败保留原fileId和原绑定键恢复,不重复上传;绑定成功才显示已保存,关闭弹框不写数据。
8. 冻结与验证安排
v1所有业务项已冻结。仅根接共享hook、File、权限及4表基线;独占包继续实现,不等待其他无关任务。
本包实现及有意义的定向测试预计2–3小时;根统一排队的编译/Maven、共享接线和环境验收另计。每阶段交付记录在本设计末尾,实际通过仅按根执行证据报告。
实现检查点(根统一验证前)
- port.OwnedBusinessOwnershipCapture及MANDATORY捕获提供者已落源码;根只需现有唯一loadingComplete hook。原dispatch实际模式MASTER/EXTERNAL原样持久化,不引入另一套资源模式枚举。
- 原排车和附加流水由UNION ALL在同一查询排序分页,History区分eventSource和两个可空版本;原排车事件operations=null、photos=[],不复制原流水。
- 四表DML已按根当前lastleg.sql末部字段实现:更新显式写NULL;命令二元唯一且摘要含操作者/action/原版本/请求。源码与直接回归仍在收口,尚未运行任何检查。
根集成所需精确门禁触点
-
scripts/synchronous-dependency-baseline.mjs:新增lastleg-service/.../fleet/ownedbusiness/gateway/HttpOwnedBusinessFileGateway.java→ file-service,REQUEST、READ_WRITE、MIXED。元数据和内容只读;上传无File远端幂等键,本域写前持久化意图、未知停写,不能自动重试。 -
lastleg-service/src/test/resources/architecture/lastleg-slice-dependencies.json:原baseline不允许宽泛fleet→dispatch-delivery,需要逐类只读边评审。本包实际复用为 Models→DispatchViews;Rows/QueryMapper/Repository仅使用本包Source与CargoRow只读投影,不依赖dispatch实体;Projection→公开DispatchContracts;QueryService→公开DispatchDetailReader/DispatchContracts/DispatchTypes。根hook的DispatchAggregateService→OwnedBusinessOwnershipCapture只依赖窄端口,不回调本页查询。后端不得借此访问或修改派送/库存/Finance持久化。 -
OpenAPI新增文档数、Gateway自动发现Controller及Mapper注册合同均由根统一执行,不在本对话重复门禁;实际通过尚待根。
-
新附加历史从可信会话保存operatorName到snapshot;原排车历史只有operator_id、缺少姓名权威时operatorName=null,不把内部ID伪装成姓名。
-
根已冻结读取边界:本包不继承DispatchAggregate,不返回DispatchItem,不引用DispatchAggregateService。Source/CargoRow仅描述原SQL所需字段;详情经新公开DispatchDetailReader提供,根唯一维护适配及精确port依赖基线。
源码候选完成,等待根统一验证
- 新包生产16文件、独占测试包(夹具与7个测试类)、独立OpenAPI已完成首轮实现,未改主单/派送/财务/库存状态机;原4表DDL及hook根独占。
- 直接回归包含H2读取根4表基线、真实MyBatis NULL清空/CAS、并发0版本单胜出、原键重放、非法目的地不落记录、照片替换与历史引用、原主历史合并分页、同筛选四统计、归属捕获随父事务回滚、异账号恢复403及未知上传不重发。只是测试源码交付,尚未执行。
- 根可统一运行test-compile和7个OwnedBusiness*Test,再按影响运行Mapper、Gateway、同步依赖、端口边及OpenAPI门禁;本对话没有启动Maven、任何检查或环境操作。
交根统一验证的冻结输入
- 25路径:生产16、测试8、OpenAPI1;Ordinal路径排序,各行
path|sha256,UTF-8/LF且末尾一个LF。 - 聚合SHA256:
b8d7c7d9108fe0475410f950579879ca5b4e3819d05f08a3b6e90cdc99ac456f。 - 当前35个自有业务测试声明已由根实际执行,结果见下方当前验证记录。
- 根已修正两份DDL的money CHECK;新增真实基线回归覆盖币种NULL/金额非空拒绝、两者NULL合法、明确CNY零金额合法及再次清空。
- 根已登记5条公开port读取边、同步File客户端依赖,并报告静态无包环;本对话未运行门禁。后续仅按根确切失败定点解冻。
lastleg-service/src/main/java/com/baiyue/lastleg/fleet/ownedbusiness/controller/OwnedBusinessController.java|913b4df8cf80f21bc42ea59459ef2a04136ab0d563a0145c4c94e3f233d7fe9a
lastleg-service/src/main/java/com/baiyue/lastleg/fleet/ownedbusiness/gateway/HttpOwnedBusinessFileGateway.java|4e2bbbd5292df8d15cb689eabf57d270f11c177c4503dc44e73387398cf1d3fd
lastleg-service/src/main/java/com/baiyue/lastleg/fleet/ownedbusiness/gateway/OwnedBusinessFileGateway.java|a6a3baedfa380dde6f0c3e9a39d6a7f5bf2fdf9e2a4e9b47f8d989ee4f546cfc
lastleg-service/src/main/java/com/baiyue/lastleg/fleet/ownedbusiness/mapper/OwnedBusinessMapper.java|6317c3428f10b2ccb6f3b71135e117e01e3da76ad742e4e1cb37f74caf808c60
lastleg-service/src/main/java/com/baiyue/lastleg/fleet/ownedbusiness/mapper/OwnedBusinessQueryMapper.java|d36deb1f2840c103e96de1aab867b03aa80bed9e1f755778afa91db564a037d8
lastleg-service/src/main/java/com/baiyue/lastleg/fleet/ownedbusiness/model/OwnedBusinessModels.java|c2c7a953c9c9cacb57b059daf6134828d160e0351be97d27e231b7c821a32c1e
lastleg-service/src/main/java/com/baiyue/lastleg/fleet/ownedbusiness/model/OwnedBusinessRows.java|5d31468f7c5f3f177fe7aef4063bc8f5e71b3f37f56e0b49beeb531ffb16277a
lastleg-service/src/main/java/com/baiyue/lastleg/fleet/ownedbusiness/port/OwnedBusinessOwnershipCapture.java|f354de8142cbab49a7d42ab6837c2ba836b4940560dbabb100b0642cadd05b08
lastleg-service/src/main/java/com/baiyue/lastleg/fleet/ownedbusiness/repository/OwnedBusinessRepository.java|27c7cf185e65907923bbbf18a05f10bdfa5e091da608e44a798600a061917ad5
lastleg-service/src/main/java/com/baiyue/lastleg/fleet/ownedbusiness/service/OwnedBusinessCommandService.java|ac507f63245436cb31a2b99001d7c5c7ff9ae2938e7417d795ae17c18cf462e8
lastleg-service/src/main/java/com/baiyue/lastleg/fleet/ownedbusiness/service/OwnedBusinessOwnershipService.java|e1a6d10ab69d44915bbff958a28df970cbdd26b6bb19c83118877c29d09e8868
lastleg-service/src/main/java/com/baiyue/lastleg/fleet/ownedbusiness/service/OwnedBusinessPhotoService.java|e951d0314f66e4b9f5ad17fa70e7c413cc5c6a74eee6ece560223fc1b5787658
lastleg-service/src/main/java/com/baiyue/lastleg/fleet/ownedbusiness/service/OwnedBusinessProjection.java|0a3a2ddb6368add9af5696f119fdbe31a1e807edcfc34a4fb0487b60d11d3cbf
lastleg-service/src/main/java/com/baiyue/lastleg/fleet/ownedbusiness/service/OwnedBusinessQueryService.java|39512a8321dc15e88080570a138ea8c8b56f8ff834c5bc3ada3db4c173b8a586
lastleg-service/src/main/java/com/baiyue/lastleg/fleet/ownedbusiness/service/OwnedBusinessRecordService.java|09a6af10f9bd4ecba2eb50ecd7b338c9b01498bf7ac397a785e5f13ca63669b6
lastleg-service/src/main/java/com/baiyue/lastleg/fleet/ownedbusiness/service/OwnedBusinessSupport.java|e6f71eca2ecb7dd1a1803a5600aa954a7517091c2f1e7c7678b743ee591a20b7
lastleg-service/src/main/resources/openapi/fleet-owned-business-v1.yaml|34ced2f1cde024701bc9900fd0248de875d2500c47b817aab42d7edc91183364
lastleg-service/src/test/java/com/baiyue/lastleg/fleet/ownedbusiness/OwnedBusinessCommandTest.java|562ef97a01e96ad8e7e951c9ac01f175caa4ee2e1b2b4dca766b9b24902ff524
lastleg-service/src/test/java/com/baiyue/lastleg/fleet/ownedbusiness/OwnedBusinessFileGatewayTest.java|df97ee090d18b8f1663715275ee18135a0bc81061edab3dd3042c35ae91a2148
lastleg-service/src/test/java/com/baiyue/lastleg/fleet/ownedbusiness/OwnedBusinessFixtures.java|dca8eb00914c23e9eec9b776a1800f063e2189e7d3a4b16190d04f35fba8d341
lastleg-service/src/test/java/com/baiyue/lastleg/fleet/ownedbusiness/OwnedBusinessOwnershipTest.java|6f22c03e61a80b8f238253645510b3f941de6ec11f18e2c5df49c8a78ed14eb2
lastleg-service/src/test/java/com/baiyue/lastleg/fleet/ownedbusiness/OwnedBusinessPersistenceTest.java|97836b6665b90186e74a17ec26ffbd2d71238ec7a7fc64d3a091e8bfe45ada08
lastleg-service/src/test/java/com/baiyue/lastleg/fleet/ownedbusiness/OwnedBusinessPhotoTest.java|b4e5f907ce6c56ac7a382788685b24f5989dd018120d0d77739f0af33bb543a2
lastleg-service/src/test/java/com/baiyue/lastleg/fleet/ownedbusiness/OwnedBusinessProjectionTest.java|412711599c45a9d547397087bf9090f48ff3efa44226af48fe41452f9b732358
lastleg-service/src/test/java/com/baiyue/lastleg/fleet/ownedbusiness/OwnedBusinessQueryTest.java|12ab10a71a61ca24556238ea2ae9ce7f6321a242b426c7171a67204db03ce3e6
当前验证记录
- Projection仅将DispatchContracts星号import展开为ConfirmedLoad/Resources两条显式import;业务方法未变。逆向还原import后精确得到原45f60872… SHA,根Maven覆盖当前显式import字节。
- 根报告session24317 BUILD SUCCESS:65项、0失败、0错误、0跳过;其中自有业务35、原DispatchAggregatePersistence24、只读适配1、Mapper2、中文schema3。本对话未执行Maven。
- 本轮只读YAML/JSON Pointer核对:48个唯一可达引用,读取2份直接相关OpenAPI,未解析引用0个。DispatchDetail对应DispatchViews.Detail的13字段、required集合及四块结构一致。这不代替根的完整公开API/权限门禁,也不代表部署或HTTP通过。
照片历史中文文案定点修正(待根复验)
- 仅OwnedBusinessRecordService和OwnedBusinessPersistenceTest解冻:7个照片槽位保存原因改为准确中文,现有真实Mapper绑定/替换历史断言均核对中文。未改状态、版本、接口或其他源码。
- 上方65项通过仅覆盖此前候选;本次新两文件输入未由本对话执行检查,待根直接受影响回归,不能扩展旧结果。
multipart文档定点修正及计划发车非空核实
- 仅独立OpenAPI解冻:上传idempotencyKey从query移到multipart requestBody,与file共同required,长度8–128保持;Controller、Java DTO、前端及业务逻辑均未改。
- plannedDepartureAt保留非空string:DispatchAggregateService.loadingComplete在写装车完成及捕获归属前调用verifyReviewResources;该方法明确拒绝row.plannedDepartureAt=null(原方法约602行)。原编辑入口requireEditing仅允许BEFORE_LOADING且零扫码,装车后不能经此清空计划发车。自有视图只纳入有新装车归属快照的三个阶段,因此正常受控写路径已保证非空;不新增业务必填门禁,不要求前端类型改为nullable。缺失/损坏存量不作为兼容依据,原无快照TEST仍不回填。
- 本次只读核对并修改文档;公开OpenAPI/权限门禁由根安排,未运行检查或环境HTTP。