feat(firstleg): close phase3 outbound customs and schedule gates

This commit is contained in:
2026-08-08 22:36:41 +08:00
parent 789d97675b
commit 66d1aa0d3d
81 changed files with 5196 additions and 36 deletions
@@ -0,0 +1,225 @@
SET NAMES utf8mb4;
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;
@@ -1,13 +1,13 @@
SET NAMES utf8mb4;
USE baiyue_firstleg;
/* 装柜证据和派送完成;不设置证据完整度门禁,允许后续覆盖编辑。 */
/* 装柜证据、PDA 拣货复核和派送完成。业务服务强制五类真实文件证据齐全后才能完成装柜。 */
CREATE TABLE IF NOT EXISTS fl_loading_evidence (
id BIGINT UNSIGNED NOT NULL,
tenant_id BIGINT UNSIGNED NOT NULL,
inbound_id BIGINT UNSIGNED NOT NULL,
evidence_type VARCHAR(32) NOT NULL COMMENT 'PHOTO_STEP_1..4/SEAL',
file_id BIGINT UNSIGNED NULL COMMENT 'File 服务逻辑文件ID',
file_id BIGINT UNSIGNED NOT NULL COMMENT 'File 服务逻辑文件ID',
file_name VARCHAR(255) NULL,
seal_no VARCHAR(64) NULL,
remark VARCHAR(1000) NULL,
@@ -20,6 +20,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='订单装柜四步照片和铅封证据';
@@ -0,0 +1,110 @@
-- Phase 3 domestic outbound aggregate and dispatch gate. MySQL 8, idempotent.
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;
@@ -0,0 +1,168 @@
SET NAMES utf8mb4;
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;
@@ -0,0 +1,93 @@
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.
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:
get:
summary: Page customs cases
responses: {'200': {description: Page result}}
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'}]
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
responses: {'200': {description: Submission batch}}
/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}}
/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:
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:
get:
summary: Download only CUSTOMER-visible receipts from the authenticated customer's case
responses: {'200': {description: Authorized file stream}}
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]*$'}
@@ -0,0 +1,86 @@
openapi: 3.0.3
info:
title: Phase 3 Loading Evidence and Picking API
version: 1.0.0
paths:
/api/platform/v1/inbounds/{inboundId}/loading/evidence:
get:
summary: List loading evidence bound to one customer inbound
parameters: [{$ref: '#/components/parameters/InboundId'}, {$ref: '#/components/parameters/TenantId'}]
responses:
'200': {description: Business envelope containing the stable evidence array}
post:
summary: Bind one real File service object to a loading evidence slot
parameters: [{$ref: '#/components/parameters/InboundId'}, {$ref: '#/components/parameters/TenantId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
requestBody:
required: true
content:
application/json: {schema: {$ref: '#/components/schemas/UpsertEvidenceRequest'}}
responses:
'200': {description: Business envelope; ownership, type and version failures use business error codes}
/api/platform/v1/inbounds/{inboundId}/loading/picking/start:
post:
summary: Start the inbound-level PDA picking review task
parameters: [{$ref: '#/components/parameters/InboundId'}, {$ref: '#/components/parameters/TenantId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
requestBody:
required: true
content:
application/json: {schema: {$ref: '#/components/schemas/PickingRequest'}}
responses:
'200': {description: Business envelope containing IN_PROGRESS task}
/api/platform/v1/inbounds/{inboundId}/loading/picking/complete:
post:
summary: Complete a started PDA picking task when planned and picked counts match
parameters: [{$ref: '#/components/parameters/InboundId'}, {$ref: '#/components/parameters/TenantId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
requestBody:
required: true
content:
application/json: {schema: {$ref: '#/components/schemas/PickingRequest'}}
responses:
'200': {description: Business envelope containing COMPLETED task}
/api/platform/v1/inbounds/{inboundId}/loading/picking:
get:
summary: Read the inbound-level PDA picking task
parameters: [{$ref: '#/components/parameters/InboundId'}, {$ref: '#/components/parameters/TenantId'}]
responses:
'200': {description: Business envelope containing task or null}
/api/platform/v1/inbounds/{inboundId}/loading/complete:
post:
summary: Complete loading only after picking and PHOTO_STEP_1..4 plus SEAL evidence are complete
parameters: [{$ref: '#/components/parameters/InboundId'}, {$ref: '#/components/parameters/TenantId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
requestBody:
required: true
content:
application/json: {schema: {$ref: '#/components/schemas/CompleteLoadingRequest'}}
responses:
'200': {description: Business envelope containing LOADED status}
components:
parameters:
InboundId: {name: inboundId, in: path, required: true, schema: {type: integer, format: int64, minimum: 1}}
TenantId: {name: tenantId, in: query, required: true, schema: {type: integer, format: int64, minimum: 1}}
IdempotencyKey: {name: Idempotency-Key, in: header, required: true, schema: {type: string, maxLength: 128}}
schemas:
UpsertEvidenceRequest:
type: object
required: [expectedVersion, evidenceType, fileId]
properties:
expectedVersion: {type: integer, minimum: 0}
evidenceType: {type: string, enum: [PHOTO_STEP_1, PHOTO_STEP_2, PHOTO_STEP_3, PHOTO_STEP_4, SEAL]}
fileId: {type: integer, format: int64, minimum: 1}
fileName: {type: string, maxLength: 255, deprecated: true, description: Ignored; server uses File metadata}
sealNo: {type: string, maxLength: 64, description: Required when evidenceType is SEAL}
remark: {type: string, maxLength: 1000}
PickingRequest:
type: object
required: [expectedVersion, expectedCount, pickedCount]
properties:
expectedVersion: {type: integer, minimum: 0}
expectedCount: {type: integer, minimum: 1}
pickedCount: {type: integer, minimum: 0}
remark: {type: string, maxLength: 1000}
CompleteLoadingRequest:
type: object
required: [expectedVersion]
properties:
expectedVersion: {type: integer, minimum: 0}
remark: {type: string, maxLength: 1000}
@@ -0,0 +1,69 @@
openapi: 3.0.3
info:
title: Phase 3 Domestic Outbound Dispatch API
version: 1.0.0
paths:
/api/platform/v1/first-leg/outbound-dispatches:
get:
summary: Query outbound aggregates by order, inbound or container
parameters:
- {$ref: '#/components/parameters/OrderId'}
- {$ref: '#/components/parameters/InboundId'}
- {$ref: '#/components/parameters/ContainerBookingId'}
responses:
'200': {description: Business envelope containing matching aggregates}
/api/platform/v1/first-leg/outbound-dispatches/detail:
get:
summary: Query one aggregate and its unified timeline
parameters:
- {$ref: '#/components/parameters/OrderId'}
- {$ref: '#/components/parameters/InboundId'}
- {$ref: '#/components/parameters/ContainerBookingId'}
responses:
'200': {description: Business envelope containing aggregate detail}
/api/platform/v1/first-leg/outbound-dispatches/check-gate:
get:
summary: Evaluate the fail-closed domestic outbound gate
parameters:
- {$ref: '#/components/parameters/OrderId'}
- {$ref: '#/components/parameters/InboundId'}
- {$ref: '#/components/parameters/ContainerBookingId'}
responses:
'200': {description: Business envelope containing every gate item}
/api/platform/v1/first-leg/outbound-dispatches/confirm-dispatch:
post:
summary: Confirm domestic dispatch and enter WAITING_FOR_LOADING
parameters:
- {$ref: '#/components/parameters/IdempotencyKey'}
requestBody:
required: true
content:
application/json: {schema: {$ref: '#/components/schemas/ActionRequest'}}
responses:
'200': {description: Business envelope; gate or version failures use business error code}
/api/platform/v1/first-leg/outbound-dispatches/reverse-dispatch:
post:
summary: Reverse WAITING_FOR_LOADING to LOADED
parameters:
- {$ref: '#/components/parameters/IdempotencyKey'}
requestBody:
required: true
content:
application/json: {schema: {$ref: '#/components/schemas/ActionRequest'}}
responses:
'200': {description: Business envelope containing reverted aggregate}
components:
parameters:
OrderId: {name: orderId, in: query, schema: {type: integer, format: int64, minimum: 1}}
InboundId: {name: inboundId, in: query, schema: {type: integer, format: int64, minimum: 1}}
ContainerBookingId: {name: containerBookingId, in: query, schema: {type: integer, format: int64, minimum: 1}}
IdempotencyKey: {name: Idempotency-Key, in: header, required: true, schema: {type: string, maxLength: 128}}
schemas:
ActionRequest:
type: object
required: [inboundId, containerBookingId, expectedVersion]
properties:
inboundId: {type: integer, format: int64, minimum: 1}
containerBookingId: {type: integer, format: int64, minimum: 1}
expectedVersion: {type: integer, minimum: 0}
remark: {type: string, maxLength: 500}
@@ -0,0 +1,114 @@
openapi: 3.0.3
info:
title: Platform Port Schedule API
version: 1.0.0
description: Independent port and vessel schedule observations linked to ocean bookings and allocated containers.
paths:
/api/platform/v1/port-schedules:
get:
summary: Query schedules by customer, ocean booking, allocated container or expiry
parameters:
- {name: customerId, in: query, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
- {name: oceanBookingId, in: query, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
- {name: containerBookingId, in: query, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
- {name: expired, in: query, schema: {type: boolean}}
- {name: keyword, in: query, schema: {type: string, maxLength: 128}}
- {name: page, in: query, schema: {type: integer, minimum: 1, default: 1}}
- {name: size, in: query, schema: {type: integer, minimum: 1, maximum: 200, default: 20}}
responses:
'200':
description: HTTP 200 business envelope containing a page of schedule views
post:
summary: Create a source observation linked to a real ocean booking
parameters: [{$ref: '#/components/parameters/IdempotencyKey'}]
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/CreateRequest'}
responses:
'200': {description: Created detail in the standard business envelope}
/api/platform/v1/port-schedules/{portScheduleId}:
parameters: [{$ref: '#/components/parameters/PortScheduleId'}]
get:
summary: Get schedule and immutable history
responses:
'200': {description: Schedule detail with computed expired flag}
put:
summary: Update source observation with optimistic locking
parameters: [{$ref: '#/components/parameters/IdempotencyKey'}]
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/UpdateRequest'}
responses:
'200': {description: Updated detail or A0409 conflict in the business envelope}
/api/platform/v1/port-schedules/{portScheduleId}/history:
parameters: [{$ref: '#/components/parameters/PortScheduleId'}]
get:
summary: Query immutable source observation history
responses:
'200': {description: Ordered CREATE and UPDATE history records}
components:
parameters:
PortScheduleId:
name: portScheduleId
in: path
required: true
schema: {type: string, pattern: '^[1-9][0-9]*$'}
IdempotencyKey:
name: Idempotency-Key
in: header
required: true
schema: {type: string, minLength: 1, maxLength: 128}
schemas:
ScheduleData:
type: object
required:
- customerId
- oceanBookingId
- sourceType
- sourceName
- sourceRef
- vesselName
- voyageNo
- departurePortCode
- destinationPortCode
- observedAt
- expiresAt
properties:
customerId: {type: string, pattern: '^[1-9][0-9]*$'}
oceanBookingId: {type: string, pattern: '^[1-9][0-9]*$'}
containerBookingId: {type: string, nullable: true, pattern: '^[1-9][0-9]*$'}
sourceType: {type: string, enum: [MANUAL, CARRIER, TERMINAL, API]}
sourceName: {type: string, maxLength: 128}
sourceRef: {type: string, maxLength: 255}
vesselName: {type: string, maxLength: 128}
voyageNo: {type: string, maxLength: 64}
departurePortCode: {type: string, maxLength: 32}
departurePortName: {type: string, nullable: true, maxLength: 128}
destinationPortCode: {type: string, maxLength: 32}
destinationPortName: {type: string, nullable: true, maxLength: 128}
etdAt: {type: string, format: date-time, nullable: true}
etaAt: {type: string, format: date-time, nullable: true}
bookingCutoffAt: {type: string, format: date-time, nullable: true, description: Booking cutoff}
customsCutoffAt: {type: string, format: date-time, nullable: true, description: Customs cutoff}
portCutoffAt: {type: string, format: date-time, nullable: true, description: Port gate cutoff}
observedAt: {type: string, format: date-time}
expiresAt: {type: string, format: date-time}
remark: {type: string, nullable: true, maxLength: 1000}
CreateRequest:
type: object
required: [expectedVersion, data]
properties:
expectedVersion: {type: integer, minimum: 0, maximum: 0}
data: {$ref: '#/components/schemas/ScheduleData'}
reason: {type: string, nullable: true, maxLength: 1000}
UpdateRequest:
type: object
required: [expectedVersion, data]
properties:
expectedVersion: {type: integer, minimum: 0}
data: {$ref: '#/components/schemas/ScheduleData'}
reason: {type: string, nullable: true, maxLength: 1000}