feat(firstleg): implement container customs workflow
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
SET NAMES utf8mb4;
|
||||
USE baiyue_firstleg;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS fl_customs_container (
|
||||
id BIGINT UNSIGNED NOT NULL,
|
||||
loading_plan_id BIGINT UNSIGNED NOT NULL COMMENT '已完成装柜计划ID;报关柜级聚合唯一键',
|
||||
container_booking_id BIGINT UNSIGNED NOT NULL COMMENT '物理货柜ID',
|
||||
status VARCHAR(40) NOT NULL COMMENT 'WAITING_CUSTOMER_DOCUMENTS、READY_TO_START、DECLARING、COMPLETED',
|
||||
started_by BIGINT UNSIGNED NULL,
|
||||
started_by_name VARCHAR(64) NULL,
|
||||
started_at DATETIME(3) NULL,
|
||||
completed_by BIGINT UNSIGNED NULL,
|
||||
completed_by_name VARCHAR(64) NULL,
|
||||
completed_at DATETIME(3) NULL,
|
||||
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),
|
||||
version INT UNSIGNED NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY(id),
|
||||
UNIQUE KEY uk_customs_container_plan(loading_plan_id),
|
||||
UNIQUE KEY uk_customs_container_booking(container_booking_id),
|
||||
KEY idx_customs_container_status(status,updated_at),
|
||||
CONSTRAINT chk_customs_container_status CHECK(status IN
|
||||
('WAITING_CUSTOMER_DOCUMENTS','READY_TO_START','DECLARING','COMPLETED'))
|
||||
) ENGINE=InnoDB COMMENT='装柜完成后的整柜出口报关聚合';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS fl_customs_customer_document (
|
||||
id BIGINT UNSIGNED NOT NULL,
|
||||
customs_container_id BIGINT UNSIGNED NOT NULL,
|
||||
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户隔离键;只允许关联同客户退税票',
|
||||
file_id BIGINT UNSIGNED NOT NULL,
|
||||
file_name VARCHAR(255) NOT NULL,
|
||||
file_hash CHAR(64) NOT NULL,
|
||||
content_type VARCHAR(128) NULL,
|
||||
size_bytes BIGINT UNSIGNED NOT NULL,
|
||||
status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE',
|
||||
uploaded_by BIGINT UNSIGNED NOT NULL,
|
||||
uploaded_at DATETIME(3) NOT NULL,
|
||||
frozen_version INT UNSIGNED NULL,
|
||||
frozen_at DATETIME(3) NULL,
|
||||
deleted_by BIGINT UNSIGNED NULL,
|
||||
deleted_at DATETIME(3) NULL,
|
||||
version INT UNSIGNED 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),
|
||||
PRIMARY KEY(id),
|
||||
KEY idx_customs_customer_document_container(customs_container_id,status,tenant_id),
|
||||
CONSTRAINT fk_customs_customer_document_container FOREIGN KEY(customs_container_id) REFERENCES fl_customs_container(id),
|
||||
CONSTRAINT chk_customs_customer_document_status CHECK(status IN('ACTIVE','DELETED')),
|
||||
CONSTRAINT chk_customs_customer_document_freeze CHECK((frozen_at IS NULL AND frozen_version IS NULL) OR
|
||||
(frozen_at IS NOT NULL AND frozen_version IS NOT NULL))
|
||||
) ENGINE=InnoDB COMMENT='客户整份退税报送资料逻辑根';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS fl_customs_customer_document_inbound (
|
||||
document_id BIGINT UNSIGNED NOT NULL,
|
||||
inbound_id BIGINT UNSIGNED NOT NULL,
|
||||
tenant_id BIGINT UNSIGNED NOT NULL,
|
||||
order_id BIGINT UNSIGNED NOT NULL,
|
||||
inbound_code VARCHAR(40) NOT NULL,
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY(document_id,inbound_id),
|
||||
KEY idx_customs_document_inbound(inbound_id,tenant_id),
|
||||
CONSTRAINT fk_customs_document_inbound_document FOREIGN KEY(document_id) REFERENCES fl_customs_customer_document(id)
|
||||
) ENGINE=InnoDB COMMENT='客户报送资料与当前柜退税入仓码多对多关系';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS fl_customs_customer_document_version (
|
||||
id BIGINT UNSIGNED NOT NULL,
|
||||
document_id BIGINT UNSIGNED NOT NULL,
|
||||
document_version INT UNSIGNED NOT NULL,
|
||||
file_id BIGINT UNSIGNED NOT NULL,
|
||||
file_name VARCHAR(255) NOT NULL,
|
||||
file_hash CHAR(64) NOT NULL,
|
||||
content_type VARCHAR(128) NULL,
|
||||
size_bytes BIGINT UNSIGNED NOT NULL,
|
||||
scope_snapshot_json JSON NOT NULL,
|
||||
action VARCHAR(24) NOT NULL COMMENT 'UPLOAD、REPLACE、SCOPE_CHANGE、DELETE',
|
||||
operator_id BIGINT UNSIGNED NOT NULL,
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY(id),
|
||||
UNIQUE KEY uk_customs_customer_document_version(document_id,document_version),
|
||||
CONSTRAINT fk_customs_customer_document_version FOREIGN KEY(document_id) REFERENCES fl_customs_customer_document(id),
|
||||
CONSTRAINT chk_customs_customer_document_version_action CHECK(action IN('UPLOAD','REPLACE','SCOPE_CHANGE','DELETE'))
|
||||
) ENGINE=InnoDB COMMENT='客户报送资料文件与覆盖范围不可变版本';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS fl_customs_frozen_document_inbound (
|
||||
document_id BIGINT UNSIGNED NOT NULL,
|
||||
inbound_id BIGINT UNSIGNED NOT NULL,
|
||||
tenant_id BIGINT UNSIGNED NOT NULL,
|
||||
order_id BIGINT UNSIGNED NOT NULL,
|
||||
inbound_code VARCHAR(40) NOT NULL,
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY(document_id,inbound_id),
|
||||
CONSTRAINT fk_customs_frozen_document FOREIGN KEY(document_id) REFERENCES fl_customs_customer_document(id)
|
||||
) ENGINE=InnoDB COMMENT='开始报关时冻结的客户资料覆盖范围';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS fl_customs_completion_document (
|
||||
id BIGINT UNSIGNED NOT NULL,
|
||||
source_document_id BIGINT UNSIGNED NOT NULL COMMENT '一份冻结客户资料严格对应一份完成文件逻辑根',
|
||||
file_id BIGINT UNSIGNED NOT NULL,
|
||||
file_name VARCHAR(255) NOT NULL,
|
||||
file_hash CHAR(64) NOT NULL,
|
||||
content_type VARCHAR(128) NULL,
|
||||
size_bytes BIGINT UNSIGNED NOT NULL,
|
||||
uploaded_by BIGINT UNSIGNED NOT NULL,
|
||||
uploaded_by_name VARCHAR(64) NULL,
|
||||
uploaded_at DATETIME(3) NOT NULL,
|
||||
version INT UNSIGNED 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),
|
||||
PRIMARY KEY(id),
|
||||
UNIQUE KEY uk_customs_completion_source(source_document_id),
|
||||
CONSTRAINT fk_customs_completion_source FOREIGN KEY(source_document_id) REFERENCES fl_customs_customer_document(id)
|
||||
) ENGINE=InnoDB COMMENT='报关完成文件当前版本';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS fl_customs_completion_document_version (
|
||||
id BIGINT UNSIGNED NOT NULL,
|
||||
completion_document_id BIGINT UNSIGNED NOT NULL,
|
||||
document_version INT UNSIGNED NOT NULL,
|
||||
file_id BIGINT UNSIGNED NOT NULL,
|
||||
file_name VARCHAR(255) NOT NULL,
|
||||
file_hash CHAR(64) NOT NULL,
|
||||
content_type VARCHAR(128) NULL,
|
||||
size_bytes BIGINT UNSIGNED NOT NULL,
|
||||
operator_id BIGINT UNSIGNED NOT NULL,
|
||||
operator_name VARCHAR(64) NULL,
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY(id),
|
||||
UNIQUE KEY uk_customs_completion_version(completion_document_id,document_version),
|
||||
CONSTRAINT fk_customs_completion_version FOREIGN KEY(completion_document_id) REFERENCES fl_customs_completion_document(id)
|
||||
) ENGINE=InnoDB COMMENT='报关完成文件不可变覆盖版本';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS fl_customs_container_history (
|
||||
id BIGINT UNSIGNED NOT NULL,
|
||||
customs_container_id BIGINT UNSIGNED NOT NULL,
|
||||
action VARCHAR(40) NOT NULL,
|
||||
from_status VARCHAR(40) NULL,
|
||||
to_status VARCHAR(40) NULL,
|
||||
remark VARCHAR(500) NULL,
|
||||
operator_id BIGINT UNSIGNED NOT NULL,
|
||||
operator_name VARCHAR(64) NULL,
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY(id),
|
||||
KEY idx_customs_container_history(customs_container_id,created_at,id),
|
||||
CONSTRAINT fk_customs_container_history FOREIGN KEY(customs_container_id) REFERENCES fl_customs_container(id)
|
||||
) ENGINE=InnoDB COMMENT='整柜报关状态与系统触发历史';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS fl_customs_command (
|
||||
id BIGINT UNSIGNED NOT NULL,
|
||||
actor_id BIGINT UNSIGNED NOT NULL,
|
||||
action VARCHAR(80) NOT NULL,
|
||||
idempotency_key VARCHAR(128) NOT NULL,
|
||||
request_hash CHAR(64) NOT NULL,
|
||||
status VARCHAR(16) NOT NULL,
|
||||
result_id BIGINT UNSIGNED NULL,
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
completed_at DATETIME(3) NULL,
|
||||
PRIMARY KEY(id),
|
||||
UNIQUE KEY uk_customs_command(actor_id,action,idempotency_key),
|
||||
CONSTRAINT chk_customs_command_status CHECK(status IN('PROCESSING','SUCCESS'))
|
||||
) ENGINE=InnoDB COMMENT='整柜报关写命令幂等事实';
|
||||
|
||||
USE baiyue_iam;
|
||||
DELETE rp FROM iam_role_permission rp WHERE rp.permission_id IN(5102171,5102173,5102174,5102175);
|
||||
DELETE FROM iam_permission WHERE id IN(5102171,5102173,5102174,5102175)
|
||||
AND permission_code IN('platform:firstleg:customs:view','platform:firstleg:customs:review','platform:firstleg:customs:submit','platform:firstleg:customs:receipt');
|
||||
INSERT INTO iam_permission(id,tenant_id,app_code,permission_type,parent_id,permission_code,permission_name,
|
||||
route_path,http_method,sort_no,status,built_in,deleted) VALUES
|
||||
(5102170,0,'PLATFORM','MENU',5102000,'platform:firstleg:customs:list','整柜报关','/first-leg/customs','GET',100,'ACTIVE',1,0),
|
||||
(5102172,0,'PLATFORM','BUTTON',5102170,'platform:firstleg:customs:manage','维护整柜报关',NULL,'POST',20,'ACTIVE',1,0)
|
||||
ON DUPLICATE KEY UPDATE permission_code=VALUES(permission_code),permission_name=VALUES(permission_name),
|
||||
parent_id=VALUES(parent_id),route_path=VALUES(route_path),http_method=VALUES(http_method),status='ACTIVE',deleted=0;
|
||||
INSERT IGNORE INTO iam_role_permission(role_id,permission_id,tenant_id)
|
||||
SELECT r.id,p.id,0 FROM iam_role r JOIN iam_permission p ON p.tenant_id=0 AND p.app_code='PLATFORM'
|
||||
AND p.permission_code IN('platform:firstleg:customs:list','platform:firstleg:customs:manage')
|
||||
WHERE r.tenant_id=0 AND r.role_code IN('PLATFORM_ADMIN','ORDER_OPERATOR');
|
||||
@@ -1,242 +0,0 @@
|
||||
SET NAMES utf8mb4;
|
||||
USE baiyue_firstleg;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS fl_customs_checklist_version (
|
||||
id BIGINT UNSIGNED NOT NULL COMMENT '报关资料清单版本逻辑ID',
|
||||
checklist_code VARCHAR(40) NOT NULL COMMENT '报关资料清单编码;同一清单的多个版本共用',
|
||||
version_no VARCHAR(32) NOT NULL COMMENT '清单业务版本号',
|
||||
country_code VARCHAR(32) NOT NULL COMMENT '清单适用的目的国家或地区编码',
|
||||
channel_code VARCHAR(40) NOT NULL COMMENT '清单适用的运输渠道编码',
|
||||
cargo_type VARCHAR(40) NOT NULL COMMENT '清单适用的货物类型编码',
|
||||
items_json JSON NOT NULL COMMENT '不可变清单项目数组;元素包含code项目编码、name项目名称和required是否必填',
|
||||
status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE' COMMENT '清单版本状态:ACTIVE启用、RETIRED停用',
|
||||
created_by BIGINT UNSIGNED NOT NULL COMMENT '创建清单版本的平台操作人ID',
|
||||
created_by_name VARCHAR(64) NULL COMMENT '创建清单版本时的平台操作人名称快照',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '清单版本创建时间',
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_customs_checklist_version (checklist_code, version_no),
|
||||
KEY idx_customs_checklist_match (country_code, channel_code, cargo_type, status),
|
||||
CONSTRAINT chk_customs_checklist_status CHECK (status IN ('ACTIVE','RETIRED'))
|
||||
) ENGINE=InnoDB COMMENT='三期报关资料清单不可变版本';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS fl_customs_case (
|
||||
id BIGINT UNSIGNED NOT NULL COMMENT '报关协同单逻辑ID',
|
||||
customs_no VARCHAR(40) NOT NULL COMMENT '全局唯一报关协同单号',
|
||||
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
|
||||
order_id BIGINT UNSIGNED NOT NULL COMMENT 'Order服务订单逻辑ID;跨库不建立外键',
|
||||
inbound_id BIGINT UNSIGNED NOT NULL COMMENT 'Firstleg入库单逻辑ID',
|
||||
container_booking_id BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '关联柜次逻辑ID;0表示创建时尚未关联柜次',
|
||||
checklist_version_id BIGINT UNSIGNED NOT NULL COMMENT '本单冻结使用的报关资料清单版本ID',
|
||||
checklist_code VARCHAR(40) NOT NULL COMMENT '创建协同单时的清单编码快照;源清单变化不回写',
|
||||
checklist_version_no VARCHAR(32) NOT NULL COMMENT '创建协同单时的清单版本号快照;源清单变化不回写',
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'DOCUMENTS_PENDING' COMMENT '协同状态:DOCUMENTS_PENDING待补资料、REVIEWING审核中、SUPPLEMENT_REQUIRED需补件、READY_TO_SUBMIT待提交代理、RECEIPT_PENDING待回执、RECEIPT_AVAILABLE已有回执、CLEARANCE_COMPLETED已放行、CANCELLED已取消',
|
||||
remark VARCHAR(500) NULL COMMENT '报关协同单内部业务备注',
|
||||
created_by BIGINT UNSIGNED NOT NULL COMMENT '创建协同单的平台操作人ID',
|
||||
created_by_name VARCHAR(64) NULL COMMENT '创建协同单时的平台操作人名称快照',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '协同单创建时间',
|
||||
updated_by BIGINT UNSIGNED NOT NULL COMMENT '最后修改协同单的平台操作人ID',
|
||||
updated_by_name VARCHAR(64) NULL 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 '乐观锁版本号;每次状态变更递增',
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_customs_no (customs_no),
|
||||
UNIQUE KEY uk_customs_case_scope (tenant_id, order_id, inbound_id, container_booking_id),
|
||||
KEY idx_customs_case_status (status, updated_at),
|
||||
KEY idx_customs_case_container (container_booking_id),
|
||||
CONSTRAINT fk_customs_case_checklist FOREIGN KEY (checklist_version_id) REFERENCES fl_customs_checklist_version(id),
|
||||
CONSTRAINT chk_customs_case_status CHECK (status IN ('DOCUMENTS_PENDING','REVIEWING','SUPPLEMENT_REQUIRED','READY_TO_SUBMIT','RECEIPT_PENDING','RECEIPT_AVAILABLE','CLEARANCE_COMPLETED','CANCELLED'))
|
||||
) ENGINE=InnoDB COMMENT='三期订单报关协同聚合';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS fl_customs_document (
|
||||
id BIGINT UNSIGNED NOT NULL COMMENT '报关资料文件版本逻辑ID',
|
||||
customs_case_id BIGINT UNSIGNED NOT NULL COMMENT '所属报关协同单ID',
|
||||
checklist_item_code VARCHAR(40) NOT NULL COMMENT '对应冻结清单中的资料项目编码',
|
||||
file_id BIGINT UNSIGNED NOT NULL COMMENT 'File服务文件逻辑ID;跨库不建立外键',
|
||||
document_version INT UNSIGNED NOT NULL COMMENT '同一协同单和清单项目下的文件版本序号',
|
||||
replaces_document_id BIGINT UNSIGNED NULL COMMENT '被本版本替换的上一资料版本ID;首个版本为空',
|
||||
file_name VARCHAR(255) NOT NULL COMMENT '上传或引用时的文件名称快照',
|
||||
file_hash CHAR(64) NOT NULL COMMENT '文件内容SHA-256摘要;用于版本审计和幂等校验',
|
||||
status VARCHAR(16) NOT NULL DEFAULT 'UPLOADED' COMMENT '资料状态:UPLOADED待审核、APPROVED审核通过、RETURNED已退回、SUPERSEDED已被替换、FROZEN已随代理提交批次冻结',
|
||||
visibility VARCHAR(16) NOT NULL DEFAULT 'CUSTOMER' COMMENT '资料可见范围:PLATFORM仅平台可见、CUSTOMER客户可见',
|
||||
uploaded_by BIGINT UNSIGNED NOT NULL COMMENT '上传或引用资料的操作人ID',
|
||||
uploaded_by_type VARCHAR(16) NOT NULL COMMENT '上传主体类型:PLATFORM平台操作人、MERCHANT客户操作人',
|
||||
uploaded_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '资料上传或引用时间',
|
||||
reviewed_by BIGINT UNSIGNED NULL COMMENT '审核资料的平台操作人ID;未审核时为空',
|
||||
reviewed_by_name VARCHAR(64) NULL COMMENT '审核时的平台操作人名称快照',
|
||||
reviewed_at DATETIME(3) NULL COMMENT '资料审核时间',
|
||||
review_reason VARCHAR(500) NULL COMMENT '审核退回原因或审核备注',
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_customs_document_version (customs_case_id, checklist_item_code, document_version),
|
||||
UNIQUE KEY uk_customs_document_file (customs_case_id, file_id),
|
||||
KEY idx_customs_document_case (customs_case_id, status),
|
||||
CONSTRAINT fk_customs_document_case FOREIGN KEY (customs_case_id) REFERENCES fl_customs_case(id),
|
||||
CONSTRAINT chk_customs_document_status CHECK (status IN ('UPLOADED','APPROVED','RETURNED','SUPERSEDED','FROZEN')),
|
||||
CONSTRAINT chk_customs_document_actor CHECK (uploaded_by_type IN ('PLATFORM','MERCHANT')),
|
||||
CONSTRAINT chk_customs_document_visibility CHECK (visibility IN ('PLATFORM','CUSTOMER'))
|
||||
) ENGINE=InnoDB COMMENT='报关客户文件版本引用';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS fl_customs_supplement_request (
|
||||
id BIGINT UNSIGNED NOT NULL COMMENT '报关补件请求逻辑ID',
|
||||
customs_case_id BIGINT UNSIGNED NOT NULL COMMENT '所属报关协同单ID',
|
||||
source_document_id BIGINT UNSIGNED NOT NULL COMMENT '触发本次补件的被退回资料版本ID',
|
||||
resolved_document_id BIGINT UNSIGNED NULL COMMENT '解决本次补件的新资料版本ID;未解决时为空',
|
||||
checklist_item_code VARCHAR(40) NOT NULL COMMENT '需要补充的报关资料项目编码',
|
||||
reason VARCHAR(500) NOT NULL COMMENT '平台审核人员填写的补件原因',
|
||||
due_at DATETIME(3) NULL COMMENT '期望完成补件时间;未设置时为空',
|
||||
status VARCHAR(16) NOT NULL DEFAULT 'OPEN' COMMENT '补件状态:OPEN待补件、RESOLVED已由新资料解决、CANCELLED已取消',
|
||||
requested_by BIGINT UNSIGNED NOT NULL COMMENT '发起补件的平台操作人ID',
|
||||
requested_by_name VARCHAR(64) NULL COMMENT '发起补件时的平台操作人名称快照',
|
||||
requested_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '补件请求发起时间',
|
||||
resolved_by BIGINT UNSIGNED NULL COMMENT '提交解决资料的操作人ID;未解决时为空',
|
||||
resolved_at DATETIME(3) NULL COMMENT '补件请求解决时间',
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_customs_supplement_case (customs_case_id, status, due_at),
|
||||
CONSTRAINT fk_customs_supplement_case FOREIGN KEY (customs_case_id) REFERENCES fl_customs_case(id),
|
||||
CONSTRAINT chk_customs_supplement_status CHECK (status IN ('OPEN','RESOLVED','CANCELLED'))
|
||||
) ENGINE=InnoDB COMMENT='报关补件请求';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS fl_customs_submission_batch (
|
||||
id BIGINT UNSIGNED NOT NULL COMMENT '报关代理提交批次逻辑ID',
|
||||
customs_case_id BIGINT UNSIGNED NOT NULL COMMENT '所属报关协同单ID',
|
||||
batch_no VARCHAR(40) NOT NULL COMMENT '全局唯一代理提交批次号',
|
||||
batch_version INT UNSIGNED NOT NULL COMMENT '同一报关协同单内的提交批次版本序号',
|
||||
customs_agent_id BIGINT UNSIGNED NULL COMMENT '本批次报关代理Supplier逻辑ID快照;历史数据无法推导时为空',
|
||||
customs_agent_name VARCHAR(128) NOT NULL COMMENT '本批次接收报关资料的报关代理名称快照',
|
||||
external_reference VARCHAR(128) NULL COMMENT '报关代理或外部系统返回的业务参考号',
|
||||
file_snapshot_json JSON NOT NULL COMMENT '提交时冻结的已审核资料数组;元素包含documentId、fileId、itemCode、documentVersion和fileHash',
|
||||
status VARCHAR(16) NOT NULL DEFAULT 'SUBMITTED' COMMENT '代理提交状态:SUBMITTED已提交、ACKNOWLEDGED代理已确认、REJECTED代理已拒绝、COMPLETED代理处理完成',
|
||||
note VARCHAR(500) NULL COMMENT '提交报关代理时的平台备注',
|
||||
submitted_by BIGINT UNSIGNED NOT NULL COMMENT '提交报关代理的平台操作人ID',
|
||||
submitted_by_name VARCHAR(64) NULL COMMENT '提交时的平台操作人名称快照',
|
||||
submitted_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '提交报关代理时间',
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_customs_batch_no (batch_no),
|
||||
UNIQUE KEY uk_customs_batch_version (customs_case_id, batch_version),
|
||||
CONSTRAINT fk_customs_batch_case FOREIGN KEY (customs_case_id) REFERENCES fl_customs_case(id),
|
||||
CONSTRAINT chk_customs_batch_status CHECK (status IN ('SUBMITTED','ACKNOWLEDGED','REJECTED','COMPLETED'))
|
||||
) ENGINE=InnoDB COMMENT='报关代理提交批次及冻结文件版本';
|
||||
|
||||
-- 历史代理名称无法安全反推Supplier ID,旧行保持NULL;新写入由服务端校验并持久化。
|
||||
DROP PROCEDURE IF EXISTS phase3_customs_supplier_snapshot_add_column;
|
||||
DELIMITER $$
|
||||
CREATE PROCEDURE phase3_customs_supplier_snapshot_add_column()
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema=DATABASE() AND table_name='fl_customs_submission_batch'
|
||||
AND column_name='customs_agent_id') THEN
|
||||
ALTER TABLE fl_customs_submission_batch
|
||||
ADD COLUMN customs_agent_id BIGINT UNSIGNED NULL COMMENT '本批次报关代理Supplier逻辑ID快照;历史数据无法推导时为空' AFTER batch_version;
|
||||
END IF;
|
||||
END$$
|
||||
DELIMITER ;
|
||||
CALL phase3_customs_supplier_snapshot_add_column();
|
||||
DROP PROCEDURE phase3_customs_supplier_snapshot_add_column;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS fl_customs_receipt (
|
||||
id BIGINT UNSIGNED NOT NULL COMMENT '报关回执文件版本逻辑ID',
|
||||
customs_case_id BIGINT UNSIGNED NOT NULL COMMENT '所属报关协同单ID',
|
||||
submission_batch_id BIGINT UNSIGNED NOT NULL COMMENT '产生本回执的报关代理提交批次ID',
|
||||
receipt_type VARCHAR(16) NOT NULL COMMENT '回执类型:RECEIVED已收件、DECLARED已申报、REJECTED已拒绝、INSPECTION查验通知、RELEASED放行回执、OTHER其他回执',
|
||||
file_id BIGINT UNSIGNED NOT NULL COMMENT 'File服务回执文件逻辑ID;跨库不建立外键',
|
||||
receipt_version INT UNSIGNED NOT NULL COMMENT '同一提交批次和回执类型下的版本序号',
|
||||
replaces_receipt_id BIGINT UNSIGNED NULL COMMENT '被本版本替换的上一回执版本ID;首个版本为空',
|
||||
file_name VARCHAR(255) NOT NULL COMMENT '上传或引用时的回执文件名称快照',
|
||||
file_hash CHAR(64) NOT NULL COMMENT '回执文件内容SHA-256摘要;用于版本审计和幂等校验',
|
||||
visibility VARCHAR(16) NOT NULL DEFAULT 'PLATFORM' COMMENT '回执可见范围:PLATFORM仅平台可见、CUSTOMER客户可查询下载',
|
||||
note VARCHAR(500) NULL COMMENT '回执业务备注',
|
||||
uploaded_by BIGINT UNSIGNED NOT NULL COMMENT '上传或引用回执的平台操作人ID',
|
||||
uploaded_by_name VARCHAR(64) NULL COMMENT '上传回执时的平台操作人名称快照',
|
||||
uploaded_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '回执上传或引用时间',
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_customs_receipt_version (submission_batch_id, receipt_type, receipt_version),
|
||||
UNIQUE KEY uk_customs_receipt_file (submission_batch_id, file_id),
|
||||
KEY idx_customs_receipt_case (customs_case_id, visibility, uploaded_at),
|
||||
CONSTRAINT fk_customs_receipt_case FOREIGN KEY (customs_case_id) REFERENCES fl_customs_case(id),
|
||||
CONSTRAINT fk_customs_receipt_batch FOREIGN KEY (submission_batch_id) REFERENCES fl_customs_submission_batch(id),
|
||||
CONSTRAINT chk_customs_receipt_type CHECK (receipt_type IN ('RECEIVED','DECLARED','REJECTED','INSPECTION','RELEASED','OTHER')),
|
||||
CONSTRAINT chk_customs_receipt_visibility CHECK (visibility IN ('PLATFORM','CUSTOMER'))
|
||||
) ENGINE=InnoDB COMMENT='报关代理回执版本与客户可见范围';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS fl_customs_history (
|
||||
id BIGINT UNSIGNED NOT NULL COMMENT '报关协同历史逻辑ID',
|
||||
customs_case_id BIGINT UNSIGNED NOT NULL COMMENT '所属报关协同单ID',
|
||||
action VARCHAR(40) NOT NULL COMMENT '业务动作编码,如CREATE、UPLOAD_DOCUMENT、REPLACE_DOCUMENT、REVIEW_DOCUMENT_APPROVE、REVIEW_DOCUMENT_RETURN、SUBMIT_AGENT_BATCH或UPLOAD_RECEIPT',
|
||||
from_status VARCHAR(32) NULL COMMENT '动作发生前的协同状态;创建动作时为空',
|
||||
to_status VARCHAR(32) NULL COMMENT '动作完成后的协同状态',
|
||||
resource_type VARCHAR(32) NOT NULL COMMENT '动作关联资源类型:CASE协同单、DOCUMENT资料、SUBMISSION_BATCH提交批次或RECEIPT回执',
|
||||
resource_id BIGINT UNSIGNED NOT NULL COMMENT '动作关联资源逻辑ID',
|
||||
note VARCHAR(500) NULL COMMENT '动作原因、资料项目编码或业务备注快照',
|
||||
operator_id BIGINT UNSIGNED NOT NULL COMMENT '执行业务动作的操作人ID',
|
||||
operator_name VARCHAR(64) NULL COMMENT '执行动作时的操作人名称快照',
|
||||
operator_type VARCHAR(16) NOT NULL COMMENT '操作主体类型:PLATFORM平台操作人、MERCHANT客户操作人',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '历史记录创建时间',
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_customs_history_case (customs_case_id, created_at, id),
|
||||
CONSTRAINT fk_customs_history_case FOREIGN KEY (customs_case_id) REFERENCES fl_customs_case(id),
|
||||
CONSTRAINT chk_customs_history_actor CHECK (operator_type IN ('PLATFORM','MERCHANT'))
|
||||
) ENGINE=InnoDB COMMENT='报关协同稳定历史';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS fl_customs_operation (
|
||||
id BIGINT UNSIGNED NOT NULL COMMENT '报关写命令幂等记录逻辑ID',
|
||||
operator_id BIGINT UNSIGNED NOT NULL COMMENT '发起写命令的操作人ID;与动作和幂等键共同限定重放范围',
|
||||
action VARCHAR(64) NOT NULL COMMENT '幂等业务动作编码,包含平台或客户动作及资料、审核、提交、回执类型',
|
||||
idempotency_key VARCHAR(128) NOT NULL COMMENT '客户端提供的幂等键',
|
||||
request_hash CHAR(64) NOT NULL COMMENT '规范化完整请求内容的SHA-256摘要;同键不同请求时拒绝',
|
||||
status VARCHAR(16) NOT NULL DEFAULT 'PROCESSING' COMMENT '幂等处理状态:PROCESSING处理中、SUCCESS处理成功',
|
||||
result_type VARCHAR(24) NULL COMMENT '成功结果资源类型:CHECKLIST、CASE、DOCUMENT、BATCH或RECEIPT',
|
||||
result_id BIGINT UNSIGNED NULL COMMENT '成功结果资源逻辑ID;处理中为空',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '幂等记录创建时间',
|
||||
completed_at DATETIME(3) NULL COMMENT '写命令成功完成时间;处理中为空',
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_customs_operation (operator_id, action, idempotency_key),
|
||||
CONSTRAINT chk_customs_operation_status CHECK (status IN ('PROCESSING','SUCCESS'))
|
||||
) ENGINE=InnoDB COMMENT='报关协同命令幂等记录';
|
||||
|
||||
USE baiyue_iam;
|
||||
DROP PROCEDURE IF EXISTS assert_phase3_customs_permission_ids;
|
||||
DELIMITER $$
|
||||
CREATE PROCEDURE assert_phase3_customs_permission_ids()
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM iam_permission
|
||||
WHERE (id BETWEEN 5102170 AND 5102175
|
||||
AND permission_code <> CASE id
|
||||
WHEN 5102170 THEN 'platform:firstleg:customs:list'
|
||||
WHEN 5102171 THEN 'platform:firstleg:customs:view'
|
||||
WHEN 5102172 THEN 'platform:firstleg:customs:manage'
|
||||
WHEN 5102173 THEN 'platform:firstleg:customs:review'
|
||||
WHEN 5102174 THEN 'platform:firstleg:customs:submit'
|
||||
WHEN 5102175 THEN 'platform:firstleg:customs:receipt'
|
||||
END)
|
||||
OR (permission_code LIKE 'platform:firstleg:customs:%'
|
||||
AND (tenant_id <> 0 OR app_code <> 'PLATFORM' OR id NOT BETWEEN 5102170 AND 5102175))
|
||||
) THEN
|
||||
SIGNAL SQLSTATE '45000'
|
||||
SET MESSAGE_TEXT = '三期报关权限迁移失败:5102170-5102175 ID 或权限码被其他语义占用';
|
||||
END IF;
|
||||
END$$
|
||||
DELIMITER ;
|
||||
CALL assert_phase3_customs_permission_ids();
|
||||
DROP PROCEDURE IF EXISTS assert_phase3_customs_permission_ids;
|
||||
|
||||
INSERT INTO iam_permission
|
||||
(id,tenant_id,app_code,permission_type,parent_id,permission_code,permission_name,
|
||||
route_path,http_method,sort_no,status,built_in,deleted)
|
||||
VALUES
|
||||
(5102170,0,'PLATFORM','MENU',5102000,'platform:firstleg:customs:list','报关协同','/first-leg/customs','GET',100,'ACTIVE',1,0),
|
||||
(5102171,0,'PLATFORM','BUTTON',5102170,'platform:firstleg:customs:view','查看报关协同',NULL,'GET',10,'ACTIVE',1,0),
|
||||
(5102172,0,'PLATFORM','BUTTON',5102170,'platform:firstleg:customs:manage','维护报关资料',NULL,'POST',20,'ACTIVE',1,0),
|
||||
(5102173,0,'PLATFORM','BUTTON',5102170,'platform:firstleg:customs:review','审核退回报关资料',NULL,'POST',30,'ACTIVE',1,0),
|
||||
(5102174,0,'PLATFORM','BUTTON',5102170,'platform:firstleg:customs:submit','提交报关代理',NULL,'POST',40,'ACTIVE',1,0),
|
||||
(5102175,0,'PLATFORM','BUTTON',5102170,'platform:firstleg:customs:receipt','维护报关回执',NULL,'POST',50,'ACTIVE',1,0)
|
||||
ON DUPLICATE KEY UPDATE tenant_id=0,app_code='PLATFORM',permission_type=VALUES(permission_type),
|
||||
permission_code=VALUES(permission_code),permission_name=VALUES(permission_name),parent_id=VALUES(parent_id),
|
||||
route_path=VALUES(route_path),http_method=VALUES(http_method),sort_no=VALUES(sort_no),status='ACTIVE',built_in=1,deleted=0;
|
||||
|
||||
INSERT IGNORE INTO iam_role_permission(role_id,permission_id,tenant_id)
|
||||
SELECT role.id,permission.id,0 FROM iam_role role JOIN iam_permission permission
|
||||
ON permission.tenant_id=0 AND permission.app_code='PLATFORM'
|
||||
AND permission.permission_code LIKE 'platform:firstleg:customs:%'
|
||||
WHERE role.tenant_id=0 AND role.role_code IN ('PLATFORM_ADMIN','ORDER_OPERATOR');
|
||||
|
||||
USE baiyue_firstleg;
|
||||
@@ -1,91 +0,0 @@
|
||||
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'));
|
||||
|
||||
-- Keep container identity/loading facts immutable while allowing release transport data to arrive later.
|
||||
DROP TRIGGER IF EXISTS trg_container_booking_before_update;
|
||||
DELIMITER $$
|
||||
CREATE TRIGGER trg_container_booking_before_update BEFORE UPDATE ON fl_container_booking 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.id;
|
||||
IF current_plan_status IN ('LOADING','COMPLETED') AND (
|
||||
NOT (OLD.id <=> NEW.id) OR NOT (OLD.booking_no <=> NEW.booking_no)
|
||||
OR NOT (OLD.cabinet_no <=> NEW.cabinet_no) OR NOT (OLD.channel_code <=> NEW.channel_code)
|
||||
OR NOT (OLD.channel_name <=> NEW.channel_name) OR NOT (OLD.ocean_booking_id <=> NEW.ocean_booking_id)
|
||||
OR NOT (OLD.ocean_booking_requirement_id <=> NEW.ocean_booking_requirement_id)
|
||||
OR NOT (OLD.warehouse_id <=> NEW.warehouse_id) OR NOT (OLD.warehouse_name <=> NEW.warehouse_name)
|
||||
OR NOT (OLD.container_type <=> NEW.container_type) OR NOT (OLD.shipping_company_id <=> NEW.shipping_company_id)
|
||||
OR NOT (OLD.shipping_company_name <=> NEW.shipping_company_name)
|
||||
OR NOT (OLD.departure_port_code <=> NEW.departure_port_code) OR NOT (OLD.departure_port_name <=> NEW.departure_port_name)
|
||||
OR NOT (OLD.destination_port_code <=> NEW.destination_port_code) OR NOT (OLD.destination_port_name <=> NEW.destination_port_name)
|
||||
OR NOT (OLD.destination_warehouse_code <=> NEW.destination_warehouse_code)
|
||||
OR NOT (OLD.destination_warehouse_name <=> NEW.destination_warehouse_name)
|
||||
OR NOT (OLD.capacity_cbm <=> NEW.capacity_cbm) OR NOT (OLD.max_weight_kg <=> NEW.max_weight_kg)
|
||||
OR NOT (OLD.terminal_trucking_company_id <=> NEW.terminal_trucking_company_id)
|
||||
OR NOT (OLD.terminal_trucking_company <=> NEW.terminal_trucking_company) OR NOT (OLD.loading_at <=> NEW.loading_at)
|
||||
OR NOT (OLD.actual_sailing_at <=> NEW.actual_sailing_at)
|
||||
OR NOT (OLD.estimated_port_arrival_at <=> NEW.estimated_port_arrival_at)
|
||||
OR NOT (OLD.actual_port_arrival_at <=> NEW.actual_port_arrival_at)
|
||||
OR NOT (OLD.estimated_warehouse_arrival_at <=> NEW.estimated_warehouse_arrival_at)
|
||||
OR NOT (OLD.actual_warehouse_arrival_at <=> NEW.actual_warehouse_arrival_at)
|
||||
OR NOT (OLD.created_by <=> NEW.created_by) OR NOT (OLD.created_by_name <=> NEW.created_by_name)
|
||||
OR NOT (OLD.created_at <=> NEW.created_at)) THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='装柜作业开始后仅允许修改集装箱号与放舱运输数据';
|
||||
END IF;
|
||||
END$$
|
||||
DELIMITER ;
|
||||
|
||||
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', 'DELIVERY_ORDER', 'PICKUP_ORDER') 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', 'DELIVERY_ORDER', 'PICKUP_ORDER') THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '装柜完成后货柜物流文件不可修改';
|
||||
END IF;
|
||||
END$$
|
||||
DELIMITER ;
|
||||
@@ -315,11 +315,11 @@ CREATE TABLE IF NOT EXISTS fl_loading_plan_item (
|
||||
CONSTRAINT chk_loading_item_deleted CHECK (deleted IN (0, 1))
|
||||
) ENGINE=InnoDB COMMENT='智能装柜客户货物明细;客户归属仅在明细层';
|
||||
|
||||
-- 目的港清关和出口报关均为柜次级简单文件事实:上传资料后由平台一键完成。
|
||||
-- 柜次级简单文件仅承载目的港清关、提货单和取货单;出口报关由 customs-v1 柜级聚合承载。
|
||||
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 '文件类型:DESTINATION_CLEARANCE目的港清关、CUSTOMS_DECLARATION出口报关、DELIVERY_ORDER提货单、PICKUP_ORDER取货单',
|
||||
document_type VARCHAR(32) NOT NULL COMMENT '文件类型:DESTINATION_CLEARANCE目的港清关、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快照',
|
||||
@@ -338,7 +338,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 ('DESTINATION_CLEARANCE', 'CUSTOMS_DECLARATION', 'DELIVERY_ORDER', 'PICKUP_ORDER'))
|
||||
CHECK (document_type IN ('DESTINATION_CLEARANCE', 'DELIVERY_ORDER', 'PICKUP_ORDER'))
|
||||
) ENGINE=InnoDB COMMENT='平台货柜清关、DO与PU文件关联';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS fl_container_exception_history (
|
||||
@@ -1152,7 +1152,7 @@ BEGIN
|
||||
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', 'DELIVERY_ORDER', 'PICKUP_ORDER') THEN
|
||||
AND NEW.document_type NOT IN ('DESTINATION_CLEARANCE', 'DELIVERY_ORDER', 'PICKUP_ORDER') THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '装柜完成后货柜文件不可新增';
|
||||
END IF;
|
||||
END$$
|
||||
@@ -1168,7 +1168,7 @@ BEGIN
|
||||
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', 'DELIVERY_ORDER', 'PICKUP_ORDER') THEN
|
||||
AND OLD.document_type NOT IN ('DESTINATION_CLEARANCE', 'DELIVERY_ORDER', 'PICKUP_ORDER') THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '装柜完成后货柜文件不可修改';
|
||||
END IF;
|
||||
END$$
|
||||
|
||||
@@ -1,127 +1,135 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: Phase 3 Customs Collaboration API
|
||||
version: 1.0.0
|
||||
description: Immutable checklist and file versions, supplement requests, frozen agent batches, receipts and history.
|
||||
info: {title: Firstleg Container Customs API, version: 2.0.0}
|
||||
paths:
|
||||
/api/platform/v1/customs/checklist-versions:
|
||||
post:
|
||||
summary: Create an immutable customs document checklist version
|
||||
parameters: [{$ref: '#/components/parameters/IdempotencyKey'}]
|
||||
responses: {'200': {description: Checklist version}}
|
||||
/api/platform/v1/customs/cases:
|
||||
/api/platform/v1/customs/loading-plans/{planId}:
|
||||
get:
|
||||
summary: Page customs cases
|
||||
responses: {'200': {description: Page result}}
|
||||
summary: 查询整柜报关状态、缺资料票及文件映射
|
||||
x-permission: platform:firstleg:customs:list
|
||||
parameters: [{$ref: '#/components/parameters/PlanId'}]
|
||||
responses: {'200': {$ref: '#/components/responses/ContainerResponse'}}
|
||||
/api/platform/v1/customs/loading-plans/{planId}/start:
|
||||
post:
|
||||
summary: Create a customer/order/inbound/container scoped customs case
|
||||
parameters: [{$ref: '#/components/parameters/IdempotencyKey'}]
|
||||
responses: {'200': {description: Customs case detail}}
|
||||
/api/platform/v1/customs/cases/{caseId}:
|
||||
parameters: [{$ref: '#/components/parameters/CaseId'}]
|
||||
summary: 确认开始整柜报关并冻结全部当前资料版本和覆盖范围
|
||||
x-permission: platform:firstleg:customs:manage
|
||||
parameters: [{$ref: '#/components/parameters/PlanId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
|
||||
requestBody: {$ref: '#/components/requestBodies/ActionBody'}
|
||||
responses: {'200': {$ref: '#/components/responses/ContainerResponse'}}
|
||||
/api/platform/v1/customs/loading-plans/{planId}/complete-all-buy:
|
||||
post:
|
||||
summary: 二次确认全买单柜报关完成
|
||||
x-permission: platform:firstleg:customs:manage
|
||||
parameters: [{$ref: '#/components/parameters/PlanId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
|
||||
requestBody: {$ref: '#/components/requestBodies/ActionBody'}
|
||||
responses: {'200': {$ref: '#/components/responses/ContainerResponse'}}
|
||||
/api/platform/v1/customs/loading-plans/documents/{documentId}/completion/upload:
|
||||
post:
|
||||
summary: 上传或覆盖冻结客户资料的一对一完成文件
|
||||
description: 完成文件自动继承冻结入仓码且不可删除;最后一份须finalConfirmation=true,File调用前服务端预检。
|
||||
x-permission: platform:firstleg:customs:manage
|
||||
parameters:
|
||||
- {$ref: '#/components/parameters/DocumentId'}
|
||||
- {$ref: '#/components/parameters/IdempotencyKey'}
|
||||
- {name: expectedVersion, in: query, required: true, schema: {type: integer, minimum: 0}}
|
||||
- {name: finalConfirmation, in: query, required: true, schema: {type: boolean}}
|
||||
requestBody: {$ref: '#/components/requestBodies/FileBody'}
|
||||
responses: {'200': {$ref: '#/components/responses/DocumentResponse'}}
|
||||
/api/platform/v1/customs/loading-plans/documents/{documentId}/download:
|
||||
get:
|
||||
summary: Query complete customs detail and stable history
|
||||
responses: {'200': {description: Detail}}
|
||||
/api/platform/v1/customs/cases/{caseId}/documents:
|
||||
parameters: [{$ref: '#/components/parameters/CaseId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
|
||||
post:
|
||||
summary: Reference a file validated against customer, CUSTOMS_DOCUMENT and case ID
|
||||
responses: {'200': {description: New document version}}
|
||||
/api/platform/v1/customs/cases/{caseId}/documents/upload:
|
||||
parameters: [{$ref: '#/components/parameters/CaseId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
|
||||
post:
|
||||
summary: Upload a document and create a new immutable version
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
multipart/form-data:
|
||||
schema:
|
||||
type: object
|
||||
required: [itemCode, expectedVersion, file]
|
||||
properties:
|
||||
itemCode: {type: string}
|
||||
expectedVersion: {type: integer, minimum: 0}
|
||||
file: {type: string, format: binary}
|
||||
responses: {'200': {description: New document version}}
|
||||
/api/platform/v1/customs/cases/{caseId}/documents/{documentId}/review:
|
||||
parameters:
|
||||
- {$ref: '#/components/parameters/CaseId'}
|
||||
- {name: documentId, in: path, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
|
||||
- {$ref: '#/components/parameters/IdempotencyKey'}
|
||||
post:
|
||||
summary: Approve a current file version or return it with a supplement task
|
||||
responses: {'200': {description: Updated detail}}
|
||||
/api/platform/v1/customs/cases/{caseId}/submission-batches:
|
||||
parameters: [{$ref: '#/components/parameters/CaseId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
|
||||
post:
|
||||
summary: Submit to customs agent and freeze approved file versions in a batch snapshot
|
||||
description: customsAgentId must identify an ACTIVE Warehouse supplier in category CUSTOMS; the persisted agent name is resolved server-side and free text cannot bypass supplier validation.
|
||||
requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/SubmitBatchRequest'}}}}
|
||||
responses:
|
||||
'200':
|
||||
description: Submission batch
|
||||
content:
|
||||
application/json:
|
||||
schema: {$ref: '#/components/schemas/CustomsSubmissionBatch'}
|
||||
/api/platform/v1/customs/submission-batches/{batchId}/receipts:
|
||||
parameters:
|
||||
- {name: batchId, in: path, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
|
||||
- {$ref: '#/components/parameters/IdempotencyKey'}
|
||||
post:
|
||||
summary: Reference a customer-owned receipt file with explicit visibility
|
||||
responses: {'200': {description: Receipt version}}
|
||||
/api/platform/v1/customs/submission-batches/{batchId}/receipts/upload:
|
||||
post:
|
||||
summary: Upload a receipt version; RELEASED closes the customs dispatch gate
|
||||
responses: {'200': {description: Receipt version}}
|
||||
summary: 平台下载客户报送资料
|
||||
x-permission: platform:firstleg:customs:list
|
||||
parameters: [{$ref: '#/components/parameters/DocumentId'}]
|
||||
responses: {'200': {$ref: '#/components/responses/BinaryResponse'}}
|
||||
/api/platform/v1/customs/loading-plans/documents/{documentId}/completion/download:
|
||||
get:
|
||||
summary: 平台下载报关完成文件
|
||||
x-permission: platform:firstleg:customs:list
|
||||
parameters: [{$ref: '#/components/parameters/DocumentId'}]
|
||||
responses: {'200': {$ref: '#/components/responses/BinaryResponse'}}
|
||||
/api/merchant/v1/customs/orders/{orderId}:
|
||||
get:
|
||||
summary: Query the authenticated customer's customs case by order
|
||||
responses: {'200': {description: Customer-filtered detail}}
|
||||
/api/merchant/v1/customs/cases/{caseId}/documents/upload:
|
||||
summary: 客户订单列表查询当前完成装柜退税报关四态
|
||||
x-permission: merchant:order:view
|
||||
parameters: [{$ref: '#/components/parameters/OrderId'}]
|
||||
responses: {'200': {$ref: '#/components/responses/ContainerResponse'}}
|
||||
/api/merchant/v1/customs/loading-plans/{planId}/documents/upload:
|
||||
post:
|
||||
summary: Upload or replace a checklist document within authenticated customer scope
|
||||
responses: {'200': {description: New document version}}
|
||||
/api/merchant/v1/customs/cases/{caseId}/receipts/{receiptId}/download:
|
||||
summary: 上传资料并关联当前柜内本客户一个或多个退税入仓码
|
||||
x-permission: merchant:order:edit
|
||||
parameters:
|
||||
- {$ref: '#/components/parameters/PlanId'}
|
||||
- {$ref: '#/components/parameters/IdempotencyKey'}
|
||||
- {$ref: '#/components/parameters/InboundIds'}
|
||||
requestBody: {$ref: '#/components/requestBodies/FileBody'}
|
||||
responses: {'200': {$ref: '#/components/responses/DocumentResponse'}}
|
||||
/api/merchant/v1/customs/documents/{documentId}/upload:
|
||||
post:
|
||||
summary: 报关开始前覆盖资料并同步修改同柜关联票
|
||||
x-permission: merchant:order:edit
|
||||
parameters:
|
||||
- {$ref: '#/components/parameters/DocumentId'}
|
||||
- {$ref: '#/components/parameters/IdempotencyKey'}
|
||||
- {$ref: '#/components/parameters/InboundIds'}
|
||||
- {name: expectedVersion, in: query, required: true, schema: {type: integer, minimum: 1}}
|
||||
requestBody: {$ref: '#/components/requestBodies/FileBody'}
|
||||
responses: {'200': {$ref: '#/components/responses/DocumentResponse'}}
|
||||
/api/merchant/v1/customs/documents/{documentId}/inbounds:
|
||||
put:
|
||||
summary: 报关开始前修改资料关联的同柜退税入仓码
|
||||
x-permission: merchant:order:edit
|
||||
parameters: [{$ref: '#/components/parameters/DocumentId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
|
||||
requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/ScopeRequest'}}}}
|
||||
responses: {'200': {$ref: '#/components/responses/DocumentResponse'}}
|
||||
/api/merchant/v1/customs/documents/{documentId}:
|
||||
delete:
|
||||
summary: 报关开始前逻辑删除误传资料并保留不可变版本
|
||||
x-permission: merchant:order:edit
|
||||
parameters:
|
||||
- {$ref: '#/components/parameters/DocumentId'}
|
||||
- {$ref: '#/components/parameters/IdempotencyKey'}
|
||||
- {name: expectedVersion, in: query, required: true, schema: {type: integer, minimum: 1}}
|
||||
responses: {'200': {$ref: '#/components/responses/ContainerResponse'}}
|
||||
/api/merchant/v1/customs/documents/{documentId}/download:
|
||||
get:
|
||||
summary: Download only CUSTOMER-visible receipts from the authenticated customer's case
|
||||
responses: {'200': {description: Authorized file stream}}
|
||||
summary: 客户下载自己当前报送资料
|
||||
x-permission: merchant:order:view
|
||||
parameters: [{$ref: '#/components/parameters/DocumentId'}]
|
||||
responses: {'200': {$ref: '#/components/responses/BinaryResponse'}}
|
||||
/api/merchant/v1/customs/documents/{documentId}/completion/download:
|
||||
get:
|
||||
summary: 整柜完成后客户下载对应完成文件
|
||||
x-permission: merchant:order:view
|
||||
parameters: [{$ref: '#/components/parameters/DocumentId'}]
|
||||
responses: {'200': {$ref: '#/components/responses/BinaryResponse'}}
|
||||
/internal/exports/v1/LOADING_LIST_BUY_DECLARATION_ITEMS/batches:
|
||||
post:
|
||||
summary: File导出中心按loadingPlanId分页获取已完成装柜买单实际行
|
||||
parameters: [{name: X-Internal-Service-Token, in: header, required: true, schema: {type: string}}]
|
||||
requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/ExportBatchRequest'}}}}
|
||||
responses: {'200': {description: 成功, content: {application/json: {schema: {$ref: '#/components/schemas/ExportBatchEnvelope'}}}}}
|
||||
components:
|
||||
parameters:
|
||||
IdempotencyKey:
|
||||
name: Idempotency-Key
|
||||
in: header
|
||||
required: true
|
||||
schema: {type: string, minLength: 1, maxLength: 128}
|
||||
CaseId:
|
||||
name: caseId
|
||||
in: path
|
||||
required: true
|
||||
schema: {type: string, pattern: '^[1-9][0-9]*$'}
|
||||
PlanId: {name: planId, in: path, required: true, schema: {$ref: '#/components/schemas/LongId'}}
|
||||
OrderId: {name: orderId, in: path, required: true, schema: {$ref: '#/components/schemas/LongId'}}
|
||||
DocumentId: {name: documentId, in: path, required: true, schema: {$ref: '#/components/schemas/LongId'}}
|
||||
IdempotencyKey: {name: Idempotency-Key, in: header, required: true, schema: {type: string, minLength: 1, maxLength: 128}}
|
||||
InboundIds: {name: inboundIds, in: query, required: true, schema: {type: array, minItems: 1, maxItems: 200, items: {$ref: '#/components/schemas/LongId'}}}
|
||||
requestBodies:
|
||||
ActionBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/ActionRequest'}}}}
|
||||
FileBody: {required: true, content: {multipart/form-data: {schema: {type: object, required: [file], properties: {file: {type: string, format: binary, description: 任意格式且最大50MB}}}}}}
|
||||
responses:
|
||||
ContainerResponse: {description: 成功或业务失败, content: {application/json: {schema: {$ref: '#/components/schemas/ContainerEnvelope'}}}}
|
||||
DocumentResponse: {description: 成功或业务失败, content: {application/json: {schema: {$ref: '#/components/schemas/DocumentEnvelope'}}}}
|
||||
BinaryResponse: {description: 文件二进制, headers: {Content-Disposition: {schema: {type: string}}, Cache-Control: {schema: {type: string, example: no-store}}}, content: {application/octet-stream: {schema: {type: string, format: binary}}}}
|
||||
schemas:
|
||||
SubmitBatchRequest:
|
||||
type: object
|
||||
required: [customsAgentId, expectedVersion]
|
||||
properties:
|
||||
customsAgentId: {type: integer, format: int64, minimum: 1, description: ACTIVE Warehouse supplier ID validated as CUSTOMS.}
|
||||
customsAgentName: {type: string, maxLength: 128, nullable: true, description: Optional consistency hint; persisted name always comes from Warehouse.}
|
||||
externalReference: {type: string, maxLength: 128, nullable: true}
|
||||
note: {type: string, maxLength: 500, nullable: true}
|
||||
expectedVersion: {type: integer, minimum: 0}
|
||||
CustomsSubmissionBatch:
|
||||
type: object
|
||||
required: [id, customsCaseId, batchNo, batchVersion, customsAgentName, status, submittedBy, submittedAt]
|
||||
properties:
|
||||
id: {type: string, pattern: '^[1-9][0-9]*$'}
|
||||
customsCaseId: {type: string, pattern: '^[1-9][0-9]*$'}
|
||||
batchNo: {type: string, maxLength: 40}
|
||||
batchVersion: {type: integer, minimum: 1}
|
||||
customsAgentId: {type: string, pattern: '^[1-9][0-9]*$', nullable: true, description: Server-validated CUSTOMS supplier ID snapshot; null only for non-migrated historical rows.}
|
||||
customsAgentName: {type: string, maxLength: 128, description: Server-frozen Warehouse supplier name snapshot.}
|
||||
externalReference: {type: string, maxLength: 128, nullable: true}
|
||||
fileSnapshotJson: {type: string}
|
||||
status: {type: string, enum: [SUBMITTED, ACKNOWLEDGED, REJECTED, COMPLETED]}
|
||||
note: {type: string, maxLength: 500, nullable: true}
|
||||
submittedBy: {type: string, pattern: '^[1-9][0-9]*$'}
|
||||
submittedByName: {type: string, maxLength: 64, nullable: true}
|
||||
submittedAt: {type: string, format: date-time}
|
||||
LongId: {type: string, pattern: '^[1-9][0-9]*$', description: Long ID序列化为字符串}
|
||||
ApiEnvelope: {type: object, required: [code, message, timestamp], properties: {code: {type: string}, message: {type: string}, requestId: {type: string, nullable: true}, traceId: {type: string, nullable: true}, timestamp: {type: string, format: date-time}}}
|
||||
ContainerEnvelope: {allOf: [{$ref: '#/components/schemas/ApiEnvelope'}, {type: object, properties: {data: {$ref: '#/components/schemas/ContainerView'}}}]}
|
||||
DocumentEnvelope: {allOf: [{$ref: '#/components/schemas/ApiEnvelope'}, {type: object, properties: {data: {$ref: '#/components/schemas/DocumentView'}}}]}
|
||||
ActionRequest: {type: object, required: [expectedVersion, confirm], additionalProperties: false, properties: {expectedVersion: {type: integer, minimum: 0}, confirm: {type: boolean}, remark: {type: string, maxLength: 500, nullable: true}}}
|
||||
ScopeRequest: {type: object, required: [inboundIds, expectedVersion], additionalProperties: false, properties: {inboundIds: {type: array, minItems: 1, maxItems: 200, items: {$ref: '#/components/schemas/LongId'}}, expectedVersion: {type: integer, minimum: 1}}}
|
||||
FileView: {type: object, required: [fileId, fileName, fileHash, sizeBytes, version, uploadedAt], properties: {fileId: {$ref: '#/components/schemas/LongId'}, fileName: {type: string}, fileHash: {type: string}, contentType: {type: string, nullable: true}, sizeBytes: {type: integer, format: int64}, version: {type: integer}, uploadedAt: {type: string, format: date-time}}}
|
||||
InboundView: {type: object, required: [inboundId, orderId, orderNo, inboundCode, tenantId, customerName, taxMode, covered], properties: {inboundId: {$ref: '#/components/schemas/LongId'}, orderId: {$ref: '#/components/schemas/LongId'}, orderNo: {type: string}, inboundCode: {type: string}, tenantId: {$ref: '#/components/schemas/LongId'}, customerName: {type: string}, taxMode: {type: string, enum: [TAX_REFUND, BUY_DECLARATION]}, covered: {type: boolean}}}
|
||||
DocumentView: {type: object, required: [documentId, tenantId, source, inboundIds, inboundCodes, frozen], properties: {documentId: {$ref: '#/components/schemas/LongId'}, tenantId: {$ref: '#/components/schemas/LongId'}, source: {$ref: '#/components/schemas/FileView'}, inboundIds: {type: array, items: {$ref: '#/components/schemas/LongId'}}, inboundCodes: {type: array, items: {type: string}}, frozen: {type: boolean}, completion: {allOf: [{$ref: '#/components/schemas/FileView'}], nullable: true}}}
|
||||
ContainerView: {type: object, required: [aggregateId, loadingPlanId, containerBookingId, status, version, taxRefundCount, coveredTaxRefundCount, inbounds, documents], properties: {aggregateId: {$ref: '#/components/schemas/LongId'}, loadingPlanId: {$ref: '#/components/schemas/LongId'}, containerBookingId: {$ref: '#/components/schemas/LongId'}, planNo: {type: string}, cabinetNo: {type: string}, containerNo: {type: string, nullable: true}, status: {type: string, enum: [WAITING_CUSTOMER_DOCUMENTS, READY_TO_START, DECLARING, COMPLETED]}, merchantStatus: {type: string, enum: [WAITING_UPLOAD, DOCUMENT_UPLOADED, DECLARING, CUSTOMS_COMPLETED], nullable: true}, version: {type: integer}, taxRefundCount: {type: integer}, coveredTaxRefundCount: {type: integer}, inbounds: {type: array, items: {$ref: '#/components/schemas/InboundView'}}, documents: {type: array, items: {$ref: '#/components/schemas/DocumentView'}}, startedAt: {type: string, format: date-time, nullable: true}, completedAt: {type: string, format: date-time, nullable: true}}}
|
||||
ExportBatchRequest: {type: object, required: [taskId, tenantId, appCode, requestedBy, dispatchVersion, parameters, limit], properties: {taskId: {type: string}, tenantId: {type: string, enum: ['0']}, appCode: {type: string, enum: [PLATFORM]}, requestedBy: {type: string}, dispatchVersion: {type: integer}, parameters: {type: object, required: [loadingPlanId], additionalProperties: false, properties: {loadingPlanId: {type: string}}}, cursor: {type: string, nullable: true}, snapshotToken: {type: string, nullable: true}, limit: {type: integer, minimum: 1, maximum: 500}}}
|
||||
ExportBatchEnvelope: {allOf: [{$ref: '#/components/schemas/ApiEnvelope'}, {type: object, properties: {data: {type: object, properties: {schemaVersion: {type: integer}, schemaHash: {type: string}, snapshotToken: {type: string}, columns: {type: array, items: {type: object}}, rows: {type: array, items: {type: object}}, nextCursor: {type: string}, hasMore: {type: boolean}, totalRows: {type: integer, format: int64, nullable: true}}}}}]}
|
||||
|
||||
@@ -81,70 +81,6 @@ 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:list
|
||||
description: 查询出口报关文档;写入与完成继续要求 platform:firstleg:customs:manage。
|
||||
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/document/{fileId}/download:
|
||||
get:
|
||||
summary: 下载装柜清单出口报关附件
|
||||
description: 完成态仍可下载。服务端校验 planId、warehouseId、当前 CUSTOMS_DECLARATION 的 fileId,以及 File 元数据 tenantId=0、businessType=LOADING_CUSTOMS_DECLARATION、businessId=planId;错误 fileId 或归属返回 A0404。响应使用 attachment 文件名并禁止缓存。
|
||||
x-permission: platform:firstleg:loading-list:list
|
||||
parameters:
|
||||
- {$ref: '#/components/parameters/PlanId'}
|
||||
- {$ref: '#/components/parameters/WarehouseId'}
|
||||
- name: fileId
|
||||
in: path
|
||||
required: true
|
||||
description: File 服务长整型文件 ID
|
||||
schema: {$ref: '#/components/schemas/LongId'}
|
||||
responses:
|
||||
'200':
|
||||
description: 报关附件二进制内容;包含 Content-Disposition,Cache-Control 为 no-store
|
||||
headers:
|
||||
Content-Disposition: {schema: {type: string}}
|
||||
Cache-Control: {schema: {type: string, example: no-store}}
|
||||
content:
|
||||
application/octet-stream:
|
||||
schema: {type: string, format: binary}
|
||||
'404': {description: 装柜计划、仓库、当前附件或 File 归属不匹配}
|
||||
/api/platform/v1/first-leg/loading-lists/{planId}/customs/document/upload:
|
||||
post:
|
||||
summary: 直接选择文件上传或替换装柜清单出口报关文档
|
||||
description: Firstleg 使用内部服务令牌上传 File,浏览器不得传递 fileId 或内部令牌;旧 JSON document 接口仅兼容存量调用。
|
||||
x-permission: platform:firstleg:customs:manage
|
||||
parameters: [{$ref: '#/components/parameters/PlanId'}, {$ref: '#/components/parameters/WarehouseId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
multipart/form-data:
|
||||
schema: {$ref: '#/components/schemas/DocumentUploadRequest'}
|
||||
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:
|
||||
@@ -218,9 +154,6 @@ 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
|
||||
@@ -279,14 +212,6 @@ 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]
|
||||
@@ -464,42 +389,3 @@ 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}
|
||||
DocumentUploadRequest:
|
||||
type: object
|
||||
required: [file, expectedVersion]
|
||||
additionalProperties: false
|
||||
properties:
|
||||
file: {type: string, format: binary, description: 用户直接选择的报关文档}
|
||||
expectedVersion: {type: integer, format: int32, minimum: 0, description: 当前文档乐观锁版本,首次上传为 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}
|
||||
|
||||
@@ -45,7 +45,7 @@ paths:
|
||||
components:
|
||||
parameters:
|
||||
ContainerId: {name: id, in: path, required: true, schema: {type: integer, format: int64}}
|
||||
DocumentType: {name: documentType, in: path, required: true, schema: {type: string, enum: [DELIVERY_ORDER, PICKUP_ORDER]}, description: '通用单证仅支持DO/PU;清关使用 /clearance,出口报关使用 loading-list 专用接口'}
|
||||
DocumentType: {name: documentType, in: path, required: true, schema: {type: string, enum: [DELIVERY_ORDER, PICKUP_ORDER]}, description: '通用单证仅支持DO/PU;目的港清关使用 /clearance;出口报关由 customs-v1 柜级聚合接口承载'}
|
||||
requestBodies:
|
||||
ExceptionRequest: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/ExceptionRequest'}}}}
|
||||
schemas:
|
||||
@@ -57,7 +57,7 @@ components:
|
||||
container: {$ref: '#/components/schemas/Container'}
|
||||
loadingPlanId: {type: integer, format: int64}
|
||||
pda: {$ref: '#/components/schemas/PdaSummary'}
|
||||
documents: {type: array, description: '按 documentType 稳定排序返回 DESTINATION_CLEARANCE、CUSTOMS_DECLARATION、DELIVERY_ORDER、PICKUP_ORDER 的当前唯一记录和真实 version', items: {$ref: '#/components/schemas/Document'}}
|
||||
documents: {type: array, description: '按 documentType 稳定排序返回 DESTINATION_CLEARANCE、DELIVERY_ORDER、PICKUP_ORDER 的当前唯一记录和真实 version;出口报关只由 customs-v1 柜级聚合承载', items: {$ref: '#/components/schemas/Document'}}
|
||||
exception: {$ref: '#/components/schemas/ExceptionState'}
|
||||
exceptionHistory: {type: array, items: {$ref: '#/components/schemas/ExceptionHistory'}}
|
||||
timeline: {type: array, items: {$ref: '#/components/schemas/TimelineEntry'}}
|
||||
@@ -65,7 +65,7 @@ components:
|
||||
PdaSummary: {type: object, properties: {taskId: {type: integer, format: int64}, loadingPlanId: {type: integer, format: int64}, containerBookingId: {type: integer, format: int64}, taskStatus: {type: string, description: '持久化任务态:LOADING/COMPLETED'}, workflowStatus: {type: string, enum: [IN_PROGRESS, AWAITING_REVIEW, COMPLETED], description: 'PDA作业展示态'}, taskVersion: {type: integer}, itemCount: {type: integer}, completedItemCount: {type: integer}, unresolvedItemCount: {type: integer}, photoCounts: {type: array, items: {$ref: '#/components/schemas/PhotoCount'}}, photos: {type: array, items: {$ref: '#/components/schemas/Photo'}}, exceptionReasons: {type: array, items: {type: string}}}}
|
||||
PhotoCount: {type: object, properties: {evidenceType: {type: string}, count: {type: integer}}}
|
||||
Photo: {type: object, properties: {evidenceId: {type: integer, format: int64}, tenantId: {type: integer, format: int64}, inboundId: {type: integer, format: int64}, evidenceType: {type: string}, fileId: {type: integer, format: int64}, fileName: {type: string}, status: {type: string, enum: [ACTIVE, VOID]}, downloadPath: {type: string, nullable: true, description: '仅 ACTIVE 照片提供鉴权下载路径;VOID 历史照片为空'}, uploadedAt: {type: string, format: date-time}}}
|
||||
Document: {type: object, properties: {id: {type: integer, format: int64}, documentType: {type: string, enum: [DESTINATION_CLEARANCE, CUSTOMS_DECLARATION, DELIVERY_ORDER, PICKUP_ORDER]}, fileId: {type: integer, format: int64}, fileName: {type: string}, fileHash: {type: string}, uploadedBy: {type: integer, format: int64}, uploadedByName: {type: string}, uploadedAt: {type: string, format: date-time}, version: {type: integer, description: '当前唯一记录的真实乐观锁版本;更新时必须原样作为 expectedVersion'}, completed: {type: boolean}}}
|
||||
Document: {type: object, properties: {id: {type: integer, format: int64}, documentType: {type: string, enum: [DESTINATION_CLEARANCE, DELIVERY_ORDER, PICKUP_ORDER]}, fileId: {type: integer, format: int64}, fileName: {type: string}, fileHash: {type: string}, uploadedBy: {type: integer, format: int64}, uploadedByName: {type: string}, uploadedAt: {type: string, format: date-time}, version: {type: integer, description: '当前唯一记录的真实乐观锁版本;更新时必须原样作为 expectedVersion'}, completed: {type: boolean}}}
|
||||
ExceptionState: {type: object, properties: {active: {type: boolean}, reason: {type: string}, markedBy: {type: integer, format: int64}, markedByName: {type: string}, markedAt: {type: string, format: date-time}}}
|
||||
ExceptionHistory: {type: object, properties: {id: {type: integer, format: int64}, action: {type: string, enum: [MARK, CANCEL]}, reason: {type: string}, operatorId: {type: integer, format: int64}, operatorName: {type: string}, occurredAt: {type: string, format: date-time}}}
|
||||
TimelineEntry: {type: object, properties: {eventType: {type: string}, detail: {type: string}, operatorId: {type: integer, format: int64}, operatorName: {type: string}, occurredAt: {type: string, format: date-time}}}
|
||||
|
||||
@@ -37,7 +37,7 @@ paths:
|
||||
/api/platform/v1/first-leg/outbound-dispatches/{containerBookingId}/gate:
|
||||
get:
|
||||
summary: Evaluate every inbound binding in the fail-closed container outbound gate
|
||||
description: Loading evidence is evaluated once for the physical container/loading plan and the same result applies to every inbound binding. The customs gate is resolved once from the current container CUSTOMS_DECLARATION whose completedAt must be non-null; upload alone remains blocked and fl_customs_case is not an alternative gate fact. A missing standalone sailing schedule is reference-only and never affects gate.passed.
|
||||
description: Loading evidence is evaluated once for the physical container/loading plan and the same result applies to every inbound binding. The customs gate is resolved once from fl_customs_container and only status=COMPLETED passes; customer upload, READY_TO_START and DECLARING remain blocked. A missing standalone sailing schedule is reference-only and never affects gate.passed. Container customs completion attempts system confirmation immediately and a bounded scheduler retries after later gates become ready.
|
||||
x-permission: platform:firstleg:outbound-dispatch:check
|
||||
parameters:
|
||||
- {$ref: '#/components/parameters/ContainerBookingIdPath'}
|
||||
|
||||
Reference in New Issue
Block a user