Merge remote-tracking branch 'origin/test' into dev

This commit is contained in:
2026-09-01 15:59:15 +08:00
26 changed files with 726 additions and 172 deletions
@@ -856,16 +856,16 @@ CREATE TABLE IF NOT EXISTS fl_dropship_pallet_task (
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户隔离键',
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '一件代发入仓单ID',
warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '作业仓库ID',
task_status VARCHAR(24) NOT NULL COMMENT 'PENDING/CLAIMED/COMPLETED',
task_status VARCHAR(24) NOT NULL COMMENT '任务状态:PENDING待领取、CLAIMED已领取、COMPLETED已完成',
total_cartons INT UNSIGNED NOT NULL COMMENT '测量确认总箱数',
allocated_cartons INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '已上托箱数',
assignee_id BIGINT UNSIGNED NULL,
assignee_name VARCHAR(64) NULL,
claimed_at DATETIME(3) NULL,
completed_at DATETIME(3) NULL,
version INT NOT NULL DEFAULT 1,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
assignee_id BIGINT UNSIGNED NULL COMMENT '任务领取人ID',
assignee_name VARCHAR(64) NULL COMMENT '任务领取人姓名快照',
claimed_at DATETIME(3) NULL COMMENT '任务领取时间',
completed_at DATETIME(3) NULL COMMENT '任务完成时间',
version INT NOT NULL DEFAULT 1 COMMENT '任务乐观锁版本',
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 '任务更新时间',
PRIMARY KEY (id),
UNIQUE KEY uk_dropship_task_inbound (tenant_id,inbound_id),
KEY idx_dropship_task_operator (task_status,assignee_id,warehouse_id,created_at),
@@ -876,22 +876,22 @@ CREATE TABLE IF NOT EXISTS fl_dropship_pallet_task (
CREATE TABLE IF NOT EXISTS fl_dropship_pallet (
id BIGINT UNSIGNED NOT NULL COMMENT '合托托盘ID',
task_id BIGINT UNSIGNED NOT NULL,
tenant_id BIGINT UNSIGNED NOT NULL,
inbound_id BIGINT UNSIGNED NOT NULL,
warehouse_id BIGINT UNSIGNED NOT NULL,
task_id BIGINT UNSIGNED NOT NULL COMMENT '所属合托任务ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户隔离键',
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '一件代发入仓单ID',
warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '作业仓库ID',
pallet_no VARCHAR(32) NULL COMMENT '封托时生成的DP托盘号',
pallet_status VARCHAR(16) NOT NULL COMMENT 'OPEN/COMPLETED',
open_operation_id VARCHAR(128) NOT NULL,
seal_operation_id VARCHAR(128) NULL,
created_by BIGINT UNSIGNED NOT NULL,
created_by_name VARCHAR(64) NULL,
completed_by BIGINT UNSIGNED NULL,
completed_by_name VARCHAR(64) NULL,
completed_at DATETIME(3) NULL,
version INT NOT NULL DEFAULT 1,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
pallet_status VARCHAR(16) NOT NULL COMMENT '托盘状态:OPEN合托中、COMPLETED已封托',
open_operation_id VARCHAR(128) NOT NULL COMMENT '创建托盘幂等操作ID',
seal_operation_id VARCHAR(128) NULL COMMENT '封托幂等操作ID',
created_by BIGINT UNSIGNED NOT NULL COMMENT '托盘创建人ID',
created_by_name VARCHAR(64) NULL COMMENT '托盘创建人姓名快照',
completed_by BIGINT UNSIGNED NULL COMMENT '封托操作人ID',
completed_by_name VARCHAR(64) NULL COMMENT '封托操作人姓名快照',
completed_at DATETIME(3) NULL COMMENT '封托完成时间',
version INT NOT NULL DEFAULT 1 COMMENT '托盘乐观锁版本',
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 '托盘更新时间',
PRIMARY KEY (id),
UNIQUE KEY uk_dropship_pallet_no (pallet_no),
UNIQUE KEY uk_dropship_pallet_open_op (open_operation_id),
@@ -902,20 +902,20 @@ CREATE TABLE IF NOT EXISTS fl_dropship_pallet (
) ENGINE=InnoDB COMMENT='一件代发合托形成的可打印托盘';
CREATE TABLE IF NOT EXISTS fl_dropship_pallet_carton (
id BIGINT UNSIGNED NOT NULL,
task_id BIGINT UNSIGNED NOT NULL,
pallet_id BIGINT UNSIGNED NOT NULL,
tenant_id BIGINT UNSIGNED NOT NULL,
inbound_id BIGINT UNSIGNED NOT NULL,
inbound_detail_id BIGINT UNSIGNED NOT NULL,
carton_label_id BIGINT UNSIGNED NOT NULL,
sku_code VARCHAR(128) NOT NULL,
cargo_name VARCHAR(255) NULL,
box_code VARCHAR(64) NOT NULL,
operation_id VARCHAR(128) NOT NULL,
bound_by BIGINT UNSIGNED NOT NULL,
bound_by_name VARCHAR(64) NULL,
bound_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
id BIGINT UNSIGNED NOT NULL COMMENT '托盘箱明细ID',
task_id BIGINT UNSIGNED NOT NULL COMMENT '所属合托任务ID',
pallet_id BIGINT UNSIGNED NOT NULL COMMENT '所属合托托盘ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户隔离键',
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '一件代发入仓单ID',
inbound_detail_id BIGINT UNSIGNED NOT NULL COMMENT '入仓货物明细ID',
carton_label_id BIGINT UNSIGNED NOT NULL COMMENT '稳定箱唛记录ID',
sku_code VARCHAR(128) NOT NULL COMMENT '箱唛冻结SKU编码',
cargo_name VARCHAR(255) NULL COMMENT '货物名称快照',
box_code VARCHAR(64) NOT NULL COMMENT '稳定箱码',
operation_id VARCHAR(128) NOT NULL COMMENT '上托幂等操作ID',
bound_by BIGINT UNSIGNED NOT NULL COMMENT '上托操作人ID',
bound_by_name VARCHAR(64) NULL COMMENT '上托操作人姓名快照',
bound_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '上托时间',
PRIMARY KEY (id),
UNIQUE KEY uk_dropship_carton_task_label (task_id,carton_label_id),
KEY idx_dropship_carton_pallet_sku (pallet_id,sku_code,id),
@@ -49,8 +49,8 @@ paths:
patch:
summary: 修改预装运输信息
description: >-
唯一纠错入口。仅已推送且仍为 EDITING、真实柜号已由 ocean-booking-link 首次写入、PDA任务尚未领取且无任何物理作业事实时允许;
containerNo 为空表示保持当前值,非空时服务端移除格式空白、转大写并校验ISO 6346结构、设备类别和校验位。禁止清空,LOADING/COMPLETED全部拒绝。
已推送后可在 EDITING 或 LOADING 阶段首次补录真实集装箱号;已有柜号的纠正仅限仍为 EDITING、PDA任务尚未领取且无任何物理作业事实时,并必须提供原因和当前版本;
containerNo 为 null 表示保持当前值,非空时服务端移除格式空白、转大写并校验ISO 6346结构、设备类别和校验位,空白字符串非法且禁止清空。
x-permission: platform:firstleg:preload:edit
parameters:
- {$ref: '#/components/parameters/PlanId'}
@@ -66,7 +66,7 @@ paths:
/api/platform/v1/first-leg/loading-plans/{planId}/confirm:
post:
summary: 原子确认整份预装计划
description: 以 loadingPlanId 为唯一范围,原子确认全部已分配入库预装票据。要求计划已 push 且仍为 EDITING、真实集装箱号已录入、稳定 bridge 完整、真实订舱已关联并为 RELEASED;任一票不满足均不写入。成功后唯一创建待领取 PDA task,claim 后才推进 LOADING。同一 Idempotency-Key 与相同请求精确重放原响应,不同 Key 重复确认返回业务冲突。
description: 以 loadingPlanId 为唯一范围,原子确认全部已分配入库预装票据。要求计划已 push 且仍为 EDITING、稳定 bridge 完整、真实订舱已关联并为 RELEASED;真实集装箱号可在PDA确认装柜完成前补录。任一票不满足均不写入。成功后唯一创建待领取 PDA task,claim 后才推进 LOADING。同一 Idempotency-Key 与相同请求精确重放原响应,不同 Key 重复确认返回业务冲突。
x-permission: platform:firstleg:preload:edit
parameters: [{$ref: '#/components/parameters/PlanId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
requestBody:
@@ -313,11 +313,11 @@ components:
pdaTaskStatus: {type: string, nullable: true, description: PDA task 当前状态}
confirmationStatus: {type: string, enum: [PENDING, PARTIALLY_CONFIRMED, CONFIRMED], description: 整份计划的预装确认状态}
canConfirm: {type: boolean, description: 当前是否可调用计划级确认接口}
readyForPda: {type: boolean, description: PDA 创建前置条件是否全部满足;task 已创建后仍为 true}
readyForPda: {type: boolean, description: PDA 创建前置条件是否全部满足;不依赖真实集装箱号,task 已创建后仍为 true}
blockingReasons:
type: array
description: 阻止计划确认的稳定原因码
items: {type: string, enum: [PRELOAD_NOT_PUSHED, LOADING_PLAN_NOT_EDITING, CONTAINER_NO_MISSING, PRELOAD_LINK_MISSING, OCEAN_BOOKING_NOT_LINKED, OCEAN_BOOKING_NOT_RELEASED, PDA_TASK_ALREADY_CREATED, PRELOAD_ALREADY_CONFIRMED]}
items: {type: string, enum: [PRELOAD_NOT_PUSHED, LOADING_PLAN_NOT_EDITING, PRELOAD_LINK_MISSING, OCEAN_BOOKING_NOT_LINKED, OCEAN_BOOKING_NOT_RELEASED, PDA_TASK_ALREADY_CREATED, PRELOAD_ALREADY_CONFIRMED]}
ConfirmPreloadRequest:
type: object
required: [expectedVersion, operationReason]
@@ -428,4 +428,4 @@ components:
containerNo: {type: string, maxLength: 64, nullable: true, description: 新真实集装箱号;null表示保持当前值,空白字符串非法,禁止清空}
voyageNo: {type: string, maxLength: 64, nullable: true, description: 航次}
expectedVersion: {type: integer, format: int32, minimum: 0, description: 当前乐观锁版本}
operationReason: {type: string, minLength: 1, maxLength: 500, description: 必填纠错原因}
operationReason: {type: string, minLength: 1, maxLength: 500, nullable: true, description: 纠正已有真实集装箱号时必填;首次补录或保持当前柜号时可省略}
@@ -2,7 +2,7 @@ openapi: 3.0.3
info:
title: Truck Appointment API
version: 1.3.0
description: Every truck appointment must select one RELEASED ocean booking and one ACTIVE real-numbered container already allocated to that booking. The bill-of-lading number is frozen from the booking, and the booking must own at least one active BOOKING_CONFIRMATION or BILL_OF_LADING file under OCEAN_BOOKING_DOCUMENT. Booking and bill files are not owned by truck appointments. After a PDA loading transaction commits, the server best-effort completes the unique active appointment explicitly linked to that container in an isolated transaction; absence, duplicates or concurrent truck edits never roll back loading completion.
description: Every truck appointment must select one RELEASED ocean booking and its allocated ACTIVE internal container identity. A real ISO 6346 container number is not required for create, edit, submit or dispatch. PICKUP may omit it and still advances normally; when supplied, PICKUP atomically records it if still missing or reuses the existing valid number. The bill-of-lading number is frozen from the booking, and the booking must own at least one active BOOKING_CONFIRMATION or BILL_OF_LADING file under OCEAN_BOOKING_DOCUMENT. Booking and bill files are not owned by truck appointments. After a PDA loading transaction commits, the server best-effort completes the unique active appointment explicitly linked to that container in an isolated transaction; absence, duplicates or concurrent truck edits never roll back loading completion.
paths:
/api/platform/v1/truck-appointments:
get:
@@ -20,7 +20,7 @@ paths:
properties:
records: {type: array, items: {$ref: '#/components/schemas/DetailStableReferences'}}
post:
summary: Create a truck appointment for a released booking and its allocated real container
summary: Create a truck appointment for a released booking and its allocated internal container
description: If the real container already has a non-terminal appointment, the server returns A0409 with the existing appointment number and instructs the operator to continue editing. This duplicate guard runs before booking-document validation; the database unique key provides the same response for concurrent creates.
requestBody:
required: true
@@ -30,6 +30,16 @@ paths:
responses:
'200': {description: 'Created appointment', content: {application/json: {schema: {$ref: '#/components/schemas/DetailEnvelope'}}}}
'409': {description: 'A0409 - this container already has a non-terminal appointment; message includes its appointment number and asks the operator to continue editing'}
/api/platform/v1/truck-appointments/candidates:
get:
summary: Query authoritative create-time ocean-booking and internal-container candidates
description: Requires platform:firstleg:truck-appointment:list. Returns only RELEASED bookings with an active booking-owned confirmation or bill file and allocated ACTIVE internal containers that have no non-terminal truck appointment and whose loading plan is absent or not COMPLETED. A missing containerNo remains eligible. Completed/LOADED containers, occupied containers and bookings without any remaining eligible allocated container are absent. Edit pages must display their current binding from appointment detail rather than injecting it into this create-only candidate result.
parameters:
- {name: keyword, in: query, required: false, schema: {type: string, maxLength: 64}, description: Matches booking number, bill of lading, cabinet number or real container number}
- {name: page, in: query, required: false, schema: {type: integer, format: int64, minimum: 1, default: 1}}
- {name: size, in: query, required: false, schema: {type: integer, format: int64, minimum: 1, maximum: 200, default: 20}}
responses:
'200': {description: Paged flat booking-container candidates, content: {application/json: {schema: {$ref: '#/components/schemas/CandidatePageEnvelope'}}}}
/api/platform/v1/truck-appointments/{id}:
parameters: [{$ref: '#/components/parameters/AppointmentId'}]
get:
@@ -53,7 +63,7 @@ paths:
- {$ref: '#/components/parameters/RequestHash'}
post:
summary: Apply an idempotent appointment lifecycle action
description: ARRIVE, LOAD and DEPART persist their actual event timestamps atomically with the status transition.
description: PICKUP accepts optional containerNo. Omitting it never blocks the transition to PICKUP_IN_PROGRESS; when supplied, the server atomically records it on the linked internal container if missing. An existing valid ISO 6346 number may be omitted or resubmitted unchanged; a different value is rejected and must use the preload correction flow. Other actions reject containerNo. ARRIVE, LOAD and DEPART persist their actual event timestamps atomically with the status transition.
requestBody:
required: true
content:
@@ -138,6 +148,45 @@ components:
IdempotencyKey: {name: Idempotency-Key, in: header, required: true, schema: {type: string, maxLength: 128}}
RequestHash: {name: X-Request-Hash, in: header, required: false, schema: {type: string, pattern: '^[0-9a-fA-F]{64}$'}}
schemas:
CandidatePageEnvelope:
type: object
required: [code, message, data, timestamp]
properties:
code: {type: string, example: '200'}
message: {type: string}
data:
type: object
required: [records, page, size, total, pages]
properties:
records: {type: array, items: {$ref: '#/components/schemas/Candidate'}}
page: {type: integer, format: int64}
size: {type: integer, format: int64}
total: {type: integer, format: int64}
pages: {type: integer, format: int64}
requestId: {type: string, nullable: true}
traceId: {type: string, nullable: true}
timestamp: {type: string, format: date-time}
Candidate:
type: object
required: [oceanBookingId, oceanBookingNo, warehouseId, requirementId, containerType, requirementQuantity, containerBookingId, cabinetNo, bookingDocumentReady]
properties:
oceanBookingId: {type: integer, format: int64}
oceanBookingNo: {type: string}
billOfLadingNo: {type: string, nullable: true}
warehouseId: {type: integer, format: int64}
warehouseName: {type: string, nullable: true}
channelCode: {type: string, nullable: true}
channelName: {type: string, nullable: true}
requirementId: {type: integer, format: int64}
containerType: {type: string}
requirementQuantity: {type: integer, minimum: 1}
containerBookingId: {type: integer, format: int64}
cabinetNo: {type: string}
containerNo: {type: string, nullable: true, description: Optional until PICKUP or preload correction; absence does not make the candidate unavailable}
loadingPlanId: {type: integer, format: int64, nullable: true}
loadingPlanNo: {type: string, nullable: true}
loadingPlanStatus: {type: string, nullable: true}
bookingDocumentReady: {type: boolean, enum: [true]}
DetailEnvelope:
type: object
required: [code, message, data, timestamp]
@@ -176,13 +225,14 @@ components:
properties:
expectedVersion: {type: integer, minimum: 0}
reason: {type: string, maxLength: 500}
containerNo: {type: string, maxLength: 32, nullable: true, description: Optional and only valid for PICKUP. Omission does not block PICKUP; when supplied it is normalized and validated as unique ISO 6346 before being written atomically. A different value from an existing number is rejected and must use preload correction.}
SaveRequest:
type: object
required: [oceanBookingId, containerBookingId, carrierId, plannedLoadingAt, expectedVersion]
properties:
outboundOrderId: {type: integer, format: int64, minimum: 1, nullable: true}
oceanBookingId: {type: integer, format: int64, minimum: 1, description: Required RELEASED booking; must own an active BOOKING_CONFIRMATION or BILL_OF_LADING file.}
containerBookingId: {type: integer, format: int64, minimum: 1, description: Required ACTIVE container already allocated to oceanBookingId and carrying a valid unique ISO 6346 container number.}
containerBookingId: {type: integer, format: int64, minimum: 1, description: Required ACTIVE internal container already allocated to oceanBookingId. Its real ISO 6346 number may remain empty until PICKUP.}
loadingPlanId: {type: integer, format: int64, minimum: 1, nullable: true, description: Optional loading-plan association. It may be supplied only with containerBookingId and must be that container's current plan.}
warehouseId: {type: integer, format: int64, minimum: 1, nullable: true, description: Optional warehouse scope. Linked booking/container warehouse facts must agree; when omitted the linked fact is frozen if available.}
carrierId: {type: integer, format: int64, minimum: 1, description: Required ACTIVE Warehouse supplier ID validated as TRUCKING.}