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
@@ -299,7 +299,7 @@ CREATE TABLE IF NOT EXISTS ll_delivery_order (
seller_name VARCHAR(128) NULL COMMENT '卖家姓名',
package_count INT NOT NULL DEFAULT 0 COMMENT '派送箱数',
pallet_count INT NOT NULL DEFAULT 0 COMMENT '派送托盘数',
total_weight_kg DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT '总重量(千克)',
total_weight_kg DECIMAL(18,4) NULL COMMENT '总重量(千克),任一来源未确认时为空',
total_volume_cbm DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT '总体积(立方米)',
planned_delivery_at DATETIME(3) NULL COMMENT '预计派送时间',
dispatched_at DATETIME(3) NULL COMMENT '实际派送时间',
@@ -968,7 +968,65 @@ CREATE TABLE IF NOT EXISTS ll_monthly_exchange_rate_history (
KEY idx_ll_exchange_rate_history (exchange_rate_id,created_at)
) ENGINE=InnoDB COMMENT='月度汇率变更历史';
CREATE TABLE ll_fleet_driver (
id BIGINT NOT NULL COMMENT '内部主键',
driver_no VARCHAR(32) NOT NULL COMMENT '司机业务编号',
warehouse_id BIGINT NULL COMMENT '所属公共仓库引用',
warehouse_code VARCHAR(64) NULL COMMENT '所属仓库编码快照',
warehouse_name VARCHAR(256) NULL COMMENT '所属仓库名称快照',
vehicle_models VARCHAR(128) NULL COMMENT '可驾驶车型描述',
languages_and_area VARCHAR(256) NULL COMMENT '语言及服务区域',
medical_expires_on DATE NULL COMMENT '体检到期日,当前不作为必需资质',
training_expires_on DATE NULL COMMENT '培训安全到期日,当前不作为必需资质',
insurance_expires_on DATE NULL COMMENT '保险到期日,当前不作为必需资质',
name VARCHAR(128) NOT NULL COMMENT '姓名',
phone VARCHAR(64) NOT NULL COMMENT '联系电话',
ownership VARCHAR(16) NOT NULL COMMENT '归属 OWNED自有 DISPATCHED外派',
region VARCHAR(16) NOT NULL COMMENT '区域 DOMESTIC国内 OVERSEAS海外',
active TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否启用',
review_status VARCHAR(16) NOT NULL DEFAULT 'PENDING' COMMENT '证照审核 PENDING待审 APPROVED通过 REJECTED打回',
license_number VARCHAR(128) NULL COMMENT '驾驶证编号',
license_type VARCHAR(64) NULL COMMENT '驾驶证类型',
license_issued_on DATE NULL COMMENT '驾驶证签发日期',
license_expires_on DATE NULL COMMENT '驾驶证有效截止日',
license_zone VARCHAR(64) NOT NULL COMMENT '证照有效日期的IANA时区',
license_file_id BIGINT NULL COMMENT '平台驾驶证文件引用',
remark VARCHAR(1000) NULL COMMENT '备注',
record_version BIGINT NOT NULL DEFAULT 0 COMMENT '并发版本',
created_by VARCHAR(64) NOT NULL COMMENT '创建人',
created_at DATETIME(3) NOT NULL COMMENT '创建时间UTC',
updated_by VARCHAR(64) NOT NULL COMMENT '修改人',
updated_at DATETIME(3) NOT NULL COMMENT '修改时间UTC',
PRIMARY KEY (id),
UNIQUE KEY uk_fleet_driver_no (driver_no),
KEY idx_fleet_driver_page (active,review_status,updated_at,id),
KEY idx_fleet_driver_expiry (license_expires_on),
CONSTRAINT chk_fleet_driver_ownership CHECK (ownership IN ('OWNED','DISPATCHED')),
CONSTRAINT chk_fleet_driver_region CHECK (region IN ('DOMESTIC','OVERSEAS')),
CONSTRAINT chk_fleet_driver_review CHECK (review_status IN ('PENDING','APPROVED','REJECTED')),
CONSTRAINT chk_fleet_driver_dates CHECK (license_issued_on IS NULL OR license_expires_on IS NULL OR license_issued_on <= license_expires_on)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='平台司机档案';
CREATE TABLE ll_fleet_driver_history (
id BIGINT NOT NULL COMMENT '内部主键',
driver_id BIGINT NOT NULL COMMENT '司机内部引用',
record_version BIGINT NOT NULL COMMENT '操作后的档案版本',
action VARCHAR(32) NOT NULL COMMENT '创建编辑启停证照上传及审核动作',
reason VARCHAR(1000) NULL COMMENT '操作原因或审核意见',
license_file_id BIGINT NULL COMMENT '当时驾驶证文件引用',
snapshot_json JSON NOT NULL COMMENT '操作后不可变档案快照',
operator_id VARCHAR(64) NOT NULL COMMENT '操作人',
occurred_at DATETIME(3) NOT NULL COMMENT '操作时间UTC',
PRIMARY KEY (id),
UNIQUE KEY uk_fleet_driver_history_version (driver_id,record_version),
KEY idx_fleet_driver_history_file (driver_id,license_file_id),
CONSTRAINT fk_fleet_driver_history_driver FOREIGN KEY (driver_id) REFERENCES ll_fleet_driver(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='司机档案及审核历史';
CREATE TABLE IF NOT EXISTS ll_vehicle (
max_pallet_spaces INT NULL COMMENT '最大标准托位,未知时为空',
max_load_kg DECIMAL(12,3) NULL COMMENT '最大载重,千克',
max_volume_cbm DECIMAL(12,3) NULL COMMENT '可用容积,立方米',
id BIGINT NOT NULL COMMENT '车辆档案ID', plate_number VARCHAR(64) NOT NULL COMMENT '车牌号',
model VARCHAR(128) NOT NULL COMMENT '车型', fuel_consumption_per100_km DECIMAL(10,2) NOT NULL COMMENT '百公里油耗,单位升',
mileage_km BIGINT NOT NULL COMMENT '当前公里码,单位公里', driver_or_dispatch_company VARCHAR(128) NULL COMMENT '司机姓名或外派单位',
@@ -979,9 +1037,181 @@ CREATE TABLE IF NOT EXISTS ll_vehicle (
created_by VARCHAR(128) NOT NULL COMMENT '创建人', updated_by VARCHAR(128) NOT NULL COMMENT '更新人',
created_at DATETIME(3) NOT NULL COMMENT '创建时间', updated_at DATETIME(3) NOT NULL COMMENT '更新时间',
PRIMARY KEY (id), UNIQUE KEY uk_ll_vehicle_plate (plate_number),
KEY idx_ll_vehicle_page (ownership,region,status,updated_at,id)
KEY idx_ll_vehicle_page (ownership,region,status,updated_at,id),
CONSTRAINT chk_ll_vehicle_capacity CHECK ((max_pallet_spaces IS NULL AND max_load_kg IS NULL AND max_volume_cbm IS NULL)
OR (max_pallet_spaces IS NOT NULL AND max_load_kg IS NOT NULL AND max_volume_cbm IS NOT NULL
AND max_pallet_spaces BETWEEN 1 AND 10000 AND max_load_kg > 0 AND max_volume_cbm > 0))
) ENGINE=InnoDB COMMENT='平台车辆主数据';
CREATE TABLE ll_fleet_vehicle_record (
id BIGINT NOT NULL COMMENT '记录内部主键',
record_no VARCHAR(32) NOT NULL COMMENT '车辆运维业务编号',
vehicle_id BIGINT NOT NULL COMMENT '所属车辆主键',
vehicle_plate VARCHAR(64) NOT NULL COMMENT '建档时车牌快照',
record_type VARCHAR(32) NOT NULL COMMENT '运维类型:保养、加油、维修、故障、救援、异常',
occurred_at DATETIME(3) NOT NULL COMMENT '记录发生时间(UTC)',
details_json JSON NOT NULL COMMENT '带明确单位的业务字段快照',
files_json JSON NOT NULL COMMENT '所属记录照片与上传幂等摘要',
idempotency_key VARCHAR(128) NOT NULL COMMENT '创建请求幂等键',
request_hash CHAR(64) NOT NULL COMMENT '原创建业务内容摘要',
record_version BIGINT NOT NULL DEFAULT 0 COMMENT '乐观锁版本',
created_by VARCHAR(64) NOT NULL COMMENT '创建操作人',
updated_by VARCHAR(64) NOT NULL COMMENT '最后操作人',
created_at DATETIME(3) NOT NULL COMMENT '创建时间(UTC)',
updated_at DATETIME(3) NOT NULL COMMENT '更新时间(UTC)',
PRIMARY KEY (id),
UNIQUE KEY uk_vehicle_record_no (record_no),
UNIQUE KEY uk_vehicle_record_creation (vehicle_id, created_by, idempotency_key),
KEY idx_vehicle_record_page (vehicle_id, record_type, occurred_at, id),
CONSTRAINT fk_vehicle_record_vehicle FOREIGN KEY (vehicle_id) REFERENCES ll_vehicle(id),
CONSTRAINT ck_vehicle_record_type CHECK (record_type IN ('MAINTENANCE','FUEL','REPAIR','BREAKDOWN','RESCUE','EXCEPTION'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='车辆运维记录';
CREATE TABLE ll_fleet_vehicle_record_history (
id BIGINT NOT NULL COMMENT '历史内部主键',
record_id BIGINT NOT NULL COMMENT '运维记录主键',
record_version BIGINT NOT NULL COMMENT '对应业务版本',
action VARCHAR(32) NOT NULL COMMENT '操作类型',
reason VARCHAR(1000) DEFAULT NULL COMMENT '更正原因或附件名称',
operator_id VARCHAR(64) NOT NULL COMMENT '操作人',
occurred_at DATETIME(3) NOT NULL COMMENT '操作时间(UTC)',
snapshot_json JSON NOT NULL COMMENT '当时完整业务快照',
PRIMARY KEY (id),
UNIQUE KEY uk_vehicle_record_revision (record_id, record_version),
CONSTRAINT fk_vehicle_record_history FOREIGN KEY (record_id) REFERENCES ll_fleet_vehicle_record(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='车辆运维记录不可变历史';
CREATE TABLE ll_fleet_trip (
id BIGINT NOT NULL COMMENT '内部主键',
trip_no VARCHAR(32) NOT NULL COMMENT '车次业务编号',
idempotency_key VARCHAR(128) NOT NULL COMMENT '创建请求幂等键',
request_hash VARCHAR(64) NOT NULL COMMENT '创建请求SHA256',
status VARCHAR(16) NOT NULL COMMENT '车次状态',
warehouse_id BIGINT NOT NULL COMMENT '起运公共仓库引用',
warehouse_code VARCHAR(64) NOT NULL COMMENT '起运仓编码快照',
warehouse_name VARCHAR(256) NOT NULL COMMENT '起运仓名称快照',
vehicle_id BIGINT NULL COMMENT '车辆档案引用',
vehicle_plate VARCHAR(64) NULL COMMENT '分配时车牌快照',
driver_id BIGINT NULL COMMENT '司机档案引用',
driver_name VARCHAR(128) NULL COMMENT '分配时司机姓名',
driver_phone VARCHAR(64) NULL COMMENT '分配时司机电话',
max_pallet_spaces INT NULL COMMENT '分配时托位容量快照',
max_load_kg DECIMAL(12,3) NULL COMMENT '分配时载重千克',
max_volume_cbm DECIMAL(12,3) NULL COMMENT '分配时可用容积立方米',
planned_departure_at DATETIME(3) NOT NULL COMMENT '计划发车UTC',
expected_driver_end_at DATETIME(3) NOT NULL COMMENT '司机预计任务结束UTC,含承担的返程转场',
departed_at DATETIME(3) NULL COMMENT '实际发车UTC',
completed_at DATETIME(3) NULL COMMENT '实际运输完成UTC',
stops_json JSON NOT NULL COMMENT '按顺序保存的路线站点',
planned_load_json JSON NULL COMMENT '拣货前自有货计划载荷与逐段剩余容量',
load_json JSON NULL COMMENT '已确认实物装载及客户货物来源快照',
loading_review_json LONGTEXT NULL COMMENT '整车理货审核快照,空值为待审核',
departure_key VARCHAR(128) NULL COMMENT '整车发车幂等标识',
departure_hash CHAR(64) NULL COMMENT '整车发车请求摘要',
departure_result_json JSON NULL COMMENT '整车发车结果及各客户出库结果',
completion_json JSON NULL COMMENT '货物结案来源及幂等结果快照',
vehicle_release_json JSON NULL COMMENT '车辆任务结束地点状态与幂等记录',
driver_release_json JSON NULL COMMENT '司机任务结束地点与幂等记录',
remark VARCHAR(1000) NULL COMMENT '备注',
record_version BIGINT NOT NULL DEFAULT 0 COMMENT '并发版本',
created_by VARCHAR(64) NOT NULL COMMENT '创建人',
created_at DATETIME(3) NOT NULL COMMENT '创建UTC时间',
updated_by VARCHAR(64) NOT NULL COMMENT '更新人',
updated_at DATETIME(3) NOT NULL COMMENT '更新UTC时间',
PRIMARY KEY (id),
UNIQUE KEY uk_fleet_trip_no (trip_no),
UNIQUE KEY uk_fleet_trip_create (created_by,idempotency_key),
KEY idx_fleet_trip_page (warehouse_id,status,updated_at,id),
CONSTRAINT fk_fleet_trip_vehicle FOREIGN KEY (vehicle_id) REFERENCES ll_vehicle(id),
CONSTRAINT fk_fleet_trip_driver FOREIGN KEY (driver_id) REFERENCES ll_fleet_driver(id),
CONSTRAINT chk_fleet_trip_status CHECK (status IN ('PLANNED','DEPARTING','IN_TRANSIT','COMPLETED','CANCELLED')),
CONSTRAINT chk_fleet_trip_time CHECK (expected_driver_end_at > planned_departure_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='平台运输车次';
CREATE TABLE ll_fleet_external_booking (
id BIGINT NOT NULL COMMENT '内部主键',
booking_no VARCHAR(32) NOT NULL COMMENT '外接预订业务编号',
trip_id BIGINT NOT NULL COMMENT '所属车次',
trip_no VARCHAR(32) NOT NULL COMMENT '车次业务编号',
customer_id VARCHAR(128) NOT NULL COMMENT '委托客户及付款归属',
customer_code VARCHAR(64) NOT NULL COMMENT '客户编号快照',
customer_name VARCHAR(255) NOT NULL COMMENT '客户名称快照',
status VARCHAR(16) NOT NULL COMMENT 'DRAFT询价/CONFIRMED占位/RECEIVED确认实收/BOUND关联发运/CANCELLED取消',
cargo_json JSON NOT NULL COMMENT '货物、计划载荷、联系人及约定报价快照',
destination_key VARCHAR(160) NOT NULL COMMENT '已有路线卸货站匹配键',
destination_label VARCHAR(255) NOT NULL COMMENT '约定卸货站名称',
agreed_amount DECIMAL(16,2) NULL COMMENT '约定金额,非已入账应收或实收',
idempotency_key VARCHAR(128) NOT NULL COMMENT '登记请求键',
request_hash CHAR(64) NOT NULL COMMENT '登记内容摘要',
confirm_key VARCHAR(128) NULL COMMENT '承运确认请求键',
confirm_hash CHAR(64) NULL COMMENT '承运确认摘要',
cancel_key VARCHAR(128) NULL COMMENT '取消请求键',
cancel_hash CHAR(64) NULL COMMENT '取消摘要',
incoming_no VARCHAR(64) NULL COMMENT '正式来货单业务编号',
dispatch_no VARCHAR(64) NULL COMMENT '已关联客户排车业务编号',
receipt_json JSON NULL COMMENT '实测来源及运营确认的托位和运费依据',
incoming_key VARCHAR(128) NULL COMMENT '来货登记请求键',
incoming_hash CHAR(64) NULL COMMENT '来货登记摘要',
receipt_key VARCHAR(128) NULL COMMENT '实收确认请求键',
receipt_hash CHAR(64) NULL COMMENT '实收确认摘要',
binding_key VARCHAR(128) NULL COMMENT '关联发运请求键',
binding_hash CHAR(64) NULL COMMENT '关联发运摘要',
record_version BIGINT NOT NULL DEFAULT 0 COMMENT '记录版本',
created_by VARCHAR(64) NOT NULL COMMENT '登记人',
created_at DATETIME(3) NOT NULL COMMENT '登记UTC时间',
updated_by VARCHAR(64) NOT NULL COMMENT '修改人',
updated_at DATETIME(3) NOT NULL COMMENT '修改UTC时间',
PRIMARY KEY(id),
UNIQUE KEY uk_fleet_booking_no(booking_no),
UNIQUE KEY uk_fleet_booking_create(trip_id,created_by,idempotency_key),
KEY idx_fleet_booking_trip(trip_id,status,id),
UNIQUE KEY uk_fleet_booking_incoming(incoming_no),
KEY idx_fleet_booking_dispatch(dispatch_no),
CONSTRAINT fk_fleet_booking_trip FOREIGN KEY(trip_id) REFERENCES ll_fleet_trip(id),
CONSTRAINT chk_fleet_booking_status CHECK(status IN ('DRAFT','CONFIRMED','RECEIVED','BOUND','CANCELLED'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='外接询价及确认承运占位;货物收发和费用入账分别引用正式事实';
CREATE TABLE ll_fleet_resource_assignment (
resource_kind VARCHAR(16) NOT NULL COMMENT '资源种类 VEHICLE或DRIVER',
resource_id BIGINT NOT NULL COMMENT '对应车辆或司机档案引用',
trip_id BIGINT NOT NULL COMMENT '占用车次',
trip_no VARCHAR(32) NOT NULL COMMENT '占用车次业务编号',
PRIMARY KEY (resource_kind,resource_id),
KEY idx_fleet_resource_trip (trip_id),
CONSTRAINT fk_fleet_resource_trip FOREIGN KEY (trip_id) REFERENCES ll_fleet_trip(id),
CONSTRAINT chk_fleet_resource_kind CHECK (resource_kind IN ('VEHICLE','DRIVER'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='车辆及司机当前车次占用,释放后删除锁行,历史保留在车次';
CREATE TABLE ll_fleet_trip_dispatch (
dispatch_id BIGINT NOT NULL COMMENT '客户排车单内部引用,唯一有效车次关联',
dispatch_no VARCHAR(64) NOT NULL COMMENT '客户排车业务单号',
customer_id VARCHAR(128) NOT NULL COMMENT '排车单自身客户归属',
trip_id BIGINT NOT NULL COMMENT '共享运输车次内部引用',
trip_no VARCHAR(32) NOT NULL COMMENT '共享运输车次业务编号',
package_count INT NOT NULL COMMENT '关联时待发运箱数快照',
bound_by VARCHAR(64) NOT NULL COMMENT '关联操作人',
bound_at DATETIME(3) NOT NULL COMMENT '关联UTC时间',
review_json LONGTEXT NULL COMMENT '客户排车调度审核快照,空值为待审核',
PRIMARY KEY (dispatch_id),
UNIQUE KEY uk_fleet_trip_dispatch_no (dispatch_no),
KEY idx_fleet_trip_dispatch_trip (trip_id,dispatch_id),
CONSTRAINT fk_fleet_trip_dispatch_trip FOREIGN KEY (trip_id) REFERENCES ll_fleet_trip(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='车次与各客户排车单的有效关联,解除前快照保留在车次历史';
CREATE TABLE ll_fleet_trip_history (
id BIGINT NOT NULL COMMENT '内部主键',
trip_id BIGINT NOT NULL COMMENT '车次内部引用',
record_version BIGINT NOT NULL COMMENT '操作后版本',
action VARCHAR(32) NOT NULL COMMENT '车次操作',
reason VARCHAR(1000) NULL COMMENT '操作原因',
operator_id VARCHAR(64) NOT NULL COMMENT '操作人',
occurred_at DATETIME(3) NOT NULL COMMENT '操作UTC时间',
snapshot_json JSON NOT NULL COMMENT '操作后不可变车次快照',
PRIMARY KEY (id),
UNIQUE KEY uk_fleet_trip_history_version (trip_id,record_version),
CONSTRAINT fk_fleet_trip_history FOREIGN KEY (trip_id) REFERENCES ll_fleet_trip(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='运输车次操作历史';
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_event_outbox' AND column_name='locked_at'), 'SELECT 1', 'ALTER TABLE ll_event_outbox ADD COLUMN locked_at DATETIME(3) NULL COMMENT ''投递租约获取时间'' AFTER published_at'));
PREPARE ll_outbox_lease_stmt FROM @ddl; EXECUTE ll_outbox_lease_stmt; DEALLOCATE PREPARE ll_outbox_lease_stmt;
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_event_outbox' AND column_name='locked_by'), 'SELECT 1', 'ALTER TABLE ll_event_outbox ADD COLUMN locked_by VARCHAR(128) NULL COMMENT ''投递租约持有者'' AFTER locked_at'));
@@ -1389,7 +1619,7 @@ CREATE TABLE IF NOT EXISTS ll_dispatch_item (
contact_name VARCHAR(128) NULL COMMENT '私人仓联系人快照',
contact_phone VARCHAR(64) NULL COMMENT '私人仓联系电话快照',
package_count INT NOT NULL DEFAULT 0 COMMENT '箱数',
gross_weight_kg DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT '毛重(千克)',
gross_weight_kg DECIMAL(18,3) NULL COMMENT '毛重(千克),未知为NULL,不以材积重代替',
volume_weight_kg DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT '材积重(千克)',
volume_cbm DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT '体积(立方米)',
source_stage VARCHAR(32) NOT NULL COMMENT '数据来源阶段',
@@ -1510,13 +1740,14 @@ CREATE TABLE IF NOT EXISTS ll_outbound_order (
outbound_no VARCHAR(64) NOT NULL COMMENT '出库单号', dispatch_order_id BIGINT NULL COMMENT '派车单ID,一件代发为空', dispatch_no VARCHAR(64) NULL COMMENT '派车单号,一件代发为空',
overseas_warehouse_id VARCHAR(128) NULL COMMENT '海外仓ID', status VARCHAR(32) NOT NULL DEFAULT 'WAIT_OUTBOUND' COMMENT '业务状态',
package_count INT NOT NULL DEFAULT 0 COMMENT '箱数', pallet_count INT NOT NULL DEFAULT 0 COMMENT '托盘数',
gross_weight_kg DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT '毛重(千克)', volume_weight_kg DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT '材积重(千克)',
gross_weight_kg DECIMAL(18,3) NULL COMMENT '毛重(千克),来源未确认时为空', volume_weight_kg DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT '材积重(千克)',
volume_cbm DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT '体积(立方米)',
idempotency_key VARCHAR(255) NOT NULL COMMENT '业务幂等键', created_by VARCHAR(128) NULL COMMENT '创建人ID',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
outbound_type VARCHAR(32) NOT NULL DEFAULT 'DISPATCH_DELIVERY' COMMENT '出库类型:派送转出库、一件代发', lx_codes VARCHAR(1000) NULL COMMENT '关联一件代发领星入库单代码汇总',
recipient_info VARCHAR(1000) NULL COMMENT '一件代发收件信息',
request_hash CHAR(64) NULL COMMENT '一件代发创建请求摘要,其他出库类型为空',
request_hash CHAR(64) NULL COMMENT '创建或转派送业务请求摘要,用于幂等内容校验',
fee_coverage_json JSON NULL COMMENT '本次发运引用的原费用版本、实际货物及明确服务覆盖确认快照',
PRIMARY KEY (id), UNIQUE KEY uk_ll_outbound_no (outbound_no),
UNIQUE KEY uk_ll_outbound_idempotency (customer_id,idempotency_key), UNIQUE KEY uk_ll_outbound_dispatch (customer_id,dispatch_order_id),
KEY idx_ll_outbound_list (customer_id,status,created_at)
@@ -1526,7 +1757,7 @@ CREATE TABLE IF NOT EXISTS ll_outbound_pallet_item (
id BIGINT NOT NULL COMMENT '主键ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', outbound_order_id BIGINT NOT NULL COMMENT '出库单ID',
warehouse_pallet_id BIGINT NULL COMMENT '仓库实物托盘ID', pallet_no VARCHAR(128) NULL COMMENT '托盘号',
destination_warehouse_id VARCHAR(128) NULL COMMENT '目的仓ID', destination_warehouse_code VARCHAR(128) NULL COMMENT '目的仓编码快照', destination_warehouse_name VARCHAR(255) NULL COMMENT '目的仓名称快照',
package_count INT NOT NULL DEFAULT 0 COMMENT '箱数', gross_weight_kg DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT '毛重(千克)', volume_weight_kg DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT '材积重(千克)', volume_cbm DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT '体积(立方米)', picked_package_count INT NOT NULL DEFAULT 0 COMMENT '已拣箱数', status VARCHAR(32) NOT NULL DEFAULT 'WAIT_PICK' COMMENT '拣货状态', sku_code VARCHAR(128) NULL COMMENT '一件代发SKU', order_refs VARCHAR(2000) NULL COMMENT '关联订单信息', inbound_code VARCHAR(128) NULL COMMENT '入仓单号快照',
package_count INT NOT NULL DEFAULT 0 COMMENT '箱数', gross_weight_kg DECIMAL(18,3) NULL COMMENT '毛重(千克),来源未确认时为空', volume_weight_kg DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT '材积重(千克)', volume_cbm DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT '体积(立方米)', picked_package_count INT NOT NULL DEFAULT 0 COMMENT '已拣箱数', status VARCHAR(32) NOT NULL DEFAULT 'WAIT_PICK' COMMENT '拣货状态', sku_code VARCHAR(128) NULL COMMENT '一件代发SKU', order_refs VARCHAR(2000) NULL COMMENT '关联订单信息', inbound_code VARCHAR(128) NULL COMMENT '入仓单号快照',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', PRIMARY KEY (id),
UNIQUE KEY uk_ll_outbound_pallet (customer_id,outbound_order_id,warehouse_pallet_id,inbound_code,sku_code),
KEY idx_ll_outbound_pallet_list (customer_id,outbound_order_id), KEY idx_ll_outbound_pallet_destination (customer_id,destination_warehouse_code)
@@ -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