feat(fleet): enforce reviewed trips and original fee coverage for dispatch

This commit is contained in:
2026-10-03 04:18:52 +08:00
parent c50e9f97d1
commit 5e683da3b9
162 changed files with 9642 additions and 218 deletions
@@ -5,7 +5,7 @@ info:
description: |
平台资料入口的customerId必须为真实派送所属客户;客户只读入口范围只取Token,只公开当前派送单已保存的RECEIPT签收文件,不提供上传、修改、历史版本、BOL或装车照片下载。
GET /delivery-orders 及 GET /delivery-orders/{deliveryNo} 的 order 行新增 bolNo 和 documentSummary,version/allowedActions 保持原契约。
RECEIPT 签收文件只有在 PUT 保存进入权威资料快照后才自动完成当前派送单;仅上传未保存不会完成。allowedActions 不再返回人工 COMPLETE_DELIVERY。
RECEIPT 签收文件只有在关联出库单已OUTBOUNDED、PUT保存进入权威资料快照后才自动完成当前派送单;仅上传未保存不会完成。发车前可保存BOL及装车资料,不能通过提前保存签收文件绕过实际出库。allowedActions 不再返回人工 COMPLETE_DELIVERY。
START_DELIVERY=DELIVERING(派送中);COMPLETE_DELIVERY=DELIVERED(完成并记录签收时间、生成待回访)。同状态重放不重复副作用。
资料不设必传门禁,不以三类齐全作为派送完成条件。业务失败使用统一 ApiResponse(A0422参数、A0409冲突),通常HTTP200。
security: [{ bearerAuth: [] }]
@@ -0,0 +1,310 @@
openapi: 3.0.3
info:
title: 平台车辆管理司机档案
version: 1.0.0
description: >-
平台司机主数据,列表权限包含详情、资格预览、历史和已关联证照读取。
写操作独立鉴权且要求当前recordVersion;创建版本0,后续每次成功写入递增。
证照或身份关键字段变化、替换附件后重新待审;电话和备注更正不撤销既有审核。
驾驶证是当前资格最低要求,其他资质不隐式必填。日期按licenseZone计算,
30天内到期但仍有效允许并提醒,任务结束达到有效截止日次日当地零点或更晚则禁止。
缺少预计结束时间时资格返回MISSING_TRIP_END;资格预览不预留司机,不代替发车事务复核。
新档案允许缺证照保存为PENDING,通过审核必须补齐编号、类型、有效期和真实附件。
所有业务错误遵循HTTP200加code,认证或基础设施错误可返回401/403/503。
security:
- bearerAuth: []
paths:
/api/platform/v1/last-leg/fleet/drivers:
get:
summary: 分页查询司机及资格
description: '权限:platform:overseas-master-data:driver:list;无逐行文件请求。'
parameters:
- {name: page, in: query, schema: {type: integer, minimum: 1, default: 1}}
- {name: size, in: query, schema: {type: integer, minimum: 1, maximum: 200, default: 20}}
- {name: keyword, in: query, schema: {type: string, maxLength: 128}}
- {name: active, in: query, schema: {type: boolean}}
- {name: reviewStatus, in: query, schema: {type: string, enum: [PENDING, APPROVED, REJECTED]}}
- {name: ownership, in: query, schema: {type: string, enum: [OWNED, DISPATCHED]}}
- {name: region, in: query, schema: {type: string, enum: [DOMESTIC, OVERSEAS]}}
- {name: licenseState, in: query, schema: {type: string, enum: [MISSING, VALID, EXPIRING, EXPIRED]}, description: 仅驾驶证日期状态,与审核和附件状态分别展示}
- {name: expectedEnd, in: query, schema: {type: string, format: date-time}}
responses:
'200':
description: 业务响应,分页数据
content:
application/json:
schema:
allOf:
- {$ref: '#/components/schemas/Envelope'}
- properties:
data:
type: object
properties:
records: {type: array, items: {$ref: '#/components/schemas/Driver'}}
page: {type: integer}
size: {type: integer}
total: {type: integer}
pages: {type: integer}
summary: {$ref: '#/components/schemas/Summary'}
post:
summary: 创建待审司机档案
description: '权限:platform:overseas-master-data:driver:create'
requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/Draft'}}}}
responses: {'200': {$ref: '#/components/responses/Driver'}}
/api/platform/v1/last-leg/fleet/drivers/warehouse-options:
get:
summary: 获取当前区域可关联的公共仓库
description: '权限:platform:overseas-master-data:driver:list;复用Warehouse内部有效目录,国内DOMESTIC,海外OVERSEAS/OVERSEAS_PARTNER,排除FBA目的仓;保存时再次校验。'
parameters:
- {name: region, in: query, required: true, schema: {type: string, enum: [DOMESTIC, OVERSEAS]}}
responses:
'200':
description: 业务响应,data为仓库候选
content:
application/json:
schema:
allOf:
- {$ref: '#/components/schemas/Envelope'}
- properties:
data: {type: array, items: {$ref: '#/components/schemas/WarehouseOption'}}
/api/platform/v1/last-leg/fleet/drivers/{id}:
parameters: [{$ref: '#/components/parameters/Id'}]
get:
summary: 司机详情
description: '权限:platform:overseas-master-data:driver:list'
parameters:
- {name: expectedEnd, in: query, schema: {type: string, format: date-time}}
responses: {'200': {$ref: '#/components/responses/Driver'}}
put:
summary: 编辑档案,关键字段变化后重新待审
description: '权限:platform:overseas-master-data:driver:edit'
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [recordVersion, details]
properties:
recordVersion: {type: integer, format: int64, minimum: 0}
details: {$ref: '#/components/schemas/Draft'}
responses: {'200': {$ref: '#/components/responses/Driver'}}
/api/platform/v1/last-leg/fleet/drivers/{id}/status:
parameters: [{$ref: '#/components/parameters/Id'}]
patch:
summary: 启停司机并记录原因
description: '权限:platform:overseas-master-data:driver:status;停用后不可派车,历史保留。'
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [recordVersion, active, reason]
properties:
recordVersion: {type: integer, format: int64, minimum: 0}
active: {type: boolean}
reason: {type: string, minLength: 1, maxLength: 1000}
responses: {'200': {$ref: '#/components/responses/Driver'}}
/api/platform/v1/last-leg/fleet/drivers/{id}/review:
parameters: [{$ref: '#/components/parameters/Id'}]
post:
summary: 通过或打回司机证照
description: '权限:platform:overseas-master-data:driver:review;仅PENDING允许审核;打回原因必填;通过前校验真实附件归属与证照有效性。'
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [recordVersion, decision]
properties:
recordVersion: {type: integer, format: int64, minimum: 0}
decision: {type: string, enum: [APPROVED, REJECTED]}
reason: {type: string, maxLength: 1000}
responses: {'200': {$ref: '#/components/responses/Driver'}}
/api/platform/v1/last-leg/fleet/drivers/{id}/submit:
parameters: [{$ref: '#/components/parameters/Id'}]
post:
summary: 已打回档案重新提交
description: '权限:platform:overseas-master-data:driver:edit;仅REJECTED;先校验证照完整及文件归属。'
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [recordVersion]
properties:
recordVersion: {type: integer, format: int64, minimum: 0}
responses: {'200': {$ref: '#/components/responses/Driver'}}
/api/platform/v1/last-leg/fleet/drivers/{id}/qualification:
parameters: [{$ref: '#/components/parameters/Id'}]
get:
summary: 按预计任务结束时间预览派车资格
description: '权限:platform:overseas-master-data:driver:list;预计结束应覆盖本司机承担的返程或转场。'
parameters:
- {name: expectedEnd, in: query, required: true, schema: {type: string, format: date-time}}
responses:
'200':
description: 资格结果,canDispatch与expiringSoon独立
content:
application/json:
schema:
allOf:
- {$ref: '#/components/schemas/Envelope'}
- properties:
data: {$ref: '#/components/schemas/Qualification'}
/api/platform/v1/last-leg/fleet/drivers/{id}/history:
parameters: [{$ref: '#/components/parameters/Id'}]
get:
summary: 分页读取司机操作与审核历史
description: '权限:platform:overseas-master-data:driver:list;按版本降序,snapshot是历史当时的档案及资格,不能用作当前派车门禁。'
parameters:
- {name: page, in: query, schema: {type: integer, minimum: 1, default: 1}}
- {name: size, in: query, schema: {type: integer, minimum: 1, maximum: 200, default: 20}}
responses:
'200':
description: 业务响应,data为含records/page/size/total/pages的分页
content:
application/json:
schema:
allOf:
- {$ref: '#/components/schemas/Envelope'}
- properties:
data:
type: object
properties:
records: {type: array, items: {$ref: '#/components/schemas/History'}}
page: {type: integer}
size: {type: integer}
total: {type: integer}
pages: {type: integer}
/api/platform/v1/last-leg/fleet/drivers/{id}/license:
parameters: [{$ref: '#/components/parameters/Id'}]
post:
summary: 上传并绑定驾驶证,重置为待审
description: '权限:platform:overseas-master-data:driver:upload;复合归属固定平台0、FLEET_DRIVER_LICENSE、当前司机;非空PDF/JPG/PNG不超过10MiB,校验MIME和签名,旧版本附件保留在历史中。'
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required: [recordVersion, file]
properties:
recordVersion: {type: integer, format: int64, minimum: 0}
file: {type: string, format: binary}
responses: {'200': {$ref: '#/components/responses/Driver'}}
/api/platform/v1/last-leg/fleet/drivers/{id}/license/{fileId}:
parameters:
- {$ref: '#/components/parameters/Id'}
- {name: fileId, in: path, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
get:
summary: 下载当前或历史已关联驾驶证
description: '权限:platform:overseas-master-data:driver:list;校验关联和File复合归属,attachment下载,Cache-Control no-store,拒绝未绑定文件。'
responses:
'200':
description: 文件内容;业务错误仍需识别JSON响应
content:
application/pdf: {schema: {type: string, format: binary}}
image/jpeg: {schema: {type: string, format: binary}}
image/png: {schema: {type: string, format: binary}}
components:
securitySchemes:
bearerAuth: {type: http, scheme: bearer, bearerFormat: JWT}
parameters:
Id: {name: id, in: path, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
responses:
Driver:
description: 业务响应,data为司机详情
content:
application/json:
schema:
allOf:
- {$ref: '#/components/schemas/Envelope'}
- properties:
data: {$ref: '#/components/schemas/Driver'}
schemas:
Envelope:
type: object
properties:
code: {type: string}
message: {type: string, nullable: true}
data: {type: object, nullable: true}
Draft:
type: object
required: [name, phone, ownership, region, licenseZone]
properties:
name: {type: string, minLength: 1, maxLength: 128}
phone: {type: string, minLength: 1, maxLength: 64}
ownership: {type: string, enum: [OWNED, DISPATCHED]}
region: {type: string, enum: [DOMESTIC, OVERSEAS]}
licenseNumber: {type: string, nullable: true, maxLength: 128}
licenseType: {type: string, nullable: true, maxLength: 64}
licenseIssuedOn: {type: string, format: date, nullable: true}
licenseExpiresOn: {type: string, format: date, nullable: true}
licenseZone: {type: string, maxLength: 64, description: IANA时区,如America/Los_Angeles}
remark: {type: string, nullable: true, maxLength: 1000}
profile: {$ref: '#/components/schemas/Profile'}
Profile:
type: object
nullable: true
description: 可选补充资料;当前其他资质日期不自动作为派车必需条件
properties:
warehouseId: {type: string, nullable: true, pattern: '^[1-9][0-9]*$'}
vehicleModels: {type: string, nullable: true, maxLength: 128}
languagesAndArea: {type: string, nullable: true, maxLength: 256}
medicalExpiresOn: {type: string, format: date, nullable: true}
trainingExpiresOn: {type: string, format: date, nullable: true}
insuranceExpiresOn: {type: string, format: date, nullable: true}
WarehouseOption:
type: object
properties:
id: {type: string}
code: {type: string}
name: {type: string}
region: {type: string, enum: [DOMESTIC, OVERSEAS]}
Summary:
type: object
description: 与列表共用筛选范围,按各证照时区计算30天(含)提醒,不以浏览器时区计算
properties:
total: {type: integer, format: int64}
owned: {type: integer, format: int64}
dispatched: {type: integer, format: int64}
expiringSoon: {type: integer, format: int64}
expired: {type: integer, format: int64}
Driver:
type: object
properties:
id: {type: string}
driverNo: {type: string, description: 独立生成的业务编号}
details: {$ref: '#/components/schemas/Draft'}
active: {type: boolean}
reviewStatus: {type: string, enum: [PENDING, APPROVED, REJECTED]}
licenseFileId: {type: string, nullable: true}
recordVersion: {type: integer, format: int64}
updatedBy: {type: string}
updatedAt: {type: string, description: UTC本地日期时间}
qualification: {$ref: '#/components/schemas/Qualification'}
licenseState: {type: string, enum: [MISSING, VALID, EXPIRING, EXPIRED]}
warehouseCode: {type: string, nullable: true}
warehouseName: {type: string, nullable: true}
Qualification:
type: object
properties:
canDispatch: {type: boolean}
expiringSoon: {type: boolean}
reason:
type: string
enum: [ELIGIBLE, INACTIVE, MISSING_LICENSE, MISSING_EXPIRY, PENDING_REVIEW, REJECTED, EXPIRED, MISSING_TRIP_END, INVALID_TRIP_END, EXPIRES_DURING_TRIP]
licenseExpiresOn: {type: string, format: date, nullable: true}
History:
type: object
properties:
recordVersion: {type: integer, format: int64}
action: {type: string, enum: [CREATE, UPDATE, ACTIVATE, DEACTIVATE, UPLOAD_LICENSE, APPROVED, REJECTED, RESUBMIT]}
reason: {type: string, nullable: true}
operatorId: {type: string}
occurredAt: {type: string, description: UTC本地日期时间}
snapshot: {$ref: '#/components/schemas/Driver'}
@@ -0,0 +1,649 @@
openapi: 3.0.3
info:
title: 平台运输车次与资源分配
version: 1.0.0
description: >-
车次是一次实际运输的稳定业务编号,客户排车、库存和派送单继续保留各自客户归属。
本文件定义计划、资源分配及客户排车单关联命令;分配或关联不会扣库存或标记发车。
创建可暂不指定车与司机,幂等键按创建人隔离;相同键相同内容返回同一车次,不同内容拒绝。
正式分配要求在役海外车辆及已确认的托位/载重/容积,以及海外司机;司机按预计任务结束时间重新核验且验证证照文件。
发车预览与实际发车重新核对司机当前海外区域和身份;分配后档案变更不能沿用原分配时的资格结果。
一个车辆或司机同时只能占用一个未释放车次;当前不提供多个未来时段的并行预约。
改派先取得新资源,成功后释放旧资源;整次写入与历史处于同一事务。
司机预计结束包含承担的返程/转场;修改预计结束须重新核验司机,所有写命令使用recordVersion。
普通计划接口不提供修改发车或签收状态的入口;PDA统一发车,结案检查所有客户派送终态及实发数量。
货物结案不释放车辆或司机;结束各资源任务独立提交实际结束时间、地点和原因,车辆另确认可用/维修/停用/已归还状态。
结案和结束任务命令携带版本及幂等键;结束司机任务不要求其证照仍有效,也不自动恢复其派车资格。
关联要求已分配车和司机,客户排车单同起运仓且未开始拣货;可跨客户关联但不改变客户数据归属。
同一排车单唯一关联一个车次;关联、改派、解除及尚未执行的派送单车/司机快照同步使用同一事务。
关联后子排车不能独立编辑、取消或单独发车;取消车次前须解除关联,已开工不允许直接解除或变更路线。
发车前允许在车次统一换车、换司机或调整时间,保留全部拣货事实与客户归属;已确认装载须能容纳于替换车辆。
改派或调整计划使原装载确认失效,发车前须重新确认;不支持用计划接口处理在途换车或撤销已执行货物。
关联只建立执行单据关系,不代表实际装载量、容量核验或审核已完成。
每张客户排车单调度审核通过后允许新拣货;整车确认实际装载并通过理货审核后允许发车。
审核仅通过或打回,通过意见选填、打回原因必填;无需额外提交或不同审核人。
换车换司机、修改路线或时间使调度与理货审核待审;仅修改备注保留审核及装载。
关联变更、重新确认装载或重新审核客户排车单使理货审核待审;保留全部拣货事实及历史审核快照。
security:
- bearerAuth: []
paths:
/api/platform/v1/last-leg/fleet/trips:
get:
summary: 分页查询车次
description: '权限:platform:fleet:trip:list'
parameters:
- {name: page, in: query, schema: {type: integer, minimum: 1, default: 1}}
- {name: size, in: query, schema: {type: integer, minimum: 1, maximum: 200, default: 20}}
- {name: keyword, in: query, schema: {type: string, maxLength: 128}, description: 车次号/车牌/司机}
- {name: status, in: query, schema: {type: string, enum: [PLANNED, IN_TRANSIT, COMPLETED, CANCELLED]}}
- {name: warehouseId, in: query, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
responses:
'200':
description: 统一业务响应,data包含records/page/size/total/pages;不逐行请求司机详情
content:
application/json:
schema:
allOf:
- {$ref: '#/components/schemas/Envelope'}
- properties:
data:
type: object
properties:
records: {type: array, items: {$ref: '#/components/schemas/Trip'}}
page: {type: integer}
size: {type: integer}
total: {type: integer}
pages: {type: integer}
post:
summary: 幂等创建车次计划
description: '权限:platform:fleet:trip:create;起运仓使用公共有效海外仓目录;不推断GPS路线。'
requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/Create'}}}}
responses: {'200': {$ref: '#/components/responses/Detail'}}
/api/platform/v1/last-leg/fleet/trips/customer-options:
get:
summary: 查询有效委托客户候选
description: '权限:platform:fleet:trip:list;读取IAM当前客户主档;不使用采购历史或允许输入不存在的客户。'
parameters: [{name: keyword, in: query, schema: {type: string}}]
responses: {'200': {description: 统一响应data为客户id/code/name/contactName/contactPhone数组}}
/api/platform/v1/last-leg/fleet/trips/{tripNo}/bookings:
parameters: [{$ref: '#/components/parameters/TripNo'}]
get:
summary: 查询本车外接询价与承运预订
description: '权限:platform:fleet:trip:list;返回tripRecordVersion及records;约定金额不是应收或实收。'
responses: {'200': {description: 统一响应data为版本及Booking数组}}
post:
summary: 登记外接询价
description: '权限:platform:fleet:trip:booking-create;DRAFT不占位。客户由IAM校验,报价和预计载荷可待确认再补;起点固定本起运仓,卸货站为既有路线站点。相同创建键与内容重放原预订。'
requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/BookingCreate'}}}}
responses: {'200': {$ref: '#/components/responses/BookingResult'}}
/api/platform/v1/last-leg/fleet/trips/{tripNo}/bookings/{bookingNo}:
parameters: [{$ref: '#/components/parameters/TripNo'}, {$ref: '#/components/parameters/BookingNo'}]
put:
summary: 编辑尚未确认的外接询价
description: '权限:platform:fleet:trip:booking-edit;仅DRAFT;客户与预订业务编号不变,更新前核对车次及预订版本。'
requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/BookingUpdate'}}}}
responses: {'200': {$ref: '#/components/responses/BookingResult'}}
/api/platform/v1/last-leg/fleet/trips/{tripNo}/bookings/{bookingNo}/confirm:
parameters: [{$ref: '#/components/parameters/TripNo'}, {$ref: '#/components/parameters/BookingNo'}]
post:
summary: 确认外接承运并占用计划容量
description: '权限:platform:fleet:trip:booking-confirm;父车次锁内核对来源仍有效的自有货容量计划及全部已确认预订;报价/币种/预计托位重量体积完整。幂等重放返回当前预订事实,不重复占位;确认不代表已收货、已发运或已入财务。'
requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/BookingCommand'}}}}
responses: {'200': {$ref: '#/components/responses/BookingResult'}, '409': {description: 版本、资格或容量不满足}}
/api/platform/v1/last-leg/fleet/trips/{tripNo}/bookings/{bookingNo}/incoming:
parameters: [{$ref: '#/components/parameters/TripNo'}, {$ref: '#/components/parameters/BookingNo'}]
get:
summary: 查询外接来货及实际测量依据
description: '权限:platform:fleet:trip:list;固定预订客户范围,返回tripRecordVersion、recordVersion与incoming;尚未登记时incoming为null,不逐行预取。'
responses: {'200': {description: 统一响应data为版本及IncomingEvidence}}
post:
summary: 为已确认承运生成真实来货单
description: '权限:platform:fleet:trip:booking-edit;复用仓储转运创建/审核,在同一事务建立客户自送来货并关联预订;不写库存,不冒充实收;已有来货拒绝重复创建,同键同内容重放。'
requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/BookingIncoming'}}}}
responses: {'200': {$ref: '#/components/responses/BookingResult'}}
/api/platform/v1/last-leg/fleet/trips/{tripNo}/bookings/{bookingNo}/receipt-confirmation:
parameters: [{$ref: '#/components/parameters/TripNo'}, {$ref: '#/components/parameters/BookingNo'}]
post:
summary: 运营确认委托方实收货量与运费
description: '权限:platform:fleet:trip:booking-confirm;实际来货已完成测量,receiptHash和实托集合一致,重新核算容量和约定金额,引用真实收货证据并确认原来货流程;不重复入库。差异必须显式确认,超容量回滚。'
requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/BookingAcceptReceipt'}}}}
responses: {'200': {$ref: '#/components/responses/BookingResult'}}
/api/platform/v1/last-leg/fleet/trips/{tripNo}/bookings/{bookingNo}/dispatch:
parameters: [{$ref: '#/components/parameters/TripNo'}, {$ref: '#/components/parameters/BookingNo'}]
post:
summary: 将外接实收货物关联客户排车
description: '权限:同时要求platform:fleet:trip:booking-edit和platform:fleet:trip:edit;排车必须同客户同仓,完整包含预订来货的实收托盘与数量且目的地一致;原子绑定共享车次,防止绕过预订挂到其他车次。解除未开工关联后回到RECEIVED并重新预留容量,已拣货不允许直接解除。'
requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/BookingBindDispatch'}}}}
responses: {'200': {$ref: '#/components/responses/BookingResult'}}
/api/platform/v1/last-leg/fleet/trips/{tripNo}/bookings/{bookingNo}/cancel:
parameters: [{$ref: '#/components/parameters/TripNo'}, {$ref: '#/components/parameters/BookingNo'}]
post:
summary: 取消未执行的外接预订并释放容量
description: '权限:platform:fleet:trip:booking-cancel;仅计划中车次的DRAFT/CONFIRMED/RECEIVED;只释放承运预留,不删除来货或库存;同键同内容重放不重复释放,历史保留报价及承运确认。'
requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/BookingCommand'}}}}
responses: {'200': {$ref: '#/components/responses/BookingResult'}}
/api/platform/v1/last-leg/fleet/trips/{tripNo}:
parameters: [{$ref: '#/components/parameters/TripNo'}]
get:
summary: 查询车次详情和当前司机资格
description: '权限:platform:fleet:trip:list;司机资格仅是详情预览,实际命令必须重新核验。'
responses: {'200': {$ref: '#/components/responses/Detail'}}
/api/platform/v1/last-leg/fleet/trips/{tripNo}/dispatches/{dispatchNo}/review:
parameters:
- {$ref: '#/components/parameters/TripNo'}
- {name: dispatchNo, in: path, required: true, schema: {type: string}}
post:
summary: 通过或打回单张客户排车调度审核
description: '权限:platform:fleet:trip:dispatch-review;仅PLANNED;审核所属车次内的排车单,成功后理货审核待审;审批不扣库存。'
requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/Review'}}}}
responses: {'200': {$ref: '#/components/responses/Detail'}, '409': {description: 版本、关联或资格冲突}}
/api/platform/v1/last-leg/fleet/trips/{tripNo}/loading-review:
parameters: [{$ref: '#/components/parameters/TripNo'}]
post:
summary: 通过或打回整车理货审核
description: '权限:platform:fleet:trip:loading-review;仅PLANNED;通过要求全部调度审核、拣货和实际装载确认已完成,当前司机资格有效。'
requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/Review'}}}}
responses: {'200': {$ref: '#/components/responses/Detail'}, '409': {description: 版本、调度审核、装载或资格冲突}}
/api/platform/v1/last-leg/fleet/trips/{tripNo}/plan:
parameters: [{$ref: '#/components/parameters/TripNo'}]
put:
summary: 修改计划时间和路线
description: '权限:platform:fleet:trip:edit;仅PLANNED;司机预计结束变更重新核验;原因必填。已开始拣货时路线须完全一致,仅可调整时间和备注;调整时间使审核及装载确认失效,仅修改备注保留。'
requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/Plan'}}}}
responses: {'200': {$ref: '#/components/responses/Detail'}}
/api/platform/v1/last-leg/fleet/trips/{tripNo}/assignment:
parameters: [{$ref: '#/components/parameters/TripNo'}]
post:
summary: 分配或更换车辆与司机
description: '权限:platform:fleet:trip:assign;仅PLANNED且全部关联单未发车、未出库;允许已拣货时统一改派并同步派送快照,保留拣货和到达时间。替换车辆须满足已确认装载容量,成功后清除原装载确认并重新核验;行锁和唯一占用键防重复派车,资格校验不得通过人工审核绕过。'
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [recordVersion, vehicleId, driverId, reason]
properties:
recordVersion: {type: integer, format: int64, minimum: 0}
vehicleId: {type: string, pattern: '^[1-9][0-9]*$'}
driverId: {type: string, pattern: '^[1-9][0-9]*$'}
reason: {type: string, minLength: 1, maxLength: 1000}
responses: {'200': {$ref: '#/components/responses/Detail'}}
/api/platform/v1/last-leg/fleet/trips/{tripNo}/cancel:
parameters: [{$ref: '#/components/parameters/TripNo'}]
post:
summary: 取消计划并释放车和司机
description: '权限:platform:fleet:trip:cancel;仅PLANNED;保留车次快照和历史。已执行货物后续须按实际执行状态校验,不允许取消抹除作业事实。'
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [recordVersion, reason]
properties:
recordVersion: {type: integer, format: int64, minimum: 0}
reason: {type: string, minLength: 1, maxLength: 1000}
responses: {'200': {$ref: '#/components/responses/Detail'}}
/api/platform/v1/last-leg/fleet/trips/{tripNo}/dispatches/bind:
parameters: [{$ref: '#/components/parameters/TripNo'}]
post:
summary: 将客户排车单关联到车次
description: '权限:platform:fleet:trip:edit;按业务单号解析客户归属,同仓且未开工;关联和车/司机快照同步原子提交。'
requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/BindingCommand'}}}}
responses: {'200': {$ref: '#/components/responses/Detail'}}
/api/platform/v1/last-leg/fleet/trips/{tripNo}/dispatches/unbind:
parameters: [{$ref: '#/components/parameters/TripNo'}]
post:
summary: 解除尚未执行的客户排车单关联
description: '权限:platform:fleet:trip:edit;清除排车及派送单当前车辆司机快照,历史保留;已拣货禁止直接解除。'
requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/BindingCommand'}}}}
responses: {'200': {$ref: '#/components/responses/Detail'}}
/api/platform/v1/last-leg/fleet/trips/{tripNo}/history:
parameters: [{$ref: '#/components/parameters/TripNo'}]
get:
summary: 分页读取车次操作历史
description: '权限:platform:fleet:trip:list;版本倒序;不将历史快照当作当前资格。'
parameters:
- {name: page, in: query, schema: {type: integer, minimum: 1, default: 1}}
- {name: size, in: query, schema: {type: integer, minimum: 1, maximum: 200, default: 20}}
responses:
'200':
description: 统一响应;data为PageResult,records项包含recordVersion/action/reason/operatorId/occurredAt/snapshot,snapshot包含trip、带review的dispatches、capacityPlan、confirmedLoad、completion及loadingReview、externalBookings不可变快照
/api/platform/v1/last-leg/fleet/trips/{tripNo}/capacity-plan:
parameters: [{$ref: '#/components/parameters/TripNo'}]
get:
summary: 读取拣货前计划载荷和逐段余位
description: '权限:platform:fleet:trip:list;按当前关联货物返回manifestHash;planValid为false表示尚未核对或来源已经变化,不能据旧计划承诺外接运力。'
responses:
'200': {description: 统一响应data为Planning, content: {application/json: {schema: {$ref: '#/components/schemas/PlanningEnvelope'}}}}
post:
summary: 核对本车货物计划载荷
description: '权限:platform:fleet:trip:edit;仅PLANNED;允许尚未拣货或审核,通过实托去重及逐段容量检查。保存计划不推进审核、库存或发车,不代替实际装载确认;调整计划/资源/关联使容量计划失效,纯备注保留。'
requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/ConfirmLoad'}}}}
responses:
'200': {description: 已保存容量计划, content: {application/json: {schema: {$ref: '#/components/schemas/PlanningEnvelope'}}}}
'409': {description: 版本、来源变化或容量超限}
/api/platform/v1/last-leg/fleet/trips/{tripNo}/loading:
parameters: [{$ref: '#/components/parameters/TripNo'}]
get:
summary: 读取整车实物装载核对依据
description: '权限:platform:fleet:trip:list;返回各客户货物组成、manifestHash和当前装载确认,实际重量不得使用计费材积重。'
responses:
'200': {description: 统一响应data为Loading, content: {application/json: {schema: {$ref: '#/components/schemas/LoadingEnvelope'}}}}
post:
summary: 确认整车实际装载及逐段容量
description: '权限:platform:fleet:trip:edit;全部客户拣货核平且转派送后确认;同一实物托盘只计量一次。货物hash或车次版本变化拒绝,托盘卸货站须匹配原货物目的地。'
requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/ConfirmLoad'}}}}
responses:
'200': {description: 已确认装载快照,统一响应data为Loading, content: {application/json: {schema: {$ref: '#/components/schemas/LoadingEnvelope'}}}}
/api/platform/v1/last-leg/fleet/trips/{tripNo}/completion:
parameters: [{$ref: '#/components/parameters/TripNo'}]
get:
summary: 核对各客户货物是否全部结束履约
description: '权限:platform:fleet:trip:list;返回tripNo、recordVersion、canComplete、blockReason、outcomes。签收以signedAt为准,退回须有RETURN_COMPLETED操作事实,不以updatedAt代替实际终态时间。'
responses:
'200': {description: 统一响应data包含结案检查结果与逐张派送Outcome, content: {application/json: {schema: {$ref: '#/components/schemas/CompletionEnvelope'}}}}
/api/platform/v1/last-leg/fleet/trips/{tripNo}/complete:
parameters: [{$ref: '#/components/parameters/TripNo'}]
post:
summary: 核对全部货物并结案车次
description: '权限:platform:fleet:trip:complete;仅IN_TRANSIT,实发数量由全部已签收或已真实退回派送单覆盖;拒收、待退回、取消不算终态完成。不改写客户单、不新增收费、不释放资源;同键同内容重放,旧版本或不同内容拒绝。'
requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/Complete'}}}}
responses: {'200': {$ref: '#/components/responses/Detail'}}
/api/platform/v1/last-leg/fleet/trips/{tripNo}/resources/release:
parameters: [{$ref: '#/components/parameters/TripNo'}]
post:
summary: 单独结束车辆或司机本次任务并释放占用
description: '权限:platform:fleet:trip:release;货物结案后才允许;实际结束时间不早于货物完成且不晚于当前时间,地点及原因必填。车辆状态与释放同事务更新;司机可继续返程而保持占用。原请求重放不释放后来另一个车次的占用。'
requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/Release'}}}}
responses: {'200': {$ref: '#/components/responses/Detail'}}
components:
securitySchemes:
bearerAuth: {type: http, scheme: bearer, bearerFormat: JWT}
parameters:
BookingNo:
name: bookingNo
in: path
required: true
schema: {type: string}
TripNo: {name: tripNo, in: path, required: true, schema: {type: string}}
responses:
BookingResult:
description: 统一响应data含tripRecordVersion、booking及replayed;客户端使用当前状态,重放不覆盖后续已发生的取消
content:
application/json:
schema:
allOf:
- {$ref: '#/components/schemas/Envelope'}
- properties:
data:
type: object
properties:
tripRecordVersion: {type: integer, format: int64}
booking: {$ref: '#/components/schemas/Booking'}
replayed: {type: boolean}
Detail:
description: 统一响应,code为200代表成功,data为车次详情;业务冲突使用业务错误,不降级为成功
content:
application/json:
schema:
allOf:
- {$ref: '#/components/schemas/Envelope'}
- properties:
data:
type: object
properties:
trip: {$ref: '#/components/schemas/Trip'}
dispatches: {type: array, items: {$ref: '#/components/schemas/DispatchBinding'}}
loadingReview: {$ref: '#/components/schemas/ReviewState'}
driverQualification:
type: object
nullable: true
description: 未分配司机为null;字段与fleet-drivers-v1的Qualification一致
properties:
canDispatch: {type: boolean}
expiringSoon: {type: boolean}
reason: {type: string}
licenseExpiresOn: {type: string, format: date, nullable: true}
schemas:
BookingCargo:
type: object
required: [destinationKey, packageCount]
properties:
destinationKey: {type: string, maxLength: 160}
cargoName: {type: string, nullable: true, maxLength: 255}
packageCount: {type: integer, minimum: 1}
palletSpaces: {type: integer, nullable: true, minimum: 1}
weightKg: {type: number, nullable: true, minimum: 0.001}
volumeCbm: {type: number, nullable: true, minimum: 0.001}
priceBasis: {type: string, nullable: true, enum: [PALLET, ORDER], description: 为空时默认PALLET}
unitPrice: {type: number, nullable: true, minimum: 0, multipleOf: 0.01}
currency: {type: string, nullable: true, maxLength: 3}
contactName: {type: string, nullable: true, maxLength: 128}
contactPhone: {type: string, nullable: true, maxLength: 128}
remark: {type: string, nullable: true, maxLength: 1000}
IncomingEvidence:
type: object
properties:
incomingNo: {type: string}
inboundCode: {type: string}
warehouseId: {type: string}
destinationKey: {type: string}
status: {type: string}
receiptHash: {type: string, description: 实托、数量、实测值及来源匹配摘要,不包含可变作业状态}
receivedPackages: {type: integer}
pallets:
type: array
items:
type: object
properties:
warehousePalletId: {type: string}
palletNo: {type: string}
packages: {type: integer}
weightKg: {type: number, nullable: true}
volumeCbm: {type: number, nullable: true}
BookingIncoming:
allOf:
- {$ref: '#/components/schemas/BookingCommand'}
- properties:
estimatedArrivalAt: {type: string, format: date-time, nullable: true, description: 缺省为计划发车前2小时的预计值}
deliveryInstructions: {type: string, nullable: true, maxLength: 1000}
privateAddress: {type: string, nullable: true, maxLength: 1000}
postalCode: {type: string, nullable: true, maxLength: 32}
recipientName: {type: string, nullable: true, maxLength: 128}
recipientPhone: {type: string, nullable: true, maxLength: 64}
BookingPalletSpaces:
type: object
required: [warehousePalletId, palletSpaces]
properties:
warehousePalletId: {type: string}
palletSpaces: {type: integer, minimum: 1}
BookingAcceptReceipt:
allOf:
- {$ref: '#/components/schemas/BookingCommand'}
- required: [receiptHash, pallets, unitPrice]
properties:
receiptHash: {type: string}
pallets: {type: array, minItems: 1, items: {$ref: '#/components/schemas/BookingPalletSpaces'}}
unitPrice: {type: number, minimum: 0, multipleOf: 0.01}
BookingBindDispatch:
allOf:
- {$ref: '#/components/schemas/BookingCommand'}
- required: [dispatchNo]
properties:
dispatchNo: {type: string, maxLength: 64}
BookingCreate:
type: object
required: [tripRecordVersion, customerId, cargo, idempotencyKey]
properties:
tripRecordVersion: {type: integer, format: int64, minimum: 0}
customerId: {type: string, pattern: '^[1-9][0-9]*$'}
cargo: {$ref: '#/components/schemas/BookingCargo'}
idempotencyKey: {type: string, minLength: 8, maxLength: 128}
BookingUpdate:
type: object
required: [tripRecordVersion, recordVersion, cargo]
properties:
tripRecordVersion: {type: integer, format: int64, minimum: 0}
recordVersion: {type: integer, format: int64, minimum: 0}
cargo: {$ref: '#/components/schemas/BookingCargo'}
BookingCommand:
type: object
required: [tripRecordVersion, recordVersion, idempotencyKey]
properties:
tripRecordVersion: {type: integer, format: int64, minimum: 0}
recordVersion: {type: integer, format: int64, minimum: 0}
idempotencyKey: {type: string, minLength: 8, maxLength: 128}
reason: {type: string, nullable: true, maxLength: 1000}
Booking:
type: object
properties:
bookingNo: {type: string}
tripNo: {type: string}
customerId: {type: string}
customerCode: {type: string}
customerName: {type: string}
status: {type: string, enum: [DRAFT, CONFIRMED, RECEIVED, BOUND, CANCELLED]}
cargo: {$ref: '#/components/schemas/BookingCargo'}
destinationLabel: {type: string}
agreedAmount: {type: number, nullable: true, description: 约定运费,非财务应收或实收}
incomingNo: {type: string, nullable: true}
dispatchNo: {type: string, nullable: true}
receipt:
type: object
nullable: true
properties:
source: {$ref: '#/components/schemas/IncomingEvidence'}
pallets: {type: array, items: {$ref: '#/components/schemas/BookingPalletSpaces'}}
operatorId: {type: string}
confirmedAt: {type: string, format: date-time}
recordVersion: {type: integer, format: int64}
updatedAt: {type: string, format: date-time}
Outcome:
type: object
description: 本车原运输的结案依据;已签收后另发起退货仍保留本次运输DELIVERED及原signedAt,不修改后续退货单据状态。
properties:
dispatchNo: {type: string}
deliveryNo: {type: string}
customerId: {type: string}
destination: {type: string, nullable: true}
status: {type: string}
packages: {type: integer}
terminalAt: {type: string, format: date-time, nullable: true}
CompletionEnvelope:
allOf:
- {$ref: '#/components/schemas/Envelope'}
- properties:
data:
type: object
properties:
tripNo: {type: string}
recordVersion: {type: integer, format: int64}
canComplete: {type: boolean}
blockReason: {type: string, nullable: true}
outcomes: {type: array, items: {$ref: '#/components/schemas/Outcome'}}
Complete:
type: object
required: [recordVersion, idempotencyKey, reason]
properties:
recordVersion: {type: integer, format: int64, minimum: 0}
idempotencyKey: {type: string, minLength: 8, maxLength: 128}
reason: {type: string, minLength: 1, maxLength: 1000}
Release:
type: object
required: [recordVersion, resourceKind, actualEndAt, endLocation, idempotencyKey, reason]
properties:
recordVersion: {type: integer, format: int64, minimum: 0}
resourceKind: {type: string, enum: [VEHICLE, DRIVER]}
actualEndAt: {type: string, format: date-time}
endLocation: {type: string, minLength: 1, maxLength: 500}
vehicleStatus: {type: string, nullable: true, enum: [IN_SERVICE, MAINTENANCE, INACTIVE, RETURNED], description: 结束车辆任务必填,结束司机任务必须为空}
idempotencyKey: {type: string, minLength: 8, maxLength: 128}
reason: {type: string, minLength: 1, maxLength: 1000}
ReleaseInfo:
type: object
nullable: true
properties:
actualEndAt: {type: string, format: date-time}
endLocation: {type: string}
vehicleStatus: {type: string, nullable: true, description: 结束本次车辆任务时的状态快照,不代表车辆此后的实时可派状态}
LoadingEnvelope:
allOf:
- {$ref: '#/components/schemas/Envelope'}
- properties:
data:
type: object
properties:
tripNo: {type: string}
recordVersion: {type: integer, format: int64}
manifestHash: {type: string}
cargo: {type: array, items: {$ref: '#/components/schemas/Cargo'}}
blockReason: {type: string, nullable: true}
confirmedLoad:
type: object
nullable: true
properties:
manifestHash: {type: string}
cargo: {type: array, items: {$ref: '#/components/schemas/Cargo'}}
pallets: {type: array, items: {$ref: '#/components/schemas/Measurement'}}
segments: {type: array, items: {type: object}, description: 逐段fromIndex/toIndex及occupied/remaining托位、weightKg、volumeCbm}
operatorId: {type: string}
confirmedAt: {type: string, format: date-time}
Cargo:
type: object
properties:
dispatchNo: {type: string}
customerId: {type: string}
dispatchItemId: {type: string}
warehousePalletId: {type: string, nullable: true}
palletNo: {type: string, nullable: true}
packages: {type: integer}
destinationKey: {type: string, nullable: true, description: 原货物目的地稳定匹配键,选择路线站点时透传}
destinationLabel: {type: string, nullable: true}
Measurement:
type: object
required: [warehousePalletId, palletSpaces, weightKg, volumeCbm, deliveryIndex]
properties:
warehousePalletId: {type: string}
palletSpaces: {type: integer, minimum: 1}
weightKg: {type: number, minimum: 0.001, multipleOf: 0.001, maximum: 999999999.999}
volumeCbm: {type: number, minimum: 0.001, multipleOf: 0.001, maximum: 999999999.999}
deliveryIndex: {type: integer, minimum: 1, description: 卸货站索引,起运仓固定为0}
PlanningEnvelope:
allOf:
- {$ref: '#/components/schemas/Envelope'}
- properties:
data:
type: object
properties:
tripNo: {type: string}
recordVersion: {type: integer, format: int64}
cargo: {type: array, items: {$ref: '#/components/schemas/Cargo'}}
manifestHash: {type: string}
planValid: {type: boolean, description: 计划已经核对且来源hash仍一致}
availableSegments: {type: array, items: {$ref: '#/components/schemas/Segment'}, description: 当前自有货计划加已确认外接预订后的逐段占用与余位;计划失效返回空数组}
blockReason: {type: string, nullable: true}
capacityPlan:
type: object
nullable: true
description: 计划值,不能作为实际装载确认
properties:
manifestHash: {type: string}
cargo: {type: array, items: {$ref: '#/components/schemas/Cargo'}}
pallets: {type: array, items: {$ref: '#/components/schemas/Measurement'}}
segments: {type: array, items: {$ref: '#/components/schemas/Segment'}}
operatorId: {type: string}
confirmedAt: {type: string, format: date-time}
Segment:
type: object
properties:
fromIndex: {type: integer}
toIndex: {type: integer}
occupied: {$ref: '#/components/schemas/CapacityLoad'}
remaining: {$ref: '#/components/schemas/CapacityLoad'}
CapacityLoad:
type: object
properties:
palletSpaces: {type: integer}
weightKg: {type: number}
volumeCbm: {type: number}
ConfirmLoad:
type: object
required: [recordVersion, manifestHash, pallets, reason]
properties:
recordVersion: {type: integer, format: int64, minimum: 0}
manifestHash: {type: string, pattern: '^[0-9a-f]{64}$'}
pallets: {type: array, minItems: 1, maxItems: 1000, items: {$ref: '#/components/schemas/Measurement'}}
reason: {type: string, minLength: 1, maxLength: 1000}
BindingCommand:
type: object
required: [recordVersion, dispatchNo, reason]
properties:
recordVersion: {type: integer, format: int64, minimum: 0}
dispatchNo: {type: string, minLength: 1, maxLength: 64}
reason: {type: string, minLength: 1, maxLength: 1000}
Review:
type: object
required: [recordVersion, decision]
properties:
recordVersion: {type: integer, format: int64, minimum: 0}
decision: {type: string, enum: [APPROVED, REJECTED]}
reason: {type: string, nullable: true, maxLength: 1000, description: 通过选填;打回必填}
ReviewState:
type: object
required: [status]
properties:
status: {type: string, enum: [PENDING, APPROVED, REJECTED]}
reason: {type: string, nullable: true}
reviewerId: {type: string, nullable: true}
reviewedAt: {type: string, format: date-time, nullable: true}
recordVersion: {type: integer, format: int64, nullable: true, description: 本次审核所核对的车次版本}
DispatchBinding:
type: object
properties:
dispatchNo: {type: string}
customerId: {type: string, description: 排车单自身客户归属,由服务端解析}
packageCount: {type: integer, description: 关联时待发运箱数,不是实物托位数}
boundBy: {type: string}
boundAt: {type: string, format: date-time}
review: {$ref: '#/components/schemas/ReviewState'}
Envelope:
type: object
properties:
code: {type: string}
message: {type: string, nullable: true}
data: {type: object, nullable: true}
Stop:
type: object
required: [name]
properties:
name: {type: string, minLength: 1, maxLength: 128}
address: {type: string, nullable: true, maxLength: 500}
destinationKey: {type: string, nullable: true, maxLength: 160, description: 卸货站匹配Loading货物的destinationKey;未配置时可以保存计划但不能确认装载}
Create:
type: object
required: [warehouseId, plannedDepartureAt, expectedDriverEndAt, stops, idempotencyKey]
properties:
warehouseId: {type: string, pattern: '^[1-9][0-9]*$'}
plannedDepartureAt: {type: string, format: date-time}
expectedDriverEndAt: {type: string, format: date-time}
stops: {type: array, minItems: 2, maxItems: 200, items: {$ref: '#/components/schemas/Stop'}}
remark: {type: string, nullable: true, maxLength: 1000}
idempotencyKey: {type: string, minLength: 8, maxLength: 128}
Plan:
type: object
required: [recordVersion, plannedDepartureAt, expectedDriverEndAt, stops, reason]
properties:
recordVersion: {type: integer, format: int64, minimum: 0}
plannedDepartureAt: {type: string, format: date-time}
expectedDriverEndAt: {type: string, format: date-time}
stops: {type: array, minItems: 2, maxItems: 200, items: {$ref: '#/components/schemas/Stop'}}
remark: {type: string, nullable: true, maxLength: 1000}
reason: {type: string, minLength: 1, maxLength: 1000}
Trip:
type: object
properties:
tripNo: {type: string}
status: {type: string, enum: [PLANNED, IN_TRANSIT, COMPLETED, CANCELLED]}
warehouseId: {type: string}
warehouseCode: {type: string}
warehouseName: {type: string}
vehicleId: {type: string, nullable: true}
vehiclePlate: {type: string, nullable: true}
driverId: {type: string, nullable: true}
driverName: {type: string, nullable: true}
driverPhone: {type: string, nullable: true}
maxPalletSpaces: {type: integer, nullable: true}
maxLoadKg: {type: number, nullable: true}
maxVolumeCbm: {type: number, nullable: true}
plannedDepartureAt: {type: string, format: date-time}
expectedDriverEndAt: {type: string, format: date-time}
departedAt: {type: string, format: date-time, nullable: true}
completedAt: {type: string, format: date-time, nullable: true}
stops: {type: array, items: {$ref: '#/components/schemas/Stop'}}
remark: {type: string, nullable: true}
recordVersion: {type: integer, format: int64}
updatedAt: {type: string, format: date-time}
vehicleRelease: {$ref: '#/components/schemas/ReleaseInfo'}
driverRelease: {$ref: '#/components/schemas/ReleaseInfo'}
@@ -0,0 +1,175 @@
openapi: 3.0.3
info:
title: 车辆运维记录
version: 1.0.0
description: >-
平台车辆运维事实,支持保养、加油、维修、紧急故障、应急救援和异常。每条记录固定属于一辆车,使用VR业务编号;读取、修改、照片和历史均校验路径中的车辆归属。
原始里程必须明确KM或MI,加油量必须明确L、US_GAL或IMP_GAL;不自动推测或换算,不更新车辆主档公里数。
核验状态是人工记录的PENDING或VERIFIED,不代表财务审批或车辆可派,不自动产生账单、付款或变更车辆状态。
创建按车辆、操作者和幂等键去重;更正须版本及原因。照片上传按记录与幂等键去重并核对内容摘要,丢失响应可原样重试。
照片最多20张,每张非空JPG/PNG且最多10MiB;File按平台0、FLEET_VEHICLE_RECORD_PHOTO和记录内部主键复合归属,验证内容签名。
维修和救援项目、地点独立保存;处理中或已完成须有处理人,异常须有明确分类。
费用只是运维申报金额,填金额则必须填有效币种;未填金额和零金额有区别,不生成应付或付款。
维修/救援paymentStatus当前固定UNLINKED表示未关联财务,不得由processingStatus推定为已付款,其他类型为空。
不提供物理删除;编辑与上传历史同事务不可变保存。本接口不提供财务结算或车辆占用/状态自动变更。
security: [{bearerAuth: []}]
paths:
/api/platform/v1/last-leg/fleet/vehicles/{vehicleId}/records:
parameters: [{$ref: '#/components/parameters/Vehicle'}]
get:
summary: 分页查询当前车辆运维记录
description: '权限:platform:overseas-master-data:vehicle:list;按发生时间倒序,列表包含已关联照片元数据,不读取图片内容。'
parameters:
- {name: page, in: query, schema: {type: integer, minimum: 1, default: 1}}
- {name: size, in: query, schema: {type: integer, minimum: 1, maximum: 100, default: 20}}
- {name: recordType, in: query, schema: {type: string, enum: [MAINTENANCE, FUEL, REPAIR, BREAKDOWN, RESCUE, EXCEPTION]}}
responses:
'200': {description: '统一响应data为records/page/size/total/pages,records元素为Record'}
post:
summary: 新增记录
description: '权限:platform:overseas-master-data:vehicle:record-create;同键同内容返回原记录,不同内容冲突。'
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [idempotencyKey, details]
properties:
idempotencyKey: {type: string, minLength: 1, maxLength: 128}
details: {$ref: '#/components/schemas/Draft'}
responses:
'200': {$ref: '#/components/responses/Record'}
/api/platform/v1/last-leg/fleet/vehicles/{vehicleId}/records/{recordNo}:
parameters: [{$ref: '#/components/parameters/Vehicle'}, {$ref: '#/components/parameters/RecordNumber'}]
get:
summary: 读取记录详情
description: '权限:platform:overseas-master-data:vehicle:list;跨车辆记录返回404,不泄露其他车辆数据。'
responses:
'200': {$ref: '#/components/responses/Record'}
put:
summary: 更正记录
description: '权限:platform:overseas-master-data:vehicle:record-edit;类型及车辆不可更换,保留历史。'
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [recordVersion, reason, details]
properties:
recordVersion: {type: integer, format: int64, minimum: 0}
reason: {type: string, minLength: 1, maxLength: 1000}
details: {$ref: '#/components/schemas/Draft'}
responses:
'200': {$ref: '#/components/responses/Record'}
/api/platform/v1/last-leg/fleet/vehicles/{vehicleId}/records/{recordNo}/files:
parameters: [{$ref: '#/components/parameters/Vehicle'}, {$ref: '#/components/parameters/RecordNumber'}]
post:
summary: 上传并关联运维图片
description: '权限:platform:overseas-master-data:vehicle:record-upload;记录保存成功后再上传,失败重试不得重复建记录。'
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required: [recordVersion, idempotencyKey, file]
properties:
recordVersion: {type: integer, format: int64, minimum: 0}
idempotencyKey: {type: string, minLength: 1, maxLength: 128}
file: {type: string, format: binary}
responses:
'200': {$ref: '#/components/responses/Record'}
/api/platform/v1/last-leg/fleet/vehicles/{vehicleId}/records/{recordNo}/files/{fileId}:
parameters:
- {$ref: '#/components/parameters/Vehicle'}
- {$ref: '#/components/parameters/RecordNumber'}
- {name: fileId, in: path, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
get:
summary: 读取当前记录已关联图片
description: '权限:platform:overseas-master-data:vehicle:list;先校验记录关联,再校验File复合归属;no-store和nosniff,不返回存储路径。'
responses:
'200': {description: 图片字节, content: {image/png: {schema: {type: string, format: binary}}, image/jpeg: {schema: {type: string, format: binary}}}}
/api/platform/v1/last-leg/fleet/vehicles/{vehicleId}/records/{recordNo}/history:
parameters: [{$ref: '#/components/parameters/Vehicle'}, {$ref: '#/components/parameters/RecordNumber'}]
get:
summary: 分页读取不可变修改历史
description: '权限:platform:overseas-master-data:vehicle:list;版本倒序,仅按需读取。'
parameters:
- {name: page, in: query, schema: {type: integer, minimum: 1, default: 1}}
- {name: size, in: query, schema: {type: integer, minimum: 1, maximum: 100, default: 20}}
responses:
'200': {description: '统一响应data为分页对象;records包含recordVersion/action/reason/operatorId/occurredAt/snapshot,snapshot为Record'}
components:
securitySchemes:
bearerAuth: {type: http, scheme: bearer, bearerFormat: JWT}
parameters:
Vehicle: {name: vehicleId, in: path, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
RecordNumber: {name: recordNo, in: path, required: true, schema: {type: string}}
responses:
Record:
description: 统一业务响应;失败保留code、message及诊断标识
content:
application/json:
schema:
type: object
properties:
code: {type: string}
data: {$ref: '#/components/schemas/Record'}
schemas:
Draft:
type: object
required: [recordType, occurredAt]
oneOf:
- properties: {recordType: {enum: [MAINTENANCE]}}
required: [distanceUnit, odometer, verificationStatus, lastServiceOdometer, nextServiceOdometer]
- properties: {recordType: {enum: [FUEL]}}
required: [distanceUnit, odometer, verificationStatus, fuelQuantity, fuelUnit]
- properties: {recordType: {enum: [REPAIR, BREAKDOWN, RESCUE, EXCEPTION]}}
required: [incident]
properties:
recordType: {type: string, enum: [MAINTENANCE, FUEL, REPAIR, BREAKDOWN, RESCUE, EXCEPTION]}
occurredAt: {type: string, format: date-time, description: 带偏移或Z的实际发生时间,不晚于当前时间,保存UTC}
distanceUnit: {type: string, nullable: true, enum: [KM, MI], description: 保养加油必填;其他类型必须为空}
odometer: {type: number, nullable: true, minimum: 0, multipleOf: 0.001, description: 保养加油必填;其他类型必须为空}
lastServiceOdometer: {type: number, nullable: true, minimum: 0, description: 保养必填,不大于记录时里程;加油必须为空}
nextServiceOdometer: {type: number, nullable: true, minimum: 0, description: 保养必填且大于上次保养里程,可低于当前里程表示已超保养里程;不默认一万迈}
fuelQuantity: {type: number, nullable: true, minimum: 0, exclusiveMinimum: true, description: 加油必填正数;保养必须为空}
fuelUnit: {type: string, nullable: true, enum: [L, US_GAL, IMP_GAL], description: 加油必填明确单位;保养必须为空}
verificationStatus: {type: string, nullable: true, enum: [PENDING, VERIFIED], description: 保养加油必填;其他类型必须为空,不能混作处理状态}
remark: {type: string, nullable: true, maxLength: 1000}
incident: {$ref: '#/components/schemas/Incident'}
Incident:
type: object
nullable: true
description: 维修/故障/救援/异常必填;保养加油必须为空,并禁止混入里程、加油或核验字段
required: [description, processingStatus]
properties:
description: {type: string, minLength: 1, maxLength: 2000}
item: {type: string, nullable: true, maxLength: 256, description: 维修或救援项目,此两类必填;其他类型为空}
place: {type: string, nullable: true, maxLength: 512, description: 维修或救援地点,此两类必填;其他类型为空}
handler: {type: string, nullable: true, maxLength: 128, description: 实际处理人;处理中或完成时必填,与系统操作人分别保留}
processingStatus: {type: string, enum: [PENDING, IN_PROGRESS, COMPLETED]}
exceptionType: {type: string, nullable: true, enum: [ROUTE, TIMEOUT, DELIVERY, VEHICLE_BREAKDOWN], description: 异常记录必填;其他类型为空}
reportedCost: {type: number, nullable: true, minimum: 0, multipleOf: 0.01, maximum: 999999999999.99, description: 仅维修/救援允许;空为未登记,零为已登记零费用,不表示付款}
currency: {type: string, nullable: true, pattern: '^[A-Z]{3}$', description: 金额非空时必填有效币种;未填金额时为空,不自动换算}
File:
type: object
properties:
fileId: {type: string}
fileName: {type: string}
contentType: {type: string, enum: [image/jpeg, image/png]}
sizeBytes: {type: integer, format: int64}
Record:
type: object
properties:
recordNo: {type: string}
vehicleId: {type: string}
vehiclePlate: {type: string, description: 建档时车牌快照}
details: {$ref: '#/components/schemas/Draft'}
files: {type: array, items: {$ref: '#/components/schemas/File'}}
recordVersion: {type: integer, format: int64, minimum: 0}
updatedBy: {type: string}
updatedAt: {type: string, format: date-time}
paymentStatus: {type: string, nullable: true, readOnly: true, enum: [UNLINKED], description: 维修/救援未关联财务时为UNLINKED,其余为空;不可用处理完成推定已付款}
@@ -600,14 +600,14 @@ paths:
'404': { description: 排车单不存在 }
put:
summary: 编辑待发车排车单
description: 权限 platform:overseas-warehouse:dispatch:edit;只按全局唯一 dispatchNo 锁定记录并从记录解析客户范围,不接收 customerId。仅允许 PENDING_DEPARTURE,且请求不包含 status/departAt,发车事实只能由 PDA 作业命令产生。
description: 权限 platform:overseas-warehouse:dispatch:edit;只按全局唯一 dispatchNo 锁定记录并从记录解析客户范围,不接收 customerId。仅允许 PENDING_DEPARTURE,且请求不包含 status/departAt,发车事实只能由 PDA 作业命令产生。已关联共享车次时拒绝独立编辑,须先按车次执行状态解除关联。
security: [{ bearerAuth: [] }]
parameters: [{ name: dispatchNo, in: path, required: true, schema: { type: string } }]
requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/DispatchUpdate' } } } }
responses: { '200': { description: 更新后的 DispatchResponse }, '404': { description: 排车单不存在 }, '409': { description: 排车单已执行或不可编辑 } }
delete:
summary: 取消待发车排车单
description: 权限 platform:overseas-warehouse:dispatch:cancel;只按全局唯一 dispatchNo 锁定记录并解析客户范围,不接收 customerId。仅 PENDING_DEPARTURE 可取消并释放正式托盘锁;重复取消幂等成功。
description: 权限 platform:overseas-warehouse:dispatch:cancel;只按全局唯一 dispatchNo 锁定记录并解析客户范围,不接收 customerId。仅 PENDING_DEPARTURE 可取消并释放正式托盘锁;重复取消幂等成功。已关联共享车次必须先解除关联,不能单独取消破坏整车计划。
security: [{ bearerAuth: [] }]
parameters: [{ name: dispatchNo, in: path, required: true, schema: { type: string } }]
responses: { '200': { description: 取消成功或已取消 }, '404': { description: 排车单不存在 }, '409': { description: 排车已开始执行 } }
@@ -650,7 +650,7 @@ paths:
/api/platform/v1/last-leg/dispatches/{dispatchNo}/items/{itemId}:
delete:
summary: 移除已排车托盘
description: 权限 platform:overseas-warehouse:dispatch:edit;仅待发车、未拣货且尚未生成出库单时允许,成功后释放托盘锁并刷新排车汇总。
description: 权限 platform:overseas-warehouse:dispatch:edit;仅待发车、未拣货且尚未生成出库单时允许,成功后释放托盘锁并刷新排车汇总。已关联共享车次时须先解除关联。
security: [{ bearerAuth: [] }]
parameters:
- { name: dispatchNo, in: path, required: true, schema: { type: string } }
@@ -715,10 +715,82 @@ paths:
- { name: draftToken, in: path, required: true, schema: { type: string, maxLength: 64 } }
- { name: containerId, in: path, required: true, schema: { type: string } }
responses: { '200': { description: 空车厢删除成功 }, '409': { description: 车厢仍绑定托盘或草稿状态冲突 } }
/api/platform/v1/last-leg/dispatches/{dispatchNo}/fee-coverage:
get:
summary: 按实际发运来源查询原头程费用
description: 权限platform:overseas-warehouse:dispatch:list;只接收排车号,客户由服务端解析。按实物库存与本次已拣数量还原各票来源并核平排车箱数;合托不使用单一汇总入仓码代替全部来源。sourceComplete=false时不返回费用候选。Finance批量查询仅返回各票该客户原入仓单的已审核有效应收最新版本,存在来源不等于任何服务已包含,查询不会免收或新增费用。
security: [{ bearerAuth: [] }]
parameters: [{ name: dispatchNo, in: path, required: true, schema: { type: string } }]
responses:
'200':
description: 来源核对结果与原费用候选
content:
application/json:
schema:
type: object
properties:
data:
type: object
properties:
dispatchNo: { type: string }
sourceComplete: { type: boolean }
blockReason: { type: string, nullable: true }
cargoHash: { type: string }
cargo:
type: array
items:
type: object
properties:
dispatchItemId: { type: string }
warehousePalletId: { type: string }
palletNo: { type: string, nullable: true }
orderId: { type: string }
sourceRegion: { type: string, nullable: true }
sourceInboundId: { type: string, nullable: true }
inboundCode: { type: string, nullable: true }
packages: { type: integer }
dispatchInboundCode: { type: string, nullable: true, description: 排车来源业务码;不能代替实物拆分后的各票inboundCode }
feeSources:
type: array
items:
type: object
properties:
financeFeeId: { type: string }
sourceFeeId: { type: string }
sourceType: { type: string }
sourceVersion: { type: integer }
customerId: { type: string }
sourceInboundId: { type: string }
inboundCode: { type: string, nullable: true }
feeItemCode: { type: string }
feeItemName: { type: string }
currency: { type: string }
amount: { type: number }
sourceHash: { type: string }
status: { type: string, enum: [AVAILABLE, SETTLED] }
'404': { description: 排车单不存在 }
'503': { description: Finance来源查询不可用或返回范围不一致 }
/api/platform/v1/last-leg/dispatches/{dispatchNo}/external-freight:
get:
summary: 读取本次排车的外接实收约定运费
description: 权限 platform:overseas-warehouse:dispatch:list;客户从排车单解析。逐票核对真实来货、车次关联、目的地及实收数量,返回不可变报价版本与货物摘要;查询不发布费用。
security: [{ bearerAuth: [] }]
parameters: [{ name: dispatchNo, in: path, required: true, schema: { type: string } }]
responses:
'200':
description: 约定运费与是否仍需填写其余货物派送费用
content:
application/json:
schema:
type: object
properties:
data: { $ref: '#/components/schemas/ExternalFreightPreview' }
'404': { description: 排车单不存在 }
'409': { description: 外接实收或排车关联不一致 }
/api/platform/v1/last-leg/dispatches/{dispatchNo}/transfer-delivery:
post:
summary: 排车转出库与派送单
description: 权限 platform:overseas-warehouse:dispatch:transfer-delivery;只按全局唯一 dispatchNo 锁定记录并从记录解析客户范围,不接收 customerId;一个排车仍生成一张出库单,并按目的FBA仓分别生成派送单,返回 deliveries 数组;同一幂等键或已转换排车重复调用返回全部既有单据。派送费用只发布一次,避免按拆分派送单重复计费。
description: 权限 platform:overseas-warehouse:dispatch:transfer-delivery;只按全局唯一 dispatchNo 锁定记录并从记录解析客户范围,不接收 customerId;一个排车仍生成一张出库单,并按目的FBA仓分别生成派送单,返回 deliveries 数组;同一幂等键或已转换排车只有业务请求内容与首次保存一致才返回既有单据,费用、计划时间、装载或其他字段变化均拒绝,不因更换幂等键绕过。派送费用只发布一次,费用更正使用正式来源调整流程。已关联车次时新请求不得覆盖车次维护的车牌、司机和电话;原请求重放不改写后续已同步的车次快照。
security: [{ bearerAuth: [] }]
parameters: [{ name: dispatchNo, in: path, required: true, schema: { type: string } }]
requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/DeliveryTransfer' } } } }
@@ -766,7 +838,7 @@ paths:
/api/platform/v1/last-leg/outbound-orders/{outboundNo}/cancel:
post:
summary: 取消未执行出库单
description: 权限 platform:overseas-warehouse:outbound:cancel;只按全局唯一 outboundNo 锁定并解析客户范围,不接收 customerId。仅未开始拣货/出库的单据可取消,重复取消幂等成功。
description: 权限 platform:overseas-warehouse:outbound:cancel;操作人取认证身份,只按全局唯一outboundNo解析客户,不接收customerId。关联排车按dispatch→outbound顺序锁定,已拣货/发车拒绝,仍绑定车次时须先解除;取消出库不自动取消原排车占用,后续在排车列表取消。当前已审核有效的DELIVERY_FEE应收按原来源版本申请作废,装车/买约/应付等其他费用保留核对,物流取消不代表账务作废成功。取消原因完整保存在派送操作记录,重复取消幂等成功。
security: [{ bearerAuth: [] }]
parameters: [{ name: outboundNo, in: path, required: true, schema: { type: string } }]
requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/OutboundCancel' } } } }
@@ -783,7 +855,7 @@ paths:
description: 权限 platform:overseas-warehouse:delivery:list;列表权限包含详情只读;order.allowedActions 为当前可执行动作。
security: [{ bearerAuth: [] }]
parameters: [{ name: deliveryNo, in: path, required: true, schema: { type: string } }]
responses: { '200': { description: 派送单详情,SKU与BOL打印数据只包含当前派送单目的FBA仓 }, '404': { description: 派送单不存在 } }
responses: { '200': { description: '派送单详情,SKU与BOL打印数据只包含当前派送单目的FBA仓;fees附reviewStatus、sourceStatus、voidStatus(NOT_REQUESTED/PENDING_FINANCE/FINANCE_ATTENTION/CONFIRMED),不得把待财务确认或待处理显示为已作废。feeCoverage为可空的原费用服务覆盖确认快照,只包含当前派送所属货物、引用的原费用版本及服务、确认人和时间;不代表新增收费或原费用已付款。' }, '404': { description: 派送单不存在 } }
/api/platform/v1/last-leg/exception-orders:
get:
summary: 分页查询异常订单
@@ -983,7 +1055,7 @@ paths:
responses: { '200': { description: 当前客户派送单分页 }, '403': { description: 缺少客户派送查看权限 } }
post:
summary: 客户从自己的排车单创建派送单
description: 权限 merchant:warehouse:delivery:create;客户范围只取认证 Token,dispatchNo 必填,不接收 customerId;同一幂等键或已转换排车重复调用返回既有单据。
description: 权限 merchant:warehouse:delivery:create;客户范围只取认证 Token,dispatchNo 必填,不接收 customerId;同一幂等键或已转换排车重复调用返回既有单据。已关联车次时请求不得覆盖车次维护的车牌、司机和电话。
security: [{ bearerAuth: [] }]
parameters: [{ name: dispatchNo, in: query, required: true, schema: { type: string, minLength: 1 } }]
requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/DeliveryTransfer' } } } }
@@ -2311,7 +2383,7 @@ components:
planNo: { type: string, nullable: true }
orderType: { type: string, nullable: true }
volumeWeightKg: { type: number, nullable: true }
grossWeightKg: { type: number, nullable: true, description: 候选托盘实际毛重(千克);按入库计划分配比例汇总,缺失时回退体积重 }
grossWeightKg: { type: number, nullable: true, description: 候选托盘分摊毛重(千克);以实际毛重按入库计划分配比例汇总,缺失时为空,禁止使用体积重替代;正式配载须核验实际装载 }
containerNo: { type: string, nullable: true }
appointmentAt: { type: string, format: date-time, nullable: true }
expectedLoadingAt: { type: string, format: date-time, nullable: true }
@@ -2327,8 +2399,6 @@ components:
destinationWarehouseName: { type: string }
fbaWarehouseCode: { type: string }
packageCount: { type: integer }
grossWeightKg: { type: number, description: 排车托盘冻结毛重(千克) }
volumeWeightKg: { type: number, description: 排车托盘冻结材积重(千克) }
volumeCbm: { type: number }
sourceStage: { type: string, enum: [PLAN, RECEIVED] }
cartonMarks: { type: string, nullable: true, description: 该托盘箱号,逗号分隔 }
@@ -2396,6 +2466,7 @@ components:
estimatedArrivalAt: { type: string, format: date-time, nullable: true }
DeliveryTransfer:
type: object
description: 外接货必须带入当前externalFreightHash,服务端按实收报价发布对应目的地的派送应收。平台可通过feeCoverage逐票确认原Finance费用所含服务,并传feeCoverageCargoHash;重新核对真实货物、费用ID/版本/hash及客户范围后冻结确认快照。只有全部相关货物已覆盖的服务才能免填对应新增应收,并禁止重复登记该应收;仍可独立登记供应商应付。未覆盖部分收费,原费用不复制入账也不伪造零金额费用。客户接口禁止自行声明feeCoverage。已有成功命令重放保留原覆盖快照。
required: [idempotencyKey, plannedDeliveryAt, deliveryMethod, pickupMethod, loadingType, fees]
properties:
idempotencyKey: { type: string, minLength: 1, maxLength: 255 }
@@ -2413,7 +2484,40 @@ components:
appointmentAccount: { type: string, nullable: true, maxLength: 128 }
sellerName: { type: string, nullable: true, maxLength: 128 }
remark: { type: string, nullable: true, maxLength: 1000 }
fees: { type: array, minItems: 1, maxItems: 20, items: { $ref: '#/components/schemas/DeliveryFeeInput' } }
fees: { type: array, maxItems: 20, items: { $ref: '#/components/schemas/DeliveryFeeInput' } }
externalFreightHash: { type: string, nullable: true, maxLength: 64 }
feeCoverageCargoHash: { type: string, nullable: true, maxLength: 64 }
feeCoverage: { type: array, nullable: true, maxItems: 2000, items: { $ref: '#/components/schemas/FeeCoverageSelection' } }
FeeCoverageSelection:
type: object
required: [financeFeeId, sourceVersion, sourceHash, services]
properties:
financeFeeId: { type: string, pattern: '^[1-9][0-9]*$' }
sourceVersion: { type: integer, minimum: 1 }
sourceHash: { type: string }
services: { type: array, minItems: 1, maxItems: 3, items: { type: string, enum: [DELIVERY_FEE, LOADING_FEE, APPOINTMENT_FEE] } }
ExternalFreightPreview:
type: object
required: [snapshotHash, requiresDeliveryFee, quotes]
properties:
snapshotHash: { type: string }
requiresDeliveryFee: { type: boolean }
quotes:
type: array
items:
type: object
properties:
bookingNo: { type: string }
recordVersion: { type: integer, format: int64 }
incomingNo: { type: string }
inboundCode: { type: string, nullable: true }
customerName: { type: string }
destinationKey: { type: string }
currency: { type: string, enum: [CNY, USD] }
amount: { type: number }
quantity: { type: number }
quantityUnit: { type: string, enum: [托位, 单] }
unitPrice: { type: number }
DeliveryStatusChange:
type: object
required: [status, expectedVersion]
@@ -10,7 +10,7 @@ paths:
- { in: query, name: businessType, schema: { $ref: '#/components/schemas/BusinessType' } }
- { in: query, name: businessNo, schema: { type: string } }
- { in: query, name: containerBookingId, description: 精确查询真实来源柜下全部客户的费用;可再传customerId收窄到单个客户, schema: { type: string, pattern: '^[1-9][0-9]*$' } }
responses: { '200': { description: 当前版本列表 } }
responses: { '200': { description: '当前版本列表;voidStatus为NOT_REQUESTED、PENDING_FINANCE、FINANCE_ATTENTION或CONFIRMED,批量查询作废确认状态,不逐行读取。等待或拒绝时sourceStatus保留ACTIVE,只有Finance持久化确认后为VOIDED。' } }
post:
summary: 创建海外业务费用来源草稿
description: 客户和业务编号必填;入仓ID、入仓码均按业务事实选填,不得为补字段伪造。海外货柜费用必须以相同的businessId和containerBookingId引用真实来源柜,且businessNo与containerNo均为该柜真实集装箱号。
@@ -34,7 +34,7 @@ paths:
/api/platform/v1/last-leg/finance-fee-sources/{sourceFeeId}/{action}:
post:
summary: 提交、审核、调整或作废海外业务费用来源
description: 审核通过与作废均事务性写入Outbox,经Event Bridge可靠投递至独立Finance;action为submit、review、adjust、void。
description: 审核通过与作废均事务性写入Outbox;此处作废仅指申请,直接可靠投递至独立Finance;action为submit、review、adjust、void。作废申请不立即更改sourceStatus,voidStatus为PENDING_FINANCE;等待原发布成功后才投递作废,收到匹配APPLIED/DUPLICATE确认时在投递审计事务内更新VOIDED。失败/结果未知仍保留原来源,拒绝或投递耗尽显示FINANCE_ATTENTION,禁止未决作废期间调整版本。
security: [{ bearerAuth: [] }]
parameters:
- { in: path, name: sourceFeeId, required: true, schema: { type: string } }
@@ -264,10 +264,14 @@ components:
VehicleStatus: { type: string, enum: [IN_SERVICE, DISPATCHED, MAINTENANCE, INACTIVE, RETURNED] }
VehicleCreate:
type: object
description: 托位/载重/容积同时填写正数,或全部留空表示容量尚未确认;未知容量不可用于正式配载,不按车型名称推算。
required: [plateNumber, model, fuelConsumptionPer100Km, mileageKm, region, ownership, status]
properties:
plateNumber: { type: string, maxLength: 64 }
model: { type: string, maxLength: 128 }
maxPalletSpaces: { type: integer, minimum: 1, maximum: 10000, nullable: true }
maxLoadKg: { type: number, minimum: 0, exclusiveMinimum: true, nullable: true }
maxVolumeCbm: { type: number, minimum: 0, exclusiveMinimum: true, nullable: true }
fuelConsumptionPer100Km: { type: number, exclusiveMinimum: true, minimum: 0 }
mileageKm: { type: integer, format: int64, minimum: 0 }
driverOrDispatchCompany: { type: string, maxLength: 128, nullable: true }
@@ -278,11 +282,15 @@ components:
remark: { type: string, maxLength: 1000, nullable: true }
VehicleUpdate:
type: object
description: 托位/载重/容积同时填写正数,或全部留空;不能部分设置容量。
required: [recordVersion, plateNumber, model, fuelConsumptionPer100Km, mileageKm, region, ownership]
properties:
recordVersion: { type: integer, format: int64, minimum: 0 }
plateNumber: { type: string, maxLength: 64 }
model: { type: string, maxLength: 128 }
maxPalletSpaces: { type: integer, minimum: 1, maximum: 10000, nullable: true }
maxLoadKg: { type: number, minimum: 0, exclusiveMinimum: true, nullable: true }
maxVolumeCbm: { type: number, minimum: 0, exclusiveMinimum: true, nullable: true }
fuelConsumptionPer100Km: { type: number, exclusiveMinimum: true, minimum: 0 }
mileageKm: { type: integer, format: int64, minimum: 0, description: 不得小于已有公里码 }
driverOrDispatchCompany: { type: string, maxLength: 128, nullable: true }
@@ -15,12 +15,13 @@ paths:
/api/pda/v1/last-leg/dispatches/{dispatchNo}:
get:
summary: 查询绑定仓排车任务详情
description: 返回 canDepart 与 departBlockReason;PDA 必须以服务端门禁决定是否开放发车,不得仅按本地拣货数量推断。
description: 返回 tripNo、tripRecordVersion、tripDispatchCount、driverWarning、canDepart、departBlockReason、canPick 与 pickBlockReason;门禁反映整车所有客户单据、实际装载及当前司机资格,PDA 不得仅按当前子单拣货数量推断。canPick/pickBlockReason仅反映当前客户排车单调度审核,不以其他客户审核结果替代。未关联车次不可拣货或发车;列表按车次复用同次查询的门禁结果。证照30天内到期但覆盖任务时driverWarning提醒且仍允许,预计结束前过期则阻断。
parameters: [{ name: dispatchNo, in: path, required: true, schema: { type: string } }]
responses: { '200': { description: 排车托盘、目的仓与库存位置详情 }, '404': { description: 当前绑定仓无此排车任务 } }
/api/pda/v1/last-leg/dispatches/{dispatchNo}/items/{itemId}/pick:
post:
summary: 扫码拣取排车托盘包裹
description: 新请求须当前排车调度审核通过;同键同内容重放已保存结果不重新扣库存。
parameters:
- { name: dispatchNo, in: path, required: true, schema: { type: string } }
- { name: itemId, in: path, required: true, schema: { type: string, pattern: '^[0-9]+$' } }
@@ -29,17 +30,17 @@ paths:
/api/pda/v1/last-leg/dispatches/{dispatchNo}/complete:
post:
summary: 完成排车拣货
description: 每个要求托盘及全部包裹必须精确完成;不允许部分完成。
description: 当前排车调度审核通过且每个要求托盘及全部包裹必须精确完成;不允许部分完成。
parameters: [{ name: dispatchNo, in: path, required: true, schema: { type: string } }]
requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/IdempotentAction' } } } }
responses: { '200': { description: READY_TO_DEPART结果 }, '409': { description: 未全部拣货或幂等冲突 } }
/api/pda/v1/last-leg/dispatches/{dispatchNo}/depart:
post:
summary: 排车发车并完成出库门禁
description: 仅完整拣货且存在待出库单时发车;同事务推进出库与排车状态、订单归属并释放托盘锁,不重复扣减扫码时已扣库存。
summary: 从排车任务确认所属车次整车发车
description: 按Token绑定仓锁定车次,校验期望车次版本、车辆占用、全部调度审核、实际装载、整车理货审核及全部客户拣货和待出库单,重新核验司机证照、当前海外区域和身份;区域或身份变化与预览门禁一致阻断。一个事务推进所有子单,任一失败全车回滚。扫码阶段已扣库存不重复扣减;同键同内容重放整车结果,不同内容冲突。DEPARTING仅是同事务内部状态,不对外提交。
parameters: [{ name: dispatchNo, in: path, required: true, schema: { type: string } }]
requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/DepartAction' } } } }
responses: { '200': { description: DEPARTED结果;同键同请求返回既有结果 }, '409': { description: 拣货、出库或幂等门禁未满足 } }
responses: { '200': { description: '统一响应data包含tripNo、status=IN_TRANSIT、departedAt、shippedPackages、dispatches数组(dispatchNo/shippedPackages)和replayed;每张子排车状态均为DEPARTED' }, '409': { description: 车次版本、装载、资格、拣货、出库或幂等门禁未满足 } }
/api/pda/v1/last-leg/outbound-tasks:
get:
summary: 查询Token绑定仓的一件代发出库任务
@@ -850,9 +851,10 @@ components:
properties: { idempotencyKey: { type: string, minLength: 1, maxLength: 255 } }
DepartAction:
type: object
required: [idempotencyKey]
required: [recordVersion, idempotencyKey]
properties:
idempotencyKey: { type: string, minLength: 1, maxLength: 255 }
recordVersion: { type: integer, format: int64, minimum: 0, description: 当前车次版本,来自tripRecordVersion}
idempotencyKey: { type: string, minLength: 8, maxLength: 128 }
remark: { type: string, maxLength: 1000, nullable: true }
DispatchPick:
type: object