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
@@ -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]