SET NAMES utf8mb4; USE baiyue_firstleg; CREATE TABLE IF NOT EXISTS fl_port_schedule ( id BIGINT UNSIGNED NOT NULL COMMENT '港务船期记录ID', schedule_no VARCHAR(40) NOT NULL COMMENT '港务船期记录编号', customer_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键', ocean_booking_id BIGINT UNSIGNED NOT NULL COMMENT '关联海运订舱ID', container_booking_id BIGINT UNSIGNED NULL COMMENT '关联柜次ID;订舱整票船期时为空', source_type VARCHAR(24) NOT NULL COMMENT '船期来源类型:MANUAL人工录入、CARRIER船公司、TERMINAL码头、API外部接口', source_name VARCHAR(128) NOT NULL COMMENT '船期来源名称', source_ref VARCHAR(255) NOT NULL COMMENT '来源侧记录唯一引用或查询凭据摘要', vessel_name VARCHAR(128) NOT NULL COMMENT '船名快照', voyage_no VARCHAR(64) NOT NULL COMMENT '航次号快照', departure_port_code VARCHAR(32) NOT NULL COMMENT '起运港编码快照', departure_port_name VARCHAR(128) NULL COMMENT '起运港名称快照', destination_port_code VARCHAR(32) NOT NULL COMMENT '目的港编码快照', destination_port_name VARCHAR(128) NULL COMMENT '目的港名称快照', etd_at DATETIME(3) NULL COMMENT '预计离港时间', eta_at DATETIME(3) NULL COMMENT '预计到港时间', 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 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_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 COMMENT '港务船期变更历史ID', port_schedule_id BIGINT UNSIGNED NOT NULL COMMENT '所属港务船期记录ID', action VARCHAR(24) NOT NULL COMMENT '变更动作:CREATE创建、UPDATE更新', from_version INT UNSIGNED NULL COMMENT '变更前版本;创建时为空', to_version INT UNSIGNED NOT NULL COMMENT '变更后版本', reason VARCHAR(1000) NULL COMMENT '变更原因或说明', before_snapshot JSON NULL COMMENT '变更前船期数据快照;创建时为空', after_snapshot JSON NOT NULL COMMENT '变更后船期数据快照', operator_id BIGINT UNSIGNED NOT NULL COMMENT '操作人ID', operator_name VARCHAR(64) NULL COMMENT '操作人姓名快照', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '历史记录创建时间', 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 COMMENT '港务船期写操作幂等记录ID', port_schedule_id BIGINT UNSIGNED NULL COMMENT '关联港务船期ID;创建请求完成前允许为空', operator_id BIGINT UNSIGNED NOT NULL COMMENT '发起写操作的平台操作人ID', operation VARCHAR(24) 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_json JSON NULL COMMENT '成功结果JSON快照;供幂等重放返回', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '幂等记录创建时间', completed_at DATETIME(3) NULL COMMENT '写操作成功完成时间', 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; DELETE role_permission FROM iam_role_permission role_permission JOIN iam_permission permission ON permission.id=role_permission.permission_id WHERE permission.tenant_id=0 AND permission.permission_code='platform:firstleg:port-schedule:view'; DELETE FROM iam_permission WHERE tenant_id=0 AND permission_code='platform:firstleg:port-schedule:view'; 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 IN (5102180,5102182,5102183) AND permission_code <> CASE id WHEN 5102180 THEN 'platform:firstleg:port-schedule:list' WHEN 5102182 THEN 'platform:firstleg:port-schedule:create' WHEN 5102183 THEN 'platform:firstleg:port-schedule:edit' END) OR id=5102181 OR (permission_code LIKE 'platform:firstleg:port-schedule:%' AND (tenant_id <> 0 OR app_code <> 'PLATFORM' OR id NOT IN (5102180,5102182,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), (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;