This commit is contained in:
2026-08-12 20:36:11 +08:00
parent 0380c0553b
commit 0ed1531683
26 changed files with 1690 additions and 15 deletions
@@ -0,0 +1,57 @@
USE baiyue_firstleg;
DROP PROCEDURE IF EXISTS add_simple_customs_column;
DELIMITER $$
CREATE PROCEDURE add_simple_customs_column(IN p_name VARCHAR(64), IN p_definition TEXT)
BEGIN
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = 'fl_container_document'
AND column_name = p_name) THEN
SET @ddl = CONCAT('ALTER TABLE fl_container_document ADD COLUMN ', p_name, ' ', p_definition);
PREPARE statement FROM @ddl; EXECUTE statement; DEALLOCATE PREPARE statement;
END IF;
END$$
DELIMITER ;
CALL add_simple_customs_column('file_hash', "VARCHAR(64) NULL COMMENT 'File服务SHA-256快照' AFTER file_name");
CALL add_simple_customs_column('completed_by', "BIGINT UNSIGNED NULL COMMENT '一键完成操作人ID' AFTER uploaded_by_name");
CALL add_simple_customs_column('completed_by_name', "VARCHAR(64) NULL COMMENT '一键完成操作人名称快照' AFTER completed_by");
CALL add_simple_customs_column('completed_at', "DATETIME(3) NULL COMMENT '一键完成时间' AFTER completed_by_name");
DROP PROCEDURE add_simple_customs_column;
SET @old_check = (SELECT CONSTRAINT_NAME FROM information_schema.table_constraints
WHERE table_schema = DATABASE() AND table_name = 'fl_container_document'
AND constraint_type = 'CHECK' AND constraint_name = 'chk_container_document_type');
SET @ddl = IF(@old_check IS NULL, 'SELECT 1',
'ALTER TABLE fl_container_document DROP CHECK chk_container_document_type');
PREPARE statement FROM @ddl; EXECUTE statement; DEALLOCATE PREPARE statement;
ALTER TABLE fl_container_document ADD CONSTRAINT chk_container_document_type
CHECK (document_type IN ('DESTINATION_CLEARANCE', 'CUSTOMS_DECLARATION', 'DELIVERY_ORDER', 'PICKUP_ORDER'));
DROP TRIGGER IF EXISTS trg_container_document_before_insert;
DELIMITER $$
CREATE TRIGGER trg_container_document_before_insert BEFORE INSERT ON fl_container_document FOR EACH ROW
BEGIN
DECLARE current_plan_status VARCHAR(24);
SELECT MAX(plan_status) INTO current_plan_status FROM fl_loading_plan
WHERE container_booking_id = NEW.container_booking_id;
IF current_plan_status = 'COMPLETED'
AND NEW.document_type NOT IN ('DESTINATION_CLEARANCE', 'CUSTOMS_DECLARATION') THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '装柜完成后货柜物流文件不可新增';
END IF;
END$$
DELIMITER ;
DROP TRIGGER IF EXISTS trg_container_document_before_update;
DELIMITER $$
CREATE TRIGGER trg_container_document_before_update BEFORE UPDATE ON fl_container_document FOR EACH ROW
BEGIN
DECLARE current_plan_status VARCHAR(24);
SELECT MAX(plan_status) INTO current_plan_status FROM fl_loading_plan
WHERE container_booking_id = OLD.container_booking_id;
IF current_plan_status = 'COMPLETED'
AND OLD.document_type NOT IN ('DESTINATION_CLEARANCE', 'CUSTOMS_DECLARATION') THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '装柜完成后货柜物流文件不可修改';
END IF;
END$$
DELIMITER ;
@@ -180,15 +180,20 @@ CREATE TABLE IF NOT EXISTS fl_loading_plan_item (
CONSTRAINT chk_loading_item_deleted CHECK (deleted IN (0, 1))
) ENGINE=InnoDB COMMENT='智能装柜客户货物明细;客户归属仅在明细层';
-- 目的港清关和出口报关均为柜次级简单文件事实:上传资料后由平台一键完成。
CREATE TABLE IF NOT EXISTS fl_container_document (
id BIGINT UNSIGNED NOT NULL COMMENT '货柜文件关联ID(雪花ID)',
container_booking_id BIGINT UNSIGNED NOT NULL COMMENT '货柜预订ID',
document_type VARCHAR(32) NOT NULL COMMENT '文件类型:CUSTOMS_CLEARANCE清关文件、DELIVERY_ORDER提货单、PICKUP_ORDER取货单',
document_type VARCHAR(32) NOT NULL COMMENT '文件类型:DESTINATION_CLEARANCE目的港清关、CUSTOMS_DECLARATION出口报关、DELIVERY_ORDER提货单、PICKUP_ORDER取货单',
file_id BIGINT UNSIGNED NOT NULL COMMENT 'File服务文件ID,不建立跨库外键',
file_name VARCHAR(255) NOT NULL COMMENT '文件名称快照',
file_hash VARCHAR(64) NULL COMMENT 'File服务SHA-256快照',
file_url VARCHAR(1000) NULL COMMENT '文件访问地址快照',
uploaded_by BIGINT UNSIGNED NOT NULL COMMENT '上传操作人ID',
uploaded_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 '一键完成时间',
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 '货柜文件关联最后更新时间',
version INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
@@ -198,7 +203,7 @@ CREATE TABLE IF NOT EXISTS fl_container_document (
CONSTRAINT fk_container_document_booking FOREIGN KEY (container_booking_id)
REFERENCES fl_container_booking (id),
CONSTRAINT chk_container_document_type
CHECK (document_type IN ('CUSTOMS_CLEARANCE', 'DELIVERY_ORDER', 'PICKUP_ORDER'))
CHECK (document_type IN ('DESTINATION_CLEARANCE', 'CUSTOMS_DECLARATION', 'DELIVERY_ORDER', 'PICKUP_ORDER'))
) ENGINE=InnoDB COMMENT='平台货柜清关、DO与PU文件关联';
CREATE TABLE IF NOT EXISTS fl_loading_plan_history (
@@ -557,7 +562,8 @@ BEGIN
DECLARE current_plan_status VARCHAR(24);
SELECT MAX(plan_status) INTO current_plan_status
FROM fl_loading_plan WHERE container_booking_id = OLD.id;
IF current_plan_status = 'COMPLETED' THEN
IF current_plan_status = 'COMPLETED'
AND NEW.document_type NOT IN ('DESTINATION_CLEARANCE', 'CUSTOMS_DECLARATION') THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '装柜完成后货柜全部只读,不允许修改';
END IF;
IF current_plan_status = 'LOADING' AND (
@@ -920,7 +926,8 @@ BEGIN
DECLARE current_plan_status VARCHAR(24);
SELECT MAX(plan_status) INTO current_plan_status FROM fl_loading_plan
WHERE container_booking_id = NEW.container_booking_id;
IF current_plan_status = 'COMPLETED' THEN
IF current_plan_status = 'COMPLETED'
AND OLD.document_type NOT IN ('DESTINATION_CLEARANCE', 'CUSTOMS_DECLARATION') THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '装柜完成后货柜文件不可新增';
END IF;
END$$
@@ -243,6 +243,27 @@ paths:
responses:
'200': {description: 成功或状态、版本、重复号冲突}
'403': {description: 无货柜编辑权限}
/api/platform/v1/containers/{id}/clearance:
get:
summary: 查询货柜目的港清关资料
parameters: [{$ref: '#/components/parameters/ContainerBookingId'}]
responses: {'200': {description: 成功或货柜不存在}}
/api/platform/v1/containers/{id}/clearance/document:
post:
summary: 上传或替换货柜目的港清关资料
parameters: [{$ref: '#/components/parameters/ContainerBookingId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
requestBody:
required: true
content: {application/json: {schema: {$ref: '#/components/schemas/ContainerDocumentAttachRequest'}}}
responses: {'200': {description: 成功、文件归属不匹配或版本冲突}}
/api/platform/v1/containers/{id}/clearance/complete:
post:
summary: 点击完成目的港清关
parameters: [{$ref: '#/components/parameters/ContainerBookingId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
requestBody:
required: true
content: {application/json: {schema: {$ref: '#/components/schemas/ContainerDocumentCompleteRequest'}}}
responses: {'200': {description: 成功、未上传资料或版本冲突}}
/api/platform/v1/first-leg/smart-loading/plans/by-booking/{bookingId}:
get:
summary: 按货柜恢复智能装柜草稿
@@ -1034,6 +1055,19 @@ components:
voyageNo: {type: string, maxLength: 64, nullable: true}
version: {type: integer, minimum: 0}
reason: {type: string, maxLength: 500}
ContainerDocumentAttachRequest:
type: object
required: [fileId, expectedVersion]
properties:
fileId: {type: string, pattern: '^[1-9][0-9]*$'}
expectedVersion: {type: integer, minimum: 0}
operationReason: {type: string, maxLength: 500, nullable: true}
ContainerDocumentCompleteRequest:
type: object
required: [expectedVersion]
properties:
expectedVersion: {type: integer, minimum: 0}
operationReason: {type: string, maxLength: 500, nullable: true}
MerchantInboundBillingEnvelope:
type: object
properties:
@@ -68,6 +68,33 @@ paths:
parameters: [{$ref: '#/components/parameters/PlanId'}, {$ref: '#/components/parameters/WarehouseId'}]
responses:
'200': {$ref: '#/components/responses/ConfirmedDetailResponse'}
/api/platform/v1/first-leg/loading-lists/{planId}/customs:
get:
summary: 查询装柜清单出口报关文档
x-permission: platform:firstleg:loading-list:view
parameters: [{$ref: '#/components/parameters/PlanId'}, {$ref: '#/components/parameters/WarehouseId'}]
responses:
'200': {$ref: '#/components/responses/BusinessDocumentResponse'}
/api/platform/v1/first-leg/loading-lists/{planId}/customs/document:
post:
summary: 上传或替换装柜清单出口报关文档
x-permission: platform:firstleg:customs:manage
parameters: [{$ref: '#/components/parameters/PlanId'}, {$ref: '#/components/parameters/WarehouseId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
requestBody:
required: true
content: {application/json: {schema: {$ref: '#/components/schemas/DocumentAttachRequest'}}}
responses:
'200': {$ref: '#/components/responses/BusinessDocumentResponse'}
/api/platform/v1/first-leg/loading-lists/{planId}/customs/complete:
post:
summary: 点击完成出口报关
x-permission: platform:firstleg:customs:manage
parameters: [{$ref: '#/components/parameters/PlanId'}, {$ref: '#/components/parameters/WarehouseId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
requestBody:
required: true
content: {application/json: {schema: {$ref: '#/components/schemas/DocumentCompleteRequest'}}}
responses:
'200': {$ref: '#/components/responses/BusinessDocumentResponse'}
components:
parameters:
PlanId:
@@ -132,6 +159,9 @@ components:
ConfirmedDetailResponse:
description: 装柜清单详情查询或确认成功,也可能返回业务失败
content: {application/json: {schema: {$ref: '#/components/schemas/ConfirmedDetailEnvelope'}}}
BusinessDocumentResponse:
description: 报关文档查询或操作结果
content: {application/json: {schema: {$ref: '#/components/schemas/BusinessDocumentEnvelope'}}}
schemas:
LongId:
type: string
@@ -182,6 +212,14 @@ components:
data:
allOf: [{$ref: '#/components/schemas/ConfirmedDetail'}]
nullable: true
BusinessDocumentEnvelope:
allOf:
- {$ref: '#/components/schemas/ApiEnvelope'}
- type: object
properties:
data:
allOf: [{$ref: '#/components/schemas/BusinessDocument'}]
nullable: true
PreloadPage:
type: object
required: [records, page, size, total, pages]
@@ -319,3 +357,34 @@ components:
voyageNo: {type: string, maxLength: 64, nullable: true, description: 航次}
expectedVersion: {type: integer, format: int32, minimum: 0, description: 当前乐观锁版本}
operationReason: {type: string, maxLength: 500, nullable: true, description: 操作原因}
DocumentAttachRequest:
type: object
required: [fileId, expectedVersion]
additionalProperties: false
properties:
fileId: {$ref: '#/components/schemas/LongId'}
expectedVersion: {type: integer, format: int32, minimum: 0}
operationReason: {type: string, maxLength: 500, nullable: true}
DocumentCompleteRequest:
type: object
required: [expectedVersion]
additionalProperties: false
properties:
expectedVersion: {type: integer, format: int32, minimum: 0}
operationReason: {type: string, maxLength: 500, nullable: true}
BusinessDocument:
type: object
required: [id, documentType, fileId, fileName, fileHash, uploadedBy, uploadedAt, version]
properties:
id: {$ref: '#/components/schemas/LongId'}
documentType: {type: string, enum: [CUSTOMS_DECLARATION]}
fileId: {$ref: '#/components/schemas/LongId'}
fileName: {type: string}
fileHash: {type: string, pattern: '^[0-9a-fA-F]{64}$'}
uploadedBy: {$ref: '#/components/schemas/LongId'}
uploadedByName: {type: string, nullable: true}
uploadedAt: {type: string, format: date-time}
completedBy: {$ref: '#/components/schemas/NullableLongId'}
completedByName: {type: string, nullable: true}
completedAt: {type: string, format: date-time, nullable: true}
version: {type: integer, format: int32, minimum: 0}