feat(firstleg): close phase3 outbound customs and schedule gates
This commit is contained in:
@@ -6458,6 +6458,7 @@ CREATE TABLE IF NOT EXISTS fl_loading_evidence (
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_loading_evidence_type (tenant_id, inbound_id, evidence_type),
|
||||
UNIQUE KEY uk_loading_evidence_seal (tenant_id, seal_no),
|
||||
KEY idx_loading_evidence_time (tenant_id, inbound_id, updated_at),
|
||||
CONSTRAINT fk_loading_evidence_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id)
|
||||
) ENGINE=InnoDB COMMENT='订单装柜四步照片和铅封证据';
|
||||
@@ -7937,6 +7938,522 @@ CALL assert_phase3_service_center_schema();
|
||||
DROP PROCEDURE IF EXISTS assert_phase3_service_center_schema;
|
||||
|
||||
|
||||
-- Phase 3 domestic outbound aggregate and dispatch gate.
|
||||
USE baiyue_firstleg;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS fl_outbound_dispatch (
|
||||
id BIGINT UNSIGNED NOT NULL,
|
||||
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户隔离ID',
|
||||
order_id BIGINT UNSIGNED NOT NULL COMMENT '订单引用,不复制订单主数据',
|
||||
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '入库单主对象',
|
||||
container_booking_id BIGINT UNSIGNED NOT NULL COMMENT '柜次引用',
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'READY_FOR_DISPATCH',
|
||||
version INT UNSIGNED NOT NULL DEFAULT 0,
|
||||
confirmed_by BIGINT UNSIGNED NULL,
|
||||
confirmed_by_name VARCHAR(64) NULL,
|
||||
confirmed_at DATETIME(3) NULL,
|
||||
reversed_by BIGINT UNSIGNED NULL,
|
||||
reversed_by_name VARCHAR(64) NULL,
|
||||
reversed_at DATETIME(3) NULL,
|
||||
remark VARCHAR(500) 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),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_outbound_dispatch_owner (tenant_id,inbound_id,container_booking_id),
|
||||
KEY idx_outbound_dispatch_order (tenant_id,order_id,updated_at),
|
||||
KEY idx_outbound_dispatch_container (container_booking_id,status,updated_at),
|
||||
CONSTRAINT fk_outbound_dispatch_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order(id),
|
||||
CONSTRAINT fk_outbound_dispatch_container FOREIGN KEY (container_booking_id) REFERENCES fl_container_booking(id),
|
||||
CONSTRAINT chk_outbound_dispatch_status CHECK (status IN ('READY_FOR_DISPATCH','WAITING_FOR_LOADING'))
|
||||
) ENGINE=InnoDB COMMENT='三期国内出仓派车状态,不重复创建出仓单';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS fl_outbound_dispatch_history (
|
||||
id BIGINT UNSIGNED NOT NULL,
|
||||
dispatch_id BIGINT UNSIGNED NOT NULL,
|
||||
tenant_id BIGINT UNSIGNED NOT NULL,
|
||||
inbound_id BIGINT UNSIGNED NOT NULL,
|
||||
container_booking_id BIGINT UNSIGNED NOT NULL,
|
||||
action VARCHAR(32) NOT NULL,
|
||||
from_status VARCHAR(32) NULL,
|
||||
to_status VARCHAR(32) NOT 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_outbound_dispatch_history (tenant_id,inbound_id,container_booking_id,created_at,id),
|
||||
CONSTRAINT fk_outbound_dispatch_history FOREIGN KEY (dispatch_id) REFERENCES fl_outbound_dispatch(id)
|
||||
) ENGINE=InnoDB COMMENT='国内出仓追加式审计历史';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS fl_outbound_dispatch_operation (
|
||||
id BIGINT UNSIGNED NOT NULL,
|
||||
dispatch_id BIGINT UNSIGNED NULL COMMENT '首次声明幂等键时允许为空',
|
||||
operator_id BIGINT UNSIGNED NOT NULL,
|
||||
operation VARCHAR(32) NOT NULL,
|
||||
idempotency_key VARCHAR(128) NOT NULL,
|
||||
request_hash CHAR(64) NOT NULL,
|
||||
status VARCHAR(16) NOT NULL DEFAULT 'PROCESSING',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
completed_at DATETIME(3) NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_outbound_dispatch_operation (operator_id,operation,idempotency_key),
|
||||
KEY idx_outbound_dispatch_operation_resource (dispatch_id,created_at),
|
||||
CONSTRAINT fk_outbound_dispatch_operation FOREIGN KEY (dispatch_id) REFERENCES fl_outbound_dispatch(id),
|
||||
CONSTRAINT chk_outbound_dispatch_operation_status CHECK (status IN ('PROCESSING','SUCCESS'))
|
||||
) ENGINE=InnoDB COMMENT='国内出仓写操作幂等声明';
|
||||
|
||||
USE baiyue_iam;
|
||||
DROP PROCEDURE IF EXISTS assert_outbound_dispatch_permission_ids_available;
|
||||
DELIMITER $$
|
||||
CREATE PROCEDURE assert_outbound_dispatch_permission_ids_available()
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM iam_permission
|
||||
WHERE (id BETWEEN 5102160 AND 5102164
|
||||
AND permission_code <> CASE id
|
||||
WHEN 5102160 THEN 'platform:firstleg:outbound-dispatch:list'
|
||||
WHEN 5102161 THEN 'platform:firstleg:outbound-dispatch:view'
|
||||
WHEN 5102162 THEN 'platform:firstleg:outbound-dispatch:check'
|
||||
WHEN 5102163 THEN 'platform:firstleg:outbound-dispatch:confirm'
|
||||
WHEN 5102164 THEN 'platform:firstleg:outbound-dispatch:reverse'
|
||||
END)
|
||||
OR (permission_code LIKE 'platform:firstleg:outbound-dispatch:%'
|
||||
AND (tenant_id <> 0 OR app_code <> 'PLATFORM' OR id NOT BETWEEN 5102160 AND 5102164))
|
||||
) THEN
|
||||
SIGNAL SQLSTATE '45000'
|
||||
SET MESSAGE_TEXT = '三期出仓权限迁移失败:5102160-5102164 ID 或权限码被其他语义占用';
|
||||
END IF;
|
||||
END$$
|
||||
DELIMITER ;
|
||||
CALL assert_outbound_dispatch_permission_ids_available();
|
||||
DROP PROCEDURE IF EXISTS assert_outbound_dispatch_permission_ids_available;
|
||||
|
||||
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
|
||||
(5102160,0,'PLATFORM','MENU',5102000,'platform:firstleg:outbound-dispatch:list','国内出仓','/first-leg/outbound-dispatches','GET',95,'ACTIVE',1,0),
|
||||
(5102161,0,'PLATFORM','BUTTON',5102160,'platform:firstleg:outbound-dispatch:view','查看出仓聚合',NULL,'GET',10,'ACTIVE',1,0),
|
||||
(5102162,0,'PLATFORM','BUTTON',5102160,'platform:firstleg:outbound-dispatch:check','检查出仓门禁',NULL,'GET',20,'ACTIVE',1,0),
|
||||
(5102163,0,'PLATFORM','BUTTON',5102160,'platform:firstleg:outbound-dispatch:confirm','确认国内出仓',NULL,'POST',30,'ACTIVE',1,0),
|
||||
(5102164,0,'PLATFORM','BUTTON',5102160,'platform:firstleg:outbound-dispatch:reverse','撤销国内出仓',NULL,'POST',40,'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:outbound-dispatch:%'
|
||||
WHERE role.tenant_id=0 AND role.role_code IN ('PLATFORM_ADMIN','ORDER_OPERATOR');
|
||||
|
||||
USE baiyue_firstleg;
|
||||
|
||||
-- Phase 3 independent port and sailing schedule observations.
|
||||
USE baiyue_firstleg;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS fl_port_schedule (
|
||||
id BIGINT UNSIGNED NOT NULL,
|
||||
schedule_no VARCHAR(40) NOT NULL,
|
||||
customer_id BIGINT UNSIGNED NOT NULL COMMENT '当前产品客户ID',
|
||||
ocean_booking_id BIGINT UNSIGNED NOT NULL,
|
||||
container_booking_id BIGINT UNSIGNED NULL,
|
||||
source_type VARCHAR(24) NOT NULL COMMENT 'MANUAL/CARRIER/TERMINAL/API',
|
||||
source_name VARCHAR(128) NOT NULL,
|
||||
source_ref VARCHAR(255) NOT NULL,
|
||||
vessel_name VARCHAR(128) NOT NULL,
|
||||
voyage_no VARCHAR(64) NOT NULL,
|
||||
departure_port_code VARCHAR(32) NOT NULL,
|
||||
departure_port_name VARCHAR(128) NULL,
|
||||
destination_port_code VARCHAR(32) NOT NULL,
|
||||
destination_port_name VARCHAR(128) NULL,
|
||||
etd_at DATETIME(3) NULL,
|
||||
eta_at DATETIME(3) NULL,
|
||||
booking_cutoff_at DATETIME(3) NULL COMMENT '截单时间',
|
||||
customs_cutoff_at DATETIME(3) NULL COMMENT '截关时间',
|
||||
port_cutoff_at DATETIME(3) NULL COMMENT '截港时间',
|
||||
observed_at DATETIME(3) NOT NULL COMMENT '来源数据观测时间',
|
||||
expires_at DATETIME(3) NOT NULL COMMENT '超过该时间即由API计算为过期',
|
||||
remark VARCHAR(1000) NULL,
|
||||
created_by BIGINT UNSIGNED NOT NULL,
|
||||
created_by_name VARCHAR(64) NULL,
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_by BIGINT UNSIGNED NOT NULL,
|
||||
updated_by_name VARCHAR(64) NULL,
|
||||
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_port_schedule_no (schedule_no),
|
||||
UNIQUE KEY uk_port_schedule_source (source_type,source_name,source_ref),
|
||||
KEY idx_port_schedule_customer (customer_id,observed_at,id),
|
||||
KEY idx_port_schedule_booking (ocean_booking_id,observed_at,id),
|
||||
KEY idx_port_schedule_container (container_booking_id,observed_at,id),
|
||||
KEY idx_port_schedule_expiry (expires_at,id),
|
||||
CONSTRAINT fk_port_schedule_booking FOREIGN KEY (ocean_booking_id) REFERENCES fl_ocean_booking(id),
|
||||
CONSTRAINT fk_port_schedule_container FOREIGN KEY (container_booking_id) REFERENCES fl_container_booking(id),
|
||||
CONSTRAINT chk_port_schedule_customer CHECK (customer_id > 0),
|
||||
CONSTRAINT chk_port_schedule_source_type CHECK (source_type IN ('MANUAL','CARRIER','TERMINAL','API')),
|
||||
CONSTRAINT chk_port_schedule_eta CHECK (etd_at IS NULL OR eta_at IS NULL OR eta_at >= etd_at),
|
||||
CONSTRAINT chk_port_schedule_expiry CHECK (expires_at > observed_at),
|
||||
CONSTRAINT chk_port_schedule_booking_cutoff CHECK
|
||||
(booking_cutoff_at IS NULL OR etd_at IS NULL OR booking_cutoff_at <= etd_at),
|
||||
CONSTRAINT chk_port_schedule_customs_cutoff CHECK
|
||||
(customs_cutoff_at IS NULL OR etd_at IS NULL OR customs_cutoff_at <= etd_at),
|
||||
CONSTRAINT chk_port_schedule_port_cutoff CHECK
|
||||
(port_cutoff_at IS NULL OR etd_at IS NULL OR port_cutoff_at <= etd_at)
|
||||
) ENGINE=InnoDB COMMENT='独立港务船期来源聚合,不替代订舱快照';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS fl_port_schedule_history (
|
||||
id BIGINT UNSIGNED NOT NULL,
|
||||
port_schedule_id BIGINT UNSIGNED NOT NULL,
|
||||
action VARCHAR(24) NOT NULL,
|
||||
from_version INT UNSIGNED NULL,
|
||||
to_version INT UNSIGNED NOT NULL,
|
||||
reason VARCHAR(1000) NULL,
|
||||
before_snapshot JSON NULL,
|
||||
after_snapshot JSON 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),
|
||||
KEY idx_port_schedule_history (port_schedule_id,created_at,id),
|
||||
CONSTRAINT fk_port_schedule_history FOREIGN KEY (port_schedule_id) REFERENCES fl_port_schedule(id),
|
||||
CONSTRAINT chk_port_schedule_history_action CHECK (action IN ('CREATE','UPDATE'))
|
||||
) ENGINE=InnoDB COMMENT='港务船期不可变变更历史';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS fl_port_schedule_operation (
|
||||
id BIGINT UNSIGNED NOT NULL,
|
||||
port_schedule_id BIGINT UNSIGNED NULL COMMENT '创建请求完成前允许为空',
|
||||
operator_id BIGINT UNSIGNED NOT NULL,
|
||||
operation VARCHAR(24) NOT NULL,
|
||||
idempotency_key VARCHAR(128) NOT NULL,
|
||||
request_hash CHAR(64) NOT NULL,
|
||||
status VARCHAR(16) NOT NULL DEFAULT 'PROCESSING',
|
||||
result_json JSON NULL,
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
completed_at DATETIME(3) NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_port_schedule_operation (operator_id,idempotency_key),
|
||||
KEY idx_port_schedule_operation_resource (port_schedule_id,created_at),
|
||||
CONSTRAINT fk_port_schedule_operation FOREIGN KEY (port_schedule_id) REFERENCES fl_port_schedule(id),
|
||||
CONSTRAINT chk_port_schedule_operation_status CHECK (status IN ('PROCESSING','SUCCESS'))
|
||||
) ENGINE=InnoDB COMMENT='港务船期写操作幂等与请求哈希';
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_port_schedule_before_insert;
|
||||
DROP TRIGGER IF EXISTS trg_port_schedule_before_update;
|
||||
DELIMITER $$
|
||||
CREATE TRIGGER trg_port_schedule_before_insert BEFORE INSERT ON fl_port_schedule FOR EACH ROW
|
||||
BEGIN
|
||||
IF (SELECT customer_id FROM fl_ocean_booking WHERE id=NEW.ocean_booking_id) <> NEW.customer_id THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='船期客户与订舱客户不一致';
|
||||
END IF;
|
||||
IF NEW.container_booking_id IS NOT NULL AND NOT EXISTS (
|
||||
SELECT 1 FROM fl_ocean_booking_container_allocation
|
||||
WHERE ocean_booking_id=NEW.ocean_booking_id AND container_booking_id=NEW.container_booking_id
|
||||
) THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='船期柜次未分配至订舱';
|
||||
END IF;
|
||||
END$$
|
||||
CREATE TRIGGER trg_port_schedule_before_update BEFORE UPDATE ON fl_port_schedule FOR EACH ROW
|
||||
BEGIN
|
||||
IF NEW.customer_id <> OLD.customer_id OR NEW.ocean_booking_id <> OLD.ocean_booking_id
|
||||
OR NOT (NEW.container_booking_id <=> OLD.container_booking_id) THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='船期客户、订舱和柜次归属不可变更';
|
||||
END IF;
|
||||
END$$
|
||||
DELIMITER ;
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_port_schedule_history_before_update;
|
||||
DROP TRIGGER IF EXISTS trg_port_schedule_history_before_delete;
|
||||
DELIMITER $$
|
||||
CREATE TRIGGER trg_port_schedule_history_before_update BEFORE UPDATE ON fl_port_schedule_history FOR EACH ROW
|
||||
BEGIN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='港务船期历史不可修改'; END$$
|
||||
CREATE TRIGGER trg_port_schedule_history_before_delete BEFORE DELETE ON fl_port_schedule_history FOR EACH ROW
|
||||
BEGIN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='港务船期历史不可删除'; END$$
|
||||
DELIMITER ;
|
||||
|
||||
USE baiyue_iam;
|
||||
DROP PROCEDURE IF EXISTS assert_port_schedule_permission_ids_available;
|
||||
DELIMITER $$
|
||||
CREATE PROCEDURE assert_port_schedule_permission_ids_available()
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM iam_permission
|
||||
WHERE (id BETWEEN 5102180 AND 5102183
|
||||
AND permission_code <> CASE id
|
||||
WHEN 5102180 THEN 'platform:firstleg:port-schedule:list'
|
||||
WHEN 5102181 THEN 'platform:firstleg:port-schedule:view'
|
||||
WHEN 5102182 THEN 'platform:firstleg:port-schedule:create'
|
||||
WHEN 5102183 THEN 'platform:firstleg:port-schedule:edit'
|
||||
END)
|
||||
OR (permission_code LIKE 'platform:firstleg:port-schedule:%'
|
||||
AND (tenant_id <> 0 OR app_code <> 'PLATFORM' OR id NOT BETWEEN 5102180 AND 5102183))
|
||||
) THEN
|
||||
SIGNAL SQLSTATE '45000'
|
||||
SET MESSAGE_TEXT = '三期港务船期权限迁移失败:5102180-5102183 ID 或权限码被其他语义占用';
|
||||
END IF;
|
||||
END$$
|
||||
DELIMITER ;
|
||||
CALL assert_port_schedule_permission_ids_available();
|
||||
DROP PROCEDURE IF EXISTS assert_port_schedule_permission_ids_available;
|
||||
|
||||
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
|
||||
(5102180,0,'PLATFORM','MENU',5102000,'platform:firstleg:port-schedule:list','港务船期','/first-leg/port-schedules','GET',120,'ACTIVE',1,0),
|
||||
(5102181,0,'PLATFORM','BUTTON',5102180,'platform:firstleg:port-schedule:view','查看港务船期',NULL,'GET',10,'ACTIVE',1,0),
|
||||
(5102182,0,'PLATFORM','BUTTON',5102180,'platform:firstleg:port-schedule:create','新增港务船期',NULL,'POST',20,'ACTIVE',1,0),
|
||||
(5102183,0,'PLATFORM','BUTTON',5102180,'platform:firstleg:port-schedule:edit','更新港务船期',NULL,'PUT',30,'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:port-schedule:%'
|
||||
WHERE role.tenant_id=0 AND role.role_code IN ('PLATFORM_ADMIN','ORDER_OPERATOR');
|
||||
|
||||
USE baiyue_firstleg;
|
||||
|
||||
-- Phase 3 customs document, supplementation, broker submission and receipt lifecycle.
|
||||
USE baiyue_firstleg;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS fl_customs_checklist_version (
|
||||
id BIGINT UNSIGNED NOT NULL, checklist_code VARCHAR(40) NOT NULL, version_no VARCHAR(32) NOT NULL,
|
||||
country_code VARCHAR(32) NOT NULL, channel_code VARCHAR(40) NOT NULL, cargo_type VARCHAR(40) NOT NULL,
|
||||
items_json JSON NOT NULL, status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE', created_by BIGINT UNSIGNED NOT NULL,
|
||||
created_by_name VARCHAR(64) NULL, created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), 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, customs_no VARCHAR(40) NOT NULL, tenant_id BIGINT UNSIGNED NOT NULL,
|
||||
order_id BIGINT UNSIGNED NOT NULL, inbound_id BIGINT UNSIGNED NOT NULL,
|
||||
container_booking_id BIGINT UNSIGNED NOT NULL DEFAULT 0, checklist_version_id BIGINT UNSIGNED NOT NULL,
|
||||
checklist_code VARCHAR(40) NOT NULL, checklist_version_no VARCHAR(32) NOT NULL,
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'DOCUMENTS_PENDING', remark VARCHAR(500) NULL,
|
||||
created_by BIGINT UNSIGNED NOT NULL, created_by_name VARCHAR(64) NULL,
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), updated_by BIGINT UNSIGNED NOT NULL,
|
||||
updated_by_name VARCHAR(64) NULL, 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_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, customs_case_id BIGINT UNSIGNED NOT NULL, checklist_item_code VARCHAR(40) NOT NULL,
|
||||
file_id BIGINT UNSIGNED NOT NULL, document_version INT UNSIGNED NOT NULL, replaces_document_id BIGINT UNSIGNED NULL,
|
||||
file_name VARCHAR(255) NOT NULL, file_hash CHAR(64) NOT NULL, status VARCHAR(16) NOT NULL DEFAULT 'UPLOADED',
|
||||
visibility VARCHAR(16) NOT NULL DEFAULT 'CUSTOMER', uploaded_by BIGINT UNSIGNED NOT NULL,
|
||||
uploaded_by_type VARCHAR(16) NOT NULL, uploaded_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
reviewed_by BIGINT UNSIGNED NULL, reviewed_by_name VARCHAR(64) NULL, reviewed_at DATETIME(3) NULL,
|
||||
review_reason VARCHAR(500) NULL, 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, customs_case_id BIGINT UNSIGNED NOT NULL, source_document_id BIGINT UNSIGNED NOT NULL,
|
||||
resolved_document_id BIGINT UNSIGNED NULL, checklist_item_code VARCHAR(40) NOT NULL, reason VARCHAR(500) NOT NULL,
|
||||
due_at DATETIME(3) NULL, status VARCHAR(16) NOT NULL DEFAULT 'OPEN', requested_by BIGINT UNSIGNED NOT NULL,
|
||||
requested_by_name VARCHAR(64) NULL, requested_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
resolved_by BIGINT UNSIGNED NULL, resolved_at DATETIME(3) NULL, 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, customs_case_id BIGINT UNSIGNED NOT NULL, batch_no VARCHAR(40) NOT NULL,
|
||||
batch_version INT UNSIGNED NOT NULL, customs_agent_name VARCHAR(128) NOT NULL, external_reference VARCHAR(128) NULL,
|
||||
file_snapshot_json JSON NOT NULL, status VARCHAR(16) NOT NULL DEFAULT 'SUBMITTED', note VARCHAR(500) NULL,
|
||||
submitted_by BIGINT UNSIGNED NOT NULL, submitted_by_name VARCHAR(64) NULL,
|
||||
submitted_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), 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='报关代理提交批次及冻结文件版本';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS fl_customs_receipt (
|
||||
id BIGINT UNSIGNED NOT NULL, customs_case_id BIGINT UNSIGNED NOT NULL, submission_batch_id BIGINT UNSIGNED NOT NULL,
|
||||
receipt_type VARCHAR(16) NOT NULL, file_id BIGINT UNSIGNED NOT NULL, receipt_version INT UNSIGNED NOT NULL,
|
||||
replaces_receipt_id BIGINT UNSIGNED NULL, file_name VARCHAR(255) NOT NULL, file_hash CHAR(64) NOT NULL,
|
||||
visibility VARCHAR(16) NOT NULL DEFAULT 'PLATFORM', note VARCHAR(500) NULL, uploaded_by BIGINT UNSIGNED NOT NULL,
|
||||
uploaded_by_name VARCHAR(64) NULL, uploaded_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), 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, customs_case_id BIGINT UNSIGNED NOT NULL, action VARCHAR(40) NOT NULL,
|
||||
from_status VARCHAR(32) NULL, to_status VARCHAR(32) NULL, resource_type VARCHAR(32) NOT NULL,
|
||||
resource_id BIGINT UNSIGNED NOT NULL, note VARCHAR(500) NULL, operator_id BIGINT UNSIGNED NOT NULL,
|
||||
operator_name VARCHAR(64) NULL, operator_type VARCHAR(16) NOT NULL,
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), 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, operator_id BIGINT UNSIGNED NOT NULL, action VARCHAR(64) NOT NULL,
|
||||
idempotency_key VARCHAR(128) NOT NULL, request_hash CHAR(64) NOT NULL,
|
||||
status VARCHAR(16) NOT NULL DEFAULT 'PROCESSING', result_type VARCHAR(24) 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_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;
|
||||
|
||||
-- Phase 3 loading evidence hardening for existing databases.
|
||||
USE baiyue_firstleg;
|
||||
UPDATE fl_loading_evidence SET seal_no=NULL WHERE evidence_type<>'SEAL' AND seal_no IS NOT NULL;
|
||||
UPDATE fl_loading_evidence duplicate_evidence
|
||||
JOIN (
|
||||
SELECT tenant_id,seal_no,MIN(id) keep_id
|
||||
FROM fl_loading_evidence
|
||||
WHERE evidence_type='SEAL' AND seal_no IS NOT NULL AND seal_no<>''
|
||||
GROUP BY tenant_id,seal_no HAVING COUNT(*)>1
|
||||
) duplicates ON duplicates.tenant_id=duplicate_evidence.tenant_id
|
||||
AND duplicates.seal_no=duplicate_evidence.seal_no AND duplicate_evidence.id<>duplicates.keep_id
|
||||
SET duplicate_evidence.seal_no=NULL;
|
||||
SET @ddl = IF(
|
||||
EXISTS(SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='fl_loading_evidence')
|
||||
AND NOT EXISTS(SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE()
|
||||
AND table_name='fl_loading_evidence' AND index_name='uk_loading_evidence_seal'),
|
||||
'ALTER TABLE fl_loading_evidence ADD UNIQUE KEY uk_loading_evidence_seal (tenant_id,seal_no)',
|
||||
'SELECT 1');
|
||||
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
DROP PROCEDURE IF EXISTS assert_phase3_final_schema;
|
||||
DELIMITER $$
|
||||
CREATE PROCEDURE assert_phase3_final_schema()
|
||||
BEGIN
|
||||
IF (SELECT COUNT(*) FROM information_schema.tables
|
||||
WHERE table_schema='baiyue_firstleg' AND table_name IN (
|
||||
'fl_outbound_dispatch','fl_outbound_dispatch_history','fl_outbound_dispatch_operation',
|
||||
'fl_customs_checklist_version','fl_customs_case','fl_customs_document','fl_customs_supplement_request',
|
||||
'fl_customs_submission_batch','fl_customs_receipt','fl_customs_history','fl_customs_operation',
|
||||
'fl_port_schedule','fl_port_schedule_history','fl_port_schedule_operation')) <> 14 THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='统一迁移失败:三期出仓、报关或船期表未完整创建';
|
||||
END IF;
|
||||
IF (SELECT COUNT(*) FROM information_schema.statistics
|
||||
WHERE table_schema='baiyue_firstleg' AND table_name='fl_loading_evidence'
|
||||
AND index_name='uk_loading_evidence_seal' AND non_unique=0
|
||||
AND ((seq_in_index=1 AND column_name='tenant_id') OR (seq_in_index=2 AND column_name='seal_no'))) <> 2
|
||||
OR (SELECT COUNT(*) FROM information_schema.statistics
|
||||
WHERE table_schema='baiyue_firstleg' AND table_name='fl_outbound_dispatch'
|
||||
AND index_name='uk_outbound_dispatch_owner' AND non_unique=0) <> 3
|
||||
OR (SELECT COUNT(*) FROM information_schema.statistics
|
||||
WHERE table_schema='baiyue_firstleg' AND table_name='fl_customs_case'
|
||||
AND index_name='uk_customs_case_scope' AND non_unique=0) <> 4
|
||||
OR (SELECT COUNT(*) FROM information_schema.statistics
|
||||
WHERE table_schema='baiyue_firstleg' AND table_name='fl_port_schedule'
|
||||
AND index_name='uk_port_schedule_source' AND non_unique=0) <> 3 THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='统一迁移失败:三期关键唯一索引未完整创建';
|
||||
END IF;
|
||||
IF (SELECT COUNT(*) FROM information_schema.referential_constraints
|
||||
WHERE constraint_schema='baiyue_firstleg' AND constraint_name IN (
|
||||
'fk_outbound_dispatch_inbound','fk_outbound_dispatch_container','fk_customs_case_checklist',
|
||||
'fk_customs_document_case','fk_customs_supplement_case','fk_customs_batch_case',
|
||||
'fk_customs_receipt_case','fk_customs_receipt_batch','fk_customs_history_case',
|
||||
'fk_port_schedule_booking','fk_port_schedule_container','fk_port_schedule_history',
|
||||
'fk_port_schedule_operation')) <> 13 THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='统一迁移失败:三期关键外键未完整创建';
|
||||
END IF;
|
||||
IF (SELECT COUNT(*) FROM information_schema.triggers
|
||||
WHERE trigger_schema='baiyue_firstleg' AND trigger_name IN (
|
||||
'trg_port_schedule_before_insert','trg_port_schedule_before_update',
|
||||
'trg_port_schedule_history_before_update','trg_port_schedule_history_before_delete')) <> 4 THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='统一迁移失败:港务船期归属或历史保护触发器未完整创建';
|
||||
END IF;
|
||||
IF (SELECT COUNT(*) FROM baiyue_iam.iam_permission WHERE tenant_id=0 AND app_code='PLATFORM'
|
||||
AND id BETWEEN 5102160 AND 5102164 AND permission_code LIKE 'platform:firstleg:outbound-dispatch:%') <> 5
|
||||
OR (SELECT COUNT(*) FROM baiyue_iam.iam_permission WHERE tenant_id=0 AND app_code='PLATFORM'
|
||||
AND id BETWEEN 5102170 AND 5102175 AND permission_code LIKE 'platform:firstleg:customs:%') <> 6
|
||||
OR (SELECT COUNT(*) FROM baiyue_iam.iam_permission WHERE tenant_id=0 AND app_code='PLATFORM'
|
||||
AND id BETWEEN 5102180 AND 5102183 AND permission_code LIKE 'platform:firstleg:port-schedule:%') <> 4 THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='统一迁移失败:三期出仓、报关或船期权限未完整写入';
|
||||
END IF;
|
||||
IF (SELECT COUNT(DISTINCT CONCAT(r.role_code,'|',p.permission_code))
|
||||
FROM baiyue_iam.iam_role_permission rp
|
||||
JOIN baiyue_iam.iam_role r ON r.id=rp.role_id AND r.tenant_id=rp.tenant_id
|
||||
JOIN baiyue_iam.iam_permission p ON p.id=rp.permission_id AND p.tenant_id=rp.tenant_id
|
||||
WHERE r.tenant_id=0 AND r.role_code IN ('PLATFORM_ADMIN','ORDER_OPERATOR')
|
||||
AND (p.permission_code LIKE 'platform:firstleg:outbound-dispatch:%'
|
||||
OR p.permission_code LIKE 'platform:firstleg:customs:%'
|
||||
OR p.permission_code LIKE 'platform:firstleg:port-schedule:%')) <> 30 THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='统一迁移失败:三期出仓、报关或船期角色授权未完整写入';
|
||||
END IF;
|
||||
END$$
|
||||
DELIMITER ;
|
||||
CALL assert_phase3_final_schema();
|
||||
DROP PROCEDURE IF EXISTS assert_phase3_final_schema;
|
||||
|
||||
-- WAIT_SPLIT 是二期当前有效终点;统一迁移不得改写主表或历史状态。
|
||||
SELECT 'PASS' AS migration_status,
|
||||
'统一增量全部执行完成;箱号映射、单箱测量、三期订舱、拖车预约、出仓、费用、公共资源及可靠审计结构校验通过' AS message;
|
||||
|
||||
Reference in New Issue
Block a user