Files
baiyue-firstleg/deploy/sql/头程数据库统一增量-MySQL8.sql
T
2026-08-13 11:57:33 +08:00

14703 lines
1.0 MiB
Plaintext
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- 柏悦头程一期、二期数据库统一增量脚本
-- 固定发布入口:后续数据库变更只在本文件末尾按日期追加,不再新增部署入口脚本。
-- 适用:MySQL 8.0+;支持空库初始化和存量库幂等补齐。
-- 涉及:baiyue_auth、baiyue_iam、baiyue_order、baiyue_warehouse、baiyue_firstleg、baiyue_file、baiyue_audit、baiyue_event_bridge。
-- 安全边界:不清表、不删业务数据、不创建固定密码账号、不写入演示仓库或演示订单。权限资源属于系统配置,会幂等写入。
-- MySQL DDL 会隐式提交,跨库脚本不能整体回滚;执行账号必须具有上述数据库的 CREATE/ALTER/INDEX/INSERT/UPDATE 权限。
-- 使用 MySQL Client、Workbench、DataGrip 或 Navicat 的“运行脚本”完整执行,客户端必须支持 DELIMITER。
SET NAMES utf8mb4;
SET SESSION sql_mode = 'ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';
-- 来源:warehouse\src\main\resources\db\phase3-pallet-location-transfer-idempotency.sql
CREATE DATABASE IF NOT EXISTS baiyue_warehouse
DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
USE baiyue_warehouse;
CREATE TABLE IF NOT EXISTS wh_pallet_location_transfer_operation (
id BIGINT UNSIGNED NOT NULL COMMENT '调拨幂等操作ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '仓储公共资源域,固定为0',
source_service VARCHAR(40) NOT NULL COMMENT '来源服务',
source_business_id BIGINT UNSIGNED NOT NULL COMMENT '来源入仓单ID',
target_business_id BIGINT UNSIGNED NOT NULL COMMENT '目标子入仓单ID',
idempotency_key VARCHAR(128) NOT NULL COMMENT '调用方幂等键',
request_hash CHAR(64) NOT NULL COMMENT '规范化请求SHA-256摘要',
operation_status VARCHAR(20) NOT NULL DEFAULT 'PROCESSING' COMMENT 'PROCESSING处理中、COMPLETED已完成',
response_snapshot JSON NULL COMMENT '完成后的调拨结果快照,用于重复请求重放',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
completed_at DATETIME(3) NULL COMMENT '完成时间',
PRIMARY KEY (id),
UNIQUE KEY uk_pallet_location_transfer_idempotency
(tenant_id, source_service, source_business_id, target_business_id, idempotency_key),
KEY idx_pallet_location_transfer_source (tenant_id, source_service, source_business_id, operation_status)
) ENGINE=InnoDB COMMENT='托盘库位归属转移的持久化幂等操作记录';
-- 来源:warehouse\src\main\resources\db\phase3-pallet-location-release-idempotency.sql
CREATE TABLE IF NOT EXISTS wh_pallet_location_release_operation (
id BIGINT UNSIGNED NOT NULL COMMENT '精确释放幂等操作ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '仓储公共资源作用域,固定为0',
source_service VARCHAR(40) NOT NULL COMMENT '来源服务',
business_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID',
idempotency_key VARCHAR(128) NOT NULL COMMENT '调用方幂等键',
request_hash CHAR(64) NOT NULL COMMENT '规范化请求SHA-256摘要',
operation_status VARCHAR(20) NOT NULL DEFAULT 'PROCESSING' COMMENT 'PROCESSING处理中,COMPLETED已完成',
response_snapshot JSON NULL COMMENT '完成后的精确释放结果快照,用于重复请求重放',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
completed_at DATETIME(3) NULL COMMENT '完成时间',
PRIMARY KEY (id),
UNIQUE KEY uk_pallet_location_release_idempotency
(tenant_id, source_service, business_id, idempotency_key),
KEY idx_pallet_location_release_business (tenant_id, source_service, business_id, operation_status)
) ENGINE=InnoDB COMMENT='PDA已拣满托盘库位精确释放的持久化幂等操作记录';
-- 来源:warehouse\src\main\resources\db\phase3-pallet-location-settlement-idempotency.sql
CREATE TABLE IF NOT EXISTS wh_pallet_location_settlement_operation (
id BIGINT UNSIGNED NOT NULL COMMENT 'PDA资源结算幂等操作ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '仓储公共资源作用域,固定为0',
source_service VARCHAR(40) NOT NULL COMMENT '来源服务',
source_business_id BIGINT UNSIGNED NOT NULL COMMENT '主入库单ID',
target_business_id BIGINT UNSIGNED NOT NULL COMMENT '二级子入库单ID;全拣完成时为0,仅作审计快照',
idempotency_key VARCHAR(128) NOT NULL COMMENT 'PDA结算幂等键',
request_hash CHAR(64) NOT NULL COMMENT '规范化请求SHA-256摘要',
operation_status VARCHAR(20) NOT NULL DEFAULT 'PROCESSING' COMMENT 'PROCESSING处理中,COMPLETED已完成',
response_snapshot JSON NULL COMMENT '完成后的释放和转移组合结果快照,用于重复请求重放',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
completed_at DATETIME(3) NULL COMMENT '完成时间',
PRIMARY KEY (id),
UNIQUE KEY uk_pallet_location_settlement_idempotency
(tenant_id, source_service, source_business_id, idempotency_key),
KEY idx_pallet_location_settlement_source
(tenant_id, source_service, source_business_id, operation_status)
) ENGINE=InnoDB COMMENT='PDA拣货资源原子结算的持久化幂等操作记录';
-- 兼容已执行过旧脚本的测试库:目标子单不属于幂等键,同一来源单据和幂等键只能对应一条请求。
ALTER TABLE wh_pallet_location_settlement_operation
DROP INDEX uk_pallet_location_settlement_idempotency,
ADD UNIQUE KEY uk_pallet_location_settlement_idempotency
(tenant_id, source_service, source_business_id, idempotency_key);
SELECT VERSION() AS mysql_version, 'START' AS unified_migration_status;
-- ============================================================================
-- 来源:audit-service\src\main\resources\db\audit.sql
-- ============================================================================
CREATE DATABASE IF NOT EXISTS baiyue_audit
DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
USE baiyue_audit;
CREATE TABLE IF NOT EXISTS sys_audit_log (
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '审计日志自增ID',
event_type VARCHAR(64) NOT NULL COMMENT '审计事件编码',
resource_type VARCHAR(64) NOT NULL COMMENT '被操作资源类型',
resource_id VARCHAR(128) DEFAULT NULL COMMENT '被操作资源ID',
description VARCHAR(255) DEFAULT NULL COMMENT '操作说明',
request_id VARCHAR(128) DEFAULT NULL COMMENT '请求ID,用于单次请求追踪',
trace_id VARCHAR(128) DEFAULT NULL COMMENT '分布式链路追踪ID',
tenant_id BIGINT DEFAULT NULL COMMENT '客户ID和数据隔离键;平台级审计可为空或为0',
operator_tenant_id BIGINT DEFAULT NULL COMMENT '操作主体所属客户ID;平台操作可为空或为0',
user_id BIGINT DEFAULT NULL COMMENT '操作用户ID',
username VARCHAR(128) DEFAULT NULL COMMENT '操作用户名快照',
app_code VARCHAR(64) DEFAULT NULL COMMENT '操作来源应用端',
client_type VARCHAR(32) DEFAULT NULL COMMENT '客户端类型',
http_method VARCHAR(16) DEFAULT NULL COMMENT 'HTTP请求方法',
request_uri VARCHAR(512) DEFAULT NULL COMMENT '请求URI',
client_ip VARCHAR(64) DEFAULT NULL COMMENT '客户端IP',
duration_ms BIGINT NOT NULL DEFAULT 0 COMMENT '请求耗时(毫秒)',
success TINYINT NOT NULL DEFAULT 0 COMMENT '操作是否成功',
error_code VARCHAR(64) DEFAULT NULL COMMENT '失败时的错误码',
error_message VARCHAR(512) DEFAULT NULL COMMENT '脱敏后的失败信息',
before_data JSON DEFAULT NULL COMMENT '变更前数据快照(已脱敏)',
after_data JSON DEFAULT NULL COMMENT '变更后数据快照(已脱敏)',
service_name VARCHAR(64) DEFAULT NULL COMMENT '产生日志的服务名',
occurred_at DATETIME(3) NOT NULL COMMENT '业务操作发生时间',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '审计服务落库时间',
PRIMARY KEY (id),
KEY idx_audit_tenant_time (tenant_id, occurred_at),
KEY idx_audit_event_time (event_type, occurred_at),
KEY idx_audit_user_time (user_id, occurred_at),
KEY idx_audit_request (request_id)
) ENGINE=InnoDB COMMENT='统一业务审计日志表:追加记录敏感操作的主体、租户、资源、结果及前后快照;不同于消息Outbox和消费幂等表';
-- ============================================================================
-- 来源:event-bridge-service\src\main\resources\db\event-bridge.sql
-- ============================================================================
CREATE DATABASE IF NOT EXISTS baiyue_event_bridge DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE baiyue_event_bridge;
CREATE TABLE IF NOT EXISTS bridge_outbox (
event_id VARCHAR(128) NOT NULL COMMENT '事件全局标识',
idempotency_key VARCHAR(191) NOT NULL COMMENT '发送幂等键',
event_type VARCHAR(64) NOT NULL COMMENT '事件类型',
event_version INT NOT NULL COMMENT '事件契约版本',
source_region VARCHAR(32) NOT NULL COMMENT '来源区域',
target_region VARCHAR(32) NOT NULL COMMENT '目标区域',
tenant_id VARCHAR(128) NOT NULL COMMENT '客户或业务隔离标识;跨区域事件沿用来源系统口径',
aggregate_type VARCHAR(64) NOT NULL COMMENT '聚合类型',
aggregate_id VARCHAR(128) NOT NULL COMMENT '聚合标识',
aggregate_version BIGINT NOT NULL COMMENT '聚合版本',
handover_version BIGINT NULL COMMENT '跨区交接版本',
trace_id VARCHAR(128) NOT NULL COMMENT '链路标识',
correlation_id VARCHAR(128) NOT NULL COMMENT '业务关联标识',
causation_id VARCHAR(128) NULL COMMENT '上游事件标识',
occurred_at DATETIME(3) NOT NULL COMMENT '业务事实发生时间',
payload_hash VARCHAR(71) NOT NULL COMMENT '规范化负载摘要',
payload_json JSON NOT NULL COMMENT '事件负载',
status VARCHAR(32) NOT NULL DEFAULT 'PENDING' COMMENT '投递状态',
retry_count INT NOT NULL DEFAULT 0 COMMENT '已重试次数',
next_retry_at DATETIME(3) NULL COMMENT '下次重试时间',
lease_owner VARCHAR(128) NULL COMMENT '租约持有者',
lease_until DATETIME(3) NULL COMMENT '租约截止时间',
last_error VARCHAR(1000) NULL COMMENT '最后错误',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
delivered_at DATETIME(3) NULL COMMENT '投递成功时间',
PRIMARY KEY (event_id),
UNIQUE KEY uk_bridge_outbox_idempotency (idempotency_key),
KEY idx_bridge_outbox_schedule (target_region, status, next_retry_at, lease_until)
) ENGINE=InnoDB COMMENT='跨区域事件待投递箱';
CREATE TABLE IF NOT EXISTS bridge_inbox (
event_id VARCHAR(128) NOT NULL COMMENT '事件全局标识',
idempotency_key VARCHAR(191) NOT NULL COMMENT '接收幂等键',
event_type VARCHAR(64) NOT NULL COMMENT '事件类型',
event_version INT NOT NULL COMMENT '事件契约版本',
source_region VARCHAR(32) NOT NULL COMMENT '来源区域',
target_region VARCHAR(32) NOT NULL COMMENT '目标区域',
tenant_id VARCHAR(128) NOT NULL COMMENT '客户或业务隔离标识;跨区域事件沿用来源系统口径',
aggregate_type VARCHAR(64) NOT NULL COMMENT '聚合类型',
aggregate_id VARCHAR(128) NOT NULL COMMENT '聚合标识',
aggregate_version BIGINT NOT NULL COMMENT '聚合版本',
handover_version BIGINT NULL COMMENT '跨区交接版本',
trace_id VARCHAR(128) NOT NULL COMMENT '链路标识',
correlation_id VARCHAR(128) NOT NULL COMMENT '业务关联标识',
causation_id VARCHAR(128) NULL COMMENT '上游事件标识',
occurred_at DATETIME(3) NOT NULL COMMENT '业务事实发生时间',
payload_hash VARCHAR(71) NOT NULL COMMENT '规范化负载摘要',
payload_json JSON NOT NULL COMMENT '事件负载',
status VARCHAR(32) NOT NULL DEFAULT 'RECEIVED' COMMENT '消费状态',
lease_owner VARCHAR(128) NULL COMMENT '消费租约持有者',
lease_until DATETIME(3) NULL COMMENT '消费租约截止时间',
retry_count INT NOT NULL DEFAULT 0 COMMENT '消费重试次数',
next_retry_at DATETIME(3) NULL COMMENT '下次消费时间',
last_error VARCHAR(1000) NULL COMMENT '最后错误',
received_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '接收时间',
processed_at DATETIME(3) NULL COMMENT '消费完成时间',
PRIMARY KEY (event_id),
UNIQUE KEY uk_bridge_inbox_idempotency (idempotency_key),
KEY idx_bridge_inbox_consume (status, next_retry_at, lease_until)
) ENGINE=InnoDB COMMENT='跨区域事件已接收箱';
CREATE TABLE IF NOT EXISTS bridge_delivery_attempt (
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
event_id VARCHAR(128) NOT NULL COMMENT '事件全局标识',
source_region VARCHAR(32) NOT NULL COMMENT '来源区域',
target_region VARCHAR(32) NOT NULL COMMENT '目标区域',
attempt_no INT NOT NULL COMMENT '投递序号',
status VARCHAR(32) NOT NULL COMMENT '投递结果',
response_code VARCHAR(64) NULL COMMENT '对端响应码',
error_message VARCHAR(1000) NULL COMMENT '错误信息',
started_at DATETIME(3) NOT NULL COMMENT '开始时间',
finished_at DATETIME(3) NULL COMMENT '完成时间',
PRIMARY KEY (id),
UNIQUE KEY uk_bridge_attempt (event_id, attempt_no),
KEY idx_bridge_attempt_event (event_id, started_at)
) ENGINE=InnoDB COMMENT='跨区域事件投递尝试记录';
-- ============================================================================
-- 来源:auth\src\main\resources\db\auth.sql
-- ============================================================================
SET NAMES utf8mb4;
CREATE DATABASE IF NOT EXISTS baiyue_auth
DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
USE baiyue_auth;
CREATE TABLE IF NOT EXISTS auth_account (
id BIGINT NOT NULL COMMENT '统一登录账号ID(雪花ID)',
username VARCHAR(64) NOT NULL COMMENT '全局唯一登录名',
password_hash VARCHAR(255) NOT NULL COMMENT 'BCrypt密码哈希,禁止保存明文密码',
display_name VARCHAR(64) NOT NULL COMMENT '登录后展示名称',
account_status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE' COMMENT '账号状态:ACTIVE正常、DISABLED停用、LOCKED锁定',
failed_login_count INT NOT NULL DEFAULT 0 COMMENT '连续登录失败次数',
locked_until DATETIME(3) DEFAULT NULL COMMENT '临时锁定截止时间',
token_version INT NOT NULL DEFAULT 1 COMMENT '令牌版本,递增后可使历史令牌失效',
password_change_required TINYINT NOT NULL DEFAULT 0 COMMENT '管理员重置后是否强制下次登录改密',
default_tenant_id BIGINT NOT NULL COMMENT '默认业务作用域ID:0为平台,正数为客户ID',
operator_tenant_id BIGINT NOT NULL COMMENT '操作主体作用域ID:0为平台,正数为客户ID,用于平台端与客户端身份隔离',
default_membership_id BIGINT DEFAULT NULL COMMENT '默认客户成员关系ID;平台账号可为空',
default_app_code VARCHAR(20) NOT NULL COMMENT '默认应用端:MERCHANT企业客户端、PLATFORM平台后台',
deleted TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
last_login_at DATETIME(3) DEFAULT NULL COMMENT '最近一次成功登录时间',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
PRIMARY KEY (id),
UNIQUE KEY uk_auth_account_username (username),
KEY idx_auth_account_status (account_status, locked_until)
) ENGINE=InnoDB COMMENT='统一登录账号表:保存平台端和商户端共用的认证凭据及账号安全状态';
-- 存量认证库补齐管理员重置密码后的强制改密标记(可重复执行)。
SET @auth_password_change_required_sql = IF(
EXISTS(SELECT 1 FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = 'auth_account'
AND column_name = 'password_change_required'),
'SELECT 1',
"ALTER TABLE auth_account ADD COLUMN password_change_required TINYINT NOT NULL DEFAULT 0 COMMENT '管理员重置后是否强制下次登录改密' AFTER token_version"
);
PREPARE auth_password_change_required_stmt FROM @auth_password_change_required_sql;
EXECUTE auth_password_change_required_stmt;
DEALLOCATE PREPARE auth_password_change_required_stmt;
-- 开发/测试环境最小登录基线:一个平台系统管理员和一个 IT 测试客户管理员。
-- 初始密码统一为 ChangeMe123!;只保存 BCrypt 哈希,不写入明文。
INSERT INTO auth_account (
id, username, password_hash, display_name, account_status,
failed_login_count, token_version, password_change_required, default_tenant_id,
operator_tenant_id, default_membership_id, default_app_code, deleted
) VALUES
(1000001, 'platform_admin', '$2a$12$AMZJjd88leYPLI9uKvjZiew2CTQXI32BXoHFX.FrCi28/IeZ7mZ96',
'平台系统管理员', 'ACTIVE', 0, 1, 0, 0, 0, 3000001, 'PLATFORM', 0),
(1000002, 'merchant_admin', '$2a$12$AMZJjd88leYPLI9uKvjZiew2CTQXI32BXoHFX.FrCi28/IeZ7mZ96',
'IT 测试客户管理员', 'ACTIVE', 0, 1, 0, 10001, 10001, 3000002, 'MERCHANT', 0)
ON DUPLICATE KEY UPDATE
password_hash = VALUES(password_hash),
display_name = VALUES(display_name),
account_status = 'ACTIVE',
failed_login_count = 0,
locked_until = NULL,
password_change_required = VALUES(password_change_required),
default_tenant_id = VALUES(default_tenant_id),
operator_tenant_id = VALUES(operator_tenant_id),
default_membership_id = VALUES(default_membership_id),
default_app_code = VALUES(default_app_code),
deleted = 0;
CREATE TABLE IF NOT EXISTS auth_refresh_token (
id BIGINT NOT NULL COMMENT '刷新令牌记录ID(雪花ID)',
token_hash CHAR(64) NOT NULL COMMENT '刷新令牌SHA-256摘要,不保存令牌明文',
user_id BIGINT NOT NULL COMMENT '认证账号ID',
tenant_id BIGINT NOT NULL COMMENT '令牌当前业务作用域ID:0为平台,正数为客户ID',
operator_tenant_id BIGINT NOT NULL COMMENT '令牌操作主体作用域ID:0为平台,正数为客户ID',
app_code VARCHAR(20) NOT NULL COMMENT '令牌所属应用端:MERCHANT企业客户端、PLATFORM平台后台',
expires_at DATETIME(3) NOT NULL COMMENT '到期时间',
revoked_at DATETIME(3) DEFAULT NULL COMMENT '吊销时间,非空表示不可继续使用',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '签发时间',
PRIMARY KEY (id),
UNIQUE KEY uk_auth_refresh_hash (token_hash),
KEY idx_auth_refresh_user (user_id, revoked_at, expires_at),
KEY idx_auth_refresh_expire (expires_at, revoked_at)
) ENGINE=InnoDB COMMENT='刷新令牌表:保存令牌摘要、平台或客户作用域、有效期和轮换/吊销状态';
-- 初始管理员密码必须由部署脚本生成 BCrypt 哈希后显式写入,仓库内禁止提供默认密码。
-- ============================================================================
-- 来源:iam\src\main\resources\db\iam.sql
-- ============================================================================
SET NAMES utf8mb4;
-- IAM 服务独占数据库:负责租户、成员、双端角色、菜单/页面/按钮/API权限和数据范围。
CREATE DATABASE IF NOT EXISTS baiyue_iam
DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
USE baiyue_iam;
CREATE TABLE IF NOT EXISTS iam_tenant (
id BIGINT NOT NULL COMMENT '客户主档ID(雪花ID);0保留为平台技术作用域',
tenant_code VARCHAR(64) NOT NULL COMMENT '全局唯一客户编码',
tenant_name VARCHAR(128) NOT NULL COMMENT '客户企业名称',
tenant_type VARCHAR(20) NOT NULL DEFAULT 'MERCHANT' COMMENT '主体类型:MERCHANT企业客户',
status VARCHAR(20) NOT NULL DEFAULT 'PROFILE_REQUIRED' COMMENT '客户状态:PROFILE_REQUIRED待完善资料、PENDING待审核、ACTIVE正常、REJECTED已驳回、DISABLED已停用',
legal_name VARCHAR(128) NULL COMMENT '企业法定名称',
legal_representative VARCHAR(64) NULL COMMENT '法定代表人',
unified_social_credit_code VARCHAR(32) NULL COMMENT '统一社会信用代码',
contact_name VARCHAR(64) NULL COMMENT '企业联系人',
contact_phone VARCHAR(32) NULL COMMENT '联系电话',
contact_email VARCHAR(128) NULL COMMENT '联系邮箱',
service_package VARCHAR(64) NOT NULL DEFAULT '基础版' COMMENT '客户服务套餐',
contact_wechat VARCHAR(64) NULL COMMENT '联系微信',
sales_owner_name VARCHAR(64) NULL COMMENT '销售负责人姓名',
payment_term_days SMALLINT UNSIGNED NULL COMMENT '账期天数',
enterprise_type VARCHAR(64) NULL COMMENT '企业类型',
established_date DATE NULL COMMENT '成立日期',
registered_capital DECIMAL(18,2) NULL COMMENT '注册资本',
registered_capital_currency CHAR(3) NULL COMMENT '注册资本币种 ISO 4217',
registered_address VARCHAR(500) NULL COMMENT '注册地址文本',
internal_remark VARCHAR(1000) NULL COMMENT '平台内部备注',
approved_at DATETIME(3) NULL COMMENT '客户资料审核通过时间',
approved_by BIGINT NULL COMMENT '客户资料审核人ID',
reject_reason VARCHAR(500) NULL COMMENT '客户资料审核驳回原因',
created_by BIGINT NULL COMMENT '创建人ID',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_by BIGINT NULL COMMENT '最后修改人ID',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
deleted TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id),
UNIQUE KEY uk_iam_tenant_code (tenant_code),
UNIQUE KEY uk_iam_tenant_credit (unified_social_credit_code),
KEY idx_iam_tenant_status (status, created_at)
) ENGINE=InnoDB COMMENT='客户企业主档表:保存企业资料、审核结果和启停状态;ID同时作为客户数据隔离键';
CREATE TABLE IF NOT EXISTS iam_tenant_review (
id BIGINT NOT NULL COMMENT '审核记录ID',
tenant_id BIGINT NOT NULL COMMENT '被审核客户ID和数据隔离键',
review_type VARCHAR(20) NOT NULL DEFAULT 'REGISTER' COMMENT '审核类型:REGISTER注册审核等',
status VARCHAR(20) NOT NULL DEFAULT 'PENDING' COMMENT '审核状态:PENDING待审核、APPROVED已通过、REJECTED已驳回',
reviewer_id BIGINT NULL COMMENT '审核人ID',
review_comment VARCHAR(500) NULL COMMENT '审核意见',
reviewed_at DATETIME(3) NULL COMMENT '审核完成时间',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '进入审核队列时间',
PRIMARY KEY (id),
KEY idx_review_queue (status, created_at),
KEY idx_review_tenant (tenant_id)
) ENGINE=InnoDB COMMENT='客户审核记录表:保存企业注册、资料变更等审核过程和审核意见';
CREATE TABLE IF NOT EXISTS iam_tenant_address (
id BIGINT NOT NULL COMMENT '客户地址ID',
tenant_id BIGINT NOT NULL COMMENT '客户ID和数据隔离键',
address_type VARCHAR(20) NOT NULL COMMENT '地址类型:注册地址、办公地址等',
country_code CHAR(2) NOT NULL DEFAULT 'CN' COMMENT '国家二字码',
province VARCHAR(64) NULL COMMENT '省/州',
city VARCHAR(64) NULL COMMENT '城市',
district VARCHAR(64) NULL COMMENT '区县',
detail VARCHAR(255) NOT NULL COMMENT '详细地址',
postal_code VARCHAR(16) NULL COMMENT '邮政编码',
is_default TINYINT NOT NULL DEFAULT 0 COMMENT '是否默认地址:0否、1是',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
deleted TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
PRIMARY KEY (id),
KEY idx_address_tenant (tenant_id, address_type)
) ENGINE=InnoDB COMMENT='客户地址表:保存企业注册地址、办公地址等客户资料';
CREATE TABLE IF NOT EXISTS iam_tenant_qualification (
id BIGINT NOT NULL COMMENT '资质记录ID',
tenant_id BIGINT NOT NULL COMMENT '客户ID和数据隔离键',
qualification_type VARCHAR(32) NOT NULL COMMENT '客户资质类型',
certificate_no VARCHAR(128) NULL COMMENT '证书编号',
document_url VARCHAR(500) NULL COMMENT '资质文件存储地址',
valid_from DATE NULL COMMENT '有效期开始日期',
valid_to DATE NULL COMMENT '有效期结束日期',
status VARCHAR(20) NOT NULL DEFAULT 'PENDING' COMMENT '资质审核状态:PENDING待审核、APPROVED已通过、REJECTED已驳回',
review_comment VARCHAR(500) NULL COMMENT '客户资质审核意见',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
deleted TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
PRIMARY KEY (id),
KEY idx_qualification_tenant (tenant_id, status)
) ENGINE=InnoDB COMMENT='客户资质表:保存企业证照、有效期、文件地址和审核状态';
CREATE TABLE IF NOT EXISTS iam_user (
id BIGINT NOT NULL COMMENT 'IAM用户资料ID',
auth_user_id BIGINT NOT NULL COMMENT '认证服务账号逻辑ID',
user_type VARCHAR(20) NOT NULL DEFAULT 'ENTERPRISE' COMMENT '用户类型:ENTERPRISE企业用户、PLATFORM平台用户',
display_name VARCHAR(64) NOT NULL COMMENT '用户展示名称',
phone VARCHAR(32) NULL COMMENT '手机号',
email VARCHAR(128) NULL COMMENT '邮箱',
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE' COMMENT '用户资料状态:ACTIVE正常、DISABLED停用',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
deleted TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id),
UNIQUE KEY uk_iam_user_auth (auth_user_id),
KEY idx_iam_user_status (status)
) ENGINE=InnoDB COMMENT='IAM用户资料表:连接统一认证账号与业务身份资料,不保存密码';
CREATE TABLE IF NOT EXISTS iam_membership (
id BIGINT NOT NULL COMMENT '客户成员关系ID',
tenant_id BIGINT NOT NULL COMMENT '客户ID和数据隔离键',
user_id BIGINT NOT NULL COMMENT 'IAM用户资料ID',
member_type VARCHAR(20) NOT NULL DEFAULT 'EMPLOYEE' COMMENT '成员类型:EMPLOYEE企业员工等',
member_no VARCHAR(64) NULL COMMENT '客户企业内部成员编号',
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE' COMMENT '成员状态:ACTIVE在职、DISABLED停用、LEFT已离开',
joined_at DATETIME(3) NULL COMMENT '加入客户企业时间',
left_at DATETIME(3) NULL COMMENT '离开客户企业时间',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
deleted TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id),
UNIQUE KEY uk_membership_tenant_user (tenant_id, user_id),
UNIQUE KEY uk_membership_tenant_no (tenant_id, member_no),
KEY idx_membership_user (user_id, status),
KEY idx_membership_tenant (tenant_id, status)
) ENGINE=InnoDB COMMENT='客户成员关系表:表示用户在客户企业中的成员身份,是分配企业客户端角色的主体';
CREATE TABLE IF NOT EXISTS iam_role (
id BIGINT NOT NULL COMMENT '角色ID',
tenant_id BIGINT NOT NULL DEFAULT 0 COMMENT '角色作用域:0为平台预置模板,正数为客户ID和数据隔离键',
app_code VARCHAR(20) NOT NULL COMMENT '应用端:MERCHANT企业客户端、PLATFORM平台后台',
role_code VARCHAR(64) NOT NULL COMMENT '作用域内唯一角色编码',
role_name VARCHAR(128) NOT NULL COMMENT '角色名称',
role_type VARCHAR(20) NOT NULL DEFAULT 'CUSTOM' COMMENT '角色类型:SYSTEM或CUSTOM',
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE' COMMENT '角色状态:ACTIVE启用、DISABLED停用',
description VARCHAR(255) NULL COMMENT '角色用途说明',
created_by BIGINT NULL COMMENT '创建人ID',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_by BIGINT NULL COMMENT '更新人ID',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
deleted TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id),
UNIQUE KEY uk_role_scope_code (tenant_id, app_code, role_code),
KEY idx_role_tenant (tenant_id, app_code, status)
) ENGINE=InnoDB COMMENT='角色表:按客户或平台作用域及应用端隔离企业客户端、平台后台两套角色体系';
CREATE TABLE IF NOT EXISTS iam_membership_role (
membership_id BIGINT NOT NULL COMMENT '客户成员关系ID',
role_id BIGINT NOT NULL COMMENT '角色ID',
tenant_id BIGINT NOT NULL COMMENT '冗余客户ID和数据隔离键,用于授权归属校验和查询',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '授权时间',
PRIMARY KEY (membership_id, role_id),
KEY idx_mr_tenant (tenant_id)
) ENGINE=InnoDB COMMENT='成员角色关联表:将客户成员分配到对应应用端的一个或多个角色';
CREATE TABLE IF NOT EXISTS iam_permission (
id BIGINT NOT NULL COMMENT '权限资源ID',
tenant_id BIGINT NOT NULL DEFAULT 0 COMMENT '权限作用域:0为平台预置权限,正数为客户ID和数据隔离键',
app_code VARCHAR(20) NOT NULL COMMENT '所属应用端:MERCHANT企业客户端、PLATFORM平台后台',
permission_type VARCHAR(20) NOT NULL COMMENT '权限类型:MENU、PAGE、BUTTON或API',
parent_id BIGINT NOT NULL DEFAULT 0 COMMENT '父权限ID,用于构造菜单和页面树',
permission_code VARCHAR(128) NOT NULL COMMENT '鉴权使用的唯一权限码',
permission_name VARCHAR(128) NOT NULL COMMENT '权限名称',
route_path VARCHAR(255) NULL COMMENT '前端路由或API路径',
http_method VARCHAR(16) NULL COMMENT 'API权限对应的HTTP方法',
sort_no INT NOT NULL DEFAULT 0 COMMENT '同级显示顺序',
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE' COMMENT '权限状态:ACTIVE启用、DISABLED停用',
built_in TINYINT NOT NULL DEFAULT 0 COMMENT '是否为系统预置权限;预置资源不可通过管理接口修改',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
deleted TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
PRIMARY KEY (id),
UNIQUE KEY uk_permission_scope_code (tenant_id, app_code, permission_code),
KEY idx_permission_parent (parent_id),
KEY idx_permission_api (http_method, route_path)
) ENGINE=InnoDB COMMENT='统一权限资源表:支持商户端和平台后台两套菜单、页面、按钮及API权限,权限最细可控制到按钮';
CREATE TABLE IF NOT EXISTS iam_role_permission (
role_id BIGINT NOT NULL COMMENT '角色ID',
permission_id BIGINT NOT NULL COMMENT '权限资源ID',
tenant_id BIGINT NOT NULL COMMENT '冗余客户ID或平台作用域ID,用于授权归属校验和查询',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '授权时间',
PRIMARY KEY (role_id, permission_id),
KEY idx_rp_tenant (tenant_id)
) ENGINE=InnoDB COMMENT='角色权限关联表:定义角色可访问的菜单、页面、按钮和API资源';
CREATE TABLE IF NOT EXISTS iam_data_scope (
id BIGINT NOT NULL COMMENT '数据范围规则ID',
role_id BIGINT NOT NULL COMMENT '角色ID',
tenant_id BIGINT NOT NULL COMMENT '规则作用域:0为平台,正数为客户ID和数据隔离键',
scope_type VARCHAR(20) NOT NULL DEFAULT 'SELF' COMMENT '数据范围类型:SELF本人、DEPARTMENT部门、TENANT当前客户、CUSTOM自定义',
scope_expression JSON NULL COMMENT '自定义数据范围表达式',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
PRIMARY KEY (id),
UNIQUE KEY uk_scope_role (role_id)
) ENGINE=InnoDB COMMENT='角色数据范围表:在功能权限之外限制角色可查看和操作的平台或客户范围内数据集合';
-- 存量数据库重复执行初始化脚本时,不重复创建审核状态索引。
SET @review_status_index_sql = IF(
EXISTS(SELECT 1 FROM information_schema.statistics
WHERE table_schema = DATABASE() AND table_name = 'iam_tenant_review'
AND index_name = 'idx_tenant_review_status'),
'SELECT 1',
'CREATE INDEX idx_tenant_review_status ON iam_tenant_review (tenant_id, status)'
);
PREPARE review_status_index_stmt FROM @review_status_index_sql;
EXECUTE review_status_index_stmt;
DEALLOCATE PREPARE review_status_index_stmt;
-- ============================================================================
-- 来源:order\src\main\resources\db\order.sql
-- ============================================================================
SET NAMES utf8mb4;
-- 订单服务独占数据库。其他服务只能通过 OpenAPI 或领域事件获取订单数据。
CREATE DATABASE IF NOT EXISTS baiyue_order
DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
USE baiyue_order;
CREATE TABLE IF NOT EXISTS ord_order (
id BIGINT UNSIGNED NOT NULL COMMENT '订单ID(雪花ID)',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
order_no VARCHAR(32) NOT NULL COMMENT '客户范围内唯一订单号',
inbound_code VARCHAR(40) NULL COMMENT '客户提交后生成的入仓码,草稿为空',
customer_id BIGINT UNSIGNED NULL COMMENT '客户逻辑ID',
customer_name VARCHAR(128) NOT NULL COMMENT '下单时客户名称快照',
status VARCHAR(24) NOT NULL DEFAULT 'DRAFT' COMMENT '订单业务状态:DRAFT草稿、SUBMITTED已提交、APPROVED审核通过、ACCEPTED已接单、REJECTED审核驳回、PROCESSING履约中、COMPLETED已完成、CANCELLED已取消',
transport_mode VARCHAR(16) NOT NULL COMMENT '运输方式',
trade_term VARCHAR(16) NULL COMMENT '贸易条款',
destination_country_code CHAR(2) NOT NULL COMMENT '目的国家二字码',
destination_warehouse_id BIGINT UNSIGNED NULL COMMENT '目的仓逻辑ID',
destination_warehouse_name VARCHAR(128) NOT NULL COMMENT '目的仓名称快照',
expected_ship_date DATE NULL COMMENT '预计发运日期',
customer_remark VARCHAR(1000) NULL COMMENT '客户可见备注',
pickup_method VARCHAR(24) NULL COMMENT '取货方式',
receiving_warehouse_id BIGINT UNSIGNED NULL COMMENT '国内收货仓逻辑ID',
receiving_warehouse_name VARCHAR(128) NULL COMMENT '下单时国内收货仓名称快照;主数据变化不回写',
receiving_warehouse_address VARCHAR(512) NULL COMMENT '国内收货仓完整地址快照',
receiving_time VARCHAR(128) NULL COMMENT '国内收货仓收货时间快照',
channel_code VARCHAR(64) NULL COMMENT '运输渠道编码',
channel_name VARCHAR(128) NULL COMMENT '下单时运输渠道名称快照;渠道主数据变化不回写',
carrier_id BIGINT UNSIGNED NULL COMMENT '船司逻辑ID快照',
carrier_name VARCHAR(128) NULL COMMENT '船司名称快照',
tax_mode VARCHAR(24) NULL COMMENT '报关税务方式:BUY_DECLARATION买单、TAX_REFUND退税',
pricing_tax_mode VARCHAR(16) NULL COMMENT '报价包税维度:TAX_INCLUDED或TAX_EXCLUDED;独立于tax_mode',
container_mode VARCHAR(24) NULL COMMENT '装运方式:FULL_CONTAINER整柜、LCL散货拼箱',
customs_inspection TINYINT(1) NULL COMMENT '是否需要商检:0否、1是',
destination_type VARCHAR(24) NULL COMMENT '目的仓类型:FBA亚马逊仓、OVERSEAS海外仓、PRIVATE私人地址',
amazon_warehouse_code VARCHAR(32) NULL COMMENT 'Amazon目的仓编码',
shipment_id VARCHAR(64) NULL COMMENT '订单级Amazon Shipment ID',
reference_id VARCHAR(64) NULL COMMENT '订单级Amazon Reference ID',
express_company VARCHAR(64) NULL COMMENT '履约快递公司名称快照',
express_tracking_no VARCHAR(128) NULL COMMENT '履约快递单号',
submitted_at DATETIME(3) NULL COMMENT '提交时间',
completed_at DATETIME(3) NULL COMMENT '完成时间',
created_by BIGINT UNSIGNED NOT NULL COMMENT '创建人ID',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_by BIGINT UNSIGNED NOT NULL COMMENT '最后修改人ID',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id),
UNIQUE KEY uk_order_tenant_no (tenant_id, order_no),
UNIQUE KEY uk_order_tenant_inbound_code (tenant_id, inbound_code),
KEY idx_order_tenant_status_time (tenant_id, status, created_at),
KEY idx_order_tenant_customer (tenant_id, customer_id),
KEY idx_order_tenant_warehouse (tenant_id, destination_warehouse_id)
) ENGINE=InnoDB COMMENT='头程订单主表:保存商户委托、目的仓、运输方式和订单当前状态';
CREATE TABLE IF NOT EXISTS ord_order_party (
id BIGINT UNSIGNED NOT NULL COMMENT '参与方快照ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
order_id BIGINT UNSIGNED NOT NULL COMMENT '所属订单ID',
party_type VARCHAR(16) NOT NULL COMMENT '参与方类型:SHIPPER或CONSIGNEE',
company_name VARCHAR(128) NOT NULL COMMENT '公司名称快照',
contact_name VARCHAR(64) NOT NULL COMMENT '联系人快照',
phone VARCHAR(32) NOT NULL COMMENT '联系电话快照',
email VARCHAR(128) NULL COMMENT '联系邮箱快照',
country_code CHAR(2) NOT NULL COMMENT '国家二字码',
province VARCHAR(64) NULL COMMENT '省/州',
city VARCHAR(64) NOT NULL COMMENT '城市',
district VARCHAR(64) NULL COMMENT '区县',
address_line VARCHAR(255) NOT NULL COMMENT '详细地址',
postal_code VARCHAR(20) NULL COMMENT '邮政编码',
created_by BIGINT UNSIGNED NOT NULL COMMENT '创建人ID',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_by BIGINT UNSIGNED NOT NULL COMMENT '最后修改人ID',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id),
UNIQUE KEY uk_party_order_type (tenant_id, order_id, party_type),
CONSTRAINT fk_party_order FOREIGN KEY (order_id) REFERENCES ord_order (id)
) ENGINE=InnoDB COMMENT='订单收发货方快照表:固化下单时发货人和收货人信息,避免主数据变化影响历史订单';
CREATE TABLE IF NOT EXISTS ord_order_cargo (
id BIGINT UNSIGNED NOT NULL COMMENT '货物明细ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
order_id BIGINT UNSIGNED NOT NULL COMMENT '所属订单ID',
item_no INT NOT NULL COMMENT '订单内明细序号',
cargo_name VARCHAR(128) NOT NULL COMMENT '货物名称',
hs_code VARCHAR(20) NULL COMMENT '海关商品编码',
package_type VARCHAR(32) NOT NULL COMMENT '包装类型',
package_count INT UNSIGNED NOT NULL COMMENT '包装件数',
gross_weight_kg DECIMAL(18,3) NOT NULL COMMENT '毛重(千克)',
volume_cbm DECIMAL(18,4) NOT NULL COMMENT '体积(立方米)',
length_cm DECIMAL(12,2) NULL COMMENT '单件长度(厘米)',
width_cm DECIMAL(12,2) NULL COMMENT '单件宽度(厘米)',
height_cm DECIMAL(12,2) NULL COMMENT '单件高度(厘米)',
declared_value DECIMAL(18,2) NULL COMMENT '申报价值',
currency CHAR(3) NULL COMMENT '币种三字码',
dangerous_goods TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否危险品',
cargo_remark VARCHAR(500) NULL COMMENT '货物备注',
sku_no VARCHAR(64) NULL COMMENT '货物SKU编码',
upc_no VARCHAR(64) NULL COMMENT '货物UPC条码',
cargo_name_en VARCHAR(128) NULL COMMENT '货物英文品名',
customs_hs_code VARCHAR(20) NULL COMMENT '目的国清关HS编码',
units_per_package INT UNSIGNED NULL COMMENT '每箱产品数量',
total_quantity INT UNSIGNED NULL COMMENT '产品总数量',
net_weight_kg DECIMAL(18,3) NULL COMMENT '货物净重,单位千克',
unit_declared_value DECIMAL(18,2) NULL COMMENT '单件申报价值;币种取订单申报币种',
material VARCHAR(128) NULL COMMENT '产品材质',
product_usage VARCHAR(128) NULL COMMENT '产品用途',
model VARCHAR(64) NULL COMMENT '产品型号',
brand VARCHAR(64) NULL COMMENT '产品品牌',
product_length_cm DECIMAL(12,2) NULL COMMENT '单件产品长度,单位厘米',
product_width_cm DECIMAL(12,2) NULL COMMENT '单件产品宽度,单位厘米',
product_height_cm DECIMAL(12,2) NULL COMMENT '单件产品高度,单位厘米',
shipment_id VARCHAR(64) NULL COMMENT '本货物明细对应的Amazon Shipment ID',
reference_id VARCHAR(64) NULL COMMENT '本货物明细对应的Amazon Reference ID',
product_image_file_id BIGINT UNSIGNED NULL COMMENT 'File服务中的产品图片逻辑文件ID',
created_by BIGINT UNSIGNED NOT NULL COMMENT '创建人ID',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_by BIGINT UNSIGNED NOT NULL COMMENT '最后修改人ID',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id),
UNIQUE KEY uk_cargo_item (tenant_id, order_id, item_no),
KEY idx_cargo_order (tenant_id, order_id),
CONSTRAINT fk_cargo_order FOREIGN KEY (order_id) REFERENCES ord_order (id)
) ENGINE=InnoDB COMMENT='订单货物明细表:记录件数、重量、体积、申报价值和危险品属性';
CREATE TABLE IF NOT EXISTS ord_order_internal_note (
id BIGINT UNSIGNED NOT NULL COMMENT '内部备注ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
order_id BIGINT UNSIGNED NOT NULL COMMENT '所属订单ID',
content VARCHAR(2000) NOT NULL COMMENT '仅内部人员可见的备注内容',
created_by BIGINT UNSIGNED NOT NULL COMMENT '创建人ID',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_by BIGINT UNSIGNED NOT NULL COMMENT '最后修改人ID',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id),
KEY idx_note_order_time (tenant_id, order_id, created_at),
CONSTRAINT fk_note_order FOREIGN KEY (order_id) REFERENCES ord_order (id)
) ENGINE=InnoDB COMMENT='订单内部备注表:保存平台运营或履约人员的内部协作记录,不向商户客户展示';
CREATE TABLE IF NOT EXISTS ord_order_status_history (
id BIGINT UNSIGNED NOT NULL COMMENT '状态历史ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
order_id BIGINT UNSIGNED NOT NULL COMMENT '所属订单ID',
from_status VARCHAR(24) NULL COMMENT '变更前状态,首次创建可为空',
to_status VARCHAR(24) NOT NULL COMMENT '变更后状态',
action VARCHAR(24) NOT NULL COMMENT '触发状态变化的业务动作',
reason VARCHAR(500) NULL COMMENT '变更原因',
operator_id BIGINT UNSIGNED NOT NULL COMMENT '操作人ID',
operator_name VARCHAR(64) NULL COMMENT '操作人名称快照',
occurred_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '状态变化时间',
PRIMARY KEY (id),
KEY idx_history_order_time (tenant_id, order_id, occurred_at),
CONSTRAINT fk_history_order FOREIGN KEY (order_id) REFERENCES ord_order (id)
) ENGINE=InnoDB COMMENT='订单状态不可变历史表:按时间记录每次状态迁移、动作、原因和操作人';
CREATE TABLE IF NOT EXISTS ord_outbox_event (
id BIGINT UNSIGNED NOT NULL COMMENT '领域事件全局ID,同时作为消费幂等键',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '事件所属客户ID和数据隔离键',
aggregate_type VARCHAR(32) NOT NULL COMMENT '聚合类型,例如 ORDER',
aggregate_id BIGINT UNSIGNED NOT NULL COMMENT '发生变更的聚合根ID,例如订单ID',
event_type VARCHAR(64) NOT NULL COMMENT '领域事件类型,例如 ORDER_SUBMITTED',
payload_json JSON NOT NULL COMMENT '供下游消费的事件负载快照',
publish_status VARCHAR(16) NOT NULL DEFAULT 'PENDING' COMMENT '发布状态:PENDING待发布、PUBLISHED已发布、FAILED发布失败',
retry_count INT NOT NULL DEFAULT 0 COMMENT '投递失败后的累计重试次数',
next_retry_at DATETIME(3) NULL COMMENT '允许再次投递的最早时间',
published_at DATETIME(3) NULL COMMENT '成功发布到消息中间件的时间',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '事件在本地事务中的创建时间',
PRIMARY KEY (id),
KEY idx_outbox_publish (publish_status, next_retry_at, created_at)
) ENGINE=InnoDB COMMENT='订单事务Outbox表:与订单业务变更在同一本地事务写入,供后台发布器可靠投递RocketMQ,保证数据库状态与领域事件最终一致;不是普通操作日志';
CREATE TABLE IF NOT EXISTS ord_idempotency_record (
id BIGINT UNSIGNED NOT NULL COMMENT '幂等记录 ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
operator_id BIGINT UNSIGNED NOT NULL COMMENT '发起写操作的用户ID',
operation VARCHAR(32) NOT NULL COMMENT '幂等操作类型',
resource_id BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '操作目标资源ID;创建前未知时为0',
idempotency_key VARCHAR(128) NOT NULL COMMENT '客户端幂等键',
request_hash CHAR(64) NOT NULL COMMENT '规范化请求内容SHA-256摘要,用于识别同键不同请求',
status VARCHAR(16) NOT NULL COMMENT '处理状态:PROCESSING处理中、COMPLETED已完成',
result_order_id BIGINT UNSIGNED NULL COMMENT '成功操作生成或更新的订单ID,用于幂等重放',
completed_at DATETIME(3) NULL COMMENT '处理完成时间',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
PRIMARY KEY (id),
UNIQUE KEY uk_order_idempotency_scope (tenant_id, operator_id, operation, idempotency_key),
KEY idx_order_idempotency_created (created_at)
) ENGINE=InnoDB COMMENT='订单写接口持久化幂等记录';
-- ============================================================================
-- 来源:warehouse\src\main\resources\db\warehouse.sql
-- ============================================================================
SET NAMES utf8mb4;
CREATE DATABASE IF NOT EXISTS baiyue_warehouse
DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
USE baiyue_warehouse;
CREATE TABLE IF NOT EXISTS wh_warehouse (
id BIGINT UNSIGNED NOT NULL COMMENT '仓库ID(雪花ID)',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '平台公共仓库固定使用 tenant_id=0',
warehouse_code VARCHAR(50) NOT NULL COMMENT '平台内唯一仓库编码',
warehouse_name VARCHAR(120) NOT NULL COMMENT '仓库名称',
warehouse_type VARCHAR(32) NOT NULL DEFAULT 'DOMESTIC' COMMENT '公共仓库类型:DOMESTIC、FBA、OVERSEAS',
country_code CHAR(2) NOT NULL COMMENT '所在国家二字码',
timezone VARCHAR(64) NOT NULL COMMENT '仓库所在地UTC偏移量',
contact_name VARCHAR(64) DEFAULT NULL COMMENT '仓库联系人',
contact_phone VARCHAR(32) DEFAULT NULL COMMENT '联系电话',
contact_email VARCHAR(128) DEFAULT NULL COMMENT '联系邮箱',
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE' COMMENT '仓库状态',
remark VARCHAR(500) DEFAULT NULL COMMENT '仓库备注',
created_by BIGINT UNSIGNED DEFAULT NULL COMMENT '创建人ID',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_by BIGINT UNSIGNED DEFAULT NULL COMMENT '最后修改人ID',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id),
UNIQUE KEY uk_wh_tenant_code (tenant_id, warehouse_code, deleted),
KEY idx_wh_tenant_status (tenant_id, status, deleted),
KEY idx_wh_tenant_type_status (tenant_id, warehouse_type, status, deleted),
KEY idx_wh_country (tenant_id, country_code, deleted)
) ENGINE=InnoDB COMMENT='平台公共仓库主数据表:统一归属平台作用域 tenant_id=0';
CREATE TABLE IF NOT EXISTS wh_warehouse_address (
id BIGINT UNSIGNED NOT NULL COMMENT '仓库地址ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '平台公共仓库地址固定使用 tenant_id=0',
warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '所属仓库ID',
address_type VARCHAR(20) NOT NULL DEFAULT 'OPERATING' COMMENT '地址类型,例如OPERATING或RETURN',
country_code CHAR(2) NOT NULL COMMENT '国家二字码',
state_province VARCHAR(100) DEFAULT NULL COMMENT '省或州',
city VARCHAR(100) NOT NULL COMMENT '城市',
district VARCHAR(100) DEFAULT NULL COMMENT '区县',
postal_code VARCHAR(20) DEFAULT NULL COMMENT '邮政编码',
address_line1 VARCHAR(255) NOT NULL COMMENT '主地址行',
address_line2 VARCHAR(255) DEFAULT NULL COMMENT '补充地址行',
latitude DECIMAL(10,7) DEFAULT NULL COMMENT '纬度',
longitude DECIMAL(10,7) DEFAULT NULL COMMENT '经度',
created_by BIGINT UNSIGNED DEFAULT NULL COMMENT '创建人ID',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_by BIGINT UNSIGNED DEFAULT NULL COMMENT '最后修改人ID',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id),
UNIQUE KEY uk_address_type (tenant_id, warehouse_id, address_type, deleted),
KEY idx_address_warehouse (tenant_id, warehouse_id, deleted)
) ENGINE=InnoDB COMMENT='仓库地址表:保存运营、退货等不同用途的地址及可选经纬度';
CREATE TABLE IF NOT EXISTS wh_service_capability (
id BIGINT UNSIGNED NOT NULL COMMENT '服务能力ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '平台公共仓库服务能力固定使用 tenant_id=0',
warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '所属仓库ID',
capability_code VARCHAR(50) NOT NULL COMMENT '能力编码:RECEIVING、STORAGE、PICK_PACK、RETURN等',
capability_name VARCHAR(100) NOT NULL COMMENT '能力名称',
enabled TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否启用',
cutoff_time TIME DEFAULT NULL COMMENT '当日作业截单时间',
description VARCHAR(500) DEFAULT NULL COMMENT '能力说明',
created_by BIGINT UNSIGNED DEFAULT NULL COMMENT '创建人ID',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_by BIGINT UNSIGNED DEFAULT NULL COMMENT '最后修改人ID',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id),
UNIQUE KEY uk_capability (tenant_id, warehouse_id, capability_code, deleted),
KEY idx_capability_query (tenant_id, warehouse_id, enabled, deleted)
) ENGINE=InnoDB COMMENT='仓库服务能力表:声明仓库可提供的收货、仓储、拣包、退货等能力及截单时间';
-- 不使用跨服务数据库外键;仓库聚合内的归属由服务代码通过 tenant_id 与 warehouse_id 共同校验。
-- ============================================================================
-- 来源:warehouse\src\main\resources\db\warehouse-partner.sql
-- ============================================================================
SET NAMES utf8mb4;
USE baiyue_warehouse;
CREATE TABLE IF NOT EXISTS wh_partner (
id BIGINT UNSIGNED NOT NULL COMMENT '往来单位ID(雪花ID)', tenant_id BIGINT UNSIGNED NOT NULL COMMENT '平台公共往来单位固定使用 tenant_id=0',
data_scope_id BIGINT UNSIGNED NOT NULL COMMENT '所属业务数据范围',
partner_code VARCHAR(50) NOT NULL COMMENT '平台内唯一往来单位编码', partner_name VARCHAR(128) NOT NULL COMMENT '往来单位名称',
partner_type VARCHAR(20) NOT NULL COMMENT 'CUSTOMER、SUPPLIER或BOTH',
contact_name VARCHAR(64) NOT NULL COMMENT '联系人姓名', contact_phone VARCHAR(32) NOT NULL COMMENT '联系电话',
contact_email VARCHAR(128) NULL COMMENT '联系邮箱', country_code CHAR(2) NOT NULL COMMENT '国家二字码',
province VARCHAR(64) NULL COMMENT '省或州', city VARCHAR(64) NOT NULL COMMENT '城市', district VARCHAR(64) NULL COMMENT '区县',
address_line VARCHAR(255) NOT NULL COMMENT '详细地址', postal_code VARCHAR(20) NULL COMMENT '邮政编码',
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE' COMMENT '状态:ACTIVE启用、DISABLED停用', remark VARCHAR(500) NULL COMMENT '备注',
created_by BIGINT UNSIGNED NULL COMMENT '创建人ID',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', updated_by BIGINT UNSIGNED NULL COMMENT '最后修改人ID',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记:0未删除、1已删除', version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY(id), UNIQUE KEY uk_wh_partner_tenant_code(tenant_id,partner_code,deleted),
KEY idx_wh_partner_tenant_status(tenant_id,status,deleted)
) ENGINE=InnoDB COMMENT='平台统一维护、供全部客户共享的往来单位主数据';
CREATE TABLE IF NOT EXISTS wh_partner_type (
partner_id BIGINT UNSIGNED NOT NULL COMMENT '往来单位ID', tenant_id BIGINT UNSIGNED NOT NULL COMMENT '平台公共往来单位固定使用 tenant_id=0',
data_scope_id BIGINT UNSIGNED NOT NULL COMMENT '所属业务数据范围,平台公共数据为0', partner_type VARCHAR(32) NOT NULL COMMENT '稳定业务类型:CUSTOMER客户、SHIPPER发货方等',
PRIMARY KEY(partner_id,partner_type),
KEY idx_wh_partner_type_scope(tenant_id,data_scope_id,partner_type,partner_id)
) ENGINE=InnoDB COMMENT='平台共享往来单位稳定业务类型';
INSERT INTO wh_partner(id,tenant_id,data_scope_id,partner_code,partner_name,partner_type,contact_name,contact_phone,contact_email,country_code,province,city,district,address_line,postal_code,status)
VALUES(191000000000000001,0,0,'CUS-001','深圳示例贸易有限公司','CUSTOMER','张三','13800138000','contact@example.com','CN','广东省','深圳市','宝安区','航城街道示例路1号','518000','ACTIVE')
ON DUPLICATE KEY UPDATE tenant_id=VALUES(tenant_id),data_scope_id=VALUES(data_scope_id),partner_name=VALUES(partner_name),status='ACTIVE';
INSERT INTO wh_partner_type(partner_id,tenant_id,data_scope_id,partner_type)
VALUES
(191000000000000001,0,0,'CUSTOMER'),
(191000000000000001,0,0,'SHIPPER')
ON DUPLICATE KEY UPDATE tenant_id=VALUES(tenant_id),data_scope_id=VALUES(data_scope_id);
-- ============================================================================
-- 来源:warehouse\src\main\resources\db\warehouse-express-company.sql
-- ============================================================================
SET NAMES utf8mb4;
USE baiyue_warehouse;
CREATE TABLE IF NOT EXISTS wh_express_company (
id BIGINT UNSIGNED NOT NULL COMMENT '快递公司记录ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '平台公共快递公司固定使用 tenant_id=0',
company_code VARCHAR(50) NOT NULL COMMENT '平台内唯一快递公司编码',
company_name VARCHAR(128) NOT NULL COMMENT '快递公司名称',
service_phone VARCHAR(32) DEFAULT NULL COMMENT '客服电话',
website VARCHAR(255) DEFAULT NULL COMMENT '官方网站',
tracking_url_template VARCHAR(500) DEFAULT NULL COMMENT '轨迹查询地址模板,可包含{trackingNo}',
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE' COMMENT '快递公司状态',
remark VARCHAR(500) DEFAULT NULL COMMENT '备注',
created_by BIGINT UNSIGNED DEFAULT NULL COMMENT '创建人ID',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_by BIGINT UNSIGNED DEFAULT NULL COMMENT '最后修改人ID',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id),
UNIQUE KEY uk_wh_express_tenant_code (tenant_id, company_code, deleted),
UNIQUE KEY uk_wh_express_tenant_name (tenant_id, company_name, deleted),
KEY idx_wh_express_tenant_status (tenant_id, status, deleted)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='平台公共快递公司主数据,固定归属 tenant_id=0';
-- 开发/测试环境公共快递基线:供订单履约快递选择和人工验收使用。
INSERT INTO wh_express_company (
id, tenant_id, company_code, company_name, service_phone, website,
tracking_url_template, status, remark, created_by, updated_by, deleted, version
) VALUES (
191000000000000101, 0, 'EXP-TEST', 'IT 测试快递', '400-000-0000', NULL,
NULL, 'ACTIVE', '统一初始化测试基线', 1000001, 1000001, 0, 0
)
ON DUPLICATE KEY UPDATE
tenant_id = VALUES(tenant_id),
company_name = VALUES(company_name),
service_phone = VALUES(service_phone),
status = 'ACTIVE',
remark = VALUES(remark),
deleted = 0;
-- ============================================================================
-- 来源:firstleg\src\main\resources\db\firstleg.sql
-- ============================================================================
-- 头程履约服务独占数据库。
CREATE DATABASE IF NOT EXISTS baiyue_firstleg
DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
USE baiyue_firstleg;
CREATE TABLE IF NOT EXISTS fl_fulfillment_task (
id BIGINT UNSIGNED NOT NULL COMMENT '履约任务ID(雪花ID)',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
order_id BIGINT UNSIGNED NOT NULL COMMENT '订单服务中的订单逻辑ID,不建立跨库外键',
order_no VARCHAR(32) NOT NULL COMMENT '订单号快照',
order_snapshot_json JSON NOT NULL COMMENT '创建履约任务时所需的订单业务快照',
status VARCHAR(24) NOT NULL DEFAULT 'PENDING_ASSIGNMENT' COMMENT '履约状态:PENDING_ASSIGNMENT待指派、ASSIGNED已指派、PROCESSING处理中、COMPLETED已完成、CANCELLED已取消',
assignee_id BIGINT UNSIGNED NULL COMMENT '当前负责人ID',
assignee_name VARCHAR(64) NULL COMMENT '当前负责人名称快照',
assigned_at DATETIME(3) NULL COMMENT '最近指派时间',
started_at DATETIME(3) NULL COMMENT '开始履约时间',
completed_at DATETIME(3) NULL COMMENT '履约完成时间',
created_by BIGINT UNSIGNED NOT NULL COMMENT '创建人或系统主体ID',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_by BIGINT UNSIGNED NOT NULL COMMENT '最后修改人ID',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id),
UNIQUE KEY uk_task_tenant_order (tenant_id, order_id),
KEY idx_task_tenant_created (tenant_id, created_at),
KEY idx_task_assignee_status (tenant_id, assignee_id, status, created_at)
) ENGINE=InnoDB COMMENT='头程履约任务表:接收订单事件后形成独立履约任务,管理指派、执行和完成状态';
CREATE TABLE IF NOT EXISTS fl_assignment_history (
id BIGINT UNSIGNED NOT NULL COMMENT '指派历史ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
task_id BIGINT UNSIGNED NOT NULL COMMENT '所属履约任务ID',
previous_assignee_id BIGINT UNSIGNED NULL COMMENT '原负责人ID',
assignee_id BIGINT UNSIGNED NOT NULL COMMENT '新负责人ID',
assignee_name VARCHAR(64) NOT NULL COMMENT '新负责人名称快照',
reason VARCHAR(500) NULL COMMENT '指派或改派原因',
operator_id BIGINT UNSIGNED NOT NULL COMMENT '执行指派的操作人ID',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '指派发生时间',
PRIMARY KEY (id),
KEY idx_assignment_task_time (tenant_id, task_id, created_at),
CONSTRAINT fk_assignment_task FOREIGN KEY (task_id) REFERENCES fl_fulfillment_task (id)
) ENGINE=InnoDB COMMENT='头程任务指派历史表:不可变记录每次负责人变更及原因,用于追踪和审计';
CREATE TABLE IF NOT EXISTS fl_consumed_event (
event_id BIGINT UNSIGNED NOT NULL COMMENT '已消费领域事件ID,与生产端Outbox事件ID一致',
event_type VARCHAR(64) NOT NULL COMMENT '已消费事件类型',
consumed_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '首次成功消费时间',
PRIMARY KEY (event_id)
) ENGINE=InnoDB COMMENT='头程消息消费幂等表:记录已处理事件并按事件ID去重,防止RocketMQ重复投递造成重复建单;不是消息发布表';
-- ============================================================================
-- 来源:file\src\main\resources\db\file.sql
-- ============================================================================
-- 文件中心独占数据库,其他服务只保存文件逻辑ID。
CREATE DATABASE IF NOT EXISTS baiyue_file
DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
USE baiyue_file;
CREATE TABLE IF NOT EXISTS file_object (
id BIGINT UNSIGNED NOT NULL COMMENT '文件ID(雪花ID)',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
business_type VARCHAR(32) NULL COMMENT '关联业务类型',
business_id VARCHAR(64) NULL COMMENT '关联业务逻辑ID',
original_name VARCHAR(255) NOT NULL COMMENT '原始文件名',
object_key VARCHAR(512) NOT NULL COMMENT '对象存储键',
storage_provider VARCHAR(32) NOT NULL COMMENT '存储提供方',
bucket_name VARCHAR(128) NOT NULL COMMENT '存储桶名称',
content_type VARCHAR(128) NOT NULL COMMENT '文件内容类型',
size_bytes BIGINT UNSIGNED NOT NULL COMMENT '文件字节数',
sha256 CHAR(64) NULL COMMENT '文件SHA-256摘要',
status VARCHAR(24) NOT NULL DEFAULT 'ACTIVE' COMMENT '文件状态',
created_by BIGINT UNSIGNED NOT NULL COMMENT '创建人ID',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_by BIGINT UNSIGNED NOT NULL COMMENT '最后修改人ID',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id),
UNIQUE KEY uk_file_tenant_object (tenant_id, object_key),
KEY idx_file_tenant_business (tenant_id, business_type, business_id),
KEY idx_file_tenant_created (tenant_id, created_at)
) ENGINE=InnoDB COMMENT='文件元数据表';
CREATE TABLE IF NOT EXISTS file_download_token (
id BIGINT UNSIGNED NOT NULL COMMENT '下载令牌ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
file_id BIGINT UNSIGNED NOT NULL COMMENT '文件逻辑ID',
token_hash CHAR(64) NOT NULL COMMENT '下载令牌SHA-256摘要',
expires_at DATETIME(3) NOT NULL COMMENT '过期时间',
max_downloads INT UNSIGNED NOT NULL DEFAULT 1 COMMENT '最大下载次数',
download_count INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '已下载次数',
revoked_at DATETIME(3) NULL COMMENT '撤销时间',
created_by BIGINT UNSIGNED NOT NULL COMMENT '创建人ID',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
PRIMARY KEY (id),
UNIQUE KEY uk_download_token_hash (token_hash),
KEY idx_download_file (tenant_id, file_id),
KEY idx_download_expiry (expires_at)
) ENGINE=InnoDB COMMENT='临时下载授权令牌表';
CREATE TABLE IF NOT EXISTS file_download_log (
id BIGINT UNSIGNED NOT NULL COMMENT '下载日志ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
file_id BIGINT UNSIGNED NOT NULL COMMENT '文件逻辑ID',
user_id BIGINT UNSIGNED NULL COMMENT '下载用户ID',
request_id VARCHAR(64) NULL COMMENT '请求ID',
client_ip VARCHAR(64) NULL COMMENT '客户端IP',
result VARCHAR(16) NOT NULL COMMENT '下载结果',
failure_reason VARCHAR(500) NULL COMMENT '失败原因',
downloaded_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '下载时间',
PRIMARY KEY (id),
KEY idx_download_log_file (tenant_id, file_id, downloaded_at),
KEY idx_download_log_user (tenant_id, user_id, downloaded_at)
) ENGINE=InnoDB COMMENT='文件下载审计日志表';
CREATE TABLE IF NOT EXISTS file_export_task (
id BIGINT UNSIGNED NOT NULL COMMENT '导出任务ID(雪花ID)',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键;平台任务固定为0',
app_code VARCHAR(24) NOT NULL COMMENT '申请端:PLATFORM或MERCHANT',
task_no VARCHAR(40) NOT NULL COMMENT '导出任务编号',
export_type VARCHAR(64) NOT NULL COMMENT '导出类型',
format VARCHAR(16) NOT NULL DEFAULT 'XLSX' COMMENT '导出格式',
task_name VARCHAR(128) NOT NULL COMMENT '任务名称',
parameters_json JSON NOT NULL COMMENT '筛选条件快照',
status VARCHAR(24) NOT NULL DEFAULT 'PENDING' COMMENT '状态:PENDING待处理、RUNNING执行中、SUCCEEDED成功、FAILED失败、EXPIRED已过期',
total_rows BIGINT UNSIGNED NULL COMMENT '预计总行数',
processed_rows BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '已处理行数',
file_name VARCHAR(255) NULL COMMENT '下载文件名',
object_key VARCHAR(512) NULL COMMENT '存储对象键',
content_type VARCHAR(128) NULL COMMENT '文件类型',
size_bytes BIGINT UNSIGNED NULL COMMENT '文件字节数',
failure_message VARCHAR(1000) NULL COMMENT '失败原因',
dispatch_version INT NOT NULL DEFAULT 1 COMMENT '派发版本,仅执行重试时递增',
attempt_count INT NOT NULL DEFAULT 0 COMMENT '已执行尝试次数',
max_attempts INT NOT NULL DEFAULT 3 COMMENT '自动重试上限',
next_retry_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '允许执行的最早时间',
lease_owner VARCHAR(128) NULL COMMENT '当前执行实例标识',
lease_until DATETIME(3) NULL COMMENT '执行租约截止时间',
last_heartbeat_at DATETIME(3) NULL COMMENT '最后心跳时间',
execution_deadline_at DATETIME(3) NULL COMMENT '本次执行截止时间',
requested_by BIGINT UNSIGNED NOT NULL COMMENT '申请用户ID',
requested_username VARCHAR(128) NULL COMMENT '申请账号快照',
requested_at DATETIME(3) NOT NULL COMMENT '申请时间',
started_at DATETIME(3) NULL COMMENT '开始时间',
completed_at DATETIME(3) NULL COMMENT '完成时间',
expires_at DATETIME(3) NULL COMMENT '文件过期时间',
created_by BIGINT UNSIGNED NULL COMMENT '创建人ID',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_by BIGINT UNSIGNED NULL COMMENT '最后修改人ID',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id),
UNIQUE KEY uk_file_export_task_no (task_no),
KEY idx_file_export_owner (tenant_id, app_code, requested_by, requested_at),
KEY idx_file_export_status (status, requested_at),
KEY idx_file_export_dispatch (status, next_retry_at, id),
KEY idx_file_export_lease (status, lease_until),
KEY idx_file_export_expiry (status, expires_at)
) ENGINE=InnoDB COMMENT='公共异步导出任务表';
CREATE TABLE IF NOT EXISTS file_export_task_guard (
guard_key VARCHAR(64) NOT NULL COMMENT '任务创建串行化锁键',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '锁记录最后更新时间',
PRIMARY KEY (guard_key)
) ENGINE=InnoDB COMMENT='导出任务创建并发保护表:低频创建入口使用单行锁保证重复提交及活动上限';
INSERT IGNORE INTO file_export_task_guard (guard_key) VALUES ('GLOBAL_CREATE');
CREATE TABLE IF NOT EXISTS file_export_outbox (
id BIGINT UNSIGNED NOT NULL COMMENT 'Outbox记录ID',
event_id VARCHAR(64) NOT NULL COMMENT '投递意图唯一标识',
task_id BIGINT UNSIGNED NOT NULL COMMENT '关联导出任务ID',
dispatch_version INT NOT NULL COMMENT '任务派发版本',
topic VARCHAR(128) NOT NULL COMMENT '目标Topic',
tag VARCHAR(64) NOT NULL COMMENT '消息Tag',
message_key VARCHAR(64) NOT NULL COMMENT '消息Key',
payload_json JSON NOT NULL COMMENT '命令消息体',
status VARCHAR(24) NOT NULL DEFAULT 'PENDING' COMMENT '状态:PENDING待发送、CLAIMED已领取、SENT已发送、QUARANTINED隔离',
attempt_count INT NOT NULL DEFAULT 0 COMMENT '累计发送尝试次数',
next_retry_at DATETIME(3) NOT NULL COMMENT '允许发送的最早时间',
lease_owner VARCHAR(128) NULL COMMENT '领取实例标识',
lease_until DATETIME(3) NULL COMMENT '领取租约截止时间',
sent_at DATETIME(3) NULL COMMENT '发送成功时间',
last_error VARCHAR(1000) NULL COMMENT '最后发送错误',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'Outbox记录创建时间',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT 'Outbox记录更新时间',
PRIMARY KEY (id),
UNIQUE KEY uk_file_export_outbox_event (event_id),
UNIQUE KEY uk_file_export_outbox_task_version (task_id, dispatch_version),
KEY idx_file_export_outbox_publish (status, next_retry_at, id),
KEY idx_file_export_outbox_task (task_id, dispatch_version, status, sent_at)
) ENGINE=InnoDB COMMENT='导出命令事务Outbox表';
-- ============================================================================
-- 来源:iam\src\main\resources\db\phase1-admin-rbac.sql
-- ============================================================================
-- Phase-one administrator identities and RBAC seed data.
-- Scope is intentionally limited to first-level menus, second-level menus and buttons.
-- PAGE and API permission resources are not part of phase one.
USE baiyue_iam;
SET NAMES utf8mb4;
-- Keep the role table aligned with the shared BaseEntity audit fields.
SET @ddl = IF(
EXISTS(SELECT 1 FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = 'iam_role' AND column_name = 'created_by'),
'SELECT 1',
'ALTER TABLE iam_role ADD COLUMN created_by BIGINT NULL AFTER description'
);
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @ddl = IF(
EXISTS(SELECT 1 FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = 'iam_role' AND column_name = 'updated_by'),
'SELECT 1',
'ALTER TABLE iam_role ADD COLUMN updated_by BIGINT NULL AFTER created_at'
);
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
INSERT INTO iam_tenant (
id, tenant_code, tenant_name, tenant_type, status, legal_name,
unified_social_credit_code, contact_name, contact_phone, contact_email,
approved_at, approved_by, created_by, deleted, version
) VALUES
(0, 'PLATFORM', '虫虫出海平台', 'PLATFORM', 'ACTIVE', '虫虫出海平台',
NULL, '平台系统管理员', NULL, NULL, NOW(3), 1000001, 1000001, 0, 0),
(10001, 'IT_TEST', 'IT 测试客户', 'MERCHANT', 'ACTIVE', 'IT 测试客户',
'91310000ITTEST0001', 'IT 测试客户管理员', '13800000000', 'it-test@example.com',
NOW(3), 1000001, 1000001, 0, 0)
ON DUPLICATE KEY UPDATE
tenant_code = VALUES(tenant_code), tenant_name = VALUES(tenant_name), tenant_type = VALUES(tenant_type),
status = 'ACTIVE', legal_name = VALUES(legal_name),
unified_social_credit_code = VALUES(unified_social_credit_code),
contact_name = VALUES(contact_name), contact_phone = VALUES(contact_phone),
contact_email = VALUES(contact_email), approved_at = COALESCE(approved_at, NOW(3)),
approved_by = VALUES(approved_by), deleted = 0;
INSERT INTO iam_user (
id, auth_user_id, user_type, display_name, phone, email, status, deleted, version
) VALUES
(2000001, 1000001, 'INTERNAL', '平台系统管理员', NULL, NULL, 'ACTIVE', 0, 0),
(2000002, 1000002, 'ENTERPRISE', 'IT 测试客户管理员', '13800000000',
'it-test@example.com', 'ACTIVE', 0, 0)
ON DUPLICATE KEY UPDATE
display_name = VALUES(display_name), phone = VALUES(phone), email = VALUES(email),
status = 'ACTIVE', deleted = 0;
INSERT INTO iam_membership (
id, tenant_id, user_id, member_type, member_no, status, joined_at, deleted, version
) VALUES
(3000001, 0, 2000001, 'EMPLOYEE', 'PLATFORM-ADMIN-001', 'ACTIVE', NOW(3), 0, 0),
(3000002, 10001, 2000002, 'OWNER', 'MERCHANT-OWNER-001', 'ACTIVE', NOW(3), 0, 0)
ON DUPLICATE KEY UPDATE
member_type = VALUES(member_type), member_no = VALUES(member_no),
status = 'ACTIVE', joined_at = COALESCE(joined_at, NOW(3)), left_at = NULL, deleted = 0;
INSERT INTO iam_role (
id, tenant_id, app_code, role_code, role_name, role_type, status, description, deleted, version
) VALUES
(4000001, 0, 'PLATFORM', 'PLATFORM_ADMIN', '平台超级管理员', 'SYSTEM', 'ACTIVE', '一期平台全部菜单和按钮', 0, 0),
(4000002, 0, 'PLATFORM', 'CUSTOMER_REVIEWER', '企业审核员', 'SYSTEM', 'ACTIVE', '企业资料与入驻审核', 0, 0),
(4000003, 0, 'PLATFORM', 'ORDER_OPERATOR', '平台订单运营', 'SYSTEM', 'ACTIVE', '头程订单审核与履约处理', 0, 0),
(4000101, 10001, 'MERCHANT', 'MERCHANT_OWNER', '企业管理员', 'SYSTEM', 'ACTIVE', '本企业一期全部菜单和按钮', 0, 0),
(4000102, 10001, 'MERCHANT', 'MERCHANT_ORDER_MANAGER', '企业订单管理员', 'SYSTEM', 'ACTIVE', '本企业头程订单操作', 0, 0)
ON DUPLICATE KEY UPDATE
role_name = VALUES(role_name), role_type = VALUES(role_type), status = 'ACTIVE',
description = VALUES(description), deleted = 0;
-- Platform: level-one menus.
INSERT INTO iam_permission
(id, tenant_id, app_code, permission_type, parent_id, permission_code, permission_name, route_path, http_method, sort_no, status, deleted)
VALUES
(5100000, 0, 'PLATFORM', 'MENU', 0, 'platform:customer', '客户管理', '/customers', NULL, 10, 'ACTIVE', 0),
(5101000, 0, 'PLATFORM', 'MENU', 0, 'platform:system', '系统管理', '/system', NULL, 20, 'ACTIVE', 0),
(5102000, 0, 'PLATFORM', 'MENU', 0, 'platform:order', '头程订单', '/first-leg-orders', NULL, 30, 'ACTIVE', 0),
(5103000, 0, 'PLATFORM', 'MENU', 0, 'platform:master-data', '基础资料', '/master-data', NULL, 40, 'ACTIVE', 0),
(5104000, 0, 'PLATFORM', 'MENU', 0, 'platform:audit', '审计管理', '/audit', NULL, 50, 'ACTIVE', 0),
-- Platform: level-two menus.
(5100010, 0, 'PLATFORM', 'MENU', 5100000, 'platform:customer:list', '企业客户', '/customers/list', NULL, 10, 'ACTIVE', 0),
(5101010, 0, 'PLATFORM', 'MENU', 5101000, 'platform:user:list', '内部用户', '/system/users', NULL, 10, 'ACTIVE', 0),
(5101020, 0, 'PLATFORM', 'MENU', 5101000, 'platform:role:list', '角色管理', '/system/roles', NULL, 20, 'ACTIVE', 0),
(5101030, 0, 'PLATFORM', 'MENU', 5101000, 'platform:permission:list', '菜单按钮', '/system/permissions', NULL, 30, 'ACTIVE', 0),
(5102010, 0, 'PLATFORM', 'MENU', 5102000, 'platform:order:list', '订单列表', '/first-leg-orders/list', NULL, 10, 'ACTIVE', 0),
(5102020, 0, 'PLATFORM', 'MENU', 5102000, 'platform:firstleg:task:list', '履约任务', '/first-leg-tasks/list', NULL, 20, 'ACTIVE', 0),
(5103010, 0, 'PLATFORM', 'MENU', 5103000, 'platform:warehouse:list', '国内仓/FBA仓', '/master-data/warehouses', NULL, 10, 'ACTIVE', 0),
(5103020, 0, 'PLATFORM', 'MENU', 5103000, 'platform:express:list', '快递公司', '/master-data/express', NULL, 20, 'ACTIVE', 0),
(5103030, 0, 'PLATFORM', 'MENU', 5103000, 'platform:counterparty:list', '往来单位', '/master-data/counterparties', NULL, 30, 'ACTIVE', 0),
(5103040, 0, 'PLATFORM', 'MENU', 5103000, 'platform:download:list', '下载中心', '/downloads', NULL, 40, 'ACTIVE', 0),
(5104010, 0, 'PLATFORM', 'MENU', 5104000, 'platform:audit:list', '操作日志', '/audit/logs', NULL, 10, 'ACTIVE', 0),
-- Platform: buttons.
(5100020, 0, 'PLATFORM', 'BUTTON', 5100010, 'platform:customer:review', '客户审核', NULL, NULL, 50, 'ACTIVE', 0),
(5100021, 0, 'PLATFORM', 'BUTTON', 5100020, 'platform:customer:approve', '审核通过', NULL, NULL, 10, 'ACTIVE', 0),
(5100022, 0, 'PLATFORM', 'BUTTON', 5100020, 'platform:customer:reject', '审核驳回', NULL, NULL, 20, 'ACTIVE', 0),
(5100015, 0, 'PLATFORM', 'BUTTON', 5100010, 'platform:customer:export', '导出企业客户', NULL, NULL, 40, 'ACTIVE', 0),
(5103041, 0, 'PLATFORM', 'BUTTON', 5103040, 'platform:download:all', '查看全部下载任务', NULL, NULL, 10, 'ACTIVE', 0),
(5101011, 0, 'PLATFORM', 'BUTTON', 5101010, 'platform:user:create', '新增用户', NULL, NULL, 10, 'ACTIVE', 0),
(5101012, 0, 'PLATFORM', 'BUTTON', 5101010, 'platform:user:edit', '编辑用户', NULL, NULL, 20, 'ACTIVE', 0),
(5101013, 0, 'PLATFORM', 'BUTTON', 5101010, 'platform:user:status', '启停用户', NULL, NULL, 30, 'ACTIVE', 0),
(5101014, 0, 'PLATFORM', 'BUTTON', 5101010, 'platform:user:reset-password', '重置密码', NULL, NULL, 40, 'ACTIVE', 0),
(5101015, 0, 'PLATFORM', 'BUTTON', 5101010, 'platform:user:grant-role', '分配用户角色', NULL, NULL, 50, 'ACTIVE', 0),
(5101021, 0, 'PLATFORM', 'BUTTON', 5101020, 'platform:role:create', '新增角色', NULL, NULL, 10, 'ACTIVE', 0),
(5101022, 0, 'PLATFORM', 'BUTTON', 5101020, 'platform:role:grant', '角色授权', NULL, NULL, 20, 'ACTIVE', 0),
(5101031, 0, 'PLATFORM', 'BUTTON', 5101030, 'platform:permission:create', '新增菜单按钮', NULL, NULL, 10, 'ACTIVE', 0),
(5102011, 0, 'PLATFORM', 'BUTTON', 5102010, 'platform:order:approve', '订单审核通过', NULL, NULL, 10, 'ACTIVE', 0),
(5102012, 0, 'PLATFORM', 'BUTTON', 5102010, 'platform:order:reject', '订单驳回', NULL, NULL, 20, 'ACTIVE', 0),
(5102013, 0, 'PLATFORM', 'BUTTON', 5102010, 'platform:order:accept', '订单受理', NULL, NULL, 30, 'ACTIVE', 0),
(5102014, 0, 'PLATFORM', 'BUTTON', 5102010, 'platform:order:process', '开始处理', NULL, NULL, 40, 'ACTIVE', 0),
(5102015, 0, 'PLATFORM', 'BUTTON', 5102010, 'platform:order:complete', '完成订单', NULL, NULL, 50, 'ACTIVE', 0),
(5102016, 0, 'PLATFORM', 'BUTTON', 5102010, 'platform:order:cancel', '取消订单', NULL, NULL, 60, 'ACTIVE', 0),
(5102017, 0, 'PLATFORM', 'BUTTON', 5102010, 'platform:order:note', '内部备注', NULL, NULL, 70, 'ACTIVE', 0),
(5102018, 0, 'PLATFORM', 'BUTTON', 5102010, 'platform:order:fulfillment', '登记履约快递', NULL, NULL, 80, 'ACTIVE', 0),
(5102021, 0, 'PLATFORM', 'BUTTON', 5102020, 'platform:firstleg:task:view', '查看履约任务', NULL, NULL, 10, 'ACTIVE', 0),
(5102022, 0, 'PLATFORM', 'BUTTON', 5102020, 'platform:firstleg:assignment:history', '查看分配历史', NULL, NULL, 20, 'ACTIVE', 0),
(5102023, 0, 'PLATFORM', 'BUTTON', 5102020, 'platform:firstleg:assignment:assign', '分配履约任务', NULL, NULL, 30, 'ACTIVE', 0),
(5103011, 0, 'PLATFORM', 'BUTTON', 5103010, 'platform:warehouse:create', '新增仓库', NULL, NULL, 10, 'ACTIVE', 0),
(5103012, 0, 'PLATFORM', 'BUTTON', 5103010, 'platform:warehouse:edit', '编辑仓库', NULL, NULL, 20, 'ACTIVE', 0),
(5103013, 0, 'PLATFORM', 'BUTTON', 5103010, 'platform:warehouse:status', '启用停用', NULL, NULL, 30, 'ACTIVE', 0),
(5103021, 0, 'PLATFORM', 'BUTTON', 5103020, 'platform:express:create', '新增快递公司', NULL, NULL, 10, 'ACTIVE', 0),
(5103022, 0, 'PLATFORM', 'BUTTON', 5103020, 'platform:express:edit', '编辑快递公司', NULL, NULL, 20, 'ACTIVE', 0),
(5103023, 0, 'PLATFORM', 'BUTTON', 5103020, 'platform:express:status', '启用停用快递公司', NULL, NULL, 30, 'ACTIVE', 0),
(5103024, 0, 'PLATFORM', 'BUTTON', 5103020, 'platform:express:import', '批量导入快递公司', NULL, NULL, 40, 'ACTIVE', 0),
(5103031, 0, 'PLATFORM', 'BUTTON', 5103030, 'platform:counterparty:create', '新增往来单位', NULL, NULL, 10, 'ACTIVE', 0),
(5103032, 0, 'PLATFORM', 'BUTTON', 5103030, 'platform:counterparty:edit', '编辑往来单位', NULL, NULL, 20, 'ACTIVE', 0),
(5103033, 0, 'PLATFORM', 'BUTTON', 5103030, 'platform:counterparty:status', '启用停用往来单位', NULL, NULL, 30, 'ACTIVE', 0),
(5103034, 0, 'PLATFORM', 'BUTTON', 5103030, 'platform:counterparty:import', '批量导入往来单位', NULL, NULL, 40, 'ACTIVE', 0)
ON DUPLICATE KEY UPDATE
permission_type = VALUES(permission_type), parent_id = VALUES(parent_id),
permission_name = VALUES(permission_name), route_path = VALUES(route_path),
http_method = NULL, sort_no = VALUES(sort_no), status = 'ACTIVE', deleted = 0;
-- 平台代客创建、编辑草稿权限;可重复执行并纳入已有平台管理员和订单运营角色授权。
INSERT INTO iam_permission
(id, tenant_id, app_code, permission_type, parent_id, permission_code, permission_name, route_path, http_method, sort_no, status, deleted)
VALUES
(5102019, 0, 'PLATFORM', 'BUTTON', 5102010, 'platform:order:create', '平台代客创建订单', NULL, NULL, 90, 'ACTIVE', 0),
(5102024, 0, 'PLATFORM', 'BUTTON', 5102010, 'platform:order:edit', '平台编辑订单草稿', NULL, NULL, 100, 'ACTIVE', 0),
(5102025, 0, 'PLATFORM', 'BUTTON', 5102010, 'platform:order:submit', '平台代客提交订单', NULL, NULL, 110, 'ACTIVE', 0)
ON DUPLICATE KEY UPDATE
permission_name = VALUES(permission_name), parent_id = VALUES(parent_id), status = 'ACTIVE', deleted = 0;
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
SELECT 4000001, id, 0 FROM iam_permission
WHERE id IN (5102019, 5102024, 5102025)
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
SELECT 4000003, id, 0 FROM iam_permission
WHERE id IN (5102019, 5102024, 5102025)
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
-- Merchant: level-one menus, level-two menus and buttons.
INSERT INTO iam_permission
(id, tenant_id, app_code, permission_type, parent_id, permission_code, permission_name, route_path, http_method, sort_no, status, deleted)
VALUES
(5200000, 10001, 'MERCHANT', 'MENU', 0, 'merchant:enterprise', '企业管理', '/enterprise', NULL, 10, 'ACTIVE', 0),
(5201000, 10001, 'MERCHANT', 'MENU', 0, 'merchant:order', '头程订单', '/first-leg-orders', NULL, 20, 'ACTIVE', 0),
(5202000, 10001, 'MERCHANT', 'MENU', 0, 'merchant:master-data', '基础资料', '/master-data', NULL, 30, 'ACTIVE', 0),
(5200010, 10001, 'MERCHANT', 'MENU', 5200000, 'merchant:profile:view', '企业资料', '/enterprise/profile', NULL, 10, 'ACTIVE', 0),
(5200020, 10001, 'MERCHANT', 'MENU', 5200000, 'merchant:member:list', '企业成员', '/enterprise/members', NULL, 20, 'ACTIVE', 0),
(5200030, 10001, 'MERCHANT', 'MENU', 5200000, 'merchant:role:list', '角色管理', '/enterprise/roles', NULL, 30, 'ACTIVE', 0),
(5201010, 10001, 'MERCHANT', 'MENU', 5201000, 'merchant:order:list', '我的订单', '/first-leg-orders/list', NULL, 10, 'ACTIVE', 0),
(5201020, 10001, 'MERCHANT', 'MENU', 5201000, 'merchant:order:create-page', '新建订单', '/first-leg-orders/create', NULL, 20, 'ACTIVE', 0),
(5202010, 10001, 'MERCHANT', 'MENU', 5202000, 'merchant:warehouse:list', '收货仓选择', '/master-data/warehouses', NULL, 10, 'ACTIVE', 0),
(5202020, 10001, 'MERCHANT', 'MENU', 5202000, 'merchant:counterparty:list', '往来单位', '/master-data/counterparties', NULL, 20, 'ACTIVE', 0),
(5200011, 10001, 'MERCHANT', 'BUTTON', 5200010, 'merchant:profile:edit', '编辑企业资料', NULL, NULL, 10, 'ACTIVE', 0),
(5200012, 10001, 'MERCHANT', 'BUTTON', 5200010, 'merchant:qualification:upload', '上传企业资质', NULL, NULL, 20, 'ACTIVE', 0),
(5200021, 10001, 'MERCHANT', 'BUTTON', 5200020, 'merchant:member:create', '新增成员', NULL, NULL, 10, 'ACTIVE', 0),
(5200022, 10001, 'MERCHANT', 'BUTTON', 5200020, 'merchant:member:grant-role', '分配角色', NULL, NULL, 20, 'ACTIVE', 0),
(5200031, 10001, 'MERCHANT', 'BUTTON', 5200030, 'merchant:role:create', '新增角色', NULL, NULL, 10, 'ACTIVE', 0),
(5200032, 10001, 'MERCHANT', 'BUTTON', 5200030, 'merchant:role:grant', '角色授权', NULL, NULL, 20, 'ACTIVE', 0),
(5201011, 10001, 'MERCHANT', 'BUTTON', 5201010, 'merchant:order:view', '查看订单', NULL, NULL, 10, 'ACTIVE', 0),
(5201012, 10001, 'MERCHANT', 'BUTTON', 5201010, 'merchant:order:cancel', '取消订单', NULL, NULL, 20, 'ACTIVE', 0),
(5201021, 10001, 'MERCHANT', 'BUTTON', 5201020, 'merchant:order:create', '创建草稿', NULL, NULL, 10, 'ACTIVE', 0),
(5201022, 10001, 'MERCHANT', 'BUTTON', 5201020, 'merchant:order:edit', '编辑草稿', NULL, NULL, 20, 'ACTIVE', 0),
(5201023, 10001, 'MERCHANT', 'BUTTON', 5201020, 'merchant:order:submit', '提交订单', NULL, NULL, 30, 'ACTIVE', 0)
ON DUPLICATE KEY UPDATE
permission_type = VALUES(permission_type), parent_id = VALUES(parent_id),
permission_name = VALUES(permission_name), route_path = VALUES(route_path),
http_method = NULL, sort_no = VALUES(sort_no), status = 'ACTIVE', deleted = 0;
INSERT INTO iam_membership_role (membership_id, role_id, tenant_id) VALUES
(3000001, 4000001, 0),
(3000002, 4000101, 10001)
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
-- Full administrator roles receive all phase-one permissions in their application scope.
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
SELECT 4000001, id, 0 FROM iam_permission
WHERE app_code = 'PLATFORM' AND tenant_id = 0 AND permission_type IN ('MENU', 'BUTTON')
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
SELECT 4000101, id, 10001 FROM iam_permission
WHERE app_code = 'MERCHANT' AND tenant_id = 10001 AND permission_type IN ('MENU', 'BUTTON')
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
-- Limited platform and merchant operational roles.
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
SELECT 4000002, id, 0 FROM iam_permission
WHERE permission_code IN (
'platform:customer', 'platform:customer:list', 'platform:customer:review',
'platform:customer:approve', 'platform:customer:reject', 'platform:customer:export',
'platform:master-data', 'platform:download:list'
)
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
SELECT 4000003, id, 0 FROM iam_permission
WHERE permission_code LIKE 'platform:order%' OR permission_code LIKE 'platform:firstleg%'
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
SELECT 4000102, id, 10001 FROM iam_permission
WHERE permission_code LIKE 'merchant:order%' OR permission_code IN ('merchant:master-data', 'merchant:warehouse:list')
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
INSERT INTO iam_data_scope (id, role_id, tenant_id, scope_type, scope_expression) VALUES
(6000001, 4000001, 0, 'TENANT', NULL),
(6000002, 4000002, 0, 'TENANT', NULL),
(6000003, 4000003, 0, 'TENANT', NULL),
(6000101, 4000101, 10001, 'TENANT', NULL),
(6000102, 4000102, 10001, 'TENANT', NULL)
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id), scope_type = VALUES(scope_type), scope_expression = NULL;
-- ============================================================================
-- 来源:iam\src\main\resources\db\phase1-onboarding.sql
-- ============================================================================
-- Phase-one merchant onboarding state migration.
USE baiyue_iam;
SET NAMES utf8mb4;
ALTER TABLE iam_tenant MODIFY COLUMN status VARCHAR(20) NOT NULL DEFAULT 'PROFILE_REQUIRED'
COMMENT 'PROFILE_REQUIRED, PENDING, ACTIVE, REJECTED or DISABLED';
DROP PROCEDURE IF EXISTS phase1_iam_add_column;
DELIMITER $$
CREATE PROCEDURE phase1_iam_add_column(
IN p_table_name VARCHAR(64), IN p_column_name VARCHAR(64), IN p_definition VARCHAR(1000)
)
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = p_table_name AND COLUMN_NAME = p_column_name
) THEN
SET @phase1_iam_sql = CONCAT('ALTER TABLE `', p_table_name, '` ADD COLUMN `',
p_column_name, '` ', p_definition);
PREPARE phase1_iam_stmt FROM @phase1_iam_sql;
EXECUTE phase1_iam_stmt;
DEALLOCATE PREPARE phase1_iam_stmt;
END IF;
END$$
DELIMITER ;
CALL phase1_iam_add_column('iam_tenant', 'approved_at', "DATETIME(3) NULL COMMENT 'Approval time'");
CALL phase1_iam_add_column('iam_tenant', 'approved_by', "BIGINT NULL COMMENT 'Reviewer ID'");
CALL phase1_iam_add_column('iam_tenant', 'reject_reason', "VARCHAR(500) NULL COMMENT 'Rejection reason'");
CALL phase1_iam_add_column('iam_tenant', 'legal_representative', "VARCHAR(64) NULL COMMENT '法定代表人' AFTER legal_name");
CALL phase1_iam_add_column('iam_tenant_qualification', 'review_comment', "VARCHAR(500) NULL COMMENT 'Review comment'");
CALL phase1_iam_add_column('iam_membership', 'created_by', "BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT 'Creator ID'");
CALL phase1_iam_add_column('iam_membership', 'updated_by', "BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT 'Updater ID'");
DROP PROCEDURE phase1_iam_add_column;
-- A global merchant owner template is assigned to newly registered enterprises.
INSERT INTO iam_role (
id, tenant_id, app_code, role_code, role_name, role_type, status,
description, created_by, updated_by, deleted, version
) VALUES (
4000100, 0, 'MERCHANT', 'MERCHANT_OWNER', '企业管理员', 'SYSTEM', 'ACTIVE',
'一期企业全部菜单和操作按钮模板', 1000001, 1000001, 0, 0
)
ON DUPLICATE KEY UPDATE status='ACTIVE', deleted=0;
-- Clone the一期 merchant menu/button template into the global scope.
INSERT INTO iam_permission (
id, tenant_id, app_code, permission_type, parent_id, permission_code,
permission_name, route_path, http_method, sort_no, status, deleted
)
SELECT id + 100000, 0, app_code, permission_type,
CASE WHEN parent_id = 0 THEN 0 ELSE parent_id + 100000 END,
permission_code, permission_name, route_path, NULL, sort_no, 'ACTIVE', 0
FROM iam_permission
WHERE tenant_id = 10001 AND app_code = 'MERCHANT'
AND permission_type IN ('MENU', 'BUTTON')
ON DUPLICATE KEY UPDATE
permission_name=VALUES(permission_name), route_path=VALUES(route_path),
parent_id=VALUES(parent_id), sort_no=VALUES(sort_no), status='ACTIVE', deleted=0;
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
SELECT owner_role.id, permission.id, 0
FROM iam_role owner_role
JOIN iam_permission permission
ON permission.app_code='MERCHANT' AND permission.tenant_id=0
AND permission.permission_type IN ('MENU','BUTTON')
AND permission.status='ACTIVE' AND permission.deleted=0
WHERE owner_role.tenant_id=0 AND owner_role.app_code='MERCHANT'
AND owner_role.role_code='MERCHANT_OWNER'
AND owner_role.status='ACTIVE' AND owner_role.deleted=0
ON DUPLICATE KEY UPDATE tenant_id=VALUES(tenant_id);
-- 不再写入伪造的资质 URL。资质必须先由 File 服务保存真实图片,再由 IAM 记录文件地址和审核结果。
-- 已部署环境中的 /phase1-seed/ 历史记录按 docs/企业资质文件迁移说明.md 处理,本脚本不得将其改成不存在的 UUID。
-- ============================================================================
-- 来源:iam\src\main\resources\db\phase1-member-permissions.sql
-- ============================================================================
SET NAMES utf8mb4;
-- 企业成员管理按钮权限增量;部署后应将权限按需授予企业管理员角色。
USE baiyue_iam;
-- 为存量数据库补齐企业内成员编号唯一约束,防止并发重复创建。
SET @member_no_index_sql = IF(
EXISTS(SELECT 1 FROM information_schema.statistics
WHERE table_schema = DATABASE() AND table_name = 'iam_membership'
AND index_name = 'uk_membership_tenant_no'),
'SELECT 1',
'ALTER TABLE iam_membership ADD UNIQUE KEY uk_membership_tenant_no (tenant_id, member_no)'
);
PREPARE member_no_index_stmt FROM @member_no_index_sql;
EXECUTE member_no_index_stmt;
DEALLOCATE PREPARE member_no_index_stmt;
INSERT INTO iam_permission
(id, tenant_id, app_code, permission_type, parent_id, permission_code, permission_name, sort_no, status, deleted)
VALUES
(5200023, 10001, 'MERCHANT', 'BUTTON', 5200020, 'merchant:member:edit', '编辑成员', 30, 'ACTIVE', 0),
(5200024, 10001, 'MERCHANT', 'BUTTON', 5200020, 'merchant:member:status', '启停成员', 40, 'ACTIVE', 0),
(5200025, 10001, 'MERCHANT', 'BUTTON', 5200020, 'merchant:member:reset-password', '重置成员密码', 50, 'ACTIVE', 0),
(5300023, 0, 'MERCHANT', 'BUTTON', 5300020, 'merchant:member:edit', '编辑成员', 30, 'ACTIVE', 0),
(5300024, 0, 'MERCHANT', 'BUTTON', 5300020, 'merchant:member:status', '启停成员', 40, 'ACTIVE', 0),
(5300025, 0, 'MERCHANT', 'BUTTON', 5300020, 'merchant:member:reset-password', '重置成员密码', 50, 'ACTIVE', 0)
ON DUPLICATE KEY UPDATE permission_name = VALUES(permission_name), status = VALUES(status), deleted = 0;
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
SELECT 4000101, id, 10001 FROM iam_permission
WHERE tenant_id = 10001 AND permission_code IN
('merchant:member:edit', 'merchant:member:status', 'merchant:member:reset-password')
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
SELECT 4000100, id, 0 FROM iam_permission
WHERE tenant_id = 0 AND permission_code IN
('merchant:member:edit', 'merchant:member:status', 'merchant:member:reset-password')
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
-- ============================================================================
-- 来源:iam\src\main\resources\db\rbac-role-management-v2.sql
-- ============================================================================
SET NAMES utf8mb4;
-- 角色管理闭环权限迁移,可重复执行。
USE baiyue_iam;
INSERT INTO iam_permission
(id, tenant_id, app_code, permission_type, parent_id, permission_code,
permission_name, sort_no, status, deleted)
VALUES
(5100011, 0, 'PLATFORM', 'BUTTON', 5100010, 'platform:customer:create', '新增企业客户', 10, 'ACTIVE', 0),
(5101023, 0, 'PLATFORM', 'BUTTON', 5101020, 'platform:role:edit', '编辑角色', 30, 'ACTIVE', 0),
(5101024, 0, 'PLATFORM', 'BUTTON', 5101020, 'platform:role:status', '启停角色', 40, 'ACTIVE', 0),
(5101025, 0, 'PLATFORM', 'BUTTON', 5101020, 'platform:role:delete', '删除角色', 50, 'ACTIVE', 0),
(5101040, 0, 'PLATFORM', 'MENU', 5101000, 'platform:member:list', '平台成员', 40, 'ACTIVE', 0),
(5101041, 0, 'PLATFORM', 'BUTTON', 5101040, 'platform:member:grant-role', '分配成员角色', 10, 'ACTIVE', 0),
(5101012, 0, 'PLATFORM', 'BUTTON', 5101010, 'platform:user:edit', '编辑用户', 20, 'ACTIVE', 0),
(5101013, 0, 'PLATFORM', 'BUTTON', 5101010, 'platform:user:status', '启停用户', 30, 'ACTIVE', 0),
(5101014, 0, 'PLATFORM', 'BUTTON', 5101010, 'platform:user:reset-password', '重置密码', 40, 'ACTIVE', 0),
(5101015, 0, 'PLATFORM', 'BUTTON', 5101010, 'platform:user:grant-role', '分配内部用户角色', 50, 'ACTIVE', 0),
(5200033, 10001, 'MERCHANT', 'BUTTON', 5200030, 'merchant:role:edit', '编辑角色', 30, 'ACTIVE', 0),
(5200034, 10001, 'MERCHANT', 'BUTTON', 5200030, 'merchant:role:status', '启停角色', 40, 'ACTIVE', 0),
(5200035, 10001, 'MERCHANT', 'BUTTON', 5200030, 'merchant:role:delete', '删除角色', 50, 'ACTIVE', 0),
(5300033, 0, 'MERCHANT', 'BUTTON', 5300030, 'merchant:role:edit', '编辑角色', 30, 'ACTIVE', 0),
(5300034, 0, 'MERCHANT', 'BUTTON', 5300030, 'merchant:role:status', '启停角色', 40, 'ACTIVE', 0),
(5300035, 0, 'MERCHANT', 'BUTTON', 5300030, 'merchant:role:delete', '删除角色', 50, 'ACTIVE', 0)
ON DUPLICATE KEY UPDATE
permission_type = VALUES(permission_type), parent_id = VALUES(parent_id),
permission_name = VALUES(permission_name), sort_no = VALUES(sort_no),
status = 'ACTIVE', deleted = 0;
-- 示例管理员获得新增权限;其他角色仍由管理员按需授权。
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
SELECT 4000001, id, 0 FROM iam_permission
WHERE tenant_id = 0 AND app_code = 'PLATFORM'
AND permission_code IN ('platform:role:edit', 'platform:role:status', 'platform:role:delete',
'platform:member:list', 'platform:member:grant-role', 'platform:customer:create',
'platform:user:edit', 'platform:user:status', 'platform:user:reset-password',
'platform:user:grant-role')
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
SELECT 4000101, id, 10001 FROM iam_permission
WHERE tenant_id = 10001 AND app_code = 'MERCHANT'
AND permission_code IN ('merchant:role:edit', 'merchant:role:status', 'merchant:role:delete')
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
SELECT 4000100, id, 0 FROM iam_permission
WHERE tenant_id = 0 AND app_code = 'MERCHANT'
AND permission_code IN ('merchant:role:edit', 'merchant:role:status', 'merchant:role:delete')
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
-- ============================================================================
-- 来源:iam\src\main\resources\db\phase1-platform-master-data-and-permission-management.sql
-- ============================================================================
-- 权限资源维护与平台主数据权限迁移。
-- 在 phase1-admin-rbac.sql、rbac-role-management-v2.sql 之后执行,可重复执行。
USE baiyue_iam;
-- 兼容不支持 ADD COLUMN IF NOT EXISTS 的 MySQL 版本,通过元数据判断后执行。
SET @iam_permission_built_in_sql = IF(
EXISTS(SELECT 1 FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = 'iam_permission' AND column_name = 'built_in'),
'SELECT 1',
"ALTER TABLE iam_permission ADD COLUMN built_in TINYINT NOT NULL DEFAULT 0 COMMENT '是否为系统预置权限;预置资源不可通过管理接口修改' AFTER status"
);
PREPARE iam_permission_built_in_stmt FROM @iam_permission_built_in_sql;
EXECUTE iam_permission_built_in_stmt;
DEALLOCATE PREPARE iam_permission_built_in_stmt;
-- 一期固定编号范围内的资源均为种子资源,运行期生成的雪花编号不作预置标记。
UPDATE iam_permission
SET built_in = 1
WHERE id BETWEEN 5000000 AND 5399999 AND built_in = 0;
INSERT INTO iam_permission
(id, tenant_id, app_code, permission_type, parent_id, permission_code, permission_name,
route_path, sort_no, status, built_in, deleted)
VALUES
(5103040, 0, 'PLATFORM', 'MENU', 5103000, 'platform:download:list', '下载中心',
'/downloads', 40, 'ACTIVE', 1, 0)
ON DUPLICATE KEY UPDATE
permission_name = VALUES(permission_name), route_path = VALUES(route_path), sort_no = VALUES(sort_no),
status = 'ACTIVE', built_in = 1, deleted = 0;
INSERT INTO iam_permission
(id, tenant_id, app_code, permission_type, parent_id, permission_code, permission_name,
sort_no, status, built_in, deleted)
VALUES
(5100014, 0, 'PLATFORM', 'BUTTON', 5100010, 'platform:customer:view', '查看企业客户档案', 15, 'ACTIVE', 1, 0),
(5100012, 0, 'PLATFORM', 'BUTTON', 5100010, 'platform:customer:edit', '编辑企业客户', 20, 'ACTIVE', 1, 0),
(5100013, 0, 'PLATFORM', 'BUTTON', 5100010, 'platform:customer:status', '启停企业客户', 30, 'ACTIVE', 1, 0),
(5100015, 0, 'PLATFORM', 'BUTTON', 5100010, 'platform:customer:export', '导出企业客户', 40, 'ACTIVE', 1, 0),
(5100016, 0, 'PLATFORM', 'BUTTON', 5100010, 'platform:customer:reset-password', '重置客户管理员密码', 50, 'ACTIVE', 1, 0),
(5100017, 0, 'PLATFORM', 'BUTTON', 5100010, 'platform:customer:delete', '归档企业客户', 60, 'ACTIVE', 1, 0),
(5100018, 0, 'PLATFORM', 'BUTTON', 5100010, 'platform:customer:impersonate', '跳转客户登录', 70, 'ACTIVE', 1, 0),
(5101015, 0, 'PLATFORM', 'BUTTON', 5101010, 'platform:user:grant-role', '分配内部用户角色', 50, 'ACTIVE', 1, 0),
(5101032, 0, 'PLATFORM', 'BUTTON', 5101030, 'platform:permission:edit', '编辑权限资源', 20, 'ACTIVE', 1, 0),
(5101033, 0, 'PLATFORM', 'BUTTON', 5101030, 'platform:permission:status', '启停权限资源', 30, 'ACTIVE', 1, 0),
(5101034, 0, 'PLATFORM', 'BUTTON', 5101030, 'platform:permission:delete', '删除权限资源', 40, 'ACTIVE', 1, 0),
(5103021, 0, 'PLATFORM', 'BUTTON', 5103020, 'platform:express:create', '新增快递公司', 10, 'ACTIVE', 1, 0),
(5103022, 0, 'PLATFORM', 'BUTTON', 5103020, 'platform:express:edit', '编辑快递公司', 20, 'ACTIVE', 1, 0),
(5103023, 0, 'PLATFORM', 'BUTTON', 5103020, 'platform:express:status', '启停快递公司', 30, 'ACTIVE', 1, 0),
(5103024, 0, 'PLATFORM', 'BUTTON', 5103020, 'platform:express:import', '批量导入快递公司', 40, 'ACTIVE', 1, 0),
(5103031, 0, 'PLATFORM', 'BUTTON', 5103030, 'platform:counterparty:create', '新增往来单位', 10, 'ACTIVE', 1, 0),
(5103032, 0, 'PLATFORM', 'BUTTON', 5103030, 'platform:counterparty:edit', '编辑往来单位', 20, 'ACTIVE', 1, 0),
(5103033, 0, 'PLATFORM', 'BUTTON', 5103030, 'platform:counterparty:status', '启停往来单位', 30, 'ACTIVE', 1, 0),
(5103041, 0, 'PLATFORM', 'BUTTON', 5103040, 'platform:download:all', '查看全部下载任务', 10, 'ACTIVE', 1, 0)
ON DUPLICATE KEY UPDATE
permission_name = VALUES(permission_name), 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, role.tenant_id
FROM iam_role role
JOIN iam_permission permission ON permission.tenant_id = 0 AND permission.app_code = 'PLATFORM'
AND permission.permission_code IN (
'platform:customer:edit', 'platform:customer:status', 'platform:customer:reset-password',
'platform:customer:delete', 'platform:customer:impersonate', 'platform:user:grant-role',
'platform:permission:edit', 'platform:permission:status', 'platform:permission:delete',
'platform:express:list', 'platform:express:create', 'platform:express:edit', 'platform:express:status', 'platform:express:import',
'platform:counterparty:list', 'platform:counterparty:create', 'platform:counterparty:edit', 'platform:counterparty:status',
'platform:customer:export', 'platform:download:list', 'platform:download:all'
)
WHERE role.tenant_id = 0 AND role.role_code IN ('PLATFORM_ADMIN', 'ORDER_OPERATOR');
-- 已有客户运营角色继续具备客户导出与本人下载任务查询能力,但不授予查看全部任务。
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
SELECT DISTINCT existing_grant.role_id, added_permission.id, existing_grant.tenant_id
FROM iam_role_permission existing_grant
JOIN iam_permission existing_permission ON existing_permission.id = existing_grant.permission_id
JOIN iam_permission added_permission
ON added_permission.tenant_id = 0 AND added_permission.app_code = 'PLATFORM'
AND added_permission.permission_code IN ('platform:customer:export', 'platform:download:list')
WHERE existing_permission.tenant_id = 0
AND existing_permission.app_code = 'PLATFORM'
AND existing_permission.permission_code IN ('platform:customer:list', 'platform:customer:review')
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
-- 详情查看从列表和审核权限中拆分;升级时保留既有运营角色的查询能力。
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
SELECT DISTINCT existing_grant.role_id, view_permission.id, existing_grant.tenant_id
FROM iam_role_permission existing_grant
JOIN iam_permission existing_permission
ON existing_permission.id = existing_grant.permission_id
JOIN iam_permission view_permission
ON view_permission.tenant_id = 0
AND view_permission.app_code = 'PLATFORM'
AND view_permission.permission_code = 'platform:customer:view'
WHERE existing_permission.tenant_id = 0
AND existing_permission.app_code = 'PLATFORM'
AND existing_permission.permission_code IN ('platform:customer:list', 'platform:customer:review')
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
-- ============================================================================
-- 来源:iam\src\main\resources\db\phase1-customer-profile.sql
-- ============================================================================
-- 一期平台客户档案字段增量迁移。可在同一数据库重复执行。
USE baiyue_iam;
SET NAMES utf8mb4;
SET @ddl = IF(
EXISTS(SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'iam_tenant' AND column_name = 'contact_wechat'),
'SELECT 1', 'ALTER TABLE iam_tenant ADD COLUMN contact_wechat VARCHAR(64) NULL COMMENT ''联系微信'' AFTER contact_email');
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @ddl = IF(
EXISTS(SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'iam_tenant' AND column_name = 'sales_owner_name'),
'SELECT 1', 'ALTER TABLE iam_tenant ADD COLUMN sales_owner_name VARCHAR(64) NULL COMMENT ''销售负责人姓名'' AFTER contact_wechat');
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @ddl = IF(
EXISTS(SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'iam_tenant' AND column_name = 'payment_term_days'),
'SELECT 1', 'ALTER TABLE iam_tenant ADD COLUMN payment_term_days SMALLINT UNSIGNED NULL COMMENT ''账期天数'' AFTER sales_owner_name');
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @ddl = IF(
EXISTS(SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'iam_tenant' AND column_name = 'enterprise_type'),
'SELECT 1', 'ALTER TABLE iam_tenant ADD COLUMN enterprise_type VARCHAR(64) NULL COMMENT ''企业类型'' AFTER payment_term_days');
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @ddl = IF(
EXISTS(SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'iam_tenant' AND column_name = 'established_date'),
'SELECT 1', 'ALTER TABLE iam_tenant ADD COLUMN established_date DATE NULL COMMENT ''成立日期'' AFTER enterprise_type');
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @ddl = IF(
EXISTS(SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'iam_tenant' AND column_name = 'registered_capital'),
'SELECT 1', 'ALTER TABLE iam_tenant ADD COLUMN registered_capital DECIMAL(18,2) NULL COMMENT ''注册资本'' AFTER established_date');
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @ddl = IF(
EXISTS(SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'iam_tenant' AND column_name = 'registered_capital_currency'),
'SELECT 1', 'ALTER TABLE iam_tenant ADD COLUMN registered_capital_currency CHAR(3) NULL COMMENT ''注册资本币种 ISO 4217'' AFTER registered_capital');
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @ddl = IF(
EXISTS(SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'iam_tenant' AND column_name = 'registered_address'),
'SELECT 1', 'ALTER TABLE iam_tenant ADD COLUMN registered_address VARCHAR(500) NULL COMMENT ''注册地址文本'' AFTER registered_capital_currency');
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @ddl = IF(
EXISTS(SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'iam_tenant' AND column_name = 'internal_remark'),
'SELECT 1', 'ALTER TABLE iam_tenant ADD COLUMN internal_remark VARCHAR(1000) NULL COMMENT ''平台内部备注'' AFTER registered_address');
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-- ============================================================================
-- 来源:order\src\main\resources\db\phase1-order-extension.sql
-- ============================================================================
-- Phase-one first-leg order fields. Safe to run repeatedly on MySQL 8.
USE baiyue_order;
SET NAMES utf8mb4;
DROP PROCEDURE IF EXISTS phase1_add_column;
DELIMITER $$
CREATE PROCEDURE phase1_add_column(
IN p_table_name VARCHAR(64),
IN p_column_name VARCHAR(64),
IN p_definition VARCHAR(1000)
)
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = p_table_name
AND COLUMN_NAME = p_column_name
) THEN
SET @phase1_sql = CONCAT('ALTER TABLE `', p_table_name, '` ADD COLUMN `',
p_column_name, '` ', p_definition);
PREPARE phase1_stmt FROM @phase1_sql;
EXECUTE phase1_stmt;
DEALLOCATE PREPARE phase1_stmt;
END IF;
END$$
DELIMITER ;
CALL phase1_add_column('ord_order', 'pickup_method', "VARCHAR(24) NULL COMMENT 'Pickup method'");
CALL phase1_add_column('ord_order', 'receiving_warehouse_id', "BIGINT UNSIGNED NULL COMMENT 'Domestic receiving warehouse ID'");
CALL phase1_add_column('ord_order', 'receiving_warehouse_name', "VARCHAR(128) NULL COMMENT 'Domestic receiving warehouse snapshot'");
CALL phase1_add_column('ord_order', 'receiving_warehouse_address', "VARCHAR(512) NULL COMMENT '国内收货仓完整地址快照'");
CALL phase1_add_column('ord_order', 'receiving_time', "VARCHAR(128) NULL COMMENT '国内收货仓收货时间快照'");
CALL phase1_add_column('ord_order', 'channel_code', "VARCHAR(64) NULL COMMENT 'Shipping channel code'");
CALL phase1_add_column('ord_order', 'channel_name', "VARCHAR(128) NULL COMMENT 'Shipping channel snapshot'");
CALL phase1_add_column('ord_order', 'tax_mode', "VARCHAR(24) NULL COMMENT 'BUY_DECLARATION or TAX_REFUND'");
CALL phase1_add_column('ord_order', 'container_mode', "VARCHAR(24) NULL COMMENT 'FULL_CONTAINER or LCL'");
CALL phase1_add_column('ord_order', 'customs_inspection', "TINYINT(1) NULL COMMENT 'Whether customs inspection is required'");
CALL phase1_add_column('ord_order', 'destination_type', "VARCHAR(24) NULL COMMENT 'FBA, OVERSEAS or PRIVATE'");
CALL phase1_add_column('ord_order', 'amazon_warehouse_code', "VARCHAR(32) NULL COMMENT 'Amazon warehouse code'");
CALL phase1_add_column('ord_order', 'shipment_id', "VARCHAR(64) NULL COMMENT 'Amazon Shipment ID'");
CALL phase1_add_column('ord_order', 'reference_id', "VARCHAR(64) NULL COMMENT 'Amazon Reference ID'");
CALL phase1_add_column('ord_order', 'express_company', "VARCHAR(64) NULL COMMENT '履约快递公司名称快照'");
CALL phase1_add_column('ord_order', 'express_tracking_no', "VARCHAR(128) NULL COMMENT '履约快递单号'");
CALL phase1_add_column('ord_order_cargo', 'sku_no', "VARCHAR(64) NULL COMMENT 'SKU number'");
CALL phase1_add_column('ord_order_cargo', 'upc_no', "VARCHAR(64) NULL COMMENT 'UPC number'");
CALL phase1_add_column('ord_order_cargo', 'cargo_name_en', "VARCHAR(128) NULL COMMENT 'English cargo name'");
CALL phase1_add_column('ord_order_cargo', 'customs_hs_code', "VARCHAR(20) NULL COMMENT 'Destination customs HS code'");
CALL phase1_add_column('ord_order_cargo', 'units_per_package', "INT UNSIGNED NULL COMMENT 'Units per carton'");
CALL phase1_add_column('ord_order_cargo', 'total_quantity', "INT UNSIGNED NULL COMMENT 'Total units'");
CALL phase1_add_column('ord_order_cargo', 'net_weight_kg', "DECIMAL(18,3) NULL COMMENT 'Net weight in kilograms'");
CALL phase1_add_column('ord_order_cargo', 'unit_declared_value', "DECIMAL(18,2) NULL COMMENT 'Declared value per unit'");
CALL phase1_add_column('ord_order_cargo', 'material', "VARCHAR(128) NULL COMMENT 'Material'");
CALL phase1_add_column('ord_order_cargo', 'product_usage', "VARCHAR(128) NULL COMMENT 'Usage'");
CALL phase1_add_column('ord_order_cargo', 'model', "VARCHAR(64) NULL COMMENT 'Model'");
CALL phase1_add_column('ord_order_cargo', 'brand', "VARCHAR(64) NULL COMMENT 'Brand'");
CALL phase1_add_column('ord_order_cargo', 'product_length_cm', "DECIMAL(12,2) NULL COMMENT 'Product length in centimeters'");
CALL phase1_add_column('ord_order_cargo', 'product_width_cm', "DECIMAL(12,2) NULL COMMENT 'Product width in centimeters'");
CALL phase1_add_column('ord_order_cargo', 'product_height_cm', "DECIMAL(12,2) NULL COMMENT 'Product height in centimeters'");
CALL phase1_add_column('ord_order_cargo', 'shipment_id', "VARCHAR(64) NULL COMMENT 'Cargo Amazon Shipment ID'");
CALL phase1_add_column('ord_order_cargo', 'reference_id', "VARCHAR(64) NULL COMMENT 'Cargo Amazon Reference ID'");
CALL phase1_add_column('ord_order_cargo', 'product_image_file_id', "BIGINT UNSIGNED NULL COMMENT 'Product image logical file ID'");
UPDATE ord_order_cargo cargo
JOIN ord_order orders ON orders.id = cargo.order_id AND orders.tenant_id = cargo.tenant_id
SET cargo.shipment_id = COALESCE(NULLIF(cargo.shipment_id, ''), orders.shipment_id),
cargo.reference_id = COALESCE(NULLIF(cargo.reference_id, ''), orders.reference_id)
WHERE orders.destination_type = 'FBA'
AND ((cargo.shipment_id IS NULL OR cargo.shipment_id = '')
OR (cargo.reference_id IS NULL OR cargo.reference_id = ''));
DROP PROCEDURE phase1_add_column;
-- 已有订单库的幂等记录迁移,和订单写入处于同一个本地事务。
CREATE TABLE IF NOT EXISTS ord_idempotency_record (
id BIGINT UNSIGNED NOT NULL COMMENT '幂等记录 ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
operator_id BIGINT UNSIGNED NOT NULL COMMENT '发起写操作的用户ID',
operation VARCHAR(32) NOT NULL COMMENT '幂等操作类型',
resource_id BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '操作目标资源ID;创建前未知时为0',
idempotency_key VARCHAR(128) NOT NULL COMMENT '客户端幂等键',
request_hash CHAR(64) NOT NULL COMMENT '规范化请求内容SHA-256摘要,用于识别同键不同请求',
status VARCHAR(16) NOT NULL COMMENT '处理状态:PROCESSING处理中、COMPLETED已完成',
result_order_id BIGINT UNSIGNED NULL COMMENT '成功操作生成或更新的订单ID,用于幂等重放',
completed_at DATETIME(3) NULL COMMENT '处理完成时间',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
PRIMARY KEY (id),
UNIQUE KEY uk_order_idempotency_scope (tenant_id, operator_id, operation, idempotency_key),
KEY idx_order_idempotency_created (created_at)
) ENGINE=InnoDB COMMENT='订单写接口持久化幂等记录';
-- ============================================================================
-- 来源:order\src\main\resources\db\phase1-masterdata-extension.sql
-- ============================================================================
SET NAMES utf8mb4;
USE baiyue_order;
-- MySQL 8 可重复执行的订单快照及履约字段迁移。
SET @schema_name = DATABASE();
SET @sql = IF((SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@schema_name AND TABLE_NAME='ord_order' AND COLUMN_NAME='receiving_warehouse_address')=0,
'ALTER TABLE ord_order ADD COLUMN receiving_warehouse_address VARCHAR(600) NULL COMMENT ''收货仓地址历史快照'' AFTER receiving_warehouse_name','SELECT 1');
PREPARE s FROM @sql; EXECUTE s; DEALLOCATE PREPARE s;
SET @sql = IF((SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@schema_name AND TABLE_NAME='ord_order' AND COLUMN_NAME='receiving_time')=0,
'ALTER TABLE ord_order ADD COLUMN receiving_time VARCHAR(100) NULL COMMENT ''收货时间历史快照'' AFTER receiving_warehouse_address','SELECT 1');
PREPARE s FROM @sql; EXECUTE s; DEALLOCATE PREPARE s;
SET @sql = IF((SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@schema_name AND TABLE_NAME='ord_order' AND COLUMN_NAME='express_company')=0,
'ALTER TABLE ord_order ADD COLUMN express_company VARCHAR(64) NULL COMMENT ''履约快递公司'' AFTER reference_id','SELECT 1');
PREPARE s FROM @sql; EXECUTE s; DEALLOCATE PREPARE s;
SET @sql = IF((SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@schema_name AND TABLE_NAME='ord_order' AND COLUMN_NAME='express_tracking_no')=0,
'ALTER TABLE ord_order ADD COLUMN express_tracking_no VARCHAR(128) NULL COMMENT ''履约快递单号'' AFTER express_company','SELECT 1');
PREPARE s FROM @sql; EXECUTE s; DEALLOCATE PREPARE s;
-- ============================================================================
-- 来源:warehouse\src\main\resources\db\phase1-warehouse-schema-migration.sql
-- ============================================================================
-- 既有 baiyue_warehouse 的一期增量迁移(只增不删,可重复执行)。
-- 执行顺序:warehouse.sql、warehouse-partner.sql、warehouse-express-company.sql、
-- 本文件、warehouse-seed.sql。应用不会在启动时自动执行这些脚本,必须由发布流程执行。
SET NAMES utf8mb4;
USE baiyue_warehouse;
-- MySQL 5.7/8.0 各小版本对条件补列语法的支持不一致。
-- 统一先查 information_schema,再通过动态 SQL 补列,确保脚本可重复执行。
DROP PROCEDURE IF EXISTS add_column_if_missing;
DELIMITER $$
CREATE PROCEDURE add_column_if_missing(
IN p_table_name VARCHAR(64),
IN p_column_name VARCHAR(64),
IN p_column_definition TEXT
)
BEGIN
IF NOT EXISTS (
SELECT 1
FROM information_schema.columns
WHERE table_schema = DATABASE()
AND table_name = p_table_name
AND column_name = p_column_name
) THEN
SET @add_column_sql = CONCAT(
'ALTER TABLE `', REPLACE(p_table_name, '`', '``'),
'` ADD COLUMN `', REPLACE(p_column_name, '`', '``'),
'` ', p_column_definition
);
PREPARE add_column_statement FROM @add_column_sql;
EXECUTE add_column_statement;
DEALLOCATE PREPARE add_column_statement;
END IF;
END$$
DELIMITER ;
-- 早期 wh_warehouse 缺少的业务与审计字段。
CALL add_column_if_missing('wh_warehouse', 'warehouse_type', 'VARCHAR(32) NOT NULL DEFAULT ''DOMESTIC'' COMMENT ''仓库类型'' AFTER warehouse_name');
CALL add_column_if_missing('wh_warehouse', 'country_code', 'CHAR(2) NOT NULL DEFAULT ''CN'' COMMENT ''所在国家二字码'' AFTER warehouse_type');
CALL add_column_if_missing('wh_warehouse', 'timezone', 'VARCHAR(64) NOT NULL DEFAULT ''Asia/Shanghai'' COMMENT ''IANA时区'' AFTER country_code');
CALL add_column_if_missing('wh_warehouse', 'contact_name', 'VARCHAR(64) NULL AFTER timezone');
CALL add_column_if_missing('wh_warehouse', 'contact_phone', 'VARCHAR(32) NULL AFTER contact_name');
CALL add_column_if_missing('wh_warehouse', 'contact_email', 'VARCHAR(128) NULL AFTER contact_phone');
CALL add_column_if_missing('wh_warehouse', 'status', 'VARCHAR(20) NOT NULL DEFAULT ''ACTIVE'' AFTER contact_email');
CALL add_column_if_missing('wh_warehouse', 'remark', 'VARCHAR(500) NULL AFTER status');
CALL add_column_if_missing('wh_warehouse', 'created_by', 'BIGINT UNSIGNED NULL');
CALL add_column_if_missing('wh_warehouse', 'created_at', 'DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)');
CALL add_column_if_missing('wh_warehouse', 'updated_by', 'BIGINT UNSIGNED NULL');
CALL add_column_if_missing('wh_warehouse', 'updated_at', 'DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)');
CALL add_column_if_missing('wh_warehouse', 'deleted', 'TINYINT(1) NOT NULL DEFAULT 0');
CALL add_column_if_missing('wh_warehouse', 'version', 'INT NOT NULL DEFAULT 0');
CALL add_column_if_missing('wh_warehouse_address', 'tenant_id', 'BIGINT UNSIGNED NOT NULL DEFAULT 0 AFTER id');
CALL add_column_if_missing('wh_warehouse_address', 'warehouse_id', 'BIGINT UNSIGNED NOT NULL DEFAULT 0 AFTER tenant_id');
CALL add_column_if_missing('wh_warehouse_address', 'address_type', 'VARCHAR(20) NOT NULL DEFAULT ''OPERATING'' AFTER warehouse_id');
CALL add_column_if_missing('wh_warehouse_address', 'country_code', 'CHAR(2) NOT NULL DEFAULT ''CN'' AFTER address_type');
CALL add_column_if_missing('wh_warehouse_address', 'state_province', 'VARCHAR(100) NULL');
CALL add_column_if_missing('wh_warehouse_address', 'city', 'VARCHAR(100) NOT NULL DEFAULT '''' AFTER state_province');
CALL add_column_if_missing('wh_warehouse_address', 'district', 'VARCHAR(100) NULL');
CALL add_column_if_missing('wh_warehouse_address', 'postal_code', 'VARCHAR(20) NULL');
CALL add_column_if_missing('wh_warehouse_address', 'address_line1', 'VARCHAR(255) NOT NULL DEFAULT '''' AFTER postal_code');
CALL add_column_if_missing('wh_warehouse_address', 'address_line2', 'VARCHAR(255) NULL');
CALL add_column_if_missing('wh_warehouse_address', 'latitude', 'DECIMAL(10,7) NULL');
CALL add_column_if_missing('wh_warehouse_address', 'longitude', 'DECIMAL(10,7) NULL');
CALL add_column_if_missing('wh_warehouse_address', 'created_by', 'BIGINT UNSIGNED NULL');
CALL add_column_if_missing('wh_warehouse_address', 'created_at', 'DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)');
CALL add_column_if_missing('wh_warehouse_address', 'updated_by', 'BIGINT UNSIGNED NULL');
CALL add_column_if_missing('wh_warehouse_address', 'updated_at', 'DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)');
CALL add_column_if_missing('wh_warehouse_address', 'deleted', 'TINYINT(1) NOT NULL DEFAULT 0');
CALL add_column_if_missing('wh_warehouse_address', 'version', 'INT NOT NULL DEFAULT 0');
CALL add_column_if_missing('wh_service_capability', 'tenant_id', 'BIGINT UNSIGNED NOT NULL DEFAULT 0 AFTER id');
CALL add_column_if_missing('wh_service_capability', 'warehouse_id', 'BIGINT UNSIGNED NOT NULL DEFAULT 0 AFTER tenant_id');
CALL add_column_if_missing('wh_service_capability', 'capability_code', 'VARCHAR(50) NOT NULL DEFAULT '''' AFTER warehouse_id');
CALL add_column_if_missing('wh_service_capability', 'capability_name', 'VARCHAR(100) NOT NULL DEFAULT '''' AFTER capability_code');
CALL add_column_if_missing('wh_service_capability', 'enabled', 'TINYINT(1) NOT NULL DEFAULT 1');
CALL add_column_if_missing('wh_service_capability', 'cutoff_time', 'TIME NULL');
CALL add_column_if_missing('wh_service_capability', 'description', 'VARCHAR(500) NULL');
CALL add_column_if_missing('wh_service_capability', 'created_by', 'BIGINT UNSIGNED NULL');
CALL add_column_if_missing('wh_service_capability', 'created_at', 'DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)');
CALL add_column_if_missing('wh_service_capability', 'updated_by', 'BIGINT UNSIGNED NULL');
CALL add_column_if_missing('wh_service_capability', 'updated_at', 'DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)');
CALL add_column_if_missing('wh_service_capability', 'deleted', 'TINYINT(1) NOT NULL DEFAULT 0');
CALL add_column_if_missing('wh_service_capability', 'version', 'INT NOT NULL DEFAULT 0');
CALL add_column_if_missing('wh_partner', 'tenant_id', 'BIGINT UNSIGNED NOT NULL DEFAULT 0 AFTER id');
CALL add_column_if_missing('wh_partner', 'partner_code', 'VARCHAR(50) NOT NULL DEFAULT '''' AFTER tenant_id');
CALL add_column_if_missing('wh_partner', 'partner_name', 'VARCHAR(128) NOT NULL DEFAULT '''' AFTER partner_code');
CALL add_column_if_missing('wh_partner', 'partner_type', 'VARCHAR(20) NOT NULL DEFAULT ''CUSTOMER'' AFTER partner_name');
CALL add_column_if_missing('wh_partner', 'contact_name', 'VARCHAR(64) NOT NULL DEFAULT '''' AFTER partner_type');
CALL add_column_if_missing('wh_partner', 'contact_phone', 'VARCHAR(32) NOT NULL DEFAULT '''' AFTER contact_name');
CALL add_column_if_missing('wh_partner', 'contact_email', 'VARCHAR(128) NULL');
CALL add_column_if_missing('wh_partner', 'country_code', 'CHAR(2) NOT NULL DEFAULT ''CN'' AFTER contact_email');
CALL add_column_if_missing('wh_partner', 'province', 'VARCHAR(64) NULL');
CALL add_column_if_missing('wh_partner', 'city', 'VARCHAR(64) NOT NULL DEFAULT '''' AFTER province');
CALL add_column_if_missing('wh_partner', 'district', 'VARCHAR(64) NULL');
CALL add_column_if_missing('wh_partner', 'address_line', 'VARCHAR(255) NOT NULL DEFAULT '''' AFTER district');
CALL add_column_if_missing('wh_partner', 'postal_code', 'VARCHAR(20) NULL');
CALL add_column_if_missing('wh_partner', 'status', 'VARCHAR(20) NOT NULL DEFAULT ''ACTIVE'' AFTER postal_code');
CALL add_column_if_missing('wh_partner', 'created_by', 'BIGINT UNSIGNED NULL');
CALL add_column_if_missing('wh_partner', 'created_at', 'DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)');
CALL add_column_if_missing('wh_partner', 'updated_by', 'BIGINT UNSIGNED NULL');
CALL add_column_if_missing('wh_partner', 'updated_at', 'DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)');
CALL add_column_if_missing('wh_partner', 'deleted', 'TINYINT(1) NOT NULL DEFAULT 0');
CALL add_column_if_missing('wh_partner', 'version', 'INT NOT NULL DEFAULT 0');
-- 该表在旧库中不存在时先由 warehouse-express-company.sql 建表;此段补齐早期部署缺列。
CALL add_column_if_missing('wh_express_company', 'tenant_id', 'BIGINT UNSIGNED NOT NULL DEFAULT 0 AFTER id');
CALL add_column_if_missing('wh_express_company', 'company_code', 'VARCHAR(50) NOT NULL DEFAULT '''' AFTER tenant_id');
CALL add_column_if_missing('wh_express_company', 'company_name', 'VARCHAR(128) NOT NULL DEFAULT '''' AFTER company_code');
CALL add_column_if_missing('wh_express_company', 'service_phone', 'VARCHAR(32) NULL');
CALL add_column_if_missing('wh_express_company', 'website', 'VARCHAR(255) NULL');
CALL add_column_if_missing('wh_express_company', 'tracking_url_template', 'VARCHAR(500) NULL');
CALL add_column_if_missing('wh_express_company', 'status', 'VARCHAR(20) NOT NULL DEFAULT ''ACTIVE'' AFTER tracking_url_template');
CALL add_column_if_missing('wh_express_company', 'remark', 'VARCHAR(500) NULL');
CALL add_column_if_missing('wh_express_company', 'created_by', 'BIGINT UNSIGNED NULL');
CALL add_column_if_missing('wh_express_company', 'created_at', 'DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)');
CALL add_column_if_missing('wh_express_company', 'updated_by', 'BIGINT UNSIGNED NULL');
CALL add_column_if_missing('wh_express_company', 'updated_at', 'DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)');
CALL add_column_if_missing('wh_express_company', 'deleted', 'TINYINT(1) NOT NULL DEFAULT 0');
CALL add_column_if_missing('wh_express_company', 'version', 'INT NOT NULL DEFAULT 0');
-- 平台公共仓储主数据和平台往来单位统一归平台作用域 0;订单自身仍按客户 tenant_id 隔离。
UPDATE wh_warehouse SET tenant_id = 0 WHERE tenant_id <> 0;
UPDATE wh_warehouse_address a
JOIN wh_warehouse w ON w.id = a.warehouse_id
SET a.tenant_id = w.tenant_id
WHERE a.tenant_id <> w.tenant_id;
UPDATE wh_service_capability c
JOIN wh_warehouse w ON w.id = c.warehouse_id
SET c.tenant_id = w.tenant_id
WHERE c.tenant_id <> w.tenant_id;
UPDATE wh_warehouse_address SET tenant_id = 0 WHERE tenant_id <> 0;
UPDATE wh_service_capability SET tenant_id = 0 WHERE tenant_id <> 0;
UPDATE wh_express_company SET tenant_id = 0 WHERE tenant_id <> 0;
UPDATE wh_partner SET tenant_id = 0, data_scope_id = 0
WHERE tenant_id <> 0 OR data_scope_id <> 0;
UPDATE wh_partner_type SET tenant_id = 0, data_scope_id = 0
WHERE tenant_id <> 0 OR data_scope_id <> 0;
DROP PROCEDURE IF EXISTS assert_platform_public_resource_scope;
DELIMITER $$
CREATE PROCEDURE assert_platform_public_resource_scope()
BEGIN
IF (SELECT COUNT(*) FROM wh_warehouse WHERE tenant_id <> 0) > 0
OR (SELECT COUNT(*) FROM wh_warehouse_address WHERE tenant_id <> 0) > 0
OR (SELECT COUNT(*) FROM wh_service_capability WHERE tenant_id <> 0) > 0
OR (SELECT COUNT(*) FROM wh_express_company WHERE tenant_id <> 0) > 0
OR (SELECT COUNT(*) FROM wh_partner WHERE tenant_id <> 0 OR data_scope_id <> 0) > 0
OR (SELECT COUNT(*) FROM wh_partner_type WHERE tenant_id <> 0 OR data_scope_id <> 0) > 0 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Warehouse 迁移失败:平台公共仓储或往来单位仍有非0作用域';
END IF;
IF EXISTS (
SELECT 1 FROM wh_warehouse_address a
JOIN wh_warehouse w ON w.id = a.warehouse_id
WHERE a.tenant_id <> w.tenant_id
) OR EXISTS (
SELECT 1 FROM wh_service_capability c
JOIN wh_warehouse w ON w.id = c.warehouse_id
WHERE c.tenant_id <> w.tenant_id
) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Warehouse 迁移失败:仓库子表与父仓库平台归属不一致';
END IF;
END$$
DELIMITER ;
CALL assert_platform_public_resource_scope();
DROP PROCEDURE IF EXISTS assert_platform_public_resource_scope;
DROP PROCEDURE IF EXISTS add_column_if_missing;
-- MySQL 8 的 ALTER ADD INDEX 不在所有小版本都支持 IF NOT EXISTS,使用元数据判断以保证重复执行安全。
SET @schema_name = DATABASE();
SET @sql = IF((SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = @schema_name AND table_name = 'wh_warehouse' AND index_name = 'idx_wh_tenant_status') = 0,
'ALTER TABLE wh_warehouse ADD KEY idx_wh_tenant_status (tenant_id, status, deleted)', 'SELECT 1');
PREPARE statement FROM @sql; EXECUTE statement; DEALLOCATE PREPARE statement;
SET @sql = IF((SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = @schema_name AND table_name = 'wh_warehouse' AND index_name = 'idx_wh_tenant_type_status') = 0,
'ALTER TABLE wh_warehouse ADD KEY idx_wh_tenant_type_status (tenant_id, warehouse_type, status, deleted)', 'SELECT 1');
PREPARE statement FROM @sql; EXECUTE statement; DEALLOCATE PREPARE statement;
SET @sql = IF((SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = @schema_name AND table_name = 'wh_warehouse_address' AND index_name = 'idx_address_warehouse') = 0,
'ALTER TABLE wh_warehouse_address ADD KEY idx_address_warehouse (tenant_id, warehouse_id, deleted)', 'SELECT 1');
PREPARE statement FROM @sql; EXECUTE statement; DEALLOCATE PREPARE statement;
SET @sql = IF((SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = @schema_name AND table_name = 'wh_service_capability' AND index_name = 'idx_capability_query') = 0,
'ALTER TABLE wh_service_capability ADD KEY idx_capability_query (tenant_id, warehouse_id, enabled, deleted)', 'SELECT 1');
PREPARE statement FROM @sql; EXECUTE statement; DEALLOCATE PREPARE statement;
SET @sql = IF((SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = @schema_name AND table_name = 'wh_partner' AND index_name = 'idx_wh_partner_tenant_status') = 0,
'ALTER TABLE wh_partner ADD KEY idx_wh_partner_tenant_status (tenant_id, status, deleted)', 'SELECT 1');
PREPARE statement FROM @sql; EXECUTE statement; DEALLOCATE PREPARE statement;
SET @sql = IF((SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = @schema_name AND table_name = 'wh_express_company' AND index_name = 'idx_wh_express_tenant_status') = 0,
'ALTER TABLE wh_express_company ADD KEY idx_wh_express_tenant_status (tenant_id, status, deleted)', 'SELECT 1');
PREPARE statement FROM @sql; EXECUTE statement; DEALLOCATE PREPARE statement;
SET @sql = IF((SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = @schema_name AND table_name = 'wh_partner' AND index_name = 'uk_wh_partner_tenant_code') = 0,
'ALTER TABLE wh_partner ADD UNIQUE KEY uk_wh_partner_tenant_code (tenant_id, partner_code, deleted)', 'SELECT 1');
PREPARE statement FROM @sql; EXECUTE statement; DEALLOCATE PREPARE statement;
SET @sql = IF((SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = @schema_name AND table_name = 'wh_express_company' AND index_name = 'uk_wh_express_tenant_code') = 0,
'ALTER TABLE wh_express_company ADD UNIQUE KEY uk_wh_express_tenant_code (tenant_id, company_code, deleted)', 'SELECT 1');
PREPARE statement FROM @sql; EXECUTE statement; DEALLOCATE PREPARE statement;
SET @sql = IF((SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = @schema_name AND table_name = 'wh_express_company' AND index_name = 'uk_wh_express_tenant_name') = 0,
'ALTER TABLE wh_express_company ADD UNIQUE KEY uk_wh_express_tenant_name (tenant_id, company_name, deleted)', 'SELECT 1');
PREPARE statement FROM @sql; EXECUTE statement; DEALLOCATE PREPARE statement;
-- ============================================================================
-- 来源:firstleg\src\main\resources\db\phase1-firstleg-task-list-index.sql
-- ============================================================================
SET NAMES utf8mb4;
USE baiyue_firstleg;
-- 既有履约任务表补充分页查询索引;重复执行时不重复创建。
SET @firstleg_task_list_index_sql = IF(
EXISTS(SELECT 1 FROM information_schema.statistics
WHERE table_schema = DATABASE() AND table_name = 'fl_fulfillment_task'
AND index_name = 'idx_task_tenant_created'),
'SELECT 1',
'CREATE INDEX idx_task_tenant_created ON fl_fulfillment_task (tenant_id, created_at)'
);
PREPARE firstleg_task_list_index_stmt FROM @firstleg_task_list_index_sql;
EXECUTE firstleg_task_list_index_stmt;
DEALLOCATE PREPARE firstleg_task_list_index_stmt;
-- ============================================================================
-- 来源:warehouse\src\main\resources\db\phase2-pallet-location-schema.sql
-- ============================================================================
-- 二期托盘、库位、预占与占用表结构。
-- 本脚本只新增表、字段、索引和约束,不包含业务数据及流程实现。
-- 可在 warehouse.sql 和一期增量脚本之后执行;所有建表均可重复执行。
SET NAMES utf8mb4;
CREATE DATABASE IF NOT EXISTS baiyue_warehouse
DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
USE baiyue_warehouse;
CREATE TABLE IF NOT EXISTS wh_pallet (
id BIGINT UNSIGNED NOT NULL COMMENT '托盘ID(雪花ID)',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '平台公共托盘固定使用 tenant_id=0',
warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '所属仓库ID',
pallet_code VARCHAR(50) NOT NULL COMMENT '平台仓库内唯一托盘编码',
pallet_type VARCHAR(32) NOT NULL COMMENT '托盘类型',
pallet_name VARCHAR(100) NULL COMMENT '托盘名称',
capacity_cbm DECIMAL(18,6) NOT NULL COMMENT '托盘可用容积CBM',
weight_kg DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT '托盘重量kg',
color VARCHAR(32) NOT NULL DEFAULT '' COMMENT '托盘颜色',
status VARCHAR(20) NOT NULL DEFAULT 'AVAILABLE' COMMENT '内部状态:AVAILABLE、RESERVED或OCCUPIED',
remark VARCHAR(500) NULL COMMENT '备注',
created_by BIGINT UNSIGNED NOT NULL COMMENT '创建人ID',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_by BIGINT UNSIGNED NOT NULL COMMENT '最后修改人ID',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id),
UNIQUE KEY uk_pallet_tenant_code (tenant_id, pallet_code),
KEY idx_pallet_available (tenant_id, warehouse_id, status, deleted),
KEY idx_pallet_type (tenant_id, warehouse_id, pallet_type, status, deleted),
CONSTRAINT fk_pallet_warehouse FOREIGN KEY (warehouse_id) REFERENCES wh_warehouse (id),
CONSTRAINT ck_pallet_capacity CHECK (capacity_cbm > 0)
) ENGINE=InnoDB COMMENT='二期托盘基础资料和物理托盘实例';
-- 存量库补齐托盘重量、颜色字段(可重复执行)。
SET @pallet_weight_sql = IF((SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = 'wh_pallet' AND column_name = 'weight_kg') = 0,
'ALTER TABLE wh_pallet ADD COLUMN weight_kg DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT ''托盘重量kg'' AFTER capacity_cbm', 'SELECT 1');
PREPARE pallet_weight_stmt FROM @pallet_weight_sql; EXECUTE pallet_weight_stmt; DEALLOCATE PREPARE pallet_weight_stmt;
SET @pallet_color_sql = IF((SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = 'wh_pallet' AND column_name = 'color') = 0,
'ALTER TABLE wh_pallet ADD COLUMN color VARCHAR(32) NOT NULL DEFAULT '''' COMMENT ''托盘颜色'' AFTER weight_kg', 'SELECT 1');
PREPARE pallet_color_stmt FROM @pallet_color_sql; EXECUTE pallet_color_stmt; DEALLOCATE PREPARE pallet_color_stmt;
CREATE TABLE IF NOT EXISTS wh_location (
id BIGINT UNSIGNED NOT NULL COMMENT '库位ID(雪花ID)',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '平台公共库位固定使用 tenant_id=0',
warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '所属仓库ID',
zone_code VARCHAR(50) NULL COMMENT '库区编码',
zone_name VARCHAR(100) NULL COMMENT '库区名称',
location_code VARCHAR(50) NOT NULL COMMENT '平台仓库内唯一库位编码',
location_name VARCHAR(100) NULL COMMENT '库位名称',
location_type VARCHAR(32) NOT NULL DEFAULT 'PALLET' COMMENT '库位类型,二期为PALLET',
max_weight_kg DECIMAL(18,3) NULL COMMENT '最大承重展示值,二期不作容量拦截',
shared_overflow TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否为系统共享溢出位A0',
status VARCHAR(20) NOT NULL DEFAULT 'AVAILABLE' COMMENT 'AVAILABLE、RESERVED、OCCUPIED或DISABLED',
remark VARCHAR(500) NULL COMMENT '备注',
created_by BIGINT UNSIGNED NOT NULL COMMENT '创建人ID',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_by BIGINT UNSIGNED NOT NULL COMMENT '最后修改人ID',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id),
UNIQUE KEY uk_location_tenant_warehouse_code (tenant_id, warehouse_id, location_code),
KEY idx_location_available (tenant_id, warehouse_id, status, deleted),
KEY idx_location_zone (tenant_id, warehouse_id, zone_code, status, deleted),
CONSTRAINT fk_location_warehouse FOREIGN KEY (warehouse_id) REFERENCES wh_warehouse (id),
CONSTRAINT ck_location_max_weight CHECK (max_weight_kg IS NULL OR max_weight_kg > 0)
) ENGINE=InnoDB COMMENT='二期库位基础资料,默认一托盘对应一个库位且不作容量校验';
CREATE TABLE IF NOT EXISTS wh_location_reservation (
id BIGINT UNSIGNED NOT NULL COMMENT '库位预占ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '平台公共库位预占固定使用 tenant_id=0',
reservation_no VARCHAR(40) NOT NULL COMMENT '平台内唯一预占业务号',
idempotency_key VARCHAR(128) NOT NULL COMMENT '调用方幂等键',
source_service VARCHAR(40) NOT NULL DEFAULT 'FIRSTLEG' COMMENT '来源服务',
business_id BIGINT UNSIGNED NOT NULL COMMENT '来源服务入库单ID',
inbound_code VARCHAR(40) NOT NULL COMMENT '入仓码快照',
warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '仓库ID',
pallet_id BIGINT UNSIGNED NOT NULL COMMENT '托盘ID',
pallet_code VARCHAR(50) NOT NULL COMMENT '托盘编码快照',
location_id BIGINT UNSIGNED NOT NULL COMMENT '库位ID',
location_code VARCHAR(50) NOT NULL COMMENT '库位编码快照',
shared_overflow TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否使用共享溢出位A0',
reservation_status VARCHAR(20) NOT NULL DEFAULT 'RESERVED' COMMENT 'RESERVED、CONFIRMED、RELEASED或EXPIRED',
expires_at DATETIME(3) NOT NULL COMMENT '预占过期时间',
confirmed_at DATETIME(3) NULL COMMENT '确认占用时间',
released_at DATETIME(3) NULL COMMENT '释放时间',
release_reason VARCHAR(500) NULL COMMENT '释放或过期原因',
created_by BIGINT UNSIGNED NOT NULL COMMENT '创建人或系统主体ID',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
active_reservation TINYINT GENERATED ALWAYS AS (CASE WHEN reservation_status = 'RESERVED' AND shared_overflow = 0 THEN 1 ELSE NULL END) STORED COMMENT '普通库位活动预占唯一标记',
active_pallet_reservation TINYINT GENERATED ALWAYS AS (CASE WHEN reservation_status = 'RESERVED' THEN 1 ELSE NULL END) STORED COMMENT '托盘活动预占唯一标记(包含A0)',
PRIMARY KEY (id),
UNIQUE KEY uk_location_reservation_no (tenant_id, reservation_no),
UNIQUE KEY uk_location_reservation_idempotency (tenant_id, source_service, idempotency_key),
UNIQUE KEY uk_location_active_reservation (tenant_id, location_id, active_reservation),
UNIQUE KEY uk_pallet_active_reservation (tenant_id, pallet_id, active_pallet_reservation),
KEY idx_location_reservation_business (tenant_id, source_service, business_id, reservation_status),
KEY idx_location_reservation_expire (reservation_status, expires_at),
KEY idx_location_reservation_target (tenant_id, warehouse_id, location_id, reservation_status),
CONSTRAINT fk_reservation_warehouse FOREIGN KEY (warehouse_id) REFERENCES wh_warehouse (id),
CONSTRAINT fk_reservation_pallet FOREIGN KEY (pallet_id) REFERENCES wh_pallet (id),
CONSTRAINT fk_reservation_location FOREIGN KEY (location_id) REFERENCES wh_location (id)
) ENGINE=InnoDB COMMENT='跨服务入库Saga的托盘和库位临时预占';
CREATE TABLE IF NOT EXISTS wh_location_occupancy (
id BIGINT UNSIGNED NOT NULL COMMENT '库位占用ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '平台公共库位占用固定使用 tenant_id=0',
occupancy_no VARCHAR(40) NOT NULL COMMENT '平台内唯一占用业务号',
reservation_id BIGINT UNSIGNED NOT NULL COMMENT '来源预占ID',
source_service VARCHAR(40) NOT NULL DEFAULT 'FIRSTLEG' COMMENT '来源服务',
business_id BIGINT UNSIGNED NOT NULL COMMENT '来源服务入库单ID',
inbound_code VARCHAR(40) NOT NULL COMMENT '入仓码快照',
warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '仓库ID',
pallet_id BIGINT UNSIGNED NOT NULL COMMENT '托盘ID',
pallet_code VARCHAR(50) NOT NULL COMMENT '托盘编码快照',
location_id BIGINT UNSIGNED NOT NULL COMMENT '库位ID',
location_code VARCHAR(50) NOT NULL COMMENT '库位编码快照',
shared_overflow TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否使用共享溢出位A0',
allocated_volume_cbm DECIMAL(18,6) NOT NULL COMMENT '本托盘分配体积CBM',
allocated_package_count INT UNSIGNED NULL COMMENT '本托盘分配箱数',
occupancy_status VARCHAR(20) NOT NULL DEFAULT 'OCCUPIED' COMMENT 'OCCUPIED或RELEASED',
occupied_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '占用开始时间',
released_at DATETIME(3) NULL COMMENT '释放时间',
release_reason VARCHAR(500) NULL COMMENT '释放原因',
created_by BIGINT UNSIGNED NOT NULL COMMENT '创建人或系统主体ID',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
active_location_occupancy TINYINT GENERATED ALWAYS AS (CASE WHEN occupancy_status = 'OCCUPIED' AND shared_overflow = 0 THEN 1 ELSE NULL END) STORED COMMENT '普通库位活动占用唯一标记',
active_pallet_occupancy TINYINT GENERATED ALWAYS AS (CASE WHEN occupancy_status = 'OCCUPIED' THEN 1 ELSE NULL END) STORED COMMENT '托盘活动占用唯一标记(包含A0)',
PRIMARY KEY (id),
UNIQUE KEY uk_location_occupancy_no (tenant_id, occupancy_no),
UNIQUE KEY uk_occupancy_reservation (tenant_id, reservation_id),
UNIQUE KEY uk_location_active_occupancy (tenant_id, location_id, active_location_occupancy),
UNIQUE KEY uk_pallet_active_occupancy (tenant_id, pallet_id, active_pallet_occupancy),
KEY idx_occupancy_business (tenant_id, source_service, business_id, occupancy_status),
KEY idx_occupancy_warehouse (tenant_id, warehouse_id, occupancy_status, occupied_at),
CONSTRAINT fk_occupancy_reservation FOREIGN KEY (reservation_id) REFERENCES wh_location_reservation (id),
CONSTRAINT fk_occupancy_warehouse FOREIGN KEY (warehouse_id) REFERENCES wh_warehouse (id),
CONSTRAINT fk_occupancy_pallet FOREIGN KEY (pallet_id) REFERENCES wh_pallet (id),
CONSTRAINT fk_occupancy_location FOREIGN KEY (location_id) REFERENCES wh_location (id),
CONSTRAINT ck_occupancy_volume CHECK (allocated_volume_cbm > 0)
) ENGINE=InnoDB COMMENT='已确认的一托一库位占用事实记录';
-- 头程入库单属于其他数据库,不建立跨库外键。
-- ============================================================================
-- 来源:warehouse\src\main\resources\db\phase2-location-overflow-migration.sql
-- ============================================================================
-- 二期库位共享溢出位 A0 增量迁移。
-- 执行顺序:phase2-pallet-location-schema.sql、本文件。
-- 普通库位仍保持一托一位;同一租户仓库只有一个 A0,且 A0 可承载多个活动资源。
SET NAMES utf8mb4;
USE baiyue_warehouse;
DROP PROCEDURE IF EXISTS add_location_overflow_column_if_missing;
DELIMITER $$
CREATE PROCEDURE add_location_overflow_column_if_missing(
IN p_table_name VARCHAR(64),
IN p_column_name VARCHAR(64),
IN p_column_definition TEXT
)
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema = DATABASE()
AND table_name = p_table_name
AND column_name = p_column_name
) THEN
SET @column_sql = CONCAT(
'ALTER TABLE `', REPLACE(p_table_name, '`', '``'),
'` ADD COLUMN `', REPLACE(p_column_name, '`', '``'), '` ', p_column_definition
);
PREPARE column_statement FROM @column_sql;
EXECUTE column_statement;
DEALLOCATE PREPARE column_statement;
END IF;
END$$
DELIMITER ;
CALL add_location_overflow_column_if_missing(
'wh_location', 'shared_overflow',
'TINYINT(1) NOT NULL DEFAULT 0 COMMENT ''是否为系统共享溢出位 A0'' AFTER max_weight_kg'
);
CALL add_location_overflow_column_if_missing(
'wh_location_reservation', 'shared_overflow',
'TINYINT(1) NOT NULL DEFAULT 0 COMMENT ''是否使用共享溢出位 A0'' AFTER location_code'
);
CALL add_location_overflow_column_if_missing(
'wh_location_occupancy', 'shared_overflow',
'TINYINT(1) NOT NULL DEFAULT 0 COMMENT ''是否使用共享溢出位 A0'' AFTER location_code'
);
-- 兼容迁移前人工或旧流程已创建的 A0,并将资源快照回填一致。
UPDATE wh_location
SET shared_overflow = 1,
status = 'AVAILABLE'
WHERE location_code = 'A0' AND deleted = 0;
UPDATE wh_location_reservation r
JOIN wh_location l
ON l.tenant_id = r.tenant_id AND l.id = r.location_id
SET r.shared_overflow = l.shared_overflow;
UPDATE wh_location_occupancy o
JOIN wh_location l
ON l.tenant_id = o.tenant_id AND l.id = o.location_id
SET o.shared_overflow = l.shared_overflow;
DROP PROCEDURE IF EXISTS add_location_overflow_column_if_missing;
-- 仅旧生成列需要移除并重建唯一索引;已经是最终定义时必须跳过,避免重复部署产生约束空窗。
SET @schema_name = DATABASE();
SET @reservation_needs_upgrade = IFNULL((
SELECT IF(LOCATE('shared_overflow', generation_expression) = 0, 1, 0)
FROM information_schema.columns
WHERE table_schema = @schema_name AND table_name = 'wh_location_reservation'
AND column_name = 'active_reservation'
), 1);
SET @sql = IF(@reservation_needs_upgrade = 1 AND (SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = @schema_name AND table_name = 'wh_location_reservation' AND index_name = 'uk_location_active_reservation') > 0,
'ALTER TABLE wh_location_reservation DROP INDEX uk_location_active_reservation', 'SELECT 1');
PREPARE statement FROM @sql; EXECUTE statement; DEALLOCATE PREPARE statement;
SET @sql = IF(@reservation_needs_upgrade = 1 AND (SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = @schema_name AND table_name = 'wh_location_reservation' AND index_name = 'uk_pallet_active_reservation') > 0,
'ALTER TABLE wh_location_reservation DROP INDEX uk_pallet_active_reservation', 'SELECT 1');
PREPARE statement FROM @sql; EXECUTE statement; DEALLOCATE PREPARE statement;
SET @sql = IF(@reservation_needs_upgrade = 1,
'ALTER TABLE wh_location_reservation MODIFY COLUMN active_reservation TINYINT GENERATED ALWAYS AS (CASE WHEN reservation_status = ''RESERVED'' AND shared_overflow = 0 THEN 1 ELSE NULL END) STORED COMMENT ''普通库位活动预占唯一标记,A0 返回 NULL''',
'SELECT 1');
PREPARE statement FROM @sql; EXECUTE statement; DEALLOCATE PREPARE statement;
SET @sql = IF((SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = @schema_name AND table_name = 'wh_location_reservation' AND index_name = 'uk_location_active_reservation') = 0,
'ALTER TABLE wh_location_reservation ADD UNIQUE KEY uk_location_active_reservation (tenant_id, location_id, active_reservation)', 'SELECT 1');
PREPARE statement FROM @sql; EXECUTE statement; DEALLOCATE PREPARE statement;
SET @occupancy_needs_upgrade = IF(
EXISTS(SELECT 1 FROM information_schema.columns
WHERE table_schema = @schema_name AND table_name = 'wh_location_occupancy'
AND column_name = 'active_location_occupancy'
AND LOCATE('shared_overflow', generation_expression) > 0)
AND EXISTS(SELECT 1 FROM information_schema.columns
WHERE table_schema = @schema_name AND table_name = 'wh_location_occupancy'
AND column_name = 'active_pallet_occupancy'),
0, 1);
SET @sql = IF(@occupancy_needs_upgrade = 1 AND (SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = @schema_name AND table_name = 'wh_location_occupancy' AND index_name = 'uk_location_active_occupancy') > 0,
'ALTER TABLE wh_location_occupancy DROP INDEX uk_location_active_occupancy', 'SELECT 1');
PREPARE statement FROM @sql; EXECUTE statement; DEALLOCATE PREPARE statement;
SET @sql = IF(@occupancy_needs_upgrade = 1 AND (SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = @schema_name AND table_name = 'wh_location_occupancy' AND index_name = 'uk_pallet_active_occupancy') > 0,
'ALTER TABLE wh_location_occupancy DROP INDEX uk_pallet_active_occupancy', 'SELECT 1');
PREPARE statement FROM @sql; EXECUTE statement; DEALLOCATE PREPARE statement;
DROP PROCEDURE IF EXISTS add_location_overflow_column_if_missing;
DELIMITER $$
CREATE PROCEDURE add_location_overflow_column_if_missing(
IN p_table_name VARCHAR(64),
IN p_column_name VARCHAR(64),
IN p_column_definition TEXT
)
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema = DATABASE()
AND table_name = p_table_name
AND column_name = p_column_name
) THEN
SET @column_sql = CONCAT(
'ALTER TABLE `', REPLACE(p_table_name, '`', '``'),
'` ADD COLUMN `', REPLACE(p_column_name, '`', '``'), '` ', p_column_definition
);
PREPARE column_statement FROM @column_sql;
EXECUTE column_statement;
DEALLOCATE PREPARE column_statement;
END IF;
END$$
DELIMITER ;
CALL add_location_overflow_column_if_missing(
'wh_location_reservation', 'active_pallet_reservation',
'TINYINT GENERATED ALWAYS AS (CASE WHEN reservation_status = ''RESERVED'' THEN 1 ELSE NULL END) STORED COMMENT ''托盘活动预占唯一标记(包含 A0)'''
);
CALL add_location_overflow_column_if_missing(
'wh_location_occupancy', 'active_location_occupancy',
'TINYINT GENERATED ALWAYS AS (CASE WHEN occupancy_status = ''OCCUPIED'' AND shared_overflow = 0 THEN 1 ELSE NULL END) STORED COMMENT ''普通库位活动占用唯一标记'''
);
CALL add_location_overflow_column_if_missing(
'wh_location_occupancy', 'active_pallet_occupancy',
'TINYINT GENERATED ALWAYS AS (CASE WHEN occupancy_status = ''OCCUPIED'' THEN 1 ELSE NULL END) STORED COMMENT ''托盘活动占用唯一标记(包含 A0)'''
);
DROP PROCEDURE IF EXISTS add_location_overflow_column_if_missing;
SET @sql = IF((SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = @schema_name AND table_name = 'wh_location_reservation' AND index_name = 'uk_pallet_active_reservation') = 0,
'ALTER TABLE wh_location_reservation ADD UNIQUE KEY uk_pallet_active_reservation (tenant_id, pallet_id, active_pallet_reservation)', 'SELECT 1');
PREPARE statement FROM @sql; EXECUTE statement; DEALLOCATE PREPARE statement;
SET @sql = IF((SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = @schema_name AND table_name = 'wh_location_occupancy' AND index_name = 'uk_location_active_occupancy') = 0,
'ALTER TABLE wh_location_occupancy ADD UNIQUE KEY uk_location_active_occupancy (tenant_id, location_id, active_location_occupancy)', 'SELECT 1');
PREPARE statement FROM @sql; EXECUTE statement; DEALLOCATE PREPARE statement;
SET @sql = IF((SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = @schema_name AND table_name = 'wh_location_occupancy' AND index_name = 'uk_pallet_active_occupancy') = 0,
'ALTER TABLE wh_location_occupancy ADD UNIQUE KEY uk_pallet_active_occupancy (tenant_id, pallet_id, active_pallet_occupancy)', 'SELECT 1');
PREPARE statement FROM @sql; EXECUTE statement; DEALLOCATE PREPARE statement;
-- 托盘、库位、预占和占用均为平台公共仓储资源,存量测试数据统一迁移到平台作用域 0。
UPDATE wh_pallet SET tenant_id = 0 WHERE tenant_id <> 0;
UPDATE wh_location SET tenant_id = 0 WHERE tenant_id <> 0;
UPDATE wh_location_reservation SET tenant_id = 0 WHERE tenant_id <> 0;
UPDATE wh_location_occupancy SET tenant_id = 0 WHERE tenant_id <> 0;
-- 托盘编码在平台公共作用域全局唯一;包含逻辑删除记录,编码不复用。
DROP PROCEDURE IF EXISTS assert_pallet_code_globally_unique;
DELIMITER $$
CREATE PROCEDURE assert_pallet_code_globally_unique()
BEGIN
IF EXISTS (SELECT 1 FROM wh_pallet GROUP BY tenant_id, pallet_code HAVING COUNT(*) > 1) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '托盘编码迁移失败:不同仓库存在重复编码,请先清理';
END IF;
END$$
DELIMITER ;
CALL assert_pallet_code_globally_unique();
DROP PROCEDURE IF EXISTS assert_pallet_code_globally_unique;
SET @pallet_code_index_sql = IF((SELECT COUNT(*) FROM information_schema.statistics
WHERE table_schema = DATABASE() AND table_name = 'wh_pallet' AND index_name = 'uk_pallet_tenant_warehouse_code') > 0,
'ALTER TABLE wh_pallet DROP INDEX uk_pallet_tenant_warehouse_code', 'SELECT 1');
PREPARE pallet_code_index_stmt FROM @pallet_code_index_sql; EXECUTE pallet_code_index_stmt; DEALLOCATE PREPARE pallet_code_index_stmt;
SET @pallet_code_index_sql = IF((SELECT COUNT(*) FROM information_schema.statistics
WHERE table_schema = DATABASE() AND table_name = 'wh_pallet' AND index_name = 'uk_pallet_tenant_code') = 0,
'ALTER TABLE wh_pallet ADD UNIQUE KEY uk_pallet_tenant_code (tenant_id, pallet_code)', 'SELECT 1');
PREPARE pallet_code_index_stmt FROM @pallet_code_index_sql; EXECUTE pallet_code_index_stmt; DEALLOCATE PREPARE pallet_code_index_stmt;
DROP PROCEDURE IF EXISTS assert_phase2_public_resource_scope;
DELIMITER $$
CREATE PROCEDURE assert_phase2_public_resource_scope()
BEGIN
IF (SELECT COUNT(*) FROM wh_pallet WHERE tenant_id <> 0) > 0
OR (SELECT COUNT(*) FROM wh_location WHERE tenant_id <> 0) > 0
OR (SELECT COUNT(*) FROM wh_location_reservation WHERE tenant_id <> 0) > 0
OR (SELECT COUNT(*) FROM wh_location_occupancy WHERE tenant_id <> 0) > 0 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Warehouse 二期迁移失败:托盘、库位、预占或占用仍有 tenant_id<>0';
END IF;
IF EXISTS (
SELECT 1 FROM wh_pallet p JOIN wh_warehouse w ON w.id = p.warehouse_id
WHERE p.tenant_id <> w.tenant_id
) OR EXISTS (
SELECT 1 FROM wh_location l JOIN wh_warehouse w ON w.id = l.warehouse_id
WHERE l.tenant_id <> w.tenant_id
) OR EXISTS (
SELECT 1 FROM wh_location_reservation r
JOIN wh_warehouse w ON w.id = r.warehouse_id
JOIN wh_pallet p ON p.id = r.pallet_id
JOIN wh_location l ON l.id = r.location_id
WHERE r.tenant_id <> w.tenant_id OR r.tenant_id <> p.tenant_id OR r.tenant_id <> l.tenant_id
) OR EXISTS (
SELECT 1 FROM wh_location_occupancy o
JOIN wh_warehouse w ON w.id = o.warehouse_id
JOIN wh_pallet p ON p.id = o.pallet_id
JOIN wh_location l ON l.id = o.location_id
JOIN wh_location_reservation r ON r.id = o.reservation_id
WHERE o.tenant_id <> w.tenant_id OR o.tenant_id <> p.tenant_id
OR o.tenant_id <> l.tenant_id OR o.tenant_id <> r.tenant_id
) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Warehouse 二期迁移失败:公共仓储资源父子归属不一致';
END IF;
END$$
DELIMITER ;
CALL assert_phase2_public_resource_scope();
DROP PROCEDURE IF EXISTS assert_phase2_public_resource_scope;
-- ============================================================================
-- 来源:order\src\main\resources\db\phase2-order-carrier-migration.sql
-- ============================================================================
SET NAMES utf8mb4;
USE baiyue_order;
SET @carrier_id_exists := (
SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = 'ord_order' AND column_name = 'carrier_id'
);
SET @carrier_id_sql := IF(@carrier_id_exists = 0,
'ALTER TABLE ord_order ADD COLUMN carrier_id BIGINT UNSIGNED NULL COMMENT ''船司逻辑ID快照'' AFTER channel_name',
'SELECT 1');
PREPARE carrier_id_statement FROM @carrier_id_sql;
EXECUTE carrier_id_statement;
DEALLOCATE PREPARE carrier_id_statement;
SET @carrier_name_exists := (
SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = 'ord_order' AND column_name = 'carrier_name'
);
SET @carrier_name_sql := IF(@carrier_name_exists = 0,
'ALTER TABLE ord_order ADD COLUMN carrier_name VARCHAR(128) NULL COMMENT ''船司名称快照'' AFTER carrier_id',
'SELECT 1');
PREPARE carrier_name_statement FROM @carrier_name_sql;
EXECUTE carrier_name_statement;
DEALLOCATE PREPARE carrier_name_statement;
-- 客户提交时由 Order 固定入仓码;审核通过后 Firstleg 沿用该码创建正式入库单。
SET @inbound_code_exists := (
SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = 'ord_order' AND column_name = 'inbound_code'
);
SET @inbound_code_sql := IF(@inbound_code_exists = 0,
'ALTER TABLE ord_order ADD COLUMN inbound_code VARCHAR(40) NULL COMMENT ''客户提交后生成的入仓码,草稿为空'' AFTER order_no',
'SELECT 1');
PREPARE inbound_code_statement FROM @inbound_code_sql;
EXECUTE inbound_code_statement;
DEALLOCATE PREPARE inbound_code_statement;
SET @inbound_code_index_exists := (
SELECT COUNT(*) FROM information_schema.statistics
WHERE table_schema = DATABASE() AND table_name = 'ord_order'
AND index_name = 'uk_order_tenant_inbound_code'
);
SET @inbound_code_index_sql := IF(@inbound_code_index_exists = 0,
'ALTER TABLE ord_order ADD UNIQUE KEY uk_order_tenant_inbound_code (tenant_id, inbound_code)',
'SELECT 1');
PREPARE inbound_code_index_statement FROM @inbound_code_index_sql;
EXECUTE inbound_code_index_statement;
DEALLOCATE PREPARE inbound_code_index_statement;
-- ============================================================================
-- 来源:firstleg\src\main\resources\db\phase2-inbound-schema.sql
-- ============================================================================
SET NAMES utf8mb4;
CREATE DATABASE IF NOT EXISTS baiyue_firstleg
DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
USE baiyue_firstleg;
CREATE TABLE IF NOT EXISTS fl_inbound_order (
id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID(雪花ID)',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
task_id BIGINT UNSIGNED NULL COMMENT '头程履约任务ID',
order_id BIGINT UNSIGNED NOT NULL COMMENT '订单服务订单ID,不建立跨库外键',
order_no VARCHAR(32) NOT NULL COMMENT '订单号快照',
inbound_code VARCHAR(40) NOT NULL COMMENT '客户内唯一入仓码',
process_status VARCHAR(24) NOT NULL DEFAULT 'WAIT_MEASURE' COMMENT '入库流程状态:WAIT_MEASURE待测量、MEASURING测量中、MEASURE_PENDING待审核、MEASURE_REJECTED测量驳回、WAIT_PRICING待计费、FEE_CONFIRMING费用确认中、INBOUNDING入库收口中、INBOUNDED已入库、WAIT_SPLIT历史待分柜',
order_status_snapshot VARCHAR(24) NOT NULL COMMENT '订单状态快照',
measure_review_status VARCHAR(24) NOT NULL DEFAULT 'NOT_SUBMITTED' COMMENT '测量审核状态:NOT_SUBMITTED未提交、PENDING待审核、APPROVED已通过、REJECTED已驳回',
fee_status VARCHAR(24) NOT NULL DEFAULT 'UNPRICED' COMMENT '费用状态:UNPRICED未计费、CALCULATED已计算、CONFIRMED已确认、SENT已发送、VOIDED已作废',
fee_review_status VARCHAR(24) NOT NULL DEFAULT 'NOT_SUBMITTED' COMMENT '费用审核状态:NOT_SUBMITTED未提交、PENDING待审核、APPROVED已通过、REJECTED已驳回、CONFIRMED已确认',
notification_status VARCHAR(24) NOT NULL DEFAULT 'NOT_CREATED' COMMENT '客户通知状态:NOT_CREATED未创建、PENDING待通知',
expected_arrival_at DATETIME(3) NULL COMMENT '预计到达时间',
customer_id BIGINT UNSIGNED NULL COMMENT '客户逻辑ID',
customer_name VARCHAR(128) NOT NULL COMMENT '客户名称快照',
company_id BIGINT UNSIGNED NULL COMMENT '运营公司逻辑ID',
company_name VARCHAR(128) NULL COMMENT '运营公司名称快照',
warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '实际作业仓库ID',
warehouse_name VARCHAR(128) NOT NULL COMMENT '实际作业仓库名称快照',
delivery_warehouse_id BIGINT UNSIGNED NULL COMMENT '交货仓逻辑ID',
delivery_warehouse_name VARCHAR(128) NULL COMMENT '交货仓名称快照',
business_type VARCHAR(32) NOT NULL COMMENT '业务类型',
channel_code VARCHAR(64) NULL COMMENT '渠道编码',
channel_name VARCHAR(128) NULL COMMENT '渠道名称快照',
carrier_id BIGINT UNSIGNED NULL COMMENT '计费船司逻辑ID',
carrier_name VARCHAR(128) NULL COMMENT '计费船司名称快照',
tax_mode VARCHAR(24) NULL COMMENT '买单或退税模式',
pricing_tax_mode VARCHAR(16) NULL COMMENT '报价包税维度:TAX_INCLUDED/TAX_EXCLUDED',
container_mode VARCHAR(24) NULL COMMENT '整柜或散货模式',
customs_inspection TINYINT(1) NULL COMMENT '是否商检',
destination_type VARCHAR(24) NULL COMMENT '目的仓类型',
destination_warehouse_id BIGINT UNSIGNED NULL COMMENT '目的仓逻辑ID',
destination_code VARCHAR(32) NULL COMMENT '目的仓编码,例如FBA仓编码',
destination_name VARCHAR(128) NULL COMMENT '目的仓名称快照',
hold_flag TINYINT(1) NOT NULL DEFAULT 0 COMMENT '当前是否扣货',
current_hold_reason VARCHAR(500) NULL COMMENT '当前扣货理由快照',
total_package_count INT UNSIGNED NULL COMMENT '确认后的总箱数',
actual_gross_weight_kg DECIMAL(18,3) NULL COMMENT '确认后的实际总毛重kg',
chargeable_weight_kg DECIMAL(18,3) NULL COMMENT '当前费用快照的计费重量kg',
actual_volume_cbm DECIMAL(18,4) NULL COMMENT '确认后的实际总体积m3',
chargeable_volume_cbm DECIMAL(18,4) NULL COMMENT '当前费用快照的计费体积m3',
pallet_count INT UNSIGNED NULL COMMENT '已分配总托盘数快照',
volume_weight_kg DECIMAL(18,3) NULL COMMENT '总材积重kg',
actual_net_weight_kg DECIMAL(18,3) NULL COMMENT '总净重kg',
measure_completed_at DATETIME(3) NULL COMMENT '测量完成时间',
inbounded_at DATETIME(3) NULL COMMENT '入库完成时间',
express_company_code VARCHAR(64) NULL COMMENT '快递公司编码快照',
express_company_name VARCHAR(128) NULL COMMENT '快递公司名称快照',
express_tracking_no VARCHAR(128) NULL COMMENT '快递单号快照',
public_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_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id),
UNIQUE KEY uk_inbound_tenant_code (tenant_id, inbound_code),
UNIQUE KEY uk_inbound_tenant_order (tenant_id, order_id),
KEY idx_inbound_process_time (tenant_id, process_status, created_at, id),
KEY idx_inbound_measure_time (tenant_id, measure_review_status, created_at, id),
KEY idx_inbound_fee_time (tenant_id, fee_review_status, created_at, id),
KEY idx_inbound_warehouse_status (tenant_id, warehouse_id, process_status, created_at),
KEY idx_inbound_customer_time (tenant_id, customer_id, created_at),
KEY idx_inbound_company_time (tenant_id, company_id, created_at),
KEY idx_inbound_arrival (tenant_id, expected_arrival_at),
KEY idx_inbound_destination (tenant_id, destination_code, created_at),
KEY idx_inbound_channel (tenant_id, channel_code, created_at),
CONSTRAINT fk_inbound_task FOREIGN KEY (task_id) REFERENCES fl_fulfillment_task (id)
) ENGINE=InnoDB COMMENT='二期头程入库单聚合根和列表查询快照';
UPDATE baiyue_order.ord_order o
LEFT JOIN baiyue_firstleg.fl_inbound_order i
ON i.tenant_id = o.tenant_id AND i.order_id = o.id AND i.deleted = 0
SET o.inbound_code = COALESCE(i.inbound_code, CONCAT('RK-', o.id))
WHERE o.deleted = 0
AND o.status <> 'DRAFT'
AND (o.submitted_at IS NOT NULL
OR o.status IN ('SUBMITTED', 'APPROVED', 'ACCEPTED', 'PROCESSING', 'COMPLETED'))
AND o.inbound_code IS NULL;
-- 从未提交的草稿或由草稿直接取消的订单不应拥有入仓码。
UPDATE baiyue_order.ord_order
SET inbound_code = NULL
WHERE deleted = 0
AND inbound_code IS NOT NULL
AND (status = 'DRAFT'
OR (status IN ('REJECTED', 'CANCELLED') AND submitted_at IS NULL));
CREATE TABLE IF NOT EXISTS fl_inbound_pickup_snapshot (
id BIGINT UNSIGNED NOT NULL COMMENT '提货信息快照ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID',
pickup_method VARCHAR(24) NULL COMMENT '提货方式',
pickup_at DATETIME(3) NULL COMMENT '提货时间',
contact_name VARCHAR(64) NULL COMMENT '提货联系人',
contact_phone VARCHAR(32) NULL COMMENT '提货联系人电话',
contact_email VARCHAR(128) NULL COMMENT '提货联系人邮箱',
country_code CHAR(2) NULL COMMENT '国家二字码',
province VARCHAR(64) NULL COMMENT '省或州',
city VARCHAR(64) NULL COMMENT '城市',
district VARCHAR(64) NULL COMMENT '区县',
address_line VARCHAR(512) NULL COMMENT '提货详细地址',
postal_code VARCHAR(20) NULL COMMENT '提货邮编',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
PRIMARY KEY (id),
UNIQUE KEY uk_pickup_inbound (tenant_id, inbound_id),
CONSTRAINT fk_pickup_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id)
) ENGINE=InnoDB COMMENT='入库单提货信息不可变快照';
CREATE TABLE IF NOT EXISTS fl_inbound_destination_snapshot (
id BIGINT UNSIGNED NOT NULL COMMENT '目的仓地址快照ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID',
destination_type VARCHAR(24) NULL COMMENT '目的仓类型',
destination_code VARCHAR(32) NULL COMMENT '目的仓编码',
destination_name VARCHAR(128) NULL COMMENT '目的仓名称',
country_code CHAR(2) NULL COMMENT '国家二字码',
state_province VARCHAR(100) NULL COMMENT '省或州',
city VARCHAR(100) NULL COMMENT '城市',
address_line VARCHAR(512) NULL COMMENT '详细地址',
postal_code VARCHAR(20) NULL COMMENT '邮编',
contact_name VARCHAR(64) NULL COMMENT '联系人',
contact_phone VARCHAR(32) NULL COMMENT '联系人电话',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
PRIMARY KEY (id),
UNIQUE KEY uk_destination_inbound (tenant_id, inbound_id),
CONSTRAINT fk_destination_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id)
) ENGINE=InnoDB COMMENT='入库单目的仓地址和联系人快照';
CREATE TABLE IF NOT EXISTS fl_inbound_transport_snapshot (
id BIGINT UNSIGNED NOT NULL COMMENT '运输信息快照ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID',
loading_status VARCHAR(24) NULL COMMENT '配载状态快照',
container_no VARCHAR(64) NULL COMMENT '集装箱号',
cabinet_no VARCHAR(64) NULL COMMENT '柜号或内部柜单号',
vessel_name VARCHAR(128) NULL COMMENT '船名',
voyage_no VARCHAR(64) NULL COMMENT '航次',
sailing_at DATETIME(3) NULL COMMENT '开船时间',
port_arrival_at DATETIME(3) NULL COMMENT '到港时间',
source_updated_at DATETIME(3) NULL COMMENT '上游数据更新时间',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
PRIMARY KEY (id),
UNIQUE KEY uk_transport_inbound (tenant_id, inbound_id),
KEY idx_transport_container (tenant_id, container_no),
KEY idx_transport_cabinet (tenant_id, cabinet_no),
KEY idx_transport_loading (tenant_id, loading_status, sailing_at),
CONSTRAINT fk_transport_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id)
) ENGINE=InnoDB COMMENT='入库单关联运输信息只读快照';
CREATE TABLE IF NOT EXISTS fl_inbound_measurement (
id BIGINT UNSIGNED NOT NULL COMMENT '测量批次ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID',
source_type VARCHAR(24) NOT NULL COMMENT '数据来源:THIRD_PARTY或MANUAL',
source_code VARCHAR(64) NULL COMMENT '第三方应用或来源编码',
external_event_no VARCHAR(128) NULL COMMENT '第三方事件号',
external_request_hash CHAR(64) NULL COMMENT '第三方data原文和图片内容联合请求哈希',
device_code VARCHAR(64) NULL COMMENT '测量设备编码',
warehouse_code VARCHAR(50) NULL COMMENT '第三方回传仓库编码',
measured_at DATETIME(3) NULL COMMENT '设备测量时间',
status VARCHAR(24) NOT NULL DEFAULT 'RECEIVED' COMMENT '测量批次状态:DRAFT草稿、RECEIVED已接收、SUBMITTED已提交、APPROVED已通过、REJECTED已驳回',
raw_payload_json JSON NULL COMMENT '第三方原始报文',
original_document_no VARCHAR(64) NULL COMMENT '原单据号码快照',
original_gross_weight_kg DECIMAL(18,3) NULL COMMENT '原单据毛重kg',
original_volume_cbm DECIMAL(18,4) NULL COMMENT '原单据体积或材积m3',
actual_goods_name VARCHAR(255) NULL COMMENT '实际品名',
total_package_count INT UNSIGNED NULL COMMENT '本批次总箱数',
total_volume_raw_cbm DECIMAL(18,6) NULL COMMENT '本批次原始总体积m3',
total_volume_cbm DECIMAL(18,4) NULL COMMENT '本批次业务总体积m3',
volume_weight_kg DECIMAL(18,3) NULL COMMENT '本批次总材积重kg',
created_by BIGINT UNSIGNED NULL COMMENT '创建人或系统主体ID',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id),
UNIQUE KEY uk_measure_external_event (tenant_id, source_code, external_event_no),
KEY idx_measure_inbound_time (tenant_id, inbound_id, created_at),
CONSTRAINT fk_measure_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id)
) ENGINE=InnoDB COMMENT='入库长宽高测量批次和第三方原始数据';
CREATE TABLE IF NOT EXISTS fl_inbound_measurement_item (
id BIGINT UNSIGNED NOT NULL COMMENT '测量明细ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
measurement_id BIGINT UNSIGNED NOT NULL COMMENT '测量批次ID',
inbound_detail_id BIGINT UNSIGNED NULL COMMENT '绑定的订单货物明细ID,平台手工明细可为空',
external_detail_no VARCHAR(128) NULL COMMENT '第三方测量明细唯一号,平台手工明细可为空',
box_code VARCHAR(128) NULL COMMENT '柏悦唛头箱号;设备单箱测量时同一客户内唯一',
scanned_shipment_id VARCHAR(64) NULL COMMENT '设备从Amazon FBA箱标实扫的Shipment ID',
item_no VARCHAR(32) NOT NULL COMMENT '批次内明细号',
box_specification VARCHAR(64) NOT NULL COMMENT '箱规标识',
package_count INT UNSIGNED NOT NULL COMMENT '相同箱规箱数',
length_cm DECIMAL(12,3) NOT NULL COMMENT '原始长度cm',
width_cm DECIMAL(12,3) NOT NULL COMMENT '原始宽度cm',
height_cm DECIMAL(12,3) NOT NULL COMMENT '原始高度cm',
girth_cm DECIMAL(12,3) NOT NULL COMMENT '周长cm,规则为长+2*(宽+高)',
unit_weight_kg DECIMAL(18,3) NULL COMMENT '单箱重量kg,第三方仅回传尺寸时可为空',
unit_volume_cbm DECIMAL(18,4) NOT NULL COMMENT '单箱业务体积m3',
volume_raw_cbm DECIMAL(18,6) NOT NULL COMMENT '原始体积m3',
volume_cbm DECIMAL(18,4) NOT NULL COMMENT '业务体积m3',
total_weight_kg DECIMAL(18,3) NULL COMMENT '本箱规总重量kg',
total_volume_cbm DECIMAL(18,4) NOT NULL COMMENT '本箱规总体积m3',
volume_weight_kg DECIMAL(18,3) NOT NULL COMMENT '本箱规材积重kg',
calculation_rule_version VARCHAR(64) NOT NULL COMMENT '体积计算和舍入规则版本',
source_image_file_id BIGINT UNSIGNED NULL COMMENT '测量设备图片文件ID',
machine_no VARCHAR(64) NULL COMMENT '本次测量设备编号',
device_measured_at DATETIME(3) NULL COMMENT '本次设备测量时间',
over_limit_warning VARCHAR(500) NULL COMMENT '超线提醒快照',
outer_length_cm DECIMAL(12,3) NULL COMMENT '外箱规长度cm',
outer_width_cm DECIMAL(12,3) NULL COMMENT '外箱规宽度cm',
outer_height_cm DECIMAL(12,3) NULL COMMENT '外箱规高度cm',
remark VARCHAR(500) NULL COMMENT '测量明细备注',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '最后更新时间',
PRIMARY KEY (id),
UNIQUE KEY uk_measure_item_no (tenant_id, measurement_id, item_no),
UNIQUE KEY uk_measure_external_detail (tenant_id, inbound_detail_id, external_detail_no),
UNIQUE KEY uk_measure_box_code (tenant_id, box_code),
CONSTRAINT fk_measure_item_batch FOREIGN KEY (measurement_id) REFERENCES fl_inbound_measurement (id)
) ENGINE=InnoDB COMMENT='入库长宽高和箱数测量明细';
CREATE TABLE IF NOT EXISTS fl_mojia_measurement_receipt (
id BIGINT UNSIGNED NOT NULL COMMENT '墨家联调收件记录ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
subject_id BIGINT UNSIGNED NOT NULL COMMENT '设备应用操作主体ID',
box_code VARCHAR(128) NOT NULL COMMENT '设备扫描的原始箱码',
scanned_shipment_id VARCHAR(64) NULL COMMENT '设备从Amazon FBA箱标实扫的Shipment ID',
length_cm DECIMAL(12,3) NOT NULL COMMENT '设备实测长度cm',
width_cm DECIMAL(12,3) NOT NULL COMMENT '设备实测宽度cm',
height_cm DECIMAL(12,3) NOT NULL COMMENT '设备实测高度cm',
weight_kg DECIMAL(18,3) NULL COMMENT '设备实测重量kg',
machine_no VARCHAR(64) NOT NULL COMMENT '设备编号',
measured_at DATETIME(3) NOT NULL COMMENT '设备测量时间或服务接收时间',
image_file_id BIGINT UNSIGNED NOT NULL COMMENT 'File服务中的墨家联调图片ID',
status VARCHAR(24) NOT NULL DEFAULT 'UNMATCHED' COMMENT '测量回执匹配状态:UNMATCHED未匹配;当前首版仅记录未匹配回执',
match_failure_code VARCHAR(16) NOT NULL COMMENT '正式业务匹配失败码',
match_failure_reason VARCHAR(500) NOT NULL COMMENT '脱敏后的匹配失败原因',
request_id VARCHAR(128) NULL COMMENT '请求链路ID',
trace_id VARCHAR(128) NULL COMMENT '调用链ID',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
PRIMARY KEY (id),
KEY idx_mojia_receipt_status_time (tenant_id, status, created_at),
KEY idx_mojia_receipt_box_time (tenant_id, box_code, created_at)
) ENGINE=InnoDB COMMENT='墨家模拟或未匹配入库单的原始测量收件箱';
CREATE TABLE IF NOT EXISTS fl_inbound_carton_label (
id BIGINT UNSIGNED NOT NULL COMMENT '实际箱唛头映射ID(雪花ID)',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID',
order_id BIGINT UNSIGNED NOT NULL COMMENT '订单服务订单ID快照',
inbound_detail_id BIGINT UNSIGNED NOT NULL COMMENT '订单货物明细ID,不建立跨库外键',
cargo_item_no INT UNSIGNED NOT NULL COMMENT '订单货物明细序号快照',
cargo_name VARCHAR(255) NULL COMMENT '货物名称快照',
detail_package_count INT UNSIGNED NOT NULL COMMENT '该订单货物明细箱数快照',
inbound_code VARCHAR(40) NOT NULL COMMENT '入仓码快照',
box_code VARCHAR(80) NOT NULL COMMENT '固定二维码内容:入仓码#总箱数-箱下标',
total_carton_count INT UNSIGNED NOT NULL COMMENT '首次分配时整单总箱数',
carton_index INT UNSIGNED NOT NULL COMMENT '整单从1开始的稳定箱下标',
box_specification VARCHAR(255) NOT NULL COMMENT '订单箱规或货物明细描述快照',
length_cm DECIMAL(12,3) NULL COMMENT '下单箱规长度cm快照',
width_cm DECIMAL(12,3) NULL COMMENT '下单箱规宽度cm快照',
height_cm DECIMAL(12,3) NULL COMMENT '下单箱规高度cm快照',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '首次分配时间',
PRIMARY KEY (id),
UNIQUE KEY uk_carton_label_box_code (tenant_id, box_code),
UNIQUE KEY uk_carton_label_index (tenant_id, inbound_id, carton_index),
KEY idx_carton_label_detail (tenant_id, inbound_id, inbound_detail_id),
CONSTRAINT fk_carton_label_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id)
) ENGINE=InnoDB COMMENT='首次打印前冻结的实际箱号与订单货物明细映射';
CREATE TABLE IF NOT EXISTS fl_inbound_weight_result (
id BIGINT UNSIGNED NOT NULL COMMENT 'OCR重量结果ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID',
file_id BIGINT UNSIGNED NOT NULL COMMENT '称重图片文件ID',
ocr_provider VARCHAR(64) NULL COMMENT 'OCR服务商编码',
ocr_raw_text VARCHAR(1000) NULL COMMENT 'OCR原始文本',
ocr_weight_value DECIMAL(18,3) NULL COMMENT 'OCR识别原始重量值',
ocr_weight_unit VARCHAR(8) NULL COMMENT 'OCR识别原始单位',
confidence DECIMAL(6,5) NULL COMMENT '识别置信度',
adjusted_weight_value DECIMAL(18,3) NULL COMMENT '人工修正后的原单位重量',
adjusted_weight_unit VARCHAR(8) NULL COMMENT '人工修正后的单位',
final_weight_kg DECIMAL(18,3) NULL COMMENT '确认并统一换算后的重量kg',
recognition_status VARCHAR(24) NOT NULL DEFAULT 'PENDING' COMMENT '重量识别状态:PENDING待识别、PENDING_CONFIRM待确认、NEEDS_MANUAL需人工录入、CONFIRMED已确认',
adjustment_reason VARCHAR(500) NULL COMMENT '人工修正原因',
adjusted_by BIGINT UNSIGNED NULL COMMENT '修正人ID',
adjusted_at DATETIME(3) NULL COMMENT '修正时间',
confirmed_by BIGINT UNSIGNED NULL COMMENT '确认人ID',
confirmed_at DATETIME(3) NULL COMMENT '确认时间',
created_by BIGINT UNSIGNED NOT NULL COMMENT '创建人ID',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_by BIGINT UNSIGNED NOT NULL COMMENT '最后修改人ID',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id),
UNIQUE KEY uk_weight_inbound_file (tenant_id, inbound_id, file_id),
KEY idx_weight_inbound_status (tenant_id, inbound_id, recognition_status),
CONSTRAINT fk_weight_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id)
) ENGINE=InnoDB COMMENT='称重图片OCR原值、人工修正值和确认重量';
CREATE TABLE IF NOT EXISTS fl_inbound_weight_revision (
id BIGINT UNSIGNED NOT NULL COMMENT 'OCR重量修订记录ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID',
weight_result_id BIGINT UNSIGNED NOT NULL COMMENT 'OCR重量结果ID',
revision_no INT UNSIGNED NOT NULL COMMENT '结果版本号,与乐观锁版本一致',
action VARCHAR(24) NOT NULL COMMENT '动作:CORRECT或CONFIRM',
before_weight_value DECIMAL(18,3) NULL COMMENT '变更前原单位重量',
before_weight_unit VARCHAR(8) NULL COMMENT '变更前单位',
after_weight_value DECIMAL(18,3) NULL COMMENT '变更后原单位重量',
after_weight_unit VARCHAR(8) NULL COMMENT '变更后单位',
final_weight_kg DECIMAL(18,3) NULL COMMENT '确认时统一换算后的重量kg',
reason VARCHAR(500) NULL COMMENT '人工修正原因',
operator_id BIGINT UNSIGNED NOT NULL COMMENT '操作人ID',
operator_name VARCHAR(64) NULL COMMENT '操作人名称快照',
operated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '操作时间',
PRIMARY KEY (id),
UNIQUE KEY uk_weight_revision_no (tenant_id, weight_result_id, revision_no),
KEY idx_weight_revision_time (tenant_id, inbound_id, operated_at),
CONSTRAINT fk_weight_revision_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id),
CONSTRAINT fk_weight_revision_result FOREIGN KEY (weight_result_id) REFERENCES fl_inbound_weight_result (id)
) ENGINE=InnoDB COMMENT='OCR重量人工修正和确认的不可变版本历史';
CREATE TABLE IF NOT EXISTS fl_measure_review_record (
id BIGINT UNSIGNED NOT NULL COMMENT '测量审核记录ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID',
measurement_id BIGINT UNSIGNED NULL COMMENT '被审核的测量批次ID',
review_result VARCHAR(24) NOT NULL COMMENT '审核结果',
review_reason VARCHAR(1000) NULL COMMENT '审核或驳回原因',
exception_release TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否异常放行',
release_reason VARCHAR(1000) NULL COMMENT '异常放行原因',
allow_rejected_config TINYINT(1) NOT NULL DEFAULT 0 COMMENT '操作时Nacos异常放行开关值',
reviewer_id BIGINT UNSIGNED NOT NULL COMMENT '审核人ID',
reviewer_name VARCHAR(64) NULL COMMENT '审核人名称快照',
reviewed_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '审核时间',
PRIMARY KEY (id),
KEY idx_review_inbound_time (tenant_id, inbound_id, reviewed_at),
KEY idx_review_exception_release (tenant_id, inbound_id, exception_release, reviewed_at),
CONSTRAINT fk_review_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id),
CONSTRAINT fk_review_measurement FOREIGN KEY (measurement_id) REFERENCES fl_inbound_measurement (id)
) ENGINE=InnoDB COMMENT='测量审核、驳回和异常放行不可变记录';
CREATE TABLE IF NOT EXISTS fl_customer_charge_rule (
id BIGINT UNSIGNED NOT NULL COMMENT '客户计费规则ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
rule_code VARCHAR(50) NOT NULL COMMENT '客户内唯一计费规则编码',
rule_version INT UNSIGNED NOT NULL COMMENT '规则版本号',
customer_id BIGINT UNSIGNED NOT NULL COMMENT '客户逻辑ID',
customer_name VARCHAR(128) NOT NULL COMMENT '客户名称快照',
business_type VARCHAR(32) NOT NULL COMMENT '业务类型',
charge_mode VARCHAR(16) NOT NULL COMMENT '计费口径:KG或CBM',
tax_mode VARCHAR(24) NULL COMMENT '包税或不包税类型',
conversion_factor DECIMAL(18,6) NOT NULL COMMENT '重量体积换算系数',
rounding_scale TINYINT UNSIGNED NOT NULL DEFAULT 3 COMMENT '结果保留小数位数',
rounding_mode VARCHAR(24) NOT NULL DEFAULT 'CEILING' COMMENT '舍入方式',
currency CHAR(3) NOT NULL COMMENT '默认币种',
effective_from DATETIME(3) NOT NULL COMMENT '生效开始时间',
effective_to DATETIME(3) NULL COMMENT '生效结束时间',
status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE' COMMENT '规则状态:ACTIVE启用、DISABLED停用',
remark VARCHAR(500) NULL COMMENT '规则备注',
created_by BIGINT UNSIGNED NOT NULL COMMENT '创建人ID',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_by BIGINT UNSIGNED NOT NULL COMMENT '最后修改人ID',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id),
UNIQUE KEY uk_charge_rule_version (tenant_id, rule_code, rule_version),
KEY idx_charge_rule_match (tenant_id, customer_id, business_type, status, effective_from, effective_to)
) ENGINE=InnoDB COMMENT='客户头程计费规则版本';
CREATE TABLE IF NOT EXISTS fl_carrier_fba_rate (
id BIGINT UNSIGNED NOT NULL COMMENT '船司FBA价格ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '平台价格作用域ID,船司价格固定为0',
price_book_version_id BIGINT UNSIGNED NULL COMMENT '整表价格版本ID;历史兼容价格可为空',
rate_code VARCHAR(50) NOT NULL COMMENT '平台公共价格编码',
rate_version INT UNSIGNED NOT NULL COMMENT '价格版本号',
origin_warehouse_id BIGINT UNSIGNED NULL COMMENT '确认前可为空;确认后绑定平台公共起运仓逻辑ID',
origin_warehouse_code VARCHAR(64) NULL COMMENT '起运仓稳定编码',
origin_warehouse_name VARCHAR(128) NOT NULL COMMENT '起运仓名称快照',
business_type VARCHAR(32) NOT NULL COMMENT '业务或计费类型',
channel_code VARCHAR(64) NULL COMMENT '渠道编码',
price_model VARCHAR(32) NOT NULL DEFAULT 'FBA_WAREHOUSE_RATE' COMMENT '价格模型',
carrier_id BIGINT UNSIGNED NULL COMMENT '船司逻辑ID',
carrier_name VARCHAR(128) NULL COMMENT '历史承运商名称;渠道价格可为空',
destination_type VARCHAR(16) NULL COMMENT '目的地类型:FBA或OVERSEAS;REGION_WEIGHT_TIER区域阶梯行为空',
destination_code VARCHAR(32) NULL COMMENT 'FBA目的仓编码;区域阶梯模型为空',
destination_warehouse_id BIGINT UNSIGNED NULL COMMENT '平台海外仓稳定ID;不存环境专属ID',
destination_name VARCHAR(128) NULL COMMENT '目的仓名称快照',
region_code VARCHAR(16) NULL COMMENT '区域阶梯模型区域编码',
pricing_tax_mode VARCHAR(16) NULL COMMENT '报价包税维度;独立于订单tax_mode',
weight_from_kg DECIMAL(18,6) NULL COMMENT '区域阶梯起始计费重量KG',
charge_unit VARCHAR(16) NOT NULL COMMENT '计费单位:KG或CBM',
unit_price DECIMAL(18,6) NOT NULL COMMENT '基础单价',
currency CHAR(3) NOT NULL COMMENT '币种',
additional_fee DECIMAL(18,2) NOT NULL DEFAULT 0 COMMENT '固定附加费',
source_sheet_name VARCHAR(128) NULL COMMENT '原始Excel Sheet',
source_row_number INT UNSIGNED NULL COMMENT '原始Excel行号',
source_destination_text VARCHAR(1000) NULL COMMENT '原始组合仓库或区域文本',
effective_from DATETIME(3) NOT NULL COMMENT '生效开始时间',
effective_to DATETIME(3) NULL COMMENT '生效结束时间',
status VARCHAR(24) NOT NULL DEFAULT 'ACTIVE' COMMENT '价格状态:PENDING_CONFIRMATION、ACTIVE或SUPERSEDED;分别表示待确认、已生效、已被替代;FAILED表示解析失败',
remark VARCHAR(500) NULL COMMENT '价格备注',
created_by BIGINT UNSIGNED NOT NULL COMMENT '创建人ID',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_by BIGINT UNSIGNED NOT NULL COMMENT '最后修改人ID',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id),
UNIQUE KEY uk_fba_rate_version (tenant_id, rate_code, rate_version),
KEY idx_fba_rate_match (tenant_id, origin_warehouse_id, business_type, channel_code, carrier_id, destination_type, destination_code, destination_warehouse_id, charge_unit, status, effective_from, effective_to),
KEY idx_fba_rate_book (price_book_version_id, channel_code, origin_warehouse_id, price_model, status),
KEY idx_fba_rate_destination (price_book_version_id, channel_code, origin_warehouse_id, destination_type, destination_warehouse_id, destination_code),
KEY idx_fba_rate_region (price_book_version_id, channel_code, origin_warehouse_id, region_code, weight_from_kg)
) ENGINE=InnoDB COMMENT='船司FBA目的仓价格版本';
ALTER TABLE fl_carrier_fba_rate
MODIFY COLUMN status VARCHAR(24) NOT NULL DEFAULT 'ACTIVE'
COMMENT '价格状态:PENDING_CONFIRMATION、ACTIVE或SUPERSEDED';
CREATE TABLE IF NOT EXISTS fl_inbound_fee (
id BIGINT UNSIGNED NOT NULL COMMENT '入库费用ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID',
fee_no VARCHAR(40) NOT NULL COMMENT '客户内唯一费用单号',
fee_version INT UNSIGNED NOT NULL DEFAULT 1 COMMENT '费用快照版本',
charge_rule_id BIGINT UNSIGNED NULL COMMENT '历史客户计费规则ID;渠道价格可为空',
charge_rule_version INT UNSIGNED NULL COMMENT '历史客户规则版本快照',
carrier_rate_id BIGINT UNSIGNED NOT NULL COMMENT '船司FBA价格ID',
carrier_rate_version INT UNSIGNED NOT NULL COMMENT '船司价格版本快照',
price_book_version_id BIGINT UNSIGNED NULL COMMENT '整表价格版本ID快照',
rule_code VARCHAR(50) NULL COMMENT '历史客户规则编码快照',
customer_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID快照',
customer_name VARCHAR(128) NOT NULL COMMENT '客户名称快照',
business_type VARCHAR(32) NOT NULL COMMENT '业务类型快照',
tax_mode VARCHAR(24) NULL COMMENT '订单买单或退税模式快照',
pricing_tax_mode VARCHAR(16) NULL COMMENT '报价包税维度快照',
rounding_scale TINYINT UNSIGNED NULL COMMENT '历史客户规则计费量保留位数快照',
rounding_mode VARCHAR(24) NULL COMMENT '历史客户规则舍入方式快照',
rule_effective_from DATETIME(3) NULL COMMENT '历史客户规则生效开始快照',
rule_effective_to DATETIME(3) NULL COMMENT '客户规则生效结束快照',
rate_code VARCHAR(50) NOT NULL COMMENT '船司价格编码快照',
origin_warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '起运仓ID快照',
origin_warehouse_name VARCHAR(128) NOT NULL COMMENT '起运仓名称快照',
channel_code VARCHAR(64) NOT NULL COMMENT '渠道编码快照',
carrier_id BIGINT UNSIGNED NULL COMMENT '历史承运商ID;渠道价格可为空',
carrier_name VARCHAR(128) NULL COMMENT '历史承运商名称;渠道价格可为空',
destination_type VARCHAR(16) NULL COMMENT '目的地类型快照:FBA或OVERSEAS',
destination_warehouse_id BIGINT UNSIGNED NULL COMMENT '海外仓或FBA目的仓ID快照',
destination_name VARCHAR(128) NULL COMMENT '目的仓名称快照',
destination_code VARCHAR(32) NULL COMMENT 'FBA仓编码快照;海外仓可为空',
rate_effective_from DATETIME(3) NOT NULL COMMENT '价格生效开始快照',
rate_effective_to DATETIME(3) NULL COMMENT '价格生效结束快照',
charge_mode VARCHAR(16) NULL COMMENT '计费口径:KG或CBM',
conversion_factor DECIMAL(18,6) NULL COMMENT '历史客户规则使用的换算系数快照',
actual_weight_kg DECIMAL(18,3) NOT NULL COMMENT '试算时实际重量kg',
actual_volume_cbm DECIMAL(18,4) NOT NULL COMMENT '试算时实际体积m3',
converted_quantity DECIMAL(18,4) NOT NULL COMMENT '换算后的重量或体积',
chargeable_quantity DECIMAL(18,4) NOT NULL COMMENT '最终计费量',
charge_unit VARCHAR(16) NOT NULL COMMENT '计费单位',
currency CHAR(3) NOT NULL COMMENT '币种',
subtotal_amount DECIMAL(18,2) NOT NULL COMMENT '基础费用金额',
additional_amount DECIMAL(18,2) NOT NULL DEFAULT 0 COMMENT '附加费金额',
total_amount DECIMAL(18,2) NOT NULL COMMENT '总金额',
fee_status VARCHAR(24) NOT NULL DEFAULT 'CALCULATED' COMMENT '费用状态:CALCULATED已计算、SUPERSEDED已被新版本替代',
review_status VARCHAR(24) NOT NULL DEFAULT 'NOT_SUBMITTED' COMMENT '费用审核状态:NOT_SUBMITTED未提交、PENDING待审核、APPROVED已通过、REJECTED已驳回、CONFIRMED已确认',
exception_release TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否基于测量异常放行生成',
calculated_by BIGINT UNSIGNED NOT NULL COMMENT '试算人ID',
calculated_at DATETIME(3) NOT NULL COMMENT '试算时间',
confirmed_by BIGINT UNSIGNED NULL COMMENT '确认人ID',
confirmed_at DATETIME(3) NULL COMMENT '确认时间',
sent_by BIGINT UNSIGNED NULL COMMENT '发送人ID',
sent_at DATETIME(3) NULL COMMENT '发送客户时间',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
PRIMARY KEY (id),
UNIQUE KEY uk_inbound_fee_no (tenant_id, fee_no),
UNIQUE KEY uk_inbound_fee_version (tenant_id, inbound_id, fee_version),
KEY idx_inbound_fee_status (tenant_id, inbound_id, fee_status, review_status),
CONSTRAINT fk_fee_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id),
CONSTRAINT fk_fee_charge_rule FOREIGN KEY (charge_rule_id) REFERENCES fl_customer_charge_rule (id),
CONSTRAINT fk_fee_carrier_rate FOREIGN KEY (carrier_rate_id) REFERENCES fl_carrier_fba_rate (id)
) ENGINE=InnoDB COMMENT='入库头程费用计算和确认快照';
CREATE TABLE IF NOT EXISTS fl_inbound_fee_item (
id BIGINT UNSIGNED NOT NULL COMMENT '费用明细ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
fee_id BIGINT UNSIGNED NOT NULL COMMENT '费用头ID',
item_no INT UNSIGNED NOT NULL COMMENT '费用单内序号',
fee_code VARCHAR(50) NOT NULL COMMENT '费用项目编码',
fee_name VARCHAR(128) NOT NULL COMMENT '费用项目名称快照',
quantity DECIMAL(18,4) NOT NULL COMMENT '计费数量',
unit VARCHAR(16) NOT NULL COMMENT '计费单位',
unit_price DECIMAL(18,6) NOT NULL COMMENT '单价',
amount DECIMAL(18,2) NOT NULL COMMENT '金额',
currency CHAR(3) NOT NULL COMMENT '币种',
automatic_flag TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否自动计算',
remark VARCHAR(500) NULL COMMENT '费用备注',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
PRIMARY KEY (id),
UNIQUE KEY uk_fee_item_no (tenant_id, fee_id, item_no),
CONSTRAINT fk_fee_item_header FOREIGN KEY (fee_id) REFERENCES fl_inbound_fee (id)
) ENGINE=InnoDB COMMENT='入库头程费用项目明细';
CREATE TABLE IF NOT EXISTS fl_inbound_pallet_allocation (
id BIGINT UNSIGNED NOT NULL COMMENT '头程托盘分配引用ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID',
allocation_no INT UNSIGNED NOT NULL COMMENT '入库单内分配序号',
warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '仓库服务仓库ID',
warehouse_resource_tenant_id BIGINT UNSIGNED NOT NULL COMMENT '平台公共仓储资源固定使用 0;tenant_id 仍是客户隔离键',
pallet_id BIGINT UNSIGNED NOT NULL COMMENT '仓库服务托盘ID',
pallet_code VARCHAR(50) NOT NULL COMMENT '托盘编码快照',
pallet_capacity_cbm DECIMAL(18,4) NOT NULL COMMENT '托盘可用容积快照',
allocated_volume_cbm DECIMAL(18,4) NOT NULL COMMENT '本托盘分配体积',
allocated_package_count INT UNSIGNED NULL COMMENT '本托盘分配箱数',
location_id BIGINT UNSIGNED NOT NULL COMMENT '仓库服务库位ID',
location_code VARCHAR(50) NOT NULL COMMENT '库位编码快照',
reservation_no VARCHAR(64) NOT NULL COMMENT '仓库库位预占单号',
allocation_status VARCHAR(24) NOT NULL DEFAULT 'RESERVED' COMMENT '托盘库位分配状态:RESERVED已预占、CONFIRMED已确认、TRANSFERRED已转移、RELEASED已释放',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
PRIMARY KEY (id),
UNIQUE KEY uk_pallet_allocation_no (tenant_id, inbound_id, allocation_no),
UNIQUE KEY uk_pallet_allocation_pallet (tenant_id, inbound_id, pallet_id),
KEY idx_allocation_location (tenant_id, warehouse_id, location_id, allocation_status),
CONSTRAINT fk_allocation_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id)
) ENGINE=InnoDB COMMENT='头程侧托盘、库位和仓库预占结果引用';
CREATE TABLE IF NOT EXISTS fl_inbound_file (
id BIGINT UNSIGNED NOT NULL COMMENT '入库单文件关系ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID',
file_id BIGINT UNSIGNED NOT NULL COMMENT '文件服务文件ID',
file_type VARCHAR(24) NOT NULL COMMENT '文件类型:RELATED、MARK、PC_ORDER、MEASURE或WEIGHT',
file_name_snapshot VARCHAR(255) NOT NULL COMMENT '文件名快照',
visibility VARCHAR(16) NOT NULL DEFAULT 'INTERNAL' COMMENT '可见范围',
file_version INT UNSIGNED NOT NULL DEFAULT 1 COMMENT '业务文件版本',
uploaded_by BIGINT UNSIGNED NOT NULL COMMENT '上传人ID',
uploaded_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '上传时间',
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
PRIMARY KEY (id),
UNIQUE KEY uk_inbound_file_relation (tenant_id, inbound_id, file_id, file_type),
KEY idx_inbound_file_type (tenant_id, inbound_id, file_type, deleted),
CONSTRAINT fk_file_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id)
) ENGINE=InnoDB COMMENT='入库单与文件中心对象的分类关系';
CREATE TABLE IF NOT EXISTS fl_inbound_hold_record (
id BIGINT UNSIGNED NOT NULL COMMENT '扣货记录ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID',
hold_action VARCHAR(16) NOT NULL COMMENT '动作:HOLD或RELEASE',
reason VARCHAR(1000) NOT NULL COMMENT '扣货或解除原因',
evidence_file_id BIGINT UNSIGNED NULL COMMENT '证据文件ID',
operator_id BIGINT UNSIGNED NOT NULL COMMENT '操作人ID',
operator_name VARCHAR(64) NULL COMMENT '操作人名称快照',
operated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '操作时间',
PRIMARY KEY (id),
KEY idx_hold_inbound_time (tenant_id, inbound_id, operated_at),
CONSTRAINT fk_hold_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id)
) ENGINE=InnoDB COMMENT='入库单扣货和解除扣货不可变历史';
CREATE TABLE IF NOT EXISTS fl_inbound_internal_note (
id BIGINT UNSIGNED NOT NULL COMMENT '客服内部备注ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID',
content VARCHAR(2000) NOT 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 '创建时间',
PRIMARY KEY (id),
KEY idx_internal_note_time (tenant_id, inbound_id, created_at),
CONSTRAINT fk_internal_note_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id)
) ENGINE=InnoDB COMMENT='入库单客服内部备注历史';
CREATE TABLE IF NOT EXISTS fl_inbound_status_history (
id BIGINT UNSIGNED NOT NULL COMMENT '入库状态历史ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID',
from_status VARCHAR(24) NULL COMMENT '变更前状态',
to_status VARCHAR(24) NOT NULL COMMENT '变更后状态',
action VARCHAR(32) NOT NULL COMMENT '触发动作',
reason VARCHAR(1000) NULL COMMENT '变更原因',
operator_id BIGINT UNSIGNED NOT NULL COMMENT '操作人ID',
operator_name VARCHAR(64) NULL COMMENT '操作人名称快照',
occurred_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '发生时间',
PRIMARY KEY (id),
KEY idx_inbound_history_time (tenant_id, inbound_id, occurred_at),
CONSTRAINT fk_history_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id)
) ENGINE=InnoDB COMMENT='入库单流程状态不可变历史';
CREATE TABLE IF NOT EXISTS fl_inbound_outbox (
id BIGINT UNSIGNED NOT NULL COMMENT '领域事件全局ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '事件所属客户ID和数据隔离键',
aggregate_type VARCHAR(32) NOT NULL DEFAULT 'INBOUND' COMMENT '聚合类型',
aggregate_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID',
event_type VARCHAR(64) NOT NULL COMMENT '事件类型',
payload_json JSON NOT NULL COMMENT '事件负载快照',
publish_status VARCHAR(16) NOT NULL DEFAULT 'PENDING' COMMENT '事件发布状态:PENDING待发布、PUBLISHED已发布、FAILED发布失败',
retry_count INT NOT NULL DEFAULT 0 COMMENT '重试次数',
next_retry_at DATETIME(3) NULL COMMENT '下次重试时间',
published_at DATETIME(3) NULL COMMENT '发布时间',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
PRIMARY KEY (id),
KEY idx_inbound_outbox_publish (publish_status, next_retry_at, created_at)
) ENGINE=InnoDB COMMENT='入库事务Outbox事件表';
CREATE TABLE IF NOT EXISTS fl_inbound_idempotency (
id BIGINT UNSIGNED NOT NULL COMMENT '入库写操作幂等记录ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
operation VARCHAR(40) NOT NULL COMMENT '操作类型',
operator_id BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '操作者或外部应用ID',
inbound_id BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '入库单ID,创建前可为0',
idempotency_key VARCHAR(128) NOT NULL COMMENT '幂等键',
request_hash CHAR(64) NOT NULL COMMENT '请求内容摘要',
status VARCHAR(16) NOT NULL COMMENT '幂等处理状态:PROCESSING处理中、SUCCEEDED已成功、FAILED已失败',
result_json JSON NULL COMMENT '成功结果摘要',
result_resource_id BIGINT UNSIGNED NULL COMMENT '成功结果资源ID',
completed_at DATETIME(3) NULL COMMENT '完成时间',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
PRIMARY KEY (id),
UNIQUE KEY uk_inbound_idempotency (tenant_id, operation, operator_id, idempotency_key),
KEY idx_inbound_idempotency_created (created_at)
) ENGINE=InnoDB COMMENT='第三方测量、审核、费用发送和入库写操作幂等记录';
-- ============================================================================
-- 来源:firstleg\src\main\resources\db\phase2-inbound-measurement-maintenance.sql
-- ============================================================================
-- 二期入库测量与 OCR 维护增量(只增不删,可重复执行)。
-- 执行顺序:phase2-inbound-schema.sql 后执行本文件;已有二期库只需执行本文件。
SET NAMES utf8mb4;
USE baiyue_firstleg;
DROP PROCEDURE IF EXISTS phase2_measure_add_column;
DELIMITER $$
CREATE PROCEDURE phase2_measure_add_column(
IN p_table_name VARCHAR(64),
IN p_column_name VARCHAR(64),
IN p_column_definition TEXT
)
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema = DATABASE()
AND table_name = p_table_name
AND column_name = p_column_name
) THEN
SET @phase2_measure_sql = CONCAT(
'ALTER TABLE `', REPLACE(p_table_name, '`', '``'),
'` ADD COLUMN `', REPLACE(p_column_name, '`', '``'), '` ', p_column_definition
);
PREPARE phase2_measure_statement FROM @phase2_measure_sql;
EXECUTE phase2_measure_statement;
DEALLOCATE PREPARE phase2_measure_statement;
END IF;
END$$
DELIMITER ;
CALL phase2_measure_add_column('fl_inbound_measurement', 'original_document_no',
'VARCHAR(64) NULL COMMENT ''原单据号码快照'' AFTER raw_payload_json');
CALL phase2_measure_add_column('fl_inbound_measurement', 'original_gross_weight_kg',
'DECIMAL(18,3) NULL COMMENT ''原单据毛重kg'' AFTER original_document_no');
CALL phase2_measure_add_column('fl_inbound_measurement', 'original_volume_cbm',
'DECIMAL(18,4) NULL COMMENT ''原单据体积或材积m3'' AFTER original_gross_weight_kg');
CALL phase2_measure_add_column('fl_inbound_measurement', 'actual_goods_name',
'VARCHAR(255) NULL COMMENT ''实际品名'' AFTER original_volume_cbm');
CALL phase2_measure_add_column('fl_inbound_measurement', 'volume_weight_kg',
'DECIMAL(18,3) NULL COMMENT ''本批次总材积重kg'' AFTER total_volume_cbm');
CALL phase2_measure_add_column('fl_inbound_measurement', 'version',
'INT NOT NULL DEFAULT 0 COMMENT ''乐观锁版本号'' AFTER updated_at');
CALL phase2_measure_add_column('fl_inbound_measurement_item', 'box_specification',
'VARCHAR(64) NOT NULL DEFAULT '''' COMMENT ''箱规标识'' AFTER item_no');
CALL phase2_measure_add_column('fl_inbound_measurement_item', 'box_code',
'VARCHAR(128) NULL COMMENT ''柏悦唛头箱号;设备单箱测量时同一客户内唯一'' AFTER external_detail_no');
CALL phase2_measure_add_column('fl_inbound_measurement_item', 'girth_cm',
'DECIMAL(12,3) NOT NULL DEFAULT 0 COMMENT ''周长cm,规则为长+2*(宽+高)'' AFTER height_cm');
CALL phase2_measure_add_column('fl_inbound_measurement_item', 'unit_weight_kg',
'DECIMAL(18,3) NULL COMMENT ''单箱重量kg'' AFTER girth_cm');
CALL phase2_measure_add_column('fl_inbound_measurement_item', 'unit_volume_cbm',
'DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT ''单箱业务体积m3'' AFTER unit_weight_kg');
CALL phase2_measure_add_column('fl_inbound_measurement_item', 'scanned_shipment_id',
'VARCHAR(64) NULL COMMENT ''设备从Amazon FBA箱标实扫的Shipment ID'' AFTER box_code');
CALL phase2_measure_add_column('fl_mojia_measurement_receipt', 'scanned_shipment_id',
'VARCHAR(64) NULL COMMENT ''设备从Amazon FBA箱标实扫的Shipment ID'' AFTER box_code');
CALL phase2_measure_add_column('fl_inbound_measurement_item', 'total_weight_kg',
'DECIMAL(18,3) NULL COMMENT ''本箱规总重量kg'' AFTER volume_cbm');
CALL phase2_measure_add_column('fl_inbound_measurement_item', 'total_volume_cbm',
'DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT ''本箱规总体积m3'' AFTER total_weight_kg');
CALL phase2_measure_add_column('fl_inbound_measurement_item', 'volume_weight_kg',
'DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT ''本箱规材积重kg'' AFTER total_volume_cbm');
CALL phase2_measure_add_column('fl_inbound_measurement_item', 'over_limit_warning',
'VARCHAR(500) NULL COMMENT ''超线提醒快照'' AFTER source_image_file_id');
CALL phase2_measure_add_column('fl_inbound_measurement_item', 'machine_no',
'VARCHAR(64) NULL COMMENT ''本次测量设备编号'' AFTER source_image_file_id');
CALL phase2_measure_add_column('fl_inbound_measurement_item', 'device_measured_at',
'DATETIME(3) NULL COMMENT ''本次设备测量时间'' AFTER machine_no');
CALL phase2_measure_add_column('fl_inbound_measurement_item', 'outer_length_cm',
'DECIMAL(12,3) NULL COMMENT ''外箱规长度cm'' AFTER over_limit_warning');
CALL phase2_measure_add_column('fl_inbound_measurement_item', 'outer_width_cm',
'DECIMAL(12,3) NULL COMMENT ''外箱规宽度cm'' AFTER outer_length_cm');
CALL phase2_measure_add_column('fl_inbound_measurement_item', 'outer_height_cm',
'DECIMAL(12,3) NULL COMMENT ''外箱规高度cm'' AFTER outer_width_cm');
CALL phase2_measure_add_column('fl_inbound_measurement_item', 'updated_at',
'DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT ''最后更新时间'' AFTER created_at');
SET @has_measure_box_code_index = (
SELECT COUNT(*) FROM information_schema.statistics
WHERE table_schema = DATABASE()
AND table_name = 'fl_inbound_measurement_item'
AND index_name = 'uk_measure_box_code'
);
SET @phase2_measure_sql = IF(
@has_measure_box_code_index = 0,
'ALTER TABLE fl_inbound_measurement_item ADD UNIQUE KEY uk_measure_box_code (tenant_id, box_code)',
'SELECT 1'
);
PREPARE phase2_measure_statement FROM @phase2_measure_sql;
EXECUTE phase2_measure_statement;
DEALLOCATE PREPARE phase2_measure_statement;
ALTER TABLE fl_inbound_measurement_item
MODIFY COLUMN calculation_rule_version VARCHAR(64) NOT NULL COMMENT '体积计算和舍入规则版本';
DROP PROCEDURE IF EXISTS phase2_measure_add_column;
CREATE TABLE IF NOT EXISTS fl_inbound_weight_revision (
id BIGINT UNSIGNED NOT NULL COMMENT 'OCR重量修订记录ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID',
weight_result_id BIGINT UNSIGNED NOT NULL COMMENT 'OCR重量结果ID',
revision_no INT UNSIGNED NOT NULL COMMENT '结果版本号,与乐观锁版本一致',
action VARCHAR(24) NOT NULL COMMENT '动作:CORRECT或CONFIRM',
before_weight_value DECIMAL(18,3) NULL COMMENT '变更前原单位重量',
before_weight_unit VARCHAR(8) NULL COMMENT '变更前单位',
after_weight_value DECIMAL(18,3) NULL COMMENT '变更后原单位重量',
after_weight_unit VARCHAR(8) NULL COMMENT '变更后单位',
final_weight_kg DECIMAL(18,3) NULL COMMENT '确认时统一换算后的重量kg',
reason VARCHAR(500) NULL COMMENT '人工修正原因',
operator_id BIGINT UNSIGNED NOT NULL COMMENT '操作人ID',
operator_name VARCHAR(64) NULL COMMENT '操作人名称快照',
operated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '操作时间',
PRIMARY KEY (id),
UNIQUE KEY uk_weight_revision_no (tenant_id, weight_result_id, revision_no),
KEY idx_weight_revision_time (tenant_id, inbound_id, operated_at),
CONSTRAINT fk_weight_revision_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id),
CONSTRAINT fk_weight_revision_result FOREIGN KEY (weight_result_id) REFERENCES fl_inbound_weight_result (id)
) ENGINE=InnoDB COMMENT='OCR重量人工修正和确认的不可变版本历史';
-- ============================================================================
-- 来源:firstleg\src\main\resources\db\phase2-inbound-weight-audit-migration.sql
-- ============================================================================
-- 二期称重结果审计字段增量迁移(可重复执行)。
-- InboundWeightResultEntity 继承 BaseEntity,持久化需要 created_by、updated_by 两个审计字段。
SET @has_created_by := (
SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = 'fl_inbound_weight_result' AND column_name = 'created_by'
);
SET @created_by_sql := IF(@has_created_by = 0,
'ALTER TABLE fl_inbound_weight_result ADD COLUMN created_by BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT ''创建人ID'' AFTER confirmed_at',
'SELECT 1');
PREPARE created_by_stmt FROM @created_by_sql;
EXECUTE created_by_stmt;
DEALLOCATE PREPARE created_by_stmt;
SET @has_updated_by := (
SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = 'fl_inbound_weight_result' AND column_name = 'updated_by'
);
SET @updated_by_sql := IF(@has_updated_by = 0,
'ALTER TABLE fl_inbound_weight_result ADD COLUMN updated_by BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT ''最后修改人ID'' AFTER created_at',
'SELECT 1');
PREPARE updated_by_stmt FROM @updated_by_sql;
EXECUTE updated_by_stmt;
DEALLOCATE PREPARE updated_by_stmt;
-- ============================================================================
-- 来源:firstleg\src\main\resources\db\phase2-inbound-workflow-migration.sql
-- ============================================================================
-- 业务块4增量迁移:既有二期库补齐异常放行关口查询索引。
-- 可重复执行;新库仍先执行 phase2-inbound-schema.sql。
USE baiyue_firstleg;
SET NAMES utf8mb4;
SET @ddl = IF(
EXISTS(SELECT 1 FROM information_schema.statistics
WHERE table_schema = DATABASE() AND table_name = 'fl_measure_review_record'
AND index_name = 'idx_review_exception_release'),
'SELECT 1',
'ALTER TABLE fl_measure_review_record ADD INDEX idx_review_exception_release (tenant_id, inbound_id, exception_release, reviewed_at)'
);
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-- ============================================================================
-- 来源:firstleg\src\main\resources\db\phase2-measurement-openapi.sql
-- ============================================================================
SET NAMES utf8mb4;
USE baiyue_firstleg;
-- 既有二期数据库增量:先确保 phase2-inbound-schema.sql 中的测量和幂等表已存在。
CREATE TABLE IF NOT EXISTS fl_third_party_call_log (
id BIGINT UNSIGNED NOT NULL COMMENT '第三方调用日志ID(雪花ID)',
tenant_id BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '第三方未知/未解析主体哨兵值;不是平台客户记录或平台作用域,不得用于业务数据授权',
app_key VARCHAR(128) NULL COMMENT '第三方应用标识',
signature_subject VARCHAR(128) NULL COMMENT '签名主体快照',
request_method VARCHAR(8) NOT NULL COMMENT 'HTTP方法',
request_path VARCHAR(255) NOT NULL COMMENT '签名使用的规范路径',
request_timestamp VARCHAR(32) NULL COMMENT '请求头Unix毫秒时间戳原值',
nonce VARCHAR(128) NULL COMMENT '请求Nonce',
idempotency_key VARCHAR(128) NULL COMMENT 'HTTP幂等键,查询接口为空',
idempotency_anchor TINYINT UNSIGNED NULL COMMENT '签名通过后首个请求为1,重复调用为空',
signature_value VARCHAR(128) NULL COMMENT '第三方签名原值',
request_hash CHAR(64) NOT NULL COMMENT '原始请求体SHA-256小写十六进制',
raw_payload LONGTEXT NOT NULL COMMENT '未经重新序列化的UTF-8原始请求体',
processing_status VARCHAR(16) NOT NULL COMMENT 'PROCESSING、ACCEPTED或REJECTED',
result_code VARCHAR(32) NULL COMMENT '统一响应业务码',
result_message VARCHAR(1000) NULL COMMENT '处理结果或失败原因',
result_json JSON NULL COMMENT '首次成功结果快照',
requested_at DATETIME(3) NOT NULL COMMENT '服务端接收时间',
processed_at DATETIME(3) NULL COMMENT '处理完成时间',
PRIMARY KEY (id),
KEY idx_third_call_app_time (tenant_id, app_key, requested_at),
KEY idx_third_call_idempotency (tenant_id, app_key, idempotency_key),
KEY idx_third_call_nonce (tenant_id, app_key, nonce),
UNIQUE KEY uk_third_call_idempotency_owner (tenant_id, app_key, idempotency_key, idempotency_anchor)
) ENGINE=InnoDB COMMENT='第三方测量开放接口原始请求、签名主体和处理结果留档';
-- 既有表也要同步未知主体 0 的语义,避免历史注释继续误导为真实租户。
SET @third_party_tenant_comment = '第三方未知/未解析主体哨兵值;不是平台客户记录或平台作用域,不得用于业务数据授权';
SET @ddl = IF(
(SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = 'fl_third_party_call_log'
AND column_name = 'tenant_id' AND column_comment = @third_party_tenant_comment) = 1,
'SELECT 1',
'ALTER TABLE fl_third_party_call_log MODIFY COLUMN tenant_id BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT ''第三方未知/未解析主体哨兵值;不是平台客户记录或平台作用域,不得用于业务数据授权'''
);
PREPARE statement FROM @ddl; EXECUTE statement; DEALLOCATE PREPARE statement;
-- ============================================================================
-- 来源:firstleg\src\main\resources\db\phase2-third-party-single-measurement-migration.sql
-- ============================================================================
-- 二期第三方单条箱规 + 单张图片绑定增量;可重复执行。
USE baiyue_firstleg;
DROP PROCEDURE IF EXISTS phase2_add_measurement_column;
DELIMITER $$
CREATE PROCEDURE phase2_add_measurement_column(
IN p_table VARCHAR(64), IN p_column VARCHAR(64), IN p_definition TEXT)
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = p_table AND COLUMN_NAME = p_column
) THEN
SET @ddl = CONCAT('ALTER TABLE `', p_table, '` ADD COLUMN `', p_column, '` ', p_definition);
PREPARE statement FROM @ddl;
EXECUTE statement;
DEALLOCATE PREPARE statement;
END IF;
END$$
DELIMITER ;
CALL phase2_add_measurement_column(
'fl_inbound_measurement', 'external_request_hash',
'CHAR(64) NULL COMMENT ''第三方data原文和图片内容联合请求哈希'' AFTER external_event_no');
CALL phase2_add_measurement_column(
'fl_inbound_measurement_item', 'inbound_detail_id',
'BIGINT UNSIGNED NULL COMMENT ''绑定的订单货物明细ID,平台手工明细可为空'' AFTER measurement_id');
CALL phase2_add_measurement_column(
'fl_inbound_measurement_item', 'external_detail_no',
'VARCHAR(128) NULL COMMENT ''第三方测量明细唯一号,平台手工明细可为空'' AFTER inbound_detail_id');
DROP PROCEDURE IF EXISTS phase2_add_measurement_column;
SET @has_index = (
SELECT COUNT(*) FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'fl_inbound_measurement_item'
AND INDEX_NAME = 'uk_measure_external_detail'
);
SET @ddl = IF(
@has_index = 0,
'ALTER TABLE fl_inbound_measurement_item ADD UNIQUE KEY uk_measure_external_detail (tenant_id, inbound_detail_id, external_detail_no)',
'SELECT 1'
);
PREPARE statement FROM @ddl;
EXECUTE statement;
DEALLOCATE PREPARE statement;
-- ============================================================================
-- 来源:firstleg\src\main\resources\db\phase2-fba-pricing-migration.sql
-- ============================================================================
SET NAMES utf8mb4;
USE baiyue_firstleg;
-- 二期 FBA 计费增量迁移。通过 information_schema 判断,允许重复部署。
DROP PROCEDURE IF EXISTS phase2_fba_add_column;
DELIMITER $$
CREATE PROCEDURE phase2_fba_add_column(IN p_table VARCHAR(64), IN p_column VARCHAR(64), IN p_definition TEXT)
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = p_table AND COLUMN_NAME = p_column
) THEN
SET @sql = CONCAT('ALTER TABLE `', p_table, '` ADD COLUMN `', p_column, '` ', p_definition);
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
END IF;
END$$
DELIMITER ;
CALL phase2_fba_add_column('fl_inbound_order', 'carrier_id', "BIGINT UNSIGNED NULL COMMENT '计费船司逻辑ID' AFTER channel_name");
CALL phase2_fba_add_column('fl_inbound_order', 'carrier_name', "VARCHAR(128) NULL COMMENT '计费船司名称快照' AFTER carrier_id");
CALL phase2_fba_add_column('fl_carrier_fba_rate', 'business_type', "VARCHAR(32) NOT NULL DEFAULT 'FBA' COMMENT '业务或计费类型' AFTER origin_warehouse_name");
CALL phase2_fba_add_column('fl_customer_charge_rule', 'version', "INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号' AFTER deleted");
CALL phase2_fba_add_column('fl_carrier_fba_rate', 'version', "INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号' AFTER deleted");
CALL phase2_fba_add_column('fl_inbound_fee', 'rule_code', "VARCHAR(50) NOT NULL DEFAULT '' COMMENT '客户规则编码快照' AFTER carrier_rate_version");
CALL phase2_fba_add_column('fl_inbound_fee', 'customer_id', "BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '客户ID快照' AFTER rule_code");
CALL phase2_fba_add_column('fl_inbound_fee', 'customer_name', "VARCHAR(128) NOT NULL DEFAULT '' COMMENT '客户名称快照' AFTER customer_id");
CALL phase2_fba_add_column('fl_inbound_fee', 'business_type', "VARCHAR(32) NOT NULL DEFAULT '' COMMENT '业务类型快照' AFTER customer_name");
CALL phase2_fba_add_column('fl_inbound_fee', 'tax_mode', "VARCHAR(24) NULL COMMENT '包税模式快照' AFTER business_type");
CALL phase2_fba_add_column('fl_inbound_fee', 'rounding_scale', "TINYINT UNSIGNED NOT NULL DEFAULT 3 COMMENT '计费量保留位数快照' AFTER tax_mode");
CALL phase2_fba_add_column('fl_inbound_fee', 'rounding_mode', "VARCHAR(24) NOT NULL DEFAULT 'CEILING' COMMENT '舍入方式快照' AFTER rounding_scale");
CALL phase2_fba_add_column('fl_inbound_fee', 'rule_effective_from', "DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '客户规则生效开始快照' AFTER rounding_mode");
CALL phase2_fba_add_column('fl_inbound_fee', 'rule_effective_to', "DATETIME(3) NULL COMMENT '客户规则生效结束快照' AFTER rule_effective_from");
CALL phase2_fba_add_column('fl_inbound_fee', 'rate_code', "VARCHAR(50) NOT NULL DEFAULT '' COMMENT '船司价格编码快照' AFTER rule_effective_to");
CALL phase2_fba_add_column('fl_inbound_fee', 'origin_warehouse_id', "BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '起运仓ID快照' AFTER rate_code");
CALL phase2_fba_add_column('fl_inbound_fee', 'origin_warehouse_name', "VARCHAR(128) NOT NULL DEFAULT '' COMMENT '起运仓名称快照' AFTER origin_warehouse_id");
CALL phase2_fba_add_column('fl_inbound_fee', 'channel_code', "VARCHAR(64) NOT NULL DEFAULT '' COMMENT '渠道编码快照' AFTER origin_warehouse_name");
CALL phase2_fba_add_column('fl_inbound_fee', 'carrier_id', "BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '船司ID快照' AFTER channel_code");
CALL phase2_fba_add_column('fl_inbound_fee', 'carrier_name', "VARCHAR(128) NOT NULL DEFAULT '' COMMENT '船司名称快照' AFTER carrier_id");
CALL phase2_fba_add_column('fl_inbound_fee', 'destination_code', "VARCHAR(32) NOT NULL DEFAULT '' COMMENT 'FBA仓编码快照' AFTER carrier_name");
CALL phase2_fba_add_column('fl_inbound_fee', 'rate_effective_from', "DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '价格生效开始快照' AFTER destination_code");
CALL phase2_fba_add_column('fl_inbound_fee', 'rate_effective_to', "DATETIME(3) NULL COMMENT '价格生效结束快照' AFTER rate_effective_from");
CALL phase2_fba_add_column('fl_inbound_idempotency', 'result_resource_id', "BIGINT UNSIGNED NULL COMMENT '成功结果资源ID' AFTER result_json");
CALL phase2_fba_add_column('fl_inbound_pallet_allocation', 'warehouse_resource_tenant_id',
"BIGINT UNSIGNED NULL COMMENT '平台公共仓储资源固定使用 0;tenant_id 仍是客户隔离键' AFTER warehouse_id");
-- 仓储资源是平台公共主数据;存量分配快照统一迁移到平台作用域 0,再收口非空约束。
UPDATE fl_inbound_pallet_allocation
SET warehouse_resource_tenant_id = 0
WHERE warehouse_resource_tenant_id IS NULL OR warehouse_resource_tenant_id <> 0;
SET @resource_tenant_needs_modify = (SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'fl_inbound_pallet_allocation'
AND COLUMN_NAME = 'warehouse_resource_tenant_id'
AND (IS_NULLABLE = 'YES' OR COLUMN_COMMENT <> '平台公共仓储资源固定使用 0;tenant_id 仍是客户隔离键'));
SET @sql = IF(@resource_tenant_needs_modify > 0,
"ALTER TABLE fl_inbound_pallet_allocation MODIFY COLUMN warehouse_resource_tenant_id BIGINT UNSIGNED NOT NULL COMMENT '平台公共仓储资源固定使用 0;tenant_id 仍是客户隔离键'",
'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
DROP PROCEDURE IF EXISTS phase2_fba_add_column;
CREATE TABLE IF NOT EXISTS fl_price_channel_catalog (
id BIGINT UNSIGNED NOT NULL COMMENT '渠道目录ID',
channel_code VARCHAR(64) NOT NULL COMMENT '稳定渠道编码',
display_name VARCHAR(128) NOT NULL COMMENT '平台渠道名称',
price_model VARCHAR(32) NOT NULL COMMENT '默认价格模型',
source_sheet_name VARCHAR(128) NOT NULL COMMENT '来源Sheet名称;海派为同一Sheet',
aliases_json JSON NOT NULL COMMENT '上传名称别名数组',
status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE' COMMENT '目录状态:ACTIVE启用、DISABLED停用',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '最后更新时间',
PRIMARY KEY (id),
UNIQUE KEY uk_price_channel_code (channel_code),
KEY idx_price_channel_status (status, price_model)
) ENGINE=InnoDB COMMENT='平台公共船期价格固定渠道目录';
CREATE TABLE IF NOT EXISTS fl_price_origin_alias (
id BIGINT UNSIGNED NOT NULL COMMENT '起运仓别名ID',
warehouse_code VARCHAR(64) NOT NULL COMMENT '平台仓库稳定编码,不存环境专属ID',
alias_name VARCHAR(128) NOT NULL COMMENT 'Excel起运仓名称别名',
status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE' COMMENT '状态:ACTIVE启用、DISABLED停用',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '最后更新时间',
PRIMARY KEY (id),
UNIQUE KEY uk_price_origin_alias (warehouse_code, alias_name),
KEY idx_price_origin_alias_name (alias_name, status)
) ENGINE=InnoDB COMMENT='价格表起运仓名称别名映射';
CREATE TABLE IF NOT EXISTS fl_price_warehouse_mapping (
id BIGINT UNSIGNED NOT NULL COMMENT '价格仓映射ID',
source_type VARCHAR(24) NOT NULL COMMENT '来源维度:ORIGIN_CODE或OVERSEAS_NAME',
source_value VARCHAR(128) NOT NULL COMMENT '解析后的来源仓编码或海外仓名称',
warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '固定平台公共仓库ID',
warehouse_code VARCHAR(64) NOT NULL COMMENT '平台仓库稳定编码快照',
warehouse_type VARCHAR(16) NOT NULL COMMENT 'DOMESTIC或OVERSEAS',
status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE' COMMENT '状态:ACTIVE启用、DISABLED停用',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '最后更新时间',
PRIMARY KEY (id),
UNIQUE KEY uk_price_warehouse_mapping_source (source_type, source_value),
KEY idx_price_warehouse_mapping_target (warehouse_id, status)
) ENGINE=InnoDB COMMENT='价格表来源仓与固定平台公共仓映射';
CREATE TABLE IF NOT EXISTS fl_price_book_version (
id BIGINT UNSIGNED NOT NULL COMMENT '整表价格版本ID',
tenant_id BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '平台公共价格作用域固定为0',
version_no INT UNSIGNED NOT NULL COMMENT '整表版本号',
original_file_name VARCHAR(255) NOT NULL COMMENT '原始Excel文件名',
source_file_id BIGINT UNSIGNED NULL COMMENT 'File服务原始文件ID',
source_sha256 CHAR(64) NULL COMMENT '原始文件SHA-256',
status VARCHAR(24) NOT NULL DEFAULT 'PENDING_CONFIRMATION' COMMENT '价格版本状态:PENDING_CONFIRMATION待确认、ACTIVE已生效、SUPERSEDED已被替代、FAILED解析失败',
blocking_error_count INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '阻断错误数',
warning_count INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '警告数',
parse_issues_json JSON NULL COMMENT '上传解析问题及待确认重新校验结果',
channel_count INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '解析渠道数',
rate_count INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '解析价格数',
created_by BIGINT UNSIGNED NOT NULL COMMENT '上传价格表的操作人ID',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '上传记录创建时间',
published_by BIGINT UNSIGNED NULL COMMENT '确认并发布价格版本的操作人ID',
published_at DATETIME(3) NULL COMMENT '确认并生效时间',
superseded_at DATETIME(3) NULL COMMENT '被下一版本替换时间',
updated_by BIGINT UNSIGNED NOT NULL COMMENT '最后修改人ID',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '最后更新时间',
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记:0未删除、1已删除',
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id),
UNIQUE KEY uk_price_book_version (tenant_id, version_no),
KEY idx_price_book_status (tenant_id, status, published_at),
KEY idx_price_book_source_hash (tenant_id, source_sha256)
) ENGINE=InnoDB COMMENT='平台公共船期价格整表版本';
CREATE TABLE IF NOT EXISTS fl_price_book_channel (
id BIGINT UNSIGNED NOT NULL COMMENT '版本渠道快照ID',
price_book_version_id BIGINT UNSIGNED NOT NULL COMMENT '整表价格版本ID',
channel_code VARCHAR(64) NOT NULL COMMENT '固定渠道编码',
channel_name VARCHAR(128) NOT NULL COMMENT '渠道名称快照',
price_model VARCHAR(32) NOT NULL COMMENT '价格模型快照',
source_sheet_name VARCHAR(128) NOT NULL COMMENT '原始Excel Sheet',
estimated_departure_date DATE NULL COMMENT '标准预计开船日期',
estimated_departure_text VARCHAR(128) NULL COMMENT '原始预计开船日期文本',
latest_warehouse_entry VARCHAR(128) NULL COMMENT '最晚进仓时间',
vessel_name VARCHAR(128) NULL COMMENT '船名',
voyage_no VARCHAR(64) NULL COMMENT '航次',
reference_transit VARCHAR(128) NULL COMMENT '参考时效',
surcharge_description VARCHAR(2000) NULL COMMENT '附加费说明,仅展示',
remark VARCHAR(2000) NULL COMMENT '渠道备注',
created_by BIGINT UNSIGNED NOT NULL COMMENT '创建人ID',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '渠道快照创建时间',
updated_by BIGINT UNSIGNED NOT NULL COMMENT '最后修改人ID',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '渠道快照最后更新时间',
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id),
UNIQUE KEY uk_price_book_channel (price_book_version_id, channel_code),
KEY idx_price_book_channel_code (channel_code, price_book_version_id)
) ENGINE=InnoDB COMMENT='价格版本渠道及船期快照';
INSERT INTO fl_price_channel_catalog
(id, channel_code, display_name, price_model, source_sheet_name, aliases_json, status)
VALUES
(1, 'MATSON_CLX_TRUCK', '美森正班海卡', 'FBA_WAREHOUSE_RATE', '美森正班海卡', JSON_ARRAY('美森正班海卡'), 'ACTIVE'),
(2, 'MATSON_MAX_TRUCK', '美森加班海卡', 'FBA_WAREHOUSE_RATE', '美森加班海卡', JSON_ARRAY('美森加班海卡'), 'ACTIVE'),
(3, 'EXX_TRUCK', 'EXX海卡', 'FBA_WAREHOUSE_RATE', 'EXX海卡', JSON_ARRAY('EXX海卡'), 'ACTIVE'),
(4, 'HEDE_TRUCK', '合德海卡', 'FBA_WAREHOUSE_RATE', '合德海卡', JSON_ARRAY('合德海卡'), 'ACTIVE'),
(5, 'EXX_RAIL_NY', '纽约EXX海铁', 'FBA_WAREHOUSE_RATE', '纽约EXX海铁', JSON_ARRAY('纽约EXX海铁'), 'ACTIVE'),
(6, 'OA_LA_TRUCK', '洛杉矶普船海卡', 'FBA_WAREHOUSE_RATE', '洛杉矶普船海卡', JSON_ARRAY('洛杉矶普船海卡'), 'ACTIVE'),
(7, 'OA_LA_FULL_TRUCK', '洛杉矶普船全卡', 'FBA_WAREHOUSE_RATE', '洛杉矶普船全卡', JSON_ARRAY('洛杉矶普船全卡'), 'ACTIVE'),
(8, 'OA_CHICAGO_RAIL', '芝加哥普船海铁', 'FBA_WAREHOUSE_RATE', '芝加哥普船海铁', JSON_ARRAY('芝加哥普船海铁'), 'ACTIVE'),
(9, 'OA_HOUSTON_TRUCK', '休斯顿普船海卡', 'FBA_WAREHOUSE_RATE', '休斯顿普船海卡', JSON_ARRAY('休斯顿普船海卡'), 'ACTIVE'),
(10, 'OA_NEW_YORK_TRUCK', '纽约普船海卡', 'FBA_WAREHOUSE_RATE', '纽约普船海卡', JSON_ARRAY('纽约普船海卡'), 'ACTIVE'),
(11, 'OA_SAVANNAH_TRUCK', '萨凡纳普船海卡', 'FBA_WAREHOUSE_RATE', '萨凡纳普船海卡', JSON_ARRAY('萨凡纳普船海卡'), 'ACTIVE'),
(12, 'MATSON_CLX_PARCEL', '美森正班海派', 'REGION_WEIGHT_TIER', '海派', JSON_ARRAY('美森正班海派'), 'ACTIVE'),
(13, 'MATSON_MAX_PARCEL', '美森加班海派', 'REGION_WEIGHT_TIER', '海派', JSON_ARRAY('美森加班海派'), 'ACTIVE'),
(14, 'HEDE_PARCEL', '合德海派', 'REGION_WEIGHT_TIER', '海派', JSON_ARRAY('合德海派'), 'ACTIVE'),
(15, 'OA_PARCEL', '普船海派', 'REGION_WEIGHT_TIER', '海派', JSON_ARRAY('普船海派'), 'ACTIVE')
ON DUPLICATE KEY UPDATE
display_name = VALUES(display_name), price_model = VALUES(price_model),
source_sheet_name = VALUES(source_sheet_name), aliases_json = VALUES(aliases_json), status = VALUES(status);
INSERT INTO fl_price_origin_alias (id, warehouse_code, alias_name, status)
VALUES
(1, 'CN-YIWU-01', '华东仓(义乌)', 'ACTIVE'),
(2, 'CN-YIWU-01', '义乌仓', 'ACTIVE'),
(3, 'CN-YIWU-01', '义乌', 'ACTIVE'),
(4, 'CN-DONGGUAN-01', '华南仓(深圳)', 'ACTIVE'),
(5, 'CN-DONGGUAN-01', '东莞仓', 'ACTIVE'),
(6, 'CN-DONGGUAN-01', '深圳仓', 'ACTIVE'),
(7, 'CN-DONGGUAN-01', '华南仓(深圳/东莞)', 'ACTIVE')
ON DUPLICATE KEY UPDATE status = VALUES(status);
INSERT INTO fl_price_warehouse_mapping
(id, source_type, source_value, warehouse_id, warehouse_code, warehouse_type, status)
VALUES
(1, 'ORIGIN_CODE', 'CN-YIWU-01', 193000000000200001, 'CN-YIWU-01', 'DOMESTIC', 'ACTIVE'),
(2, 'ORIGIN_CODE', 'CN-DONGGUAN-01', 193000000000200002, 'CN-DONGGUAN-01', 'DOMESTIC', 'ACTIVE'),
(3, 'OVERSEAS_NAME', '洛杉矶仓', 193000000000200003, 'US-LAX-01', 'OVERSEAS', 'ACTIVE'),
(4, 'OVERSEAS_NAME', '纽约仓', 193000000000200004, 'US-NYC-01', 'OVERSEAS', 'ACTIVE')
ON DUPLICATE KEY UPDATE
warehouse_id = VALUES(warehouse_id), warehouse_code = VALUES(warehouse_code),
warehouse_type = VALUES(warehouse_type), status = VALUES(status);
-- ============================================================================
-- 来源:iam\src\main\resources\db\phase2-inbound-query-permissions.sql
-- ============================================================================
-- 二期入库单列表和详情只读权限,可在一期 RBAC 脚本后重复执行。
USE baiyue_iam;
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
(5102030, 0, 'PLATFORM', 'MENU', 5102000, 'platform:firstleg:inbound:list',
'入库单', '/inbounds', 'GET', 30, 'ACTIVE', 1, 0),
(5102031, 0, 'PLATFORM', 'BUTTON', 5102030, 'platform:firstleg:inbound:view',
'查看入库单详情', NULL, 'GET', 10, 'ACTIVE', 1, 0)
ON DUPLICATE KEY UPDATE
permission_name = VALUES(permission_name), parent_id = VALUES(parent_id),
route_path = VALUES(route_path), http_method = VALUES(http_method),
status = 'ACTIVE', built_in = 1, deleted = 0;
INSERT IGNORE INTO iam_role_permission (role_id, permission_id, tenant_id)
SELECT role.id, permission.id, role.tenant_id
FROM iam_role role
JOIN iam_permission permission
ON permission.tenant_id = 0
AND permission.permission_code IN (
'platform:firstleg:inbound:list', 'platform:firstleg:inbound:view')
WHERE role.tenant_id = 0
AND role.role_code IN ('PLATFORM_ADMIN', 'ORDER_OPERATOR');
-- ============================================================================
-- 来源:iam\src\main\resources\db\phase2-inbound-measurement-permissions.sql
-- ============================================================================
-- 二期入库测量平台菜单和按钮权限,可重复执行。
SET NAMES utf8mb4;
USE baiyue_iam;
INSERT INTO iam_permission
(id, tenant_id, app_code, permission_type, parent_id, permission_code, permission_name,
route_path, http_method, sort_no, status, deleted)
VALUES
(5102041, 0, 'PLATFORM', 'BUTTON', 5102030,
'platform:firstleg:inbound:measure:view', '查看入库测量', NULL, NULL, 10, 'ACTIVE', 0),
(5102042, 0, 'PLATFORM', 'BUTTON', 5102030,
'platform:firstleg:inbound:measure:edit', '保存测量草稿', NULL, NULL, 10, 'ACTIVE', 0),
(5102043, 0, 'PLATFORM', 'BUTTON', 5102030,
'platform:firstleg:inbound:measure:submit', '确认并提交测量', NULL, NULL, 20, 'ACTIVE', 0),
(5102044, 0, 'PLATFORM', 'BUTTON', 5102030,
'platform:firstleg:inbound:weight:edit', '维护OCR重量', NULL, NULL, 30, 'ACTIVE', 0),
(5102045, 0, 'PLATFORM', 'BUTTON', 5102030,
'platform:firstleg:inbound:weight:confirm', '确认OCR重量', NULL, NULL, 40, 'ACTIVE', 0),
(5102194, 0, 'PLATFORM', 'BUTTON', 5102030,
'platform:firstleg:inbound:measure:manual-entry', '手动录入箱规', NULL, NULL, 25, 'ACTIVE', 0)
ON DUPLICATE KEY UPDATE
permission_type = VALUES(permission_type), parent_id = VALUES(parent_id),
permission_name = VALUES(permission_name), route_path = VALUES(route_path),
http_method = NULL, sort_no = VALUES(sort_no), status = 'ACTIVE', deleted = 0;
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
SELECT 4000001, id, 0 FROM iam_permission
WHERE id IN (5102041, 5102042, 5102043, 5102044, 5102045, 5102194)
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
SELECT 4000003, id, 0 FROM iam_permission
WHERE id IN (5102041, 5102042, 5102043, 5102044, 5102045, 5102194)
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
INSERT 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.permission_code = 'platform:firstleg:inbound:measure:manual-entry'
WHERE role.tenant_id = 0 AND role.role_code = 'WAREHOUSE_TALLY'
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
-- ============================================================================
-- 来源:iam\src\main\resources\db\phase2-inbound-permissions.sql
-- ============================================================================
-- 二期入库审核与异常放行按钮权限,可重复部署。
USE baiyue_iam;
SET NAMES utf8mb4;
DROP PROCEDURE IF EXISTS repair_location_permission_ids;
DELIMITER $$
CREATE PROCEDURE repair_location_permission_ids()
BEGIN
IF EXISTS (
SELECT 1 FROM iam_permission
WHERE id IN (5103060, 5103061, 5103062, 5103063, 5103064)
AND permission_code <> CASE id
WHEN 5103060 THEN 'platform:location:list'
WHEN 5103061 THEN 'platform:location:create'
WHEN 5103062 THEN 'platform:location:edit'
WHEN 5103063 THEN 'platform:location:status'
WHEN 5103064 THEN 'platform:location:delete'
END
) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '库位权限迁移失败:5103060-5103064 已被其他权限占用';
END IF;
IF EXISTS (
SELECT 1 FROM iam_permission
WHERE (id = 5103040 AND NOT (
(permission_code = 'platform:download:list' AND parent_id = 5103000)
OR (permission_code = 'platform:location:list' AND parent_id = 5103000)))
OR (id = 5103041 AND NOT (
(permission_code = 'platform:download:all' AND parent_id = 5103040)
OR (permission_code = 'platform:location:create' AND parent_id = 5103040)))
OR (permission_code = 'platform:download:list' AND id <> 5103040)
OR (permission_code = 'platform:download:all' AND id <> 5103041)
) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '库位权限迁移失败:下载中心稳定 ID 被未知权限占用';
END IF;
DROP TEMPORARY TABLE IF EXISTS tmp_location_role_grant;
CREATE TEMPORARY TABLE tmp_location_role_grant (
role_id BIGINT UNSIGNED NOT NULL, tenant_id BIGINT UNSIGNED NOT NULL,
permission_code VARCHAR(128) NOT NULL,
PRIMARY KEY (role_id, tenant_id, permission_code)
) ENGINE=MEMORY;
INSERT IGNORE INTO tmp_location_role_grant (role_id, tenant_id, permission_code)
SELECT rp.role_id, rp.tenant_id, permission.permission_code
FROM iam_role_permission rp JOIN iam_permission permission ON permission.id = rp.permission_id
WHERE permission.permission_code IN ('platform:location:list', 'platform:location:create',
'platform:location:edit', 'platform:location:status', 'platform:location:delete');
DELETE rp FROM iam_role_permission rp
JOIN iam_permission permission ON permission.id = rp.permission_id
WHERE permission.permission_code IN ('platform:location:list', 'platform:location:create',
'platform:location:edit', 'platform:location:status', 'platform:location:delete');
DELETE FROM iam_permission WHERE permission_code IN ('platform:location:create',
'platform:location:edit', 'platform:location:status', 'platform:location:delete');
DELETE FROM iam_permission WHERE permission_code = 'platform:location:list';
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
(5103040, 0, 'PLATFORM', 'MENU', 5103000, 'platform:download:list', '下载中心', '/downloads', NULL, 40, 'ACTIVE', 1, 0),
(5103041, 0, 'PLATFORM', 'BUTTON', 5103040, 'platform:download:all', '查看全部下载任务', NULL, NULL, 10, 'ACTIVE', 1, 0)
ON DUPLICATE KEY UPDATE permission_type = VALUES(permission_type), parent_id = VALUES(parent_id),
permission_name = VALUES(permission_name), route_path = VALUES(route_path),
http_method = VALUES(http_method), sort_no = VALUES(sort_no), status = 'ACTIVE', built_in = 1, deleted = 0;
END$$
DELIMITER ;
CALL repair_location_permission_ids();
DROP PROCEDURE repair_location_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, deleted)
VALUES
(5102051, 0, 'PLATFORM', 'BUTTON', 5102030, 'firstleg:inbound:measurement-review',
'测量审核', NULL, NULL, 10, 'ACTIVE', 0),
(5102052, 0, 'PLATFORM', 'BUTTON', 5102030, 'firstleg:inbound:measurement-review-history',
'查看测量审核历史', NULL, NULL, 20, 'ACTIVE', 0),
(5102053, 0, 'PLATFORM', 'BUTTON', 5102030, 'firstleg:inbound:exception-release',
'测量异常放行', NULL, NULL, 30, 'ACTIVE', 0),
(5102054, 0, 'PLATFORM', 'BUTTON', 5102030, 'firstleg:inbound:send-fee',
'发送费用并入库', NULL, NULL, 40, 'ACTIVE', 0)
ON DUPLICATE KEY UPDATE
permission_name = VALUES(permission_name), parent_id = VALUES(parent_id),
status = 'ACTIVE', deleted = 0;
-- 仅给平台超级管理员补齐默认权限;业务角色由 IAM 页面按职责授权。
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
SELECT 4000001, p.id, 0
FROM iam_permission p
WHERE p.id BETWEEN 5102051 AND 5102054
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
-- 客服(订单运营)负责测量审核;费用发送由审核通过后的服务端编排自动执行。
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
SELECT 4000003, p.id, 0
FROM iam_permission p
WHERE p.id IN (5102051, 5102052)
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
-- ============================================================================
-- 来源:iam\src\main\resources\db\phase2-fba-pricing-permissions.sql
-- ============================================================================
-- 二期 FBA 计费权限;在入库查询、测量和审核权限脚本后执行,可重复部署。
USE baiyue_iam;
SET NAMES utf8mb4;
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
(5102061, 0, 'PLATFORM', 'BUTTON', 5102030, 'firstleg:fba-pricing:rule:view', '查看客户计费规则', NULL, NULL, 10, 'ACTIVE', 1, 0),
(5102062, 0, 'PLATFORM', 'BUTTON', 5102030, 'firstleg:fba-pricing:rule:edit', '维护客户计费规则', NULL, NULL, 20, 'ACTIVE', 1, 0),
(5102063, 0, 'PLATFORM', 'BUTTON', 5102030, 'firstleg:fba-pricing:rate:view', '查看承运商价格', NULL, NULL, 30, 'ACTIVE', 1, 0),
(5102064, 0, 'PLATFORM', 'BUTTON', 5102030, 'firstleg:fba-pricing:rate:edit', '维护承运商价格', NULL, NULL, 40, 'ACTIVE', 1, 0),
(5102065, 0, 'PLATFORM', 'BUTTON', 5102030, 'firstleg:fba-pricing:calculate', '计算入库费用', NULL, NULL, 50, 'ACTIVE', 1, 0),
(5102066, 0, 'PLATFORM', 'BUTTON', 5102030, 'firstleg:fba-pricing:confirm', '内部确认入库费用', NULL, NULL, 60, 'ACTIVE', 1, 0),
(5102067, 0, 'PLATFORM', 'BUTTON', 5102030, 'firstleg:fba-pricing:view', '查看入库费用', NULL, NULL, 70, 'ACTIVE', 1, 0),
(5102068, 0, 'PLATFORM', 'BUTTON', 5102030, 'firstleg:fba-pricing:adjust', '调整已发布费用', NULL, NULL, 80, 'ACTIVE', 1, 0)
ON DUPLICATE KEY UPDATE
permission_name = VALUES(permission_name), parent_id = VALUES(parent_id),
status = 'ACTIVE', built_in = 1, deleted = 0;
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
SELECT 4000001, id, 0 FROM iam_permission
WHERE id BETWEEN 5102061 AND 5102068
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
SELECT 4000003, id, 0 FROM iam_permission
WHERE id IN (5102061, 5102063, 5102065, 5102066, 5102067, 5102068)
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
-- 客户尺寸计算配置权限;与 FBA 计费规则相互独立。
DROP PROCEDURE IF EXISTS unified_guard_dimension_config_permissions;
DELIMITER $$
CREATE PROCEDURE unified_guard_dimension_config_permissions()
BEGIN
IF EXISTS (
SELECT 1 FROM iam_permission
WHERE (id = 5102111 AND COALESCE(permission_code, '') <> 'firstleg:dimension-config:view')
OR (id = 5102112 AND COALESCE(permission_code, '') <> 'firstleg:dimension-config:edit')
) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:客户尺寸计算配置权限固定ID已被其他权限码占用';
END IF;
IF EXISTS (
SELECT 1 FROM iam_permission
WHERE (permission_code = 'firstleg:dimension-config:view' AND id <> 5102111)
OR (permission_code = 'firstleg:dimension-config:edit' AND id <> 5102112)
) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:客户尺寸计算配置权限码已绑定其他固定ID';
END IF;
END$$
DELIMITER ;
CALL unified_guard_dimension_config_permissions();
DROP PROCEDURE IF EXISTS unified_guard_dimension_config_permissions;
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
(5102111, 0, 'PLATFORM', 'BUTTON', 5100010, 'firstleg:dimension-config:view',
'查看客户尺寸计算配置', NULL, NULL, 90, 'ACTIVE', 1, 0),
(5102112, 0, 'PLATFORM', 'BUTTON', 5100010, 'firstleg:dimension-config:edit',
'维护客户尺寸计算配置', NULL, NULL, 100, 'ACTIVE', 1, 0)
ON DUPLICATE KEY UPDATE
permission_name = VALUES(permission_name), parent_id = VALUES(parent_id),
status = 'ACTIVE', built_in = 1, deleted = 0;
INSERT 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 ('firstleg:dimension-config:view', 'firstleg:dimension-config:edit')
WHERE r.tenant_id = 0 AND r.app_code = 'PLATFORM' AND r.role_code = 'PLATFORM_ADMIN'
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
INSERT 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 = 'firstleg:dimension-config:view'
WHERE r.tenant_id = 0 AND r.app_code = 'PLATFORM' AND r.role_code = 'ORDER_OPERATOR'
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
-- ============================================================================
-- 来源:iam\src\main\resources\db\phase2-location-permissions.sql
-- ============================================================================
-- 二期库位管理菜单和按钮权限。
-- 在 phase1-admin-rbac.sql、phase1-platform-master-data-and-permission-management.sql 之后执行,可重复执行。
USE baiyue_iam;
SET NAMES utf8mb4;
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
(5103060, 0, 'PLATFORM', 'MENU', 5103000, 'platform:location:list', '库位管理',
'/master-data/locations', NULL, 40, 'ACTIVE', 1, 0),
(5103061, 0, 'PLATFORM', 'BUTTON', 5103060, 'platform:location:create', '新增库位',
NULL, NULL, 10, 'ACTIVE', 1, 0),
(5103062, 0, 'PLATFORM', 'BUTTON', 5103060, 'platform:location:edit', '编辑库位',
NULL, NULL, 20, 'ACTIVE', 1, 0),
(5103063, 0, 'PLATFORM', 'BUTTON', 5103060, 'platform:location:status', '启停库位',
NULL, NULL, 30, 'ACTIVE', 1, 0),
(5103064, 0, 'PLATFORM', 'BUTTON', 5103060, 'platform:location:delete', '删除库位',
NULL, NULL, 40, 'ACTIVE', 1, 0)
ON DUPLICATE KEY UPDATE
permission_type = VALUES(permission_type), parent_id = VALUES(parent_id),
permission_name = VALUES(permission_name), route_path = VALUES(route_path),
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 saved.role_id, permission.id, saved.tenant_id
FROM tmp_location_role_grant saved
JOIN iam_permission permission ON permission.permission_code = saved.permission_code
AND permission.tenant_id = 0 AND permission.app_code = 'PLATFORM';
-- 二期新增库位权限默认只授予平台系统管理员。
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
SELECT 4000001, id, 0
FROM iam_permission
WHERE tenant_id = 0 AND app_code = 'PLATFORM'
AND permission_code IN (
'platform:location:list', 'platform:location:create', 'platform:location:edit',
'platform:location:status', 'platform:location:delete'
)
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
DROP TEMPORARY TABLE IF EXISTS tmp_location_role_grant;
-- ============================================================================
-- 来源:iam\src\main\resources\db\phase2-pallet-permissions.sql
-- ============================================================================
-- 二期托盘管理菜单和按钮权限。
-- 在 phase1-admin-rbac.sql、phase1-platform-master-data-and-permission-management.sql 之后执行,可重复执行。
USE baiyue_iam;
SET NAMES utf8mb4;
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
(5103050, 0, 'PLATFORM', 'MENU', 5103000, 'platform:pallet:list', '托盘管理',
'/master-data/pallets', NULL, 50, 'ACTIVE', 1, 0),
(5103051, 0, 'PLATFORM', 'BUTTON', 5103050, 'platform:pallet:create', '新增托盘',
NULL, NULL, 10, 'ACTIVE', 1, 0),
(5103052, 0, 'PLATFORM', 'BUTTON', 5103050, 'platform:pallet:edit', '编辑托盘',
NULL, NULL, 20, 'ACTIVE', 1, 0),
(5103054, 0, 'PLATFORM', 'BUTTON', 5103050, 'platform:pallet:delete', '删除托盘',
NULL, NULL, 30, 'ACTIVE', 1, 0)
ON DUPLICATE KEY UPDATE
permission_type = VALUES(permission_type), parent_id = VALUES(parent_id),
permission_name = VALUES(permission_name), route_path = VALUES(route_path),
sort_no = VALUES(sort_no), status = 'ACTIVE', built_in = 1, deleted = 0;
-- 二期新增托盘权限默认只授予平台系统管理员。
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
SELECT 4000001, id, 0
FROM iam_permission
WHERE tenant_id = 0 AND app_code = 'PLATFORM'
AND permission_code IN (
'platform:pallet:list', 'platform:pallet:create', 'platform:pallet:edit', 'platform:pallet:delete'
)
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
-- 兼容已经试运行过旧脚本的环境:移除人工启停权限。
DELETE rp FROM iam_role_permission rp
JOIN iam_permission p ON p.id = rp.permission_id
WHERE p.tenant_id = 0 AND p.app_code = 'PLATFORM' AND p.permission_code = 'platform:pallet:status';
UPDATE iam_permission
SET status = 'INACTIVE', deleted = 1
WHERE tenant_id = 0 AND app_code = 'PLATFORM' AND permission_code = 'platform:pallet:status';
-- ============================================================================
-- 存量 File 导出中心安全升级
-- ============================================================================
USE baiyue_file;
DROP PROCEDURE IF EXISTS unified_file_add_column;
DELIMITER $$
CREATE PROCEDURE unified_file_add_column(
IN p_column_name VARCHAR(64), IN p_definition TEXT
)
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema = DATABASE()
AND table_name = 'file_export_task'
AND column_name = p_column_name
) THEN
SET @unified_ddl = CONCAT(
'ALTER TABLE file_export_task ADD COLUMN `',
REPLACE(p_column_name, '`', '``'), '` ', p_definition
);
PREPARE unified_stmt FROM @unified_ddl;
EXECUTE unified_stmt;
DEALLOCATE PREPARE unified_stmt;
END IF;
END$$
DELIMITER ;
CALL unified_file_add_column('dispatch_version',
'INT NOT NULL DEFAULT 1 COMMENT ''派发版本,仅执行重试时递增'' AFTER failure_message');
CALL unified_file_add_column('attempt_count',
'INT NOT NULL DEFAULT 0 COMMENT ''已执行尝试次数'' AFTER dispatch_version');
CALL unified_file_add_column('max_attempts',
'INT NOT NULL DEFAULT 3 COMMENT ''自动重试上限'' AFTER attempt_count');
CALL unified_file_add_column('next_retry_at',
'DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT ''允许执行的最早时间'' AFTER max_attempts');
CALL unified_file_add_column('lease_owner',
'VARCHAR(128) NULL COMMENT ''当前执行实例标识'' AFTER next_retry_at');
CALL unified_file_add_column('lease_until',
'DATETIME(3) NULL COMMENT ''执行租约截止时间'' AFTER lease_owner');
CALL unified_file_add_column('last_heartbeat_at',
'DATETIME(3) NULL COMMENT ''最后心跳时间'' AFTER lease_until');
CALL unified_file_add_column('execution_deadline_at',
'DATETIME(3) NULL COMMENT ''本次执行截止时间'' AFTER last_heartbeat_at');
DROP PROCEDURE IF EXISTS unified_file_add_column;
SET @unified_ddl = IF(
EXISTS(SELECT 1 FROM information_schema.statistics
WHERE table_schema = DATABASE() AND table_name = 'file_export_task'
AND index_name = 'idx_file_export_dispatch'),
'SELECT 1',
'ALTER TABLE file_export_task ADD INDEX idx_file_export_dispatch (status, next_retry_at, id)'
);
PREPARE unified_stmt FROM @unified_ddl; EXECUTE unified_stmt; DEALLOCATE PREPARE unified_stmt;
SET @unified_ddl = IF(
EXISTS(SELECT 1 FROM information_schema.statistics
WHERE table_schema = DATABASE() AND table_name = 'file_export_task'
AND index_name = 'idx_file_export_lease'),
'SELECT 1',
'ALTER TABLE file_export_task ADD INDEX idx_file_export_lease (status, lease_until)'
);
PREPARE unified_stmt FROM @unified_ddl; EXECUTE unified_stmt; DEALLOCATE PREPARE unified_stmt;
-- ============================================================================
-- 一期订单快照字段规格统一
-- ============================================================================
USE baiyue_order;
SET @unified_ddl = IF(
COALESCE((SELECT character_maximum_length FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = 'ord_order'
AND column_name = 'receiving_warehouse_address'), 0) < 600,
'ALTER TABLE ord_order MODIFY COLUMN receiving_warehouse_address VARCHAR(600) NULL COMMENT ''收货仓地址历史快照''',
'SELECT 1'
);
PREPARE unified_stmt FROM @unified_ddl; EXECUTE unified_stmt; DEALLOCATE PREPARE unified_stmt;
SET @unified_ddl = IF(
COALESCE((SELECT character_maximum_length FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = 'ord_order'
AND column_name = 'receiving_time'), 0) < 128,
'ALTER TABLE ord_order MODIFY COLUMN receiving_time VARCHAR(128) NULL COMMENT ''收货时间历史快照''',
'SELECT 1'
);
PREPARE unified_stmt FROM @unified_ddl; EXECUTE unified_stmt; DEALLOCATE PREPARE unified_stmt;
-- ============================================================================
-- 重新同步一期商户管理员全局模板,包含一期后续追加的菜单和按钮
-- ============================================================================
USE baiyue_iam;
INSERT INTO iam_permission (
id, tenant_id, app_code, permission_type, parent_id, permission_code,
permission_name, route_path, http_method, sort_no, status, deleted
)
SELECT id + 100000, 0, app_code, permission_type,
CASE WHEN parent_id = 0 THEN 0 ELSE parent_id + 100000 END,
permission_code, permission_name, route_path, NULL, sort_no, 'ACTIVE', 0
FROM iam_permission
WHERE tenant_id = 10001 AND app_code = 'MERCHANT'
AND permission_type IN ('MENU', 'BUTTON')
ON DUPLICATE KEY UPDATE
permission_name = VALUES(permission_name), route_path = VALUES(route_path),
parent_id = VALUES(parent_id), sort_no = VALUES(sort_no), status = 'ACTIVE', deleted = 0;
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
SELECT owner_role.id, permission.id, 0
FROM iam_role owner_role
JOIN iam_permission permission
ON permission.app_code = 'MERCHANT' AND permission.tenant_id = 0
AND permission.permission_type IN ('MENU', 'BUTTON')
AND permission.status = 'ACTIVE' AND permission.deleted = 0
WHERE owner_role.tenant_id = 0 AND owner_role.app_code = 'MERCHANT'
AND owner_role.role_code = 'MERCHANT_OWNER'
AND owner_role.status = 'ACTIVE' AND owner_role.deleted = 0
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
-- ============================================================================
-- 统一执行结果强校验:任一关键结构缺失立即终止,不输出虚假 PASS
-- ============================================================================
DROP PROCEDURE IF EXISTS unified_assert_table;
DROP PROCEDURE IF EXISTS unified_assert_column;
DELIMITER $$
CREATE PROCEDURE unified_assert_table(IN p_schema VARCHAR(64), IN p_table VARCHAR(64))
BEGIN
DECLARE v_count INT DEFAULT 0;
DECLARE v_message VARCHAR(255);
SELECT COUNT(*) INTO v_count FROM information_schema.tables
WHERE table_schema = p_schema AND table_name = p_table;
IF v_count <> 1 THEN
SET v_message = CONCAT('统一迁移失败,缺少表:', p_schema, '.', p_table);
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = v_message;
END IF;
END$$
CREATE PROCEDURE unified_assert_column(
IN p_schema VARCHAR(64), IN p_table VARCHAR(64), IN p_column VARCHAR(64)
)
BEGIN
DECLARE v_count INT DEFAULT 0;
DECLARE v_message VARCHAR(255);
SELECT COUNT(*) INTO v_count FROM information_schema.columns
WHERE table_schema = p_schema AND table_name = p_table AND column_name = p_column;
IF v_count <> 1 THEN
SET v_message = CONCAT('统一迁移失败,缺少字段:', p_schema, '.', p_table, '.', p_column);
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = v_message;
END IF;
END$$
DELIMITER ;
CALL unified_assert_table('baiyue_auth', 'auth_account');
CALL unified_assert_table('baiyue_iam', 'iam_permission');
CALL unified_assert_table('baiyue_order', 'ord_order');
CALL unified_assert_table('baiyue_warehouse', 'wh_warehouse');
CALL unified_assert_table('baiyue_warehouse', 'wh_pallet');
CALL unified_assert_table('baiyue_warehouse', 'wh_location');
CALL unified_assert_table('baiyue_firstleg', 'fl_fulfillment_task');
CALL unified_assert_table('baiyue_firstleg', 'fl_inbound_order');
CALL unified_assert_table('baiyue_firstleg', 'fl_third_party_call_log');
CALL unified_assert_table('baiyue_file', 'file_export_task_guard');
CALL unified_assert_table('baiyue_file', 'file_export_outbox');
CALL unified_assert_table('baiyue_audit', 'sys_audit_log');
CALL unified_assert_table('baiyue_event_bridge', 'bridge_inbox');
CALL unified_assert_column('baiyue_order', 'ord_order', 'carrier_id');
CALL unified_assert_column('baiyue_order', 'ord_order', 'carrier_name');
CALL unified_assert_column('baiyue_order', 'ord_order', 'pricing_tax_mode');
CALL unified_assert_column('baiyue_order', 'ord_order', 'inbound_code');
CALL unified_assert_column('baiyue_firstleg', 'fl_inbound_pallet_allocation', 'warehouse_resource_tenant_id');
CALL unified_assert_column('baiyue_firstleg', 'fl_inbound_order', 'pricing_tax_mode');
CALL unified_assert_column('baiyue_firstleg', 'fl_inbound_measurement_item', 'external_detail_no');
CALL unified_assert_table('baiyue_firstleg', 'fl_price_channel_catalog');
CALL unified_assert_table('baiyue_firstleg', 'fl_price_origin_alias');
CALL unified_assert_table('baiyue_firstleg', 'fl_price_warehouse_mapping');
CALL unified_assert_table('baiyue_firstleg', 'fl_price_book_version');
CALL unified_assert_column('baiyue_firstleg', 'fl_price_book_version', 'parse_issues_json');
CALL unified_assert_table('baiyue_firstleg', 'fl_price_book_channel');
CALL unified_assert_column('baiyue_firstleg', 'fl_carrier_fba_rate', 'price_book_version_id');
CALL unified_assert_column('baiyue_firstleg', 'fl_carrier_fba_rate', 'pricing_tax_mode');
CALL unified_assert_column('baiyue_firstleg', 'fl_carrier_fba_rate', 'source_sheet_name');
CALL unified_assert_column('baiyue_firstleg', 'fl_carrier_fba_rate', 'destination_type');
CALL unified_assert_column('baiyue_firstleg', 'fl_carrier_fba_rate', 'destination_warehouse_id');
CALL unified_assert_column('baiyue_firstleg', 'fl_carrier_fba_rate', 'destination_name');
CALL unified_assert_column('baiyue_firstleg', 'fl_inbound_fee', 'price_book_version_id');
CALL unified_assert_column('baiyue_file', 'file_export_task', 'execution_deadline_at');
DROP PROCEDURE IF EXISTS unified_assert_table;
DROP PROCEDURE IF EXISTS unified_assert_column;
SET @phase2_permission_count = (
SELECT COUNT(DISTINCT permission_code)
FROM iam_permission
WHERE tenant_id = 0 AND deleted = 0
AND permission_code IN (
'platform:firstleg:inbound:list', 'platform:firstleg:inbound:view',
'platform:firstleg:inbound:measure:view', 'platform:firstleg:inbound:measure:edit',
'platform:firstleg:inbound:measure:submit', 'platform:firstleg:inbound:weight:edit',
'platform:firstleg:inbound:weight:confirm', 'firstleg:inbound:measurement-review',
'firstleg:inbound:measurement-review-history', 'firstleg:inbound:exception-release',
'firstleg:inbound:send-fee', 'firstleg:fba-pricing:rule:view',
'firstleg:fba-pricing:rule:edit', 'firstleg:fba-pricing:rate:view',
'firstleg:fba-pricing:rate:edit', 'firstleg:fba-pricing:calculate',
'firstleg:fba-pricing:confirm', 'firstleg:fba-pricing:view',
'firstleg:fba-pricing:adjust', 'firstleg:dimension-config:view',
'firstleg:dimension-config:edit', 'platform:location:list',
'platform:location:create', 'platform:location:edit', 'platform:location:status',
'platform:location:delete', 'platform:pallet:list', 'platform:pallet:create',
'platform:pallet:edit', 'platform:pallet:delete'
)
);
DROP PROCEDURE IF EXISTS unified_assert_permission_count;
DELIMITER $$
CREATE PROCEDURE unified_assert_permission_count()
BEGIN
IF @phase2_permission_count <> 30 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:二期 IAM 权限资源未达到 30 项';
END IF;
END$$
DELIMITER ;
CALL unified_assert_permission_count();
DROP PROCEDURE IF EXISTS unified_assert_permission_count;
-- ============================================================================
-- 2026-08-05:海外仓阶梯费用基础资料 CRUD 增量
-- ============================================================================
USE baiyue_warehouse;
CREATE TABLE IF NOT EXISTS wh_overseas_storage_fee_rule (
id BIGINT UNSIGNED NOT NULL COMMENT '海外仓费用规则ID(雪花ID)',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键;客户费用规则不得使用0',
customer_name VARCHAR(128) NOT NULL COMMENT '客户名称快照',
currency CHAR(3) NOT NULL COMMENT '费用币种三字码;首版为USD',
initialization_key VARCHAR(191) NULL COMMENT '初始化或导入幂等键',
created_by BIGINT UNSIGNED NULL COMMENT '创建人ID',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_by BIGINT UNSIGNED NULL COMMENT '最后修改人ID',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记:0未删除、1已删除',
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
active_customer_id BIGINT UNSIGNED
GENERATED ALWAYS AS (CASE WHEN deleted = 0 THEN tenant_id ELSE NULL END) STORED
COMMENT '未删除规则的客户唯一索引值',
PRIMARY KEY (id),
UNIQUE KEY uk_overseas_fee_active_customer (active_customer_id),
KEY idx_overseas_fee_page (deleted, updated_at, id),
KEY idx_overseas_fee_customer_name (customer_name, deleted)
) ENGINE=InnoDB COMMENT='客户海外仓费用规则';
CREATE TABLE IF NOT EXISTS wh_overseas_storage_fee_tier (
id BIGINT UNSIGNED NOT NULL COMMENT '海外仓费用阶梯ID(雪花ID)',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键;与规则归属一致',
rule_id BIGINT UNSIGNED NOT NULL COMMENT '所属海外仓费用规则ID',
start_day SMALLINT UNSIGNED NOT NULL COMMENT '计费阶梯起始存储天数,含边界',
end_day SMALLINT UNSIGNED NULL COMMENT '计费阶梯结束存储天数,含边界;NULL表示以上',
fee_type VARCHAR(32) NOT NULL DEFAULT 'STORAGE' COMMENT '费用类型;首版为STORAGE仓储费',
billing_unit VARCHAR(16) NOT NULL DEFAULT 'CBM' COMMENT '计费单位;首版为CBM立方米',
unit_price DECIMAL(18,6) NOT NULL COMMENT '每计费单位单价,非负',
sort_no SMALLINT UNSIGNED NOT NULL COMMENT '阶梯展示和计算顺序',
created_by BIGINT UNSIGNED NULL COMMENT '创建人ID',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_by BIGINT UNSIGNED NULL COMMENT '最后修改人ID',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记:0未删除、1已删除',
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
active_start_day SMALLINT UNSIGNED
GENERATED ALWAYS AS (CASE WHEN deleted = 0 THEN start_day ELSE NULL END) STORED
COMMENT '未删除阶梯的起始天唯一索引值',
PRIMARY KEY (id),
UNIQUE KEY uk_overseas_fee_tier_start (rule_id, fee_type, active_start_day),
KEY idx_overseas_fee_tier_rule (tenant_id, rule_id, deleted, sort_no, id),
CONSTRAINT chk_overseas_fee_tier_days CHECK (start_day >= 1 AND (end_day IS NULL OR end_day >= start_day)),
CONSTRAINT chk_overseas_fee_tier_price CHECK (unit_price >= 0)
) ENGINE=InnoDB COMMENT='客户海外仓费用阶梯';
-- 兼容已存在的旧版海外仓费用表:补齐新字段及免费档/无上限档约束。
DROP PROCEDURE IF EXISTS migrate_overseas_storage_fee_schema;
DELIMITER $$
CREATE PROCEDURE migrate_overseas_storage_fee_schema()
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema = DATABASE()
AND table_name = 'wh_overseas_storage_fee_rule'
AND column_name = 'initialization_key'
) THEN
SET @overseas_fee_ddl =
'ALTER TABLE wh_overseas_storage_fee_rule ADD COLUMN initialization_key VARCHAR(191) NULL COMMENT ''初始化幂等键''';
PREPARE overseas_fee_statement FROM @overseas_fee_ddl;
EXECUTE overseas_fee_statement;
DEALLOCATE PREPARE overseas_fee_statement;
END IF;
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema = DATABASE()
AND table_name = 'wh_overseas_storage_fee_tier'
AND column_name = 'fee_type'
) THEN
SET @overseas_fee_ddl =
'ALTER TABLE wh_overseas_storage_fee_tier ADD COLUMN fee_type VARCHAR(32) NOT NULL DEFAULT ''STORAGE'' COMMENT ''费用类型''';
PREPARE overseas_fee_statement FROM @overseas_fee_ddl;
EXECUTE overseas_fee_statement;
DEALLOCATE PREPARE overseas_fee_statement;
END IF;
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema = DATABASE()
AND table_name = 'wh_overseas_storage_fee_tier'
AND column_name = 'billing_unit'
) THEN
SET @overseas_fee_ddl =
'ALTER TABLE wh_overseas_storage_fee_tier ADD COLUMN billing_unit VARCHAR(16) NOT NULL DEFAULT ''CBM'' COMMENT ''计费单位''';
PREPARE overseas_fee_statement FROM @overseas_fee_ddl;
EXECUTE overseas_fee_statement;
DEALLOCATE PREPARE overseas_fee_statement;
END IF;
ALTER TABLE wh_overseas_storage_fee_tier
MODIFY COLUMN end_day SMALLINT UNSIGNED NULL COMMENT '结束天,含边界;NULL表示以上',
MODIFY COLUMN unit_price DECIMAL(18,6) NOT NULL COMMENT '仓储费标准,可为0';
IF EXISTS (
SELECT 1 FROM wh_overseas_storage_fee_tier
WHERE start_day < 1
OR (end_day IS NOT NULL AND end_day < start_day)
OR unit_price < 0
) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Warehouse 迁移失败:海外仓费用阶梯存在非法天数或负价格';
END IF;
IF EXISTS (
SELECT 1
FROM wh_overseas_storage_fee_tier
WHERE active_start_day IS NOT NULL
GROUP BY rule_id, fee_type, active_start_day
HAVING COUNT(*) > 1
) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Warehouse 迁移失败:海外仓费用阶梯目标唯一键存在重复数据';
END IF;
IF EXISTS (
SELECT 1 FROM information_schema.table_constraints
WHERE constraint_schema = DATABASE()
AND table_name = 'wh_overseas_storage_fee_tier'
AND constraint_name = 'chk_overseas_fee_tier_days'
AND constraint_type = 'CHECK'
) THEN
ALTER TABLE wh_overseas_storage_fee_tier DROP CHECK chk_overseas_fee_tier_days;
END IF;
IF EXISTS (
SELECT 1 FROM information_schema.table_constraints
WHERE constraint_schema = DATABASE()
AND table_name = 'wh_overseas_storage_fee_tier'
AND constraint_name = 'chk_overseas_fee_tier_price'
AND constraint_type = 'CHECK'
) THEN
ALTER TABLE wh_overseas_storage_fee_tier DROP CHECK chk_overseas_fee_tier_price;
END IF;
ALTER TABLE wh_overseas_storage_fee_tier
ADD CONSTRAINT chk_overseas_fee_tier_days
CHECK (start_day >= 1 AND (end_day IS NULL OR end_day >= start_day)),
ADD CONSTRAINT chk_overseas_fee_tier_price CHECK (unit_price >= 0);
IF NOT EXISTS (
SELECT 1
FROM (
SELECT index_name, non_unique,
GROUP_CONCAT(column_name ORDER BY seq_in_index SEPARATOR ',') AS index_columns
FROM information_schema.statistics
WHERE table_schema = DATABASE()
AND table_name = 'wh_overseas_storage_fee_tier'
AND index_name = 'uk_overseas_fee_tier_start'
GROUP BY index_name, non_unique
) existing_index
WHERE non_unique = 0
AND index_columns = 'rule_id,fee_type,active_start_day'
) THEN
IF EXISTS (
SELECT 1 FROM information_schema.statistics
WHERE table_schema = DATABASE()
AND table_name = 'wh_overseas_storage_fee_tier'
AND index_name = 'uk_overseas_fee_tier_start'
) THEN
ALTER TABLE wh_overseas_storage_fee_tier DROP INDEX uk_overseas_fee_tier_start;
END IF;
ALTER TABLE wh_overseas_storage_fee_tier
ADD UNIQUE KEY uk_overseas_fee_tier_start (rule_id, fee_type, active_start_day);
END IF;
END$$
DELIMITER ;
CALL migrate_overseas_storage_fee_schema();
DROP PROCEDURE IF EXISTS migrate_overseas_storage_fee_schema;
USE baiyue_iam;
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
(5100030, 0, 'PLATFORM', 'MENU', 5100000,
'platform:overseas-storage-fee:list', '海外仓阶梯费用',
'/customers/overseas-storage-fees', 'GET', 20, 'ACTIVE', 1, 0),
(5100031, 0, 'PLATFORM', 'BUTTON', 5100030,
'platform:overseas-storage-fee:create', '新增海外仓阶梯费用',
NULL, NULL, 10, 'ACTIVE', 1, 0),
(5100032, 0, 'PLATFORM', 'BUTTON', 5100030,
'platform:overseas-storage-fee:edit', '编辑海外仓阶梯费用',
NULL, NULL, 20, 'ACTIVE', 1, 0),
(5100033, 0, 'PLATFORM', 'BUTTON', 5100030,
'platform:overseas-storage-fee:delete', '删除海外仓阶梯费用',
NULL, NULL, 30, 'ACTIVE', 1, 0)
ON DUPLICATE KEY UPDATE
permission_type = VALUES(permission_type), parent_id = VALUES(parent_id),
permission_name = VALUES(permission_name), route_path = VALUES(route_path),
http_method = VALUES(http_method), sort_no = VALUES(sort_no),
status = 'ACTIVE', built_in = 1, deleted = 0;
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
SELECT 4000001, id, 0
FROM iam_permission
WHERE tenant_id = 0 AND app_code = 'PLATFORM'
AND permission_code IN (
'platform:overseas-storage-fee:list',
'platform:overseas-storage-fee:create',
'platform:overseas-storage-fee:edit',
'platform:overseas-storage-fee:delete'
)
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
DROP PROCEDURE IF EXISTS unified_assert_overseas_storage_fee;
DELIMITER $$
CREATE PROCEDURE unified_assert_overseas_storage_fee()
BEGIN
IF (SELECT COUNT(*) FROM information_schema.tables
WHERE table_schema = 'baiyue_warehouse'
AND table_name IN ('wh_overseas_storage_fee_rule', 'wh_overseas_storage_fee_tier')) <> 2 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:海外仓阶梯费用表未完整创建';
END IF;
IF (SELECT COUNT(DISTINCT index_name) FROM information_schema.statistics
WHERE table_schema = 'baiyue_warehouse'
AND table_name IN ('wh_overseas_storage_fee_rule', 'wh_overseas_storage_fee_tier')
AND index_name IN ('uk_overseas_fee_active_customer', 'idx_overseas_fee_page',
'idx_overseas_fee_customer_name', 'uk_overseas_fee_tier_start',
'idx_overseas_fee_tier_rule')) <> 5 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:海外仓阶梯费用索引未完整创建';
END IF;
IF (SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = 'baiyue_warehouse'
AND table_name = 'wh_overseas_storage_fee_rule'
AND column_name = 'initialization_key'
AND LOWER(column_type) = 'varchar(191)'
AND is_nullable = 'YES' AND column_default IS NULL) <> 1
OR (SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = 'baiyue_warehouse'
AND table_name = 'wh_overseas_storage_fee_tier'
AND ((column_name = 'fee_type' AND LOWER(column_type) = 'varchar(32)'
AND is_nullable = 'NO' AND column_default = 'STORAGE')
OR (column_name = 'billing_unit' AND LOWER(column_type) = 'varchar(16)'
AND is_nullable = 'NO' AND column_default = 'CBM')
OR (column_name = 'end_day' AND LOWER(column_type) = 'smallint unsigned'
AND is_nullable = 'YES' AND column_default IS NULL)
OR (column_name = 'unit_price' AND LOWER(column_type) = 'decimal(18,6)'
AND is_nullable = 'NO'))) <> 4 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:海外仓阶梯费用字段签名不正确';
END IF;
IF (SELECT COUNT(*)
FROM (
SELECT non_unique,
GROUP_CONCAT(column_name ORDER BY seq_in_index SEPARATOR ',') AS index_columns
FROM information_schema.statistics
WHERE table_schema = 'baiyue_warehouse'
AND table_name = 'wh_overseas_storage_fee_tier'
AND index_name = 'uk_overseas_fee_tier_start'
GROUP BY index_name, non_unique
) exact_index
WHERE non_unique = 0
AND index_columns = 'rule_id,fee_type,active_start_day') <> 1
OR (SELECT COUNT(*) FROM information_schema.table_constraints
WHERE constraint_schema = 'baiyue_warehouse'
AND table_name = 'wh_overseas_storage_fee_tier'
AND constraint_type = 'CHECK'
AND constraint_name IN ('chk_overseas_fee_tier_days',
'chk_overseas_fee_tier_price')) <> 2 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:海外仓阶梯费用唯一键或 CHECK 结构不正确';
END IF;
IF (SELECT COUNT(DISTINCT permission_code) FROM baiyue_iam.iam_permission
WHERE tenant_id = 0 AND status = 'ACTIVE' AND deleted = 0
AND permission_code IN (
'platform:overseas-storage-fee:list', 'platform:overseas-storage-fee:create',
'platform:overseas-storage-fee:edit', 'platform:overseas-storage-fee:delete')) <> 2 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:海外仓阶梯费用列表/编辑权限未完整创建';
END IF;
END$$
DELIMITER ;
CALL unified_assert_overseas_storage_fee();
DROP PROCEDURE IF EXISTS unified_assert_overseas_storage_fee;
-- 海外仓阶梯费用结构校验已完成;最终 PASS 统一在脚本末尾输出。
-- ============================================================
-- 二期:费用项目主数据与入库单应收应付费用明细
-- ============================================================
USE baiyue_firstleg;
CREATE TABLE IF NOT EXISTS fl_fee_item (
id BIGINT UNSIGNED NOT NULL COMMENT '费用项目ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '平台公共费用项目作用域,固定为0;物理字段名沿用 tenant_id',
category VARCHAR(64) NOT NULL COMMENT '费用分类',
trade_scope VARCHAR(20) NOT NULL COMMENT 'DOMESTIC国内、INTERNATIONAL国际',
fee_code VARCHAR(50) NOT NULL COMMENT '稳定费用编码;系统费用使用固定编码',
fee_type_name VARCHAR(128) NOT NULL COMMENT '费用类型名称',
pickup_payable TINYINT(1) NOT NULL DEFAULT 0 COMMENT '提柜应付是否参与',
pickup_receivable TINYINT(1) NOT NULL DEFAULT 0 COMMENT '提柜应收是否参与',
warehouse_payable TINYINT(1) NOT NULL DEFAULT 0 COMMENT '仓储应付是否参与',
warehouse_receivable TINYINT(1) NOT NULL DEFAULT 0 COMMENT '仓储应收是否参与',
allocation_fee TINYINT(1) NOT NULL DEFAULT 0 COMMENT '平摊费用是否参与',
description VARCHAR(500) NULL COMMENT '说明',
status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE' COMMENT '状态:ACTIVE启用、DISABLED停用',
created_by BIGINT NULL COMMENT '创建人ID;系统初始化时可为空',
created_by_name VARCHAR(64) NULL COMMENT '创建人名称快照',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_by BIGINT 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 '最后更新时间',
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记:0未删除、1已删除',
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id),
UNIQUE KEY uk_fee_item_code (fee_code),
KEY idx_fee_item_query (tenant_id, trade_scope, category, status, deleted),
KEY idx_fee_item_name (tenant_id, fee_type_name, deleted)
) ENGINE=InnoDB COMMENT='平台公共费用项目主数据';
CREATE TABLE IF NOT EXISTS fl_inbound_fee_detail (
id BIGINT UNSIGNED NOT NULL COMMENT '入库费用明细ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID(客户隔离键);费用明细引用平台公共费用项目',
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID',
fee_item_id BIGINT UNSIGNED NOT NULL COMMENT '绑定费用项目ID',
inbound_code VARCHAR(40) NOT NULL COMMENT '入仓码快照',
direction VARCHAR(16) NOT NULL COMMENT 'RECEIVABLE应收、PAYABLE应付',
business_date DATE NOT NULL COMMENT '业务发生日',
fee_category_snapshot VARCHAR(64) NOT NULL COMMENT '费用分类快照',
trade_scope_snapshot VARCHAR(20) NOT NULL COMMENT '国内国际快照',
fee_type_name_snapshot VARCHAR(128) NOT NULL COMMENT '费用类型名称快照',
channel_code VARCHAR(64) NULL COMMENT '渠道编码快照',
channel_name VARCHAR(128) NULL COMMENT '渠道名称快照',
destination_code VARCHAR(32) NULL COMMENT '目的仓编码快照',
destination_name VARCHAR(128) NULL COMMENT '目的仓名称快照',
package_count INT NULL COMMENT '件数快照',
actual_gross_weight_kg DECIMAL(18,4) NULL COMMENT '仓库实测毛重快照,单位千克',
actual_volume_cbm DECIMAL(18,6) NULL COMMENT '实际体积快照,单位立方米',
volume_weight_kg DECIMAL(18,4) NULL COMMENT '材积重快照,单位千克',
chargeable_weight_kg DECIMAL(18,4) NULL COMMENT '费用计算采用的计费重量,单位千克',
chargeable_volume_cbm DECIMAL(18,6) NULL COMMENT '费用计算采用的计费体积,单位立方米',
unit_price DECIMAL(18,4) NOT NULL COMMENT '费用单价;计价单位由对应费用项目规则确定',
currency CHAR(3) NOT NULL COMMENT '金额币种三字码,如CNY',
amount DECIMAL(18,2) NOT NULL COMMENT '该费用明细金额,币种由currency字段指定',
remark VARCHAR(500) NULL COMMENT '备注',
review_status VARCHAR(16) NOT NULL DEFAULT 'PENDING' COMMENT '审核状态:PENDING待审核、APPROVED已通过、REJECTED已驳回',
reviewer_id BIGINT NULL COMMENT '审核人ID',
reviewer_name VARCHAR(64) NULL COMMENT '审核人名称快照',
reviewed_at DATETIME(3) NULL COMMENT '审核时间',
review_reason VARCHAR(500) NULL COMMENT '审核驳回原因',
created_by BIGINT NULL COMMENT '创建人或系统主体ID',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_by BIGINT NULL COMMENT '最后修改人或系统主体ID',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '最后更新时间',
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记:0未删除、1已删除',
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id),
KEY idx_inbound_fee_detail (tenant_id, inbound_id, business_date, deleted),
KEY idx_inbound_fee_review (tenant_id, review_status, business_date, deleted),
KEY idx_inbound_fee_item (tenant_id, fee_item_id, deleted),
CONSTRAINT fk_inbound_fee_detail_order FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id),
CONSTRAINT fk_inbound_fee_detail_item FOREIGN KEY (fee_item_id) REFERENCES fl_fee_item (id)
) ENGINE=InnoDB COMMENT='入库单应收应付费用明细';
-- 既有表同步字段注释:tenant_id 当前承载客户 ID,不是上级租户 ID。
SET @fee_tenant_comment = '当前客户ID(客户隔离键);物理字段名沿用 tenant_id,当前不表示上级租户';
SET @ddl = IF(
(SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = 'fl_fee_item'
AND column_name = 'tenant_id' AND column_comment = @fee_tenant_comment) = 1,
'SELECT 1',
'ALTER TABLE fl_fee_item MODIFY COLUMN tenant_id BIGINT UNSIGNED NOT NULL COMMENT ''当前客户ID(客户隔离键);物理字段名沿用 tenant_id,当前不表示上级租户'''
);
PREPARE statement FROM @ddl; EXECUTE statement; DEALLOCATE PREPARE statement;
SET @ddl = IF(
(SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = 'fl_inbound_fee_detail'
AND column_name = 'tenant_id' AND column_comment = @fee_tenant_comment) = 1,
'SELECT 1',
'ALTER TABLE fl_inbound_fee_detail MODIFY COLUMN tenant_id BIGINT UNSIGNED NOT NULL COMMENT ''当前客户ID(客户隔离键);物理字段名沿用 tenant_id,当前不表示上级租户'''
);
PREPARE statement FROM @ddl; EXECUTE statement; DEALLOCATE PREPARE statement;
USE baiyue_iam;
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
(5102070, 0, 'PLATFORM', 'MENU', 5103000, 'firstleg:fee-item:view',
'费用项目', '/master-data/fee-items', NULL, 40, 'ACTIVE', 1, 0),
(5102071, 0, 'PLATFORM', 'BUTTON', 5102070, 'firstleg:fee-item:create',
'新增费用项目', NULL, NULL, 10, 'ACTIVE', 1, 0),
(5102072, 0, 'PLATFORM', 'BUTTON', 5102070, 'firstleg:fee-item:edit',
'编辑或停用费用项目', NULL, NULL, 20, 'ACTIVE', 1, 0),
(5102073, 0, 'PLATFORM', 'BUTTON', 5102070, 'firstleg:fee-item:delete',
'删除费用项目', NULL, NULL, 30, 'ACTIVE', 1, 0),
(5102081, 0, 'PLATFORM', 'BUTTON', 5102030, 'firstleg:inbound:fee-detail:view',
'查看入库费用明细', NULL, NULL, 100, 'ACTIVE', 1, 0),
(5102082, 0, 'PLATFORM', 'BUTTON', 5102030, 'firstleg:inbound:fee-detail:edit',
'新增编辑入库费用明细', NULL, NULL, 110, 'ACTIVE', 1, 0),
(5102083, 0, 'PLATFORM', 'BUTTON', 5102030, 'firstleg:inbound:fee-detail:delete',
'删除入库费用明细', NULL, NULL, 120, 'ACTIVE', 1, 0),
(5102084, 0, 'PLATFORM', 'BUTTON', 5102030, 'firstleg:inbound:fee-detail:review',
'审核入库费用明细', NULL, NULL, 130, 'ACTIVE', 1, 0)
ON DUPLICATE KEY UPDATE
permission_type = VALUES(permission_type), parent_id = VALUES(parent_id),
permission_name = VALUES(permission_name), route_path = VALUES(route_path),
sort_no = VALUES(sort_no), status = 'ACTIVE', built_in = 1, deleted = 0;
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
SELECT 4000001, id, 0
FROM iam_permission
WHERE id IN (5102070, 5102071, 5102072, 5102073, 5102081, 5102082, 5102083, 5102084)
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
-- 费用项目、入库费用明细及按钮权限结构校验延后至脚本末尾统一执行。
-- ============================================================
-- 可靠审计:本地 Outbox、RocketMQ 消费幂等和死信补偿
-- ============================================================
-- 可靠审计 Outbox、消费幂等和死信补偿增量(MySQL 8)
SET NAMES utf8mb4;
USE baiyue_audit;
DROP PROCEDURE IF EXISTS create_reliable_audit_outbox;
DELIMITER $$
CREATE PROCEDURE create_reliable_audit_outbox(IN schema_name VARCHAR(64))
BEGIN
SET @ddl = CONCAT(
'CREATE TABLE IF NOT EXISTS \`', schema_name, '\`.sys_audit_outbox (',
'event_id VARCHAR(64) NOT NULL,event_type VARCHAR(64) NOT NULL,resource_type VARCHAR(64) NOT NULL,',
'resource_id VARCHAR(128) NULL,service_name VARCHAR(64) NULL,payload_json JSON NOT NULL,',
'status VARCHAR(16) NOT NULL DEFAULT ''PENDING'',attempt_count INT NOT NULL DEFAULT 0,',
'compensation_count INT NOT NULL DEFAULT 0,next_retry_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),',
'lease_owner VARCHAR(128) NULL,lease_until DATETIME(3) NULL,last_error VARCHAR(1000) NULL,',
'dead_at DATETIME(3) NULL,sent_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),',
'PRIMARY KEY(event_id),KEY idx_audit_outbox_publish(status,next_retry_at,lease_until,created_at),',
'KEY idx_audit_outbox_retention(status,sent_at)) ENGINE=InnoDB COMMENT=''可靠审计事件本地发送箱'''
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END$$
DELIMITER ;
CALL create_reliable_audit_outbox('baiyue_auth');
CALL create_reliable_audit_outbox('baiyue_iam');
CALL create_reliable_audit_outbox('baiyue_order');
CALL create_reliable_audit_outbox('baiyue_firstleg');
CALL create_reliable_audit_outbox('baiyue_warehouse');
DROP PROCEDURE IF EXISTS create_reliable_audit_outbox;
USE baiyue_audit;
DROP PROCEDURE IF EXISTS add_audit_event_id;
DELIMITER $$
CREATE PROCEDURE add_audit_event_id()
BEGIN
IF NOT EXISTS (SELECT 1 FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA='baiyue_audit' AND TABLE_NAME='sys_audit_log' AND COLUMN_NAME='event_id') THEN
ALTER TABLE sys_audit_log ADD COLUMN event_id VARCHAR(64) NULL COMMENT '审计事件全局唯一ID' AFTER id;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA='baiyue_audit' AND TABLE_NAME='sys_audit_log' AND INDEX_NAME='uk_audit_event_id') THEN
ALTER TABLE sys_audit_log ADD UNIQUE KEY uk_audit_event_id(event_id);
END IF;
END$$
DELIMITER ;
CALL add_audit_event_id();
DROP PROCEDURE IF EXISTS add_audit_event_id;
CREATE TABLE IF NOT EXISTS sys_audit_dead_letter (
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '死信记录自增ID',event_id VARCHAR(64) NOT NULL COMMENT '审计事件全局唯一ID',payload_text LONGTEXT NOT NULL COMMENT 'RocketMQ死信原始载荷',
status VARCHAR(16) NOT NULL DEFAULT 'PENDING' COMMENT '状态:PENDING待补偿、PROCESSING处理中、PROCESSED已处理、DEAD永久失败',retry_count INT NOT NULL DEFAULT 0 COMMENT '死信重试次数',
next_retry_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '下次允许补偿时间',lease_owner VARCHAR(128) NULL COMMENT '当前补偿租约持有实例',
lease_until DATETIME(3) NULL COMMENT '当前补偿租约截止时间',last_error VARCHAR(1000) NULL COMMENT '最近一次补偿错误',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '死信记录创建时间',processed_at DATETIME(3) NULL COMMENT '补偿处理完成时间',
PRIMARY KEY(id),UNIQUE KEY uk_audit_dead_event(event_id),
KEY idx_audit_dead_retry(status,next_retry_at,lease_until)
) ENGINE=InnoDB COMMENT='审计消息死信补偿表';
-- ============================================================
-- 三期智能装柜、预装冻结与装柜清单
-- 来源:firstleg/src/main/resources/db/phase3-smart-loading-schema.sql
-- ============================================================
SET NAMES utf8mb4;
CREATE DATABASE IF NOT EXISTS baiyue_firstleg
DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
USE baiyue_firstleg;
-- 兼容已执行二期初始化的存量库:PDA 拣货需要累计每个托盘分配已拣箱数。
DROP PROCEDURE IF EXISTS phase3_add_allocation_picked_count;
DELIMITER $$
CREATE PROCEDURE phase3_add_allocation_picked_count()
BEGIN
IF EXISTS (
SELECT 1 FROM information_schema.tables
WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_inbound_pallet_allocation'
) AND NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_inbound_pallet_allocation'
AND column_name = 'picked_package_count'
) THEN
ALTER TABLE fl_inbound_pallet_allocation
ADD COLUMN picked_package_count INT UNSIGNED NOT NULL DEFAULT 0
COMMENT '已拣货箱数' AFTER allocated_package_count;
END IF;
END$$
DELIMITER ;
CALL phase3_add_allocation_picked_count();
DROP PROCEDURE phase3_add_allocation_picked_count;
CREATE TABLE IF NOT EXISTS fl_container_booking (
id BIGINT UNSIGNED NOT NULL COMMENT '货柜预订ID(雪花ID)',
booking_no VARCHAR(40) NOT NULL COMMENT '平台唯一货柜预订单号',
container_no VARCHAR(64) NULL COMMENT '集装箱号;确认前允许修订',
cabinet_no VARCHAR(64) NOT NULL COMMENT '平台唯一内部柜号',
channel_code VARCHAR(64) NULL COMMENT '订舱渠道编码快照;存量回填前禁止混装',
channel_name VARCHAR(128) NULL COMMENT '订舱渠道名称快照',
ocean_booking_id BIGINT UNSIGNED NULL COMMENT '来源订舱ID',
ocean_booking_requirement_id BIGINT UNSIGNED NULL COMMENT '来源订舱箱型需求ID',
warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '装柜仓库ID;平台公共资源',
warehouse_name VARCHAR(128) NOT NULL COMMENT '装柜仓库名称快照',
bill_of_lading_no VARCHAR(128) NULL COMMENT '提单号',
vessel_name VARCHAR(128) NULL COMMENT '船名快照',
voyage_no VARCHAR(64) NULL COMMENT '航次;确认前允许修订',
container_type VARCHAR(32) NOT NULL COMMENT '箱型,例如20GP、40GP、40HQ',
shipping_company_id BIGINT UNSIGNED NULL COMMENT '船司ID快照',
shipping_company_name VARCHAR(128) NULL COMMENT '船司名称快照',
departure_port_code VARCHAR(32) NULL COMMENT '起运港编码快照',
departure_port_name VARCHAR(128) NULL COMMENT '起运港名称快照',
destination_port_code VARCHAR(32) NULL COMMENT '目的港编码快照',
destination_port_name VARCHAR(128) NULL COMMENT '目的港名称快照',
destination_warehouse_code VARCHAR(64) NULL COMMENT '目的仓编码快照',
destination_warehouse_name VARCHAR(128) NULL COMMENT '目的仓名称快照',
capacity_cbm DECIMAL(18,4) NULL COMMENT '货柜容量m3',
max_weight_kg DECIMAL(18,3) NULL COMMENT '货柜最大承重kg',
terminal_trucking_company VARCHAR(128) NULL COMMENT '码头拖车公司',
loading_at DATETIME(3) NULL COMMENT '计划或实际装柜时间',
estimated_sailing_at DATETIME(3) NULL COMMENT '预计开船时间',
actual_sailing_at DATETIME(3) NULL COMMENT '实际开船时间',
estimated_port_arrival_at DATETIME(3) NULL COMMENT '预计到港时间',
actual_port_arrival_at DATETIME(3) NULL COMMENT '实际到港时间',
estimated_warehouse_arrival_at DATETIME(3) NULL COMMENT '预计到仓时间',
actual_warehouse_arrival_at DATETIME(3) 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_container_booking_no (booking_no),
UNIQUE KEY uk_container_booking_cabinet (cabinet_no),
KEY idx_container_booking_container (container_no),
KEY idx_container_booking_warehouse (warehouse_id, updated_at, id),
KEY idx_container_booking_voyage (voyage_no, updated_at),
CONSTRAINT chk_container_booking_capacity CHECK (capacity_cbm IS NULL OR capacity_cbm > 0),
CONSTRAINT chk_container_booking_weight CHECK (max_weight_kg IS NULL OR max_weight_kg > 0)
) ENGINE=InnoDB COMMENT='平台货柜预订资料;预装状态唯一来源为装柜计划';
CREATE TABLE IF NOT EXISTS fl_loading_plan (
id BIGINT UNSIGNED NOT NULL COMMENT '智能装柜计划ID(雪花ID)',
plan_no VARCHAR(40) NOT NULL COMMENT '平台唯一装柜计划号',
container_booking_id BIGINT UNSIGNED NOT NULL COMMENT '货柜预订ID',
plan_status VARCHAR(24) NOT NULL DEFAULT 'EDITING'
COMMENT '装柜计划状态:EDITING编辑中、LOADING装柜中、COMPLETED装柜完成;建议须经人工确认',
push_idempotency_key VARCHAR(128) NULL COMMENT '推送预装幂等键',
preload_pushed_by BIGINT UNSIGNED NULL COMMENT '推送预装操作人ID',
preload_pushed_by_name VARCHAR(64) NULL COMMENT '推送预装操作人名称快照',
preload_pushed_at DATETIME(3) NULL COMMENT '开始装柜并进入LOADING的时间',
preload_completed_by BIGINT UNSIGNED NULL COMMENT '预装完成操作人ID',
preload_completed_by_name VARCHAR(64) NULL COMMENT '预装完成操作人名称快照',
preload_completed_at DATETIME(3) NULL COMMENT '预装完成时间',
confirmed_by BIGINT UNSIGNED NULL COMMENT '确认操作人ID',
confirmed_by_name VARCHAR(64) NULL COMMENT '确认操作人名称快照',
confirmed_at DATETIME(3) NULL COMMENT '确认时间',
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_loading_plan_no (plan_no),
UNIQUE KEY uk_loading_plan_booking (container_booking_id),
UNIQUE KEY uk_loading_plan_push_key (push_idempotency_key),
KEY idx_loading_plan_status (plan_status, updated_at, id),
CONSTRAINT fk_loading_plan_booking FOREIGN KEY (container_booking_id)
REFERENCES fl_container_booking (id),
CONSTRAINT chk_loading_plan_status
CHECK (plan_status IN ('EDITING', 'LOADING', 'COMPLETED'))
) ENGINE=InnoDB COMMENT='平台智能装柜计划与唯一预装状态源';
CREATE TABLE IF NOT EXISTS fl_loading_plan_item (
id BIGINT UNSIGNED NOT NULL COMMENT '装柜计划明细ID(雪花ID)',
loading_plan_id BIGINT UNSIGNED NOT NULL COMMENT '智能装柜计划ID',
account_id BIGINT UNSIGNED NOT NULL COMMENT '客户账号ID;装柜业务唯一客户归属字段',
customer_name VARCHAR(128) NOT NULL COMMENT '客户名称快照',
inbound_id BIGINT UNSIGNED NOT NULL COMMENT 'Firstleg入库单ID',
inbound_detail_id BIGINT UNSIGNED NOT NULL COMMENT '订单货物或入库货物明细ID,不建立跨库外键',
order_id BIGINT UNSIGNED NOT NULL COMMENT '订单服务订单ID快照,不建立跨库外键',
order_no VARCHAR(32) NOT NULL COMMENT '订单号快照',
inbound_code VARCHAR(40) NOT NULL COMMENT '入仓码快照',
destination_warehouse_id BIGINT UNSIGNED NULL COMMENT '目的仓ID快照',
destination_warehouse_code VARCHAR(64) NULL COMMENT '目的仓编码快照',
destination_warehouse_name VARCHAR(128) NULL COMMENT '目的仓名称快照',
sku_code VARCHAR(128) NULL COMMENT 'SKU编码快照',
product_name_zh VARCHAR(255) NULL COMMENT '中文品名快照',
product_name_en VARCHAR(255) NULL COMMENT '英文品名快照',
product_image_file_id BIGINT UNSIGNED NULL COMMENT '产品图片File对象ID',
product_image_url VARCHAR(1000) NULL COMMENT '产品图片访问地址快照',
package_count INT UNSIGNED NOT NULL COMMENT '本次装柜箱数',
product_quantity INT UNSIGNED NULL COMMENT '产品总数量',
units_per_carton INT UNSIGNED NULL COMMENT '每箱产品数',
pallet_count INT UNSIGNED NULL COMMENT '托盘数',
gross_weight_kg DECIMAL(18,3) NULL COMMENT '本次装柜毛重kg',
net_weight_kg DECIMAL(18,3) NULL COMMENT '本次装柜净重kg',
dimensional_weight_kg DECIMAL(18,3) NULL COMMENT '本次装柜材积重kg',
volume_cbm DECIMAL(18,4) NULL COMMENT '本次装柜体积m3',
fba_tracking_no VARCHAR(128) NULL COMMENT 'FBA追踪号快照',
shipment_no VARCHAR(128) NULL COMMENT '货件编号快照',
shipping_mark VARCHAR(500) NULL COMMENT '唛头快照',
load_sequence INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '装柜顺序',
position_snapshot JSON NULL COMMENT '智能装柜位置与摆放结果快照',
internal_remark VARCHAR(1000) NULL COMMENT '平台内部备注',
customer_remark VARCHAR(1000) NULL COMMENT '客户备注快照',
warehouse_remark VARCHAR(1000) NULL COMMENT '仓库备注快照',
frozen_at DATETIME(3) NULL COMMENT '装柜完成后的永久冻结时间',
frozen_by BIGINT UNSIGNED NULL COMMENT '冻结操作人ID',
removed_by BIGINT UNSIGNED NULL COMMENT '推送前逻辑删除操作人ID',
removed_by_name VARCHAR(64) NULL COMMENT '推送前逻辑删除操作人名称快照',
removed_at DATETIME(3) NULL COMMENT '推送前逻辑删除时间',
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '推送前逻辑删除标记;冻结后不可修改',
active_inbound_detail_id BIGINT UNSIGNED GENERATED ALWAYS AS (
CASE WHEN deleted = 0 THEN inbound_detail_id ELSE NULL END
) STORED COMMENT '未删除明细唯一锁;删除后返回NULL释放分配资格',
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_loading_item_plan_detail (loading_plan_id, active_inbound_detail_id),
KEY idx_loading_item_active_detail (account_id, active_inbound_detail_id),
KEY idx_loading_item_plan (loading_plan_id, deleted, load_sequence, id),
KEY idx_loading_item_account (account_id, created_at, id),
KEY idx_loading_item_inbound (account_id, inbound_id, deleted),
KEY idx_loading_item_order (account_id, order_id, deleted),
CONSTRAINT fk_loading_item_plan FOREIGN KEY (loading_plan_id) REFERENCES fl_loading_plan (id),
CONSTRAINT chk_loading_item_account CHECK (account_id > 0),
CONSTRAINT chk_loading_item_package CHECK (package_count > 0),
CONSTRAINT chk_loading_item_product_quantity CHECK (product_quantity IS NULL OR product_quantity > 0),
CONSTRAINT chk_loading_item_units CHECK (units_per_carton IS NULL OR units_per_carton > 0),
CONSTRAINT chk_loading_item_pallet CHECK (pallet_count IS NULL OR pallet_count >= 0),
CONSTRAINT chk_loading_item_weight CHECK (
(gross_weight_kg IS NULL OR gross_weight_kg >= 0)
AND (net_weight_kg IS NULL OR net_weight_kg >= 0)
AND (dimensional_weight_kg IS NULL OR dimensional_weight_kg >= 0)
),
CONSTRAINT chk_loading_item_volume CHECK (volume_cbm IS NULL OR volume_cbm >= 0),
CONSTRAINT chk_loading_item_deleted CHECK (deleted IN (0, 1))
) ENGINE=InnoDB COMMENT='智能装柜客户货物明细;客户归属仅在明细层';
CREATE TABLE IF NOT EXISTS fl_container_document (
id BIGINT UNSIGNED NOT NULL COMMENT '货柜文件关联ID(雪花ID)',
container_booking_id BIGINT UNSIGNED NOT NULL COMMENT '货柜预订ID',
document_type VARCHAR(32) NOT NULL COMMENT '文件类型:DESTINATION_CLEARANCE目的港清关、CUSTOMS_DECLARATION出口报关、DELIVERY_ORDER提货单、PICKUP_ORDER取货单',
file_id BIGINT UNSIGNED NOT NULL COMMENT 'File服务文件ID,不建立跨库外键',
file_name VARCHAR(255) NOT NULL COMMENT '文件名称快照',
file_hash VARCHAR(64) NULL COMMENT 'File服务SHA-256快照',
file_url VARCHAR(1000) NULL COMMENT '文件访问地址快照',
uploaded_by BIGINT UNSIGNED NOT NULL COMMENT '上传操作人ID',
uploaded_by_name VARCHAR(64) NULL COMMENT '上传操作人名称快照',
completed_by BIGINT UNSIGNED NULL COMMENT '一键完成操作人ID',
completed_by_name VARCHAR(64) NULL COMMENT '一键完成操作人名称快照',
completed_at DATETIME(3) NULL COMMENT '一键完成时间',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)
COMMENT '货柜文件关联最后更新时间',
version INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id),
UNIQUE KEY uk_container_document_type (container_booking_id, document_type),
KEY idx_container_document_file (file_id),
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'))
) ENGINE=InnoDB COMMENT='平台货柜清关、DO与PU文件关联';
CREATE TABLE IF NOT EXISTS fl_loading_plan_history (
id BIGINT UNSIGNED NOT NULL COMMENT '装柜操作历史ID(雪花ID)',
loading_plan_id BIGINT UNSIGNED NOT NULL COMMENT '智能装柜计划ID',
loading_plan_item_id BIGINT UNSIGNED NULL COMMENT '装柜计划明细ID',
operation_id VARCHAR(128) NOT NULL COMMENT '操作幂等ID',
action_type VARCHAR(32) NOT NULL
COMMENT '动作类型:CREATE_PLAN创建计划、ADD_ITEM新增明细、ADJUST_ITEM调整明细、REMOVE_ITEM移除明细、START_LOADING开始装柜、COMPLETE_LOADING完成装柜、PICK_ITEM拣货',
from_status VARCHAR(24) NULL COMMENT '变更前状态',
to_status VARCHAR(24) NULL COMMENT '变更后状态',
before_snapshot JSON NULL COMMENT '变更前快照',
after_snapshot JSON NULL COMMENT '变更后快照',
operator_id BIGINT UNSIGNED NOT NULL COMMENT '操作人ID',
operator_name VARCHAR(64) NULL COMMENT '操作人名称快照',
operation_reason VARCHAR(500) NULL COMMENT '操作原因',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '操作时间',
PRIMARY KEY (id),
UNIQUE KEY uk_loading_history_operation (operation_id),
KEY idx_loading_history_plan (loading_plan_id, created_at, id),
KEY idx_loading_history_item (loading_plan_item_id, created_at, id),
CONSTRAINT fk_loading_history_plan FOREIGN KEY (loading_plan_id) REFERENCES fl_loading_plan (id),
CONSTRAINT fk_loading_history_item FOREIGN KEY (loading_plan_item_id)
REFERENCES fl_loading_plan_item (id)
) ENGINE=InnoDB COMMENT='智能装柜追加式不可变操作历史';
-- fl_loading_pick_task 曾被另一套入库汇总模型复用。先按字段签名识别并迁走,
-- 再创建智能装柜任务表;未知或混合结构必须人工处理,禁止静默套用错误表型。
DROP PROCEDURE IF EXISTS assert_loading_pick_task_shapes;
DELIMITER $$
CREATE PROCEDURE assert_loading_pick_task_shapes(IN require_both TINYINT)
BEGIN
DECLARE source_exists INT DEFAULT 0;
DECLARE target_exists INT DEFAULT 0;
DECLARE source_total INT DEFAULT 0;
DECLARE source_smart INT DEFAULT 0;
DECLARE source_stable_smart INT DEFAULT 0;
DECLARE source_inbound INT DEFAULT 0;
DECLARE target_total INT DEFAULT 0;
DECLARE target_inbound INT DEFAULT 0;
SELECT COUNT(*) INTO source_exists FROM information_schema.tables
WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_loading_pick_task';
SELECT COUNT(*) INTO target_exists FROM information_schema.tables
WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_inbound_loading_pick_task';
IF source_exists = 1 THEN
SELECT COUNT(*) INTO source_total FROM information_schema.columns
WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_loading_pick_task';
SELECT COUNT(*) INTO source_smart FROM information_schema.columns
WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_loading_pick_task' AND (
(column_name = 'id' AND LOWER(column_type) = 'bigint unsigned' AND is_nullable = 'NO')
OR (column_name = 'loading_plan_id' AND LOWER(column_type) = 'bigint unsigned' AND is_nullable = 'NO')
OR (column_name = 'container_no' AND LOWER(column_type) = 'varchar(64)' AND is_nullable = 'NO')
OR (column_name = 'task_status' AND LOWER(column_type) = 'varchar(16)' AND is_nullable = 'NO' AND column_default = 'LOADING')
OR (column_name = 'started_by' AND LOWER(column_type) = 'bigint unsigned' AND is_nullable = 'NO')
OR (column_name = 'started_by_name' AND LOWER(column_type) = 'varchar(64)' AND is_nullable = 'YES')
OR (column_name = 'started_at' AND data_type = 'datetime' AND datetime_precision = 3 AND is_nullable = 'NO' AND LOWER(column_default) LIKE 'current_timestamp%')
OR (column_name = 'completed_by' AND LOWER(column_type) = 'bigint unsigned' AND is_nullable = 'YES')
OR (column_name = 'completed_by_name' AND LOWER(column_type) = 'varchar(64)' AND is_nullable = 'YES')
OR (column_name = 'completed_at' AND data_type = 'datetime' AND datetime_precision = 3 AND is_nullable = 'YES')
OR (column_name = 'version' AND LOWER(column_type) = 'int unsigned' AND is_nullable = 'NO' AND CAST(column_default AS CHAR) = '0'));
SELECT COUNT(*) INTO source_inbound FROM information_schema.columns
WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_loading_pick_task' AND (
(column_name = 'id' AND LOWER(column_type) = 'bigint unsigned' AND is_nullable = 'NO')
OR (column_name = 'tenant_id' AND LOWER(column_type) = 'bigint unsigned' AND is_nullable = 'NO')
OR (column_name = 'inbound_id' AND LOWER(column_type) = 'bigint unsigned' AND is_nullable = 'NO')
OR (column_name = 'status' AND LOWER(column_type) = 'varchar(24)' AND is_nullable = 'NO' AND column_default = 'PENDING')
OR (column_name = 'version' AND LOWER(column_type) = 'int' AND is_nullable = 'NO' AND CAST(column_default AS CHAR) = '0')
OR (column_name = 'expected_count' AND LOWER(column_type) = 'int unsigned' AND is_nullable = 'YES')
OR (column_name = 'picked_count' AND LOWER(column_type) = 'int unsigned' AND is_nullable = 'YES')
OR (column_name = 'remark' AND LOWER(column_type) = 'varchar(1000)' AND is_nullable = 'YES')
OR (column_name = 'updated_by' AND LOWER(column_type) = 'bigint unsigned' AND is_nullable = 'NO')
OR (column_name = 'updated_by_name' AND LOWER(column_type) = 'varchar(64)' AND is_nullable = 'YES')
OR (column_name = 'created_at' AND data_type = 'datetime' AND datetime_precision = 3 AND is_nullable = 'NO' AND LOWER(column_default) LIKE 'current_timestamp%')
OR (column_name = 'updated_at' AND data_type = 'datetime' AND datetime_precision = 3 AND is_nullable = 'NO' AND LOWER(column_default) LIKE 'current_timestamp%' AND LOWER(extra) LIKE '%on update current_timestamp%'));
SELECT COUNT(*) INTO source_stable_smart FROM information_schema.columns
WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_loading_pick_task' AND (
(column_name = 'id' AND LOWER(column_type) = 'bigint unsigned' AND is_nullable = 'NO')
OR (column_name = 'loading_plan_id' AND LOWER(column_type) = 'bigint unsigned' AND is_nullable = 'NO')
OR (column_name = 'container_booking_id' AND LOWER(column_type) = 'bigint unsigned' AND is_nullable = 'NO')
OR (column_name = 'cabinet_no' AND LOWER(column_type) = 'varchar(64)' AND is_nullable = 'NO')
OR (column_name = 'container_no' AND LOWER(column_type) = 'varchar(64)' AND is_nullable = 'YES')
OR (column_name = 'task_status' AND LOWER(column_type) = 'varchar(16)' AND is_nullable = 'NO' AND column_default = 'LOADING')
OR (column_name = 'started_by' AND LOWER(column_type) = 'bigint unsigned' AND is_nullable = 'NO')
OR (column_name = 'started_by_name' AND LOWER(column_type) = 'varchar(64)' AND is_nullable = 'YES')
OR (column_name = 'started_at' AND data_type = 'datetime' AND datetime_precision = 3 AND is_nullable = 'NO' AND LOWER(column_default) LIKE 'current_timestamp%')
OR (column_name = 'completed_by' AND LOWER(column_type) = 'bigint unsigned' AND is_nullable = 'YES')
OR (column_name = 'completed_by_name' AND LOWER(column_type) = 'varchar(64)' AND is_nullable = 'YES')
OR (column_name = 'completed_at' AND data_type = 'datetime' AND datetime_precision = 3 AND is_nullable = 'YES')
OR (column_name = 'version' AND LOWER(column_type) = 'int unsigned' AND is_nullable = 'NO' AND CAST(column_default AS CHAR) = '0'));
IF NOT (
(source_total = 11 AND source_smart = 11
AND (SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_loading_pick_task' AND index_name = 'uk_loading_pick_task_plan' AND non_unique = 0 AND seq_in_index = 1 AND column_name = 'loading_plan_id') = 1
AND (SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_loading_pick_task' AND index_name = 'idx_loading_pick_container' AND ((seq_in_index = 1 AND column_name = 'container_no') OR (seq_in_index = 2 AND column_name = 'task_status') OR (seq_in_index = 3 AND column_name = 'started_at'))) = 3
AND (SELECT COUNT(*) FROM information_schema.key_column_usage WHERE constraint_schema = 'baiyue_firstleg' AND table_name = 'fl_loading_pick_task' AND column_name = 'loading_plan_id' AND referenced_table_name = 'fl_loading_plan' AND referenced_column_name = 'id') = 1)
OR
(source_total = 12 AND source_inbound = 12
AND (SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_loading_pick_task' AND index_name = 'uk_loading_pick_tenant_inbound' AND non_unique = 0 AND ((seq_in_index = 1 AND column_name = 'tenant_id') OR (seq_in_index = 2 AND column_name = 'inbound_id'))) = 2
AND (SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_loading_pick_task' AND index_name = 'idx_loading_pick_status' AND ((seq_in_index = 1 AND column_name = 'tenant_id') OR (seq_in_index = 2 AND column_name = 'status') OR (seq_in_index = 3 AND column_name = 'updated_at'))) = 3
AND (SELECT COUNT(*) FROM information_schema.key_column_usage WHERE constraint_schema = 'baiyue_firstleg' AND table_name = 'fl_loading_pick_task' AND column_name = 'inbound_id' AND referenced_table_name = 'fl_inbound_order' AND referenced_column_name = 'id') = 1)
OR
(source_total = 13 AND source_stable_smart = 13
AND (SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_loading_pick_task' AND index_name = 'uk_loading_pick_task_plan' AND non_unique = 0 AND seq_in_index = 1 AND column_name = 'loading_plan_id') = 1
AND (SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_loading_pick_task' AND index_name = 'uk_loading_pick_task_booking' AND non_unique = 0 AND seq_in_index = 1 AND column_name = 'container_booking_id') = 1
AND (SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_loading_pick_task' AND index_name = 'idx_loading_pick_container' AND ((seq_in_index = 1 AND column_name = 'container_no') OR (seq_in_index = 2 AND column_name = 'task_status') OR (seq_in_index = 3 AND column_name = 'started_at'))) = 3
AND (SELECT COUNT(*) FROM information_schema.key_column_usage WHERE constraint_schema = 'baiyue_firstleg' AND table_name = 'fl_loading_pick_task' AND column_name = 'loading_plan_id' AND referenced_table_name = 'fl_loading_plan' AND referenced_column_name = 'id') = 1)
) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '拣货任务迁移失败:fl_loading_pick_task 不是完整 canonical 表型';
END IF;
END IF;
IF target_exists = 1 THEN
SELECT COUNT(*) INTO target_total FROM information_schema.columns
WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_inbound_loading_pick_task';
SELECT COUNT(*) INTO target_inbound FROM information_schema.columns
WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_inbound_loading_pick_task' AND (
(column_name = 'id' AND LOWER(column_type) = 'bigint unsigned' AND is_nullable = 'NO')
OR (column_name = 'tenant_id' AND LOWER(column_type) = 'bigint unsigned' AND is_nullable = 'NO')
OR (column_name = 'inbound_id' AND LOWER(column_type) = 'bigint unsigned' AND is_nullable = 'NO')
OR (column_name = 'status' AND LOWER(column_type) = 'varchar(24)' AND is_nullable = 'NO' AND column_default = 'PENDING')
OR (column_name = 'version' AND LOWER(column_type) = 'int' AND is_nullable = 'NO' AND CAST(column_default AS CHAR) = '0')
OR (column_name = 'expected_count' AND LOWER(column_type) = 'int unsigned' AND is_nullable = 'YES')
OR (column_name = 'picked_count' AND LOWER(column_type) = 'int unsigned' AND is_nullable = 'YES')
OR (column_name = 'remark' AND LOWER(column_type) = 'varchar(1000)' AND is_nullable = 'YES')
OR (column_name = 'updated_by' AND LOWER(column_type) = 'bigint unsigned' AND is_nullable = 'NO')
OR (column_name = 'updated_by_name' AND LOWER(column_type) = 'varchar(64)' AND is_nullable = 'YES')
OR (column_name = 'created_at' AND data_type = 'datetime' AND datetime_precision = 3 AND is_nullable = 'NO' AND LOWER(column_default) LIKE 'current_timestamp%')
OR (column_name = 'updated_at' AND data_type = 'datetime' AND datetime_precision = 3 AND is_nullable = 'NO' AND LOWER(column_default) LIKE 'current_timestamp%' AND LOWER(extra) LIKE '%on update current_timestamp%'));
IF target_total <> 12 OR target_inbound <> 12
OR (SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_inbound_loading_pick_task' AND index_name = 'uk_loading_pick_tenant_inbound' AND non_unique = 0 AND ((seq_in_index = 1 AND column_name = 'tenant_id') OR (seq_in_index = 2 AND column_name = 'inbound_id'))) <> 2
OR (SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_inbound_loading_pick_task' AND index_name = 'idx_loading_pick_status' AND ((seq_in_index = 1 AND column_name = 'tenant_id') OR (seq_in_index = 2 AND column_name = 'status') OR (seq_in_index = 3 AND column_name = 'updated_at'))) <> 3
OR (SELECT COUNT(*) FROM information_schema.key_column_usage WHERE constraint_schema = 'baiyue_firstleg' AND table_name = 'fl_inbound_loading_pick_task' AND column_name = 'inbound_id' AND referenced_table_name = 'fl_inbound_order' AND referenced_column_name = 'id') <> 1 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '拣货任务迁移失败:fl_inbound_loading_pick_task 不是完整 canonical 表型';
END IF;
END IF;
IF require_both = 1 AND (source_exists <> 1 OR target_exists <> 1 OR source_stable_smart <> 13) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '拣货任务终验失败:两张 canonical 表未同时就绪';
END IF;
END$$
DELIMITER ;
CALL assert_loading_pick_task_shapes(0);
DROP PROCEDURE IF EXISTS migrate_legacy_loading_pick_task;
DELIMITER $$
CREATE PROCEDURE migrate_legacy_loading_pick_task()
BEGIN
DECLARE source_exists INT DEFAULT 0;
DECLARE source_smart_columns INT DEFAULT 0;
DECLARE source_inbound_columns INT DEFAULT 0;
DECLARE target_exists INT DEFAULT 0;
DECLARE target_inbound_columns INT DEFAULT 0;
SELECT COUNT(*) INTO source_exists
FROM information_schema.tables
WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_loading_pick_task';
IF source_exists = 1 THEN
SELECT COUNT(*) INTO source_smart_columns
FROM information_schema.columns
WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_loading_pick_task'
AND column_name IN ('loading_plan_id', 'container_no', 'task_status');
SELECT COUNT(*) INTO source_inbound_columns
FROM information_schema.columns
WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_loading_pick_task'
AND column_name IN ('tenant_id', 'inbound_id', 'status', 'expected_count', 'picked_count');
IF source_smart_columns = 3 AND source_inbound_columns = 0 THEN
SET source_exists = 1;
ELSEIF source_smart_columns = 0 AND source_inbound_columns = 5 THEN
SELECT COUNT(*) INTO target_exists
FROM information_schema.tables
WHERE table_schema = 'baiyue_firstleg'
AND table_name = 'fl_inbound_loading_pick_task';
IF target_exists = 0 THEN
RENAME TABLE fl_loading_pick_task TO fl_inbound_loading_pick_task;
ELSE
SELECT COUNT(*) INTO target_inbound_columns
FROM information_schema.columns
WHERE table_schema = 'baiyue_firstleg'
AND table_name = 'fl_inbound_loading_pick_task'
AND column_name IN ('tenant_id', 'inbound_id', 'status', 'expected_count', 'picked_count');
IF target_inbound_columns <> 5 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '拣货任务迁移失败:fl_inbound_loading_pick_task 不是入库汇总结构';
END IF;
INSERT INTO fl_inbound_loading_pick_task
(id, tenant_id, inbound_id, status, version, expected_count, picked_count,
remark, updated_by, updated_by_name, created_at, updated_at)
SELECT source.id, source.tenant_id, source.inbound_id, source.status, source.version,
source.expected_count, source.picked_count, source.remark, source.updated_by,
source.updated_by_name, source.created_at, source.updated_at
FROM fl_loading_pick_task source
LEFT JOIN fl_inbound_loading_pick_task target
ON target.id = source.id
WHERE target.id IS NULL;
IF EXISTS (
SELECT 1
FROM fl_loading_pick_task source
LEFT JOIN fl_inbound_loading_pick_task target ON target.id = source.id
WHERE target.id IS NULL
OR NOT (target.tenant_id <=> source.tenant_id)
OR NOT (target.inbound_id <=> source.inbound_id)
OR NOT (target.status <=> source.status)
OR NOT (target.version <=> source.version)
OR NOT (target.expected_count <=> source.expected_count)
OR NOT (target.picked_count <=> source.picked_count)
OR NOT (target.remark <=> source.remark)
OR NOT (target.updated_by <=> source.updated_by)
OR NOT (target.updated_by_name <=> source.updated_by_name)
OR NOT (target.created_at <=> source.created_at)
OR NOT (target.updated_at <=> source.updated_at)
) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '拣货任务迁移失败:新旧入库汇总任务存在主键冲突';
END IF;
DROP TABLE fl_loading_pick_task;
END IF;
ELSE
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '拣货任务迁移失败:fl_loading_pick_task 字段签名未知或混合';
END IF;
END IF;
END$$
DELIMITER ;
CALL migrate_legacy_loading_pick_task();
DROP PROCEDURE migrate_legacy_loading_pick_task;
CREATE TABLE IF NOT EXISTS fl_loading_pick_task (
id BIGINT UNSIGNED NOT NULL COMMENT 'PDA拣货任务ID',
loading_plan_id BIGINT UNSIGNED NOT NULL COMMENT '装柜计划ID',
container_booking_id BIGINT UNSIGNED NOT NULL COMMENT '稳定柜次ID',
cabinet_no VARCHAR(64) NOT NULL COMMENT '稳定内部柜次号快照',
container_no VARCHAR(64) NULL COMMENT '废弃兼容字段;不得作为任务身份或随运输号变更重写',
task_status VARCHAR(16) NOT NULL DEFAULT 'LOADING' COMMENT 'PDA任务状态:LOADING拣货装柜中、COMPLETED已完成',
started_by BIGINT UNSIGNED NOT NULL COMMENT '开始装柜人',
started_by_name VARCHAR(64) NULL COMMENT '开始装柜人名称',
started_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '开始时间',
completed_by BIGINT UNSIGNED NULL COMMENT '完成装柜人',
completed_by_name VARCHAR(64) NULL COMMENT '完成装柜人名称',
completed_at DATETIME(3) NULL COMMENT '完成时间',
version INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id),
UNIQUE KEY uk_loading_pick_task_plan (loading_plan_id),
UNIQUE KEY uk_loading_pick_task_booking (container_booking_id),
KEY idx_loading_pick_container (container_no, task_status, started_at),
CONSTRAINT fk_loading_pick_task_plan FOREIGN KEY (loading_plan_id) REFERENCES fl_loading_plan (id),
CONSTRAINT chk_loading_pick_task_status CHECK (task_status IN ('LOADING', 'COMPLETED'))
) ENGINE=InnoDB COMMENT='PDA装柜拣货任务';
CREATE TABLE IF NOT EXISTS fl_loading_pick_item (
id BIGINT UNSIGNED NOT NULL COMMENT 'PDA拣货明细ID',
pick_task_id BIGINT UNSIGNED NOT NULL COMMENT 'PDA拣货任务ID',
loading_plan_item_id BIGINT UNSIGNED NOT NULL COMMENT '装柜计划明细ID',
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '父入库单ID',
inbound_detail_id BIGINT UNSIGNED NOT NULL COMMENT '订单货物明细ID',
planned_package_count INT UNSIGNED NOT NULL COMMENT '计划拣货箱数;上层拆分按业务规则向上取整',
picked_package_count INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '已拣货箱数',
item_status VARCHAR(16) NOT NULL DEFAULT 'PENDING' COMMENT '拣货明细状态:PENDING待拣、PICKING拣货中、PICKED已拣完、PARTIAL部分拣货、SKIPPED跳拣',
version INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'PDA拣货明细创建时间',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT 'PDA拣货明细最后更新时间',
PRIMARY KEY (id),
UNIQUE KEY uk_loading_pick_item_plan_item (loading_plan_item_id),
KEY idx_loading_pick_item_task (pick_task_id, item_status, id),
CONSTRAINT fk_loading_pick_item_task FOREIGN KEY (pick_task_id) REFERENCES fl_loading_pick_task (id),
CONSTRAINT fk_loading_pick_item_plan_item FOREIGN KEY (loading_plan_item_id) REFERENCES fl_loading_plan_item (id),
CONSTRAINT chk_loading_pick_item_planned CHECK (planned_package_count > 0),
CONSTRAINT chk_loading_pick_item_count CHECK (picked_package_count <= planned_package_count),
CONSTRAINT chk_loading_pick_item_status CHECK (item_status IN ('PENDING', 'PICKING', 'PICKED', 'PARTIAL', 'SKIPPED'))
) ENGINE=InnoDB COMMENT='PDA装柜拣货明细';
CREATE TABLE IF NOT EXISTS fl_loading_pick_history (
id BIGINT UNSIGNED NOT NULL COMMENT 'PDA拣货操作历史ID',
pick_task_id BIGINT UNSIGNED NOT NULL COMMENT 'PDA拣货任务ID',
pick_item_id BIGINT UNSIGNED NOT NULL COMMENT 'PDA拣货明细ID',
operation_id VARCHAR(128) NOT NULL COMMENT '客户端幂等操作号',
request_hash CHAR(64) NOT NULL COMMENT '完整批量请求内容SHA-256摘要',
action_type VARCHAR(24) NOT NULL DEFAULT 'PICK' COMMENT '拣货动作:PICK正常拣货、PARTIAL部分拣货、SKIP跳拣',
picked_package_count INT UNSIGNED NOT NULL COMMENT '本次拣货箱数',
operation_reason VARCHAR(500) NULL COMMENT '部分拣货或跳拣原因',
operator_id BIGINT UNSIGNED NOT NULL COMMENT '执行PDA拣货操作的作业员ID',
operator_name VARCHAR(64) NULL COMMENT '作业员姓名快照',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '不可变拣货操作历史创建时间',
PRIMARY KEY (id),
UNIQUE KEY uk_loading_pick_history_operation (operation_id),
KEY idx_loading_pick_history_item (pick_item_id, created_at),
CONSTRAINT fk_loading_pick_history_task FOREIGN KEY (pick_task_id) REFERENCES fl_loading_pick_task (id),
CONSTRAINT fk_loading_pick_history_item FOREIGN KEY (pick_item_id) REFERENCES fl_loading_pick_item (id)
) ENGINE=InnoDB COMMENT='PDA拣货幂等和操作审计历史';
DROP PROCEDURE IF EXISTS repair_phase3_loading_pick_task_foreign_keys;
DELIMITER $$
CREATE PROCEDURE repair_phase3_loading_pick_task_foreign_keys()
BEGIN
DECLARE item_fk_count INT DEFAULT 0;
DECLARE item_target_count INT DEFAULT 0;
DECLARE history_fk_count INT DEFAULT 0;
DECLARE history_target_count INT DEFAULT 0;
DECLARE item_drop_clauses TEXT;
DECLARE history_drop_clauses TEXT;
IF EXISTS (
SELECT 1 FROM fl_loading_pick_item item
LEFT JOIN fl_loading_pick_task task ON task.id = item.pick_task_id
WHERE task.id IS NULL
) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '柜次PDA明细存在无法迁移到智能装柜任务的引用';
END IF;
IF EXISTS (
SELECT 1 FROM fl_loading_pick_history history
LEFT JOIN fl_loading_pick_task task ON task.id = history.pick_task_id
WHERE task.id IS NULL
) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '柜次PDA历史存在无法迁移到智能装柜任务的引用';
END IF;
SELECT COUNT(*),
SUM(k.referenced_table_name = 'fl_loading_pick_task'
AND k.referenced_column_name = 'id'
AND (SELECT COUNT(*) FROM information_schema.key_column_usage peer
WHERE peer.constraint_schema = k.constraint_schema
AND peer.table_name = k.table_name
AND peer.constraint_name = k.constraint_name) = 1),
GROUP_CONCAT(CONCAT('DROP FOREIGN KEY `', REPLACE(k.constraint_name, '`', '``'), '`')
ORDER BY k.constraint_name SEPARATOR ', ')
INTO item_fk_count, item_target_count, item_drop_clauses
FROM information_schema.key_column_usage k
WHERE k.constraint_schema = 'baiyue_firstleg'
AND k.table_name = 'fl_loading_pick_item'
AND k.column_name = 'pick_task_id'
AND k.referenced_table_name IS NOT NULL;
IF item_fk_count <> 1 OR item_target_count <> 1 THEN
IF item_drop_clauses IS NOT NULL THEN
SET @phase3_fk_ddl = CONCAT('ALTER TABLE fl_loading_pick_item ', item_drop_clauses);
PREPARE phase3_fk_statement FROM @phase3_fk_ddl;
EXECUTE phase3_fk_statement;
DEALLOCATE PREPARE phase3_fk_statement;
END IF;
ALTER TABLE fl_loading_pick_item
ADD CONSTRAINT fk_loading_pick_item_task
FOREIGN KEY (pick_task_id) REFERENCES fl_loading_pick_task (id);
END IF;
SELECT COUNT(*),
SUM(k.referenced_table_name = 'fl_loading_pick_task'
AND k.referenced_column_name = 'id'
AND (SELECT COUNT(*) FROM information_schema.key_column_usage peer
WHERE peer.constraint_schema = k.constraint_schema
AND peer.table_name = k.table_name
AND peer.constraint_name = k.constraint_name) = 1),
GROUP_CONCAT(CONCAT('DROP FOREIGN KEY `', REPLACE(k.constraint_name, '`', '``'), '`')
ORDER BY k.constraint_name SEPARATOR ', ')
INTO history_fk_count, history_target_count, history_drop_clauses
FROM information_schema.key_column_usage k
WHERE k.constraint_schema = 'baiyue_firstleg'
AND k.table_name = 'fl_loading_pick_history'
AND k.column_name = 'pick_task_id'
AND k.referenced_table_name IS NOT NULL;
IF history_fk_count <> 1 OR history_target_count <> 1 THEN
IF history_drop_clauses IS NOT NULL THEN
SET @phase3_fk_ddl = CONCAT('ALTER TABLE fl_loading_pick_history ', history_drop_clauses);
PREPARE phase3_fk_statement FROM @phase3_fk_ddl;
EXECUTE phase3_fk_statement;
DEALLOCATE PREPARE phase3_fk_statement;
END IF;
ALTER TABLE fl_loading_pick_history
ADD CONSTRAINT fk_loading_pick_history_task
FOREIGN KEY (pick_task_id) REFERENCES fl_loading_pick_task (id);
END IF;
END$$
DELIMITER ;
CALL repair_phase3_loading_pick_task_foreign_keys();
DROP PROCEDURE repair_phase3_loading_pick_task_foreign_keys;
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 = 'COMPLETED' THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '装柜完成后货柜全部只读,不允许修改';
END IF;
IF current_plan_status = 'LOADING' 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.bill_of_lading_no <=> NEW.bill_of_lading_no)
OR NOT (OLD.vessel_name <=> NEW.vessel_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 <=> NEW.terminal_trucking_company)
OR NOT (OLD.loading_at <=> NEW.loading_at)
OR NOT (OLD.estimated_sailing_at <=> NEW.estimated_sailing_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_loading_plan_before_update;
DELIMITER $$
CREATE TRIGGER trg_loading_plan_before_update
BEFORE UPDATE ON fl_loading_plan
FOR EACH ROW
BEGIN
IF OLD.plan_status = 'COMPLETED' THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '已完成装柜计划全部只读';
END IF;
IF NOT (
(OLD.plan_status = 'EDITING' AND NEW.plan_status IN ('EDITING', 'LOADING'))
OR (OLD.plan_status = 'LOADING'
AND NEW.plan_status IN ('LOADING', 'COMPLETED'))
OR (OLD.plan_status = 'LOADING' AND NEW.plan_status = 'EDITING'
AND NOT EXISTS (
SELECT 1
FROM fl_loading_pick_item pick_item
JOIN fl_loading_pick_task pick_task ON pick_task.id = pick_item.pick_task_id
WHERE pick_task.loading_plan_id = OLD.id
AND pick_item.picked_package_count > 0
))
) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '装柜状态只允许单向推进';
END IF;
IF OLD.plan_status = 'LOADING' AND (
NOT (OLD.id <=> NEW.id) OR NOT (OLD.plan_no <=> NEW.plan_no)
OR NOT (OLD.container_booking_id <=> NEW.container_booking_id)
OR NOT (OLD.push_idempotency_key <=> NEW.push_idempotency_key)
OR NOT (OLD.preload_pushed_by <=> NEW.preload_pushed_by)
OR NOT (OLD.preload_pushed_by_name <=> NEW.preload_pushed_by_name)
OR NOT (OLD.preload_pushed_at <=> NEW.preload_pushed_at)
OR NOT (OLD.remark <=> NEW.remark)
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;
IF OLD.plan_status = NEW.plan_status
AND OLD.plan_status = 'LOADING' AND (
NOT (OLD.preload_completed_by <=> NEW.preload_completed_by)
OR NOT (OLD.preload_completed_by_name <=> NEW.preload_completed_by_name)
OR NOT (OLD.preload_completed_at <=> NEW.preload_completed_at)
OR NOT (OLD.confirmed_by <=> NEW.confirmed_by)
OR NOT (OLD.confirmed_by_name <=> NEW.confirmed_by_name)
OR NOT (OLD.confirmed_at <=> NEW.confirmed_at)
) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '同状态乐观锁触碰不得修改阶段审计字段';
END IF;
IF OLD.plan_status = 'EDITING' AND NEW.plan_status = 'LOADING' THEN
IF NEW.push_idempotency_key IS NULL OR CHAR_LENGTH(TRIM(NEW.push_idempotency_key)) = 0 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '开始装柜必须提供幂等键';
END IF;
SET NEW.preload_pushed_at = COALESCE(NEW.preload_pushed_at, CURRENT_TIMESTAMP(3));
ELSEIF OLD.plan_status = 'LOADING' AND NEW.plan_status = 'COMPLETED' THEN
IF EXISTS (SELECT 1 FROM fl_loading_plan_item item
LEFT JOIN fl_loading_pick_item pick_item
ON pick_item.loading_plan_item_id = item.id
LEFT JOIN fl_loading_pick_task pick_task
ON pick_task.id = pick_item.pick_task_id
AND pick_task.loading_plan_id = OLD.id
WHERE item.loading_plan_id = OLD.id AND item.deleted = 0
AND (pick_task.id IS NULL
OR pick_item.picked_package_count <> item.package_count)) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '仍有未完成拣货明细,不能完成装柜';
END IF;
SET NEW.confirmed_at = COALESCE(NEW.confirmed_at, CURRENT_TIMESTAMP(3));
END IF;
END$$
DELIMITER ;
DROP TRIGGER IF EXISTS trg_loading_plan_after_update;
DELIMITER $$
CREATE TRIGGER trg_loading_plan_after_update
AFTER UPDATE ON fl_loading_plan
FOR EACH ROW
BEGIN
IF OLD.plan_status = 'LOADING' AND NEW.plan_status = 'COMPLETED' THEN
UPDATE fl_loading_plan_item
SET frozen_at = COALESCE(frozen_at, NEW.confirmed_at, CURRENT_TIMESTAMP(3)),
frozen_by = COALESCE(frozen_by, NEW.updated_by),
updated_by = NEW.updated_by,
updated_by_name = NEW.updated_by_name,
version = version + 1
WHERE loading_plan_id = NEW.id AND deleted = 0;
UPDATE fl_loading_pick_task
SET task_status = 'COMPLETED', completed_by = NEW.updated_by,
completed_by_name = NEW.updated_by_name, completed_at = CURRENT_TIMESTAMP(3),
version = version + 1
WHERE loading_plan_id = NEW.id AND task_status = 'LOADING';
END IF;
END$$
DELIMITER ;
DROP TRIGGER IF EXISTS trg_loading_item_before_insert;
DELIMITER $$
CREATE TRIGGER trg_loading_item_before_insert
BEFORE INSERT ON fl_loading_plan_item
FOR EACH ROW
BEGIN
DECLARE current_plan_status VARCHAR(24);
SELECT MAX(plan_status) INTO current_plan_status FROM fl_loading_plan WHERE id = NEW.loading_plan_id;
IF current_plan_status IS NULL OR current_plan_status NOT IN ('EDITING', 'LOADING') THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '仅编辑中或装柜中的计划允许新增明细';
END IF;
IF NEW.deleted <> 0 OR NEW.frozen_at IS NOT NULL OR NEW.frozen_by IS NOT NULL THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '新增装柜明细必须为未删除且未冻结状态';
END IF;
END$$
DELIMITER ;
DROP TRIGGER IF EXISTS trg_loading_item_before_update;
DELIMITER $$
CREATE TRIGGER trg_loading_item_before_update
BEFORE UPDATE ON fl_loading_plan_item
FOR EACH ROW
BEGIN
DECLARE current_plan_status VARCHAR(24);
SELECT MAX(plan_status) INTO current_plan_status
FROM fl_loading_plan WHERE id = OLD.loading_plan_id;
IF current_plan_status = 'LOADING' AND EXISTS (
SELECT 1 FROM fl_loading_pick_item
WHERE loading_plan_item_id = OLD.id AND picked_package_count > 0) THEN
IF NOT EXISTS (
SELECT 1 FROM fl_loading_pick_item
WHERE loading_plan_item_id = OLD.id
AND item_status = 'PARTIAL'
AND NEW.deleted = 0
AND NEW.package_count = picked_package_count
) OR NEW.deleted <> OLD.deleted
OR NOT (NEW.customer_name <=> OLD.customer_name)
OR NOT (NEW.inbound_id <=> OLD.inbound_id)
OR NOT (NEW.inbound_detail_id <=> OLD.inbound_detail_id)
OR NOT (NEW.order_id <=> OLD.order_id)
OR NOT (NEW.order_no <=> OLD.order_no)
OR NOT (NEW.inbound_code <=> OLD.inbound_code)
OR NOT (NEW.destination_warehouse_id <=> OLD.destination_warehouse_id)
OR NOT (NEW.destination_warehouse_code <=> OLD.destination_warehouse_code)
OR NOT (NEW.destination_warehouse_name <=> OLD.destination_warehouse_name)
OR NOT (NEW.sku_code <=> OLD.sku_code)
OR NOT (NEW.product_name_zh <=> OLD.product_name_zh)
OR NOT (NEW.product_name_en <=> OLD.product_name_en)
OR NOT (NEW.product_image_file_id <=> OLD.product_image_file_id)
OR NOT (NEW.product_image_url <=> OLD.product_image_url)
OR NOT (NEW.product_quantity <=> OLD.product_quantity)
OR NOT (NEW.units_per_carton <=> OLD.units_per_carton)
OR NOT (NEW.pallet_count <=> OLD.pallet_count)
OR NOT (NEW.gross_weight_kg <=> OLD.gross_weight_kg)
OR NOT (NEW.net_weight_kg <=> OLD.net_weight_kg)
OR NOT (NEW.dimensional_weight_kg <=> OLD.dimensional_weight_kg)
OR NOT (NEW.volume_cbm <=> OLD.volume_cbm)
OR NOT (NEW.fba_tracking_no <=> OLD.fba_tracking_no)
OR NOT (NEW.shipment_no <=> OLD.shipment_no)
OR NOT (NEW.shipping_mark <=> OLD.shipping_mark)
OR NOT (NEW.load_sequence <=> OLD.load_sequence)
OR NOT (NEW.position_snapshot <=> OLD.position_snapshot)
OR NOT (NEW.internal_remark <=> OLD.internal_remark)
OR NOT (NEW.customer_remark <=> OLD.customer_remark)
OR NOT (NEW.warehouse_remark <=> OLD.warehouse_remark)
OR NOT (NEW.frozen_at <=> OLD.frozen_at)
OR NOT (NEW.frozen_by <=> OLD.frozen_by)
OR NOT (NEW.removed_by <=> OLD.removed_by)
OR NOT (NEW.removed_by_name <=> OLD.removed_by_name)
OR NOT (NEW.removed_at <=> OLD.removed_at) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '已有PDA拣货记录的装柜明细仅允许收敛部分拣货实际箱数';
END IF;
END IF;
IF OLD.frozen_at IS NOT NULL THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '装柜完成后的明细永久冻结';
END IF;
IF OLD.deleted = 1 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '已删除装柜明细不可恢复或修改';
END IF;
IF NEW.loading_plan_id <> OLD.loading_plan_id OR NEW.account_id <> OLD.account_id
OR NEW.inbound_detail_id <> OLD.inbound_detail_id THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '装柜明细归属键不可修改,请删除后重新分配';
END IF;
IF NEW.deleted = 1 THEN
IF NEW.removed_by IS NULL THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '删除装柜明细必须记录操作人';
END IF;
SET NEW.removed_at = COALESCE(NEW.removed_at, CURRENT_TIMESTAMP(3));
ELSEIF NEW.removed_by IS NOT NULL OR NEW.removed_by_name IS NOT NULL OR NEW.removed_at IS NOT NULL THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '删除审计字段只能在逻辑删除时写入';
END IF;
END$$
DELIMITER ;
DROP TRIGGER IF EXISTS trg_loading_pick_task_before_update;
DELIMITER $$
CREATE TRIGGER trg_loading_pick_task_before_update
BEFORE UPDATE ON fl_loading_pick_task
FOR EACH ROW
BEGIN
IF OLD.task_status = 'COMPLETED' THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '已完成PDA拣货任务不可修改';
END IF;
IF NEW.loading_plan_id <> OLD.loading_plan_id
OR NOT (NEW.container_booking_id <=> OLD.container_booking_id)
OR NOT (NEW.cabinet_no <=> OLD.cabinet_no) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'PDA拣货任务稳定归属不可修改';
END IF;
IF NOT (OLD.task_status = 'LOADING' AND NEW.task_status IN ('LOADING', 'COMPLETED')) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'PDA拣货任务状态只允许完成';
END IF;
END$$
DELIMITER ;
DROP TRIGGER IF EXISTS trg_loading_pick_item_before_update;
DELIMITER $$
CREATE TRIGGER trg_loading_pick_item_before_update
BEFORE UPDATE ON fl_loading_pick_item
FOR EACH ROW
BEGIN
DECLARE current_task_status VARCHAR(16);
SELECT MAX(task_status) INTO current_task_status
FROM fl_loading_pick_task WHERE id = OLD.pick_task_id;
IF current_task_status = 'COMPLETED' THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '装柜完成后PDA拣货明细不可修改';
END IF;
IF NEW.pick_task_id <> OLD.pick_task_id
OR NEW.loading_plan_item_id <> OLD.loading_plan_item_id
OR NEW.inbound_id <> OLD.inbound_id
OR NEW.inbound_detail_id <> OLD.inbound_detail_id THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'PDA拣货明细归属不可修改';
END IF;
IF NEW.item_status = 'PARTIAL' AND NOT (
OLD.item_status IN ('PENDING', 'PICKING')
AND OLD.picked_package_count < NEW.picked_package_count
AND NEW.picked_package_count < OLD.planned_package_count
AND NEW.planned_package_count = OLD.planned_package_count
) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '部分拣货必须在计划箱数内增加实际拣出箱数';
END IF;
IF NEW.item_status = 'SKIPPED' AND NOT (
OLD.item_status = 'PENDING'
AND OLD.picked_package_count = 0
AND NEW.picked_package_count = 0
AND NEW.planned_package_count = OLD.planned_package_count
) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '跳拣仅允许零拣货的待拣明细';
END IF;
IF OLD.item_status = 'PARTIAL' AND NEW.item_status = 'PICKED' AND NOT (
NEW.picked_package_count = OLD.picked_package_count
AND NEW.planned_package_count = OLD.picked_package_count
) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '部分拣货收敛后计划箱数必须等于实际拣出箱数';
END IF;
END$$
DELIMITER ;
DROP TRIGGER IF EXISTS trg_loading_pick_item_before_delete;
DELIMITER $$
CREATE TRIGGER trg_loading_pick_item_before_delete
BEFORE DELETE ON fl_loading_pick_item
FOR EACH ROW
BEGIN
DECLARE current_task_status VARCHAR(16);
SELECT MAX(task_status) INTO current_task_status
FROM fl_loading_pick_task WHERE id = OLD.pick_task_id;
IF current_task_status = 'COMPLETED' OR OLD.picked_package_count > 0 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '已拣货或已完成的PDA明细不可删除';
END IF;
END$$
DELIMITER ;
DROP TRIGGER IF EXISTS trg_loading_pick_history_before_update;
DELIMITER $$
CREATE TRIGGER trg_loading_pick_history_before_update
BEFORE UPDATE ON fl_loading_pick_history
FOR EACH ROW
BEGIN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'PDA拣货历史不可修改';
END$$
DELIMITER ;
DROP TRIGGER IF EXISTS trg_loading_pick_history_before_delete;
DELIMITER $$
CREATE TRIGGER trg_loading_pick_history_before_delete
BEFORE DELETE ON fl_loading_pick_history
FOR EACH ROW
BEGIN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'PDA拣货历史不可删除';
END$$
DELIMITER ;
DROP TRIGGER IF EXISTS trg_loading_item_before_delete;
DELIMITER $$
CREATE TRIGGER trg_loading_item_before_delete
BEFORE DELETE ON fl_loading_plan_item
FOR EACH ROW
BEGIN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '装柜明细只允许推送前逻辑删除';
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') THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '装柜完成后货柜文件不可新增';
END IF;
END$$
DELIMITER ;
DROP TRIGGER IF EXISTS trg_container_document_before_update;
DELIMITER $$
CREATE TRIGGER trg_container_document_before_update
BEFORE UPDATE ON fl_container_document
FOR EACH ROW
BEGIN
DECLARE current_plan_status VARCHAR(24);
SELECT MAX(plan_status) INTO current_plan_status FROM fl_loading_plan
WHERE container_booking_id = OLD.container_booking_id;
IF current_plan_status = 'COMPLETED'
AND OLD.document_type NOT IN ('DESTINATION_CLEARANCE', 'CUSTOMS_DECLARATION') THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '装柜完成后货柜文件不可修改';
END IF;
END$$
DELIMITER ;
DROP TRIGGER IF EXISTS trg_container_document_before_delete;
DELIMITER $$
CREATE TRIGGER trg_container_document_before_delete
BEFORE DELETE 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' THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '装柜完成后货柜文件不可删除';
END IF;
END$$
DELIMITER ;
DROP TRIGGER IF EXISTS trg_loading_history_before_update;
DELIMITER $$
CREATE TRIGGER trg_loading_history_before_update
BEFORE UPDATE ON fl_loading_plan_history
FOR EACH ROW
BEGIN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '装柜操作历史不可修改';
END$$
DELIMITER ;
DROP TRIGGER IF EXISTS trg_loading_history_before_delete;
DELIMITER $$
CREATE TRIGGER trg_loading_history_before_delete
BEFORE DELETE ON fl_loading_plan_history
FOR EACH ROW
BEGIN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '装柜操作历史不可删除';
END$$
DELIMITER ;
-- 三期智能装柜、预装清单和装柜清单平台权限;与一期平台根菜单幂等合并。
USE baiyue_iam;
DROP PROCEDURE IF EXISTS assert_smart_loading_permission_ids_available;
DELIMITER $$
CREATE PROCEDURE assert_smart_loading_permission_ids_available()
BEGIN
IF EXISTS (
SELECT 1 FROM iam_permission
WHERE id IN (5102091, 5102092, 5102093, 5102094, 5102096, 5102097, 5102098, 5102099)
AND permission_code NOT IN (
'platform:firstleg:container:view', 'platform:firstleg:container:create',
'platform:firstleg:container:edit', 'platform:firstleg:smart-loading:list',
'platform:firstleg:smart-loading:create', 'platform:firstleg:smart-loading:assign',
'platform:firstleg:smart-loading:delete', 'platform:firstleg:smart-loading:push',
'firstleg:inbound:split', 'firstleg:inbound:preload', 'firstleg:inbound:preload-view',
'platform:firstleg:inbound:loading:edit', 'platform:firstleg:inbound:loading:view',
'platform:firstleg:inbound:loading:complete', 'platform:firstleg:inbound:delivery:complete',
'platform:firstleg:inbound:picking:edit', 'platform:firstleg:inbound:picking:view'
)
) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '三期权限迁移失败:5102091-5102099 存在未知权限占用';
END IF;
END$$
DELIMITER ;
CALL assert_smart_loading_permission_ids_available();
DROP PROCEDURE assert_smart_loading_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
(5102090, 0, 'PLATFORM', 'MENU', 5102000, 'platform:firstleg:container:list',
'货柜', '/first-leg/containers', 'GET', 40, 'ACTIVE', 1, 0),
(5102091, 0, 'PLATFORM', 'BUTTON', 5102090, 'platform:firstleg:container:view',
'查看货柜', NULL, 'GET', 10, 'ACTIVE', 1, 0),
(5102092, 0, 'PLATFORM', 'BUTTON', 5102090, 'platform:firstleg:container:create',
'建立货柜', NULL, 'POST', 20, 'ACTIVE', 1, 0),
(5102093, 0, 'PLATFORM', 'BUTTON', 5102090, 'platform:firstleg:container:edit',
'编辑货柜', NULL, 'PUT', 30, 'ACTIVE', 1, 0),
(5102094, 0, 'PLATFORM', 'MENU', 5102000, 'platform:firstleg:smart-loading:list',
'智能装柜', '/first-leg/smart-loading', 'GET', 50, 'ACTIVE', 1, 0),
(5102096, 0, 'PLATFORM', 'BUTTON', 5102094, 'platform:firstleg:smart-loading:create',
'创建智能装柜计划', NULL, 'POST', 20, 'ACTIVE', 1, 0),
(5102097, 0, 'PLATFORM', 'BUTTON', 5102094, 'platform:firstleg:smart-loading:assign',
'分配装柜明细', NULL, 'POST', 30, 'ACTIVE', 1, 0),
(5102098, 0, 'PLATFORM', 'BUTTON', 5102094, 'platform:firstleg:smart-loading:delete',
'删除装柜明细', NULL, 'DELETE', 40, 'ACTIVE', 1, 0),
(5102099, 0, 'PLATFORM', 'BUTTON', 5102094, 'platform:firstleg:smart-loading:push',
'推送预装', NULL, 'POST', 50, 'ACTIVE', 1, 0),
(5102100, 0, 'PLATFORM', 'MENU', 5102000, 'platform:firstleg:preload:list',
'预装清单', '/first-leg/preload-list', 'GET', 60, 'ACTIVE', 1, 0),
(5102102, 0, 'PLATFORM', 'BUTTON', 5102100, 'platform:firstleg:preload:view',
'查看预装详情', NULL, 'GET', 20, 'ACTIVE', 1, 0),
(5102103, 0, 'PLATFORM', 'BUTTON', 5102100, 'platform:firstleg:preload:edit',
'编辑预装运输字段', NULL, 'PUT', 30, 'ACTIVE', 1, 0),
(5102104, 0, 'PLATFORM', 'BUTTON', 5102100, 'platform:firstleg:preload:complete',
'完成预装', NULL, 'POST', 40, 'ACTIVE', 1, 0),
(5102105, 0, 'PLATFORM', 'BUTTON', 5102100, 'platform:firstleg:preload:confirm',
'确认预装', NULL, 'POST', 50, 'ACTIVE', 1, 0),
(5102106, 0, 'PLATFORM', 'MENU', 5102000, 'platform:firstleg:loading-list:list',
'装柜清单', '/first-leg/loading-list', 'GET', 70, 'ACTIVE', 1, 0),
(5102108, 0, 'PLATFORM', 'BUTTON', 5102106, 'platform:firstleg:loading-list:view',
'查看装柜详情', NULL, 'GET', 20, 'ACTIVE', 1, 0)
ON DUPLICATE KEY UPDATE
permission_type = VALUES(permission_type), parent_id = VALUES(parent_id),
permission_name = VALUES(permission_name), 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, role.tenant_id
FROM iam_role role
JOIN iam_permission permission
ON permission.tenant_id = 0
AND permission.permission_code IN (
'platform:firstleg:container:list', 'platform:firstleg:container:view',
'platform:firstleg:container:create',
'platform:firstleg:container:edit', 'platform:firstleg:smart-loading:list',
'platform:firstleg:smart-loading:create', 'platform:firstleg:smart-loading:assign',
'platform:firstleg:smart-loading:delete', 'platform:firstleg:smart-loading:push',
'platform:firstleg:preload:list', 'platform:firstleg:preload:view',
'platform:firstleg:preload:edit', 'platform:firstleg:preload:complete',
'platform:firstleg:preload:confirm', 'platform:firstleg:loading-list:list',
'platform:firstleg:loading-list:view')
WHERE role.tenant_id = 0
AND role.role_code IN ('PLATFORM_ADMIN', 'ORDER_OPERATOR');
USE baiyue_firstleg;
DROP PROCEDURE IF EXISTS assert_phase3_smart_loading_schema;
DELIMITER $$
CREATE PROCEDURE assert_phase3_smart_loading_schema()
BEGIN
IF (SELECT COUNT(*) FROM information_schema.tables
WHERE table_schema = 'baiyue_firstleg'
AND table_name IN ('fl_container_booking', 'fl_loading_plan', 'fl_loading_plan_item',
'fl_container_document', 'fl_loading_plan_history',
'fl_loading_pick_task', 'fl_loading_pick_item',
'fl_loading_pick_history')) <> 8 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '智能装柜迁移失败:核心表未完整创建';
END IF;
IF EXISTS (SELECT 1 FROM fl_loading_plan
WHERE plan_status NOT IN ('EDITING', 'LOADING', 'COMPLETED')) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '智能装柜迁移失败:存在旧装柜状态';
END IF;
IF (SELECT COUNT(*) FROM information_schema.check_constraints
WHERE constraint_schema = 'baiyue_firstleg'
AND constraint_name = 'chk_loading_plan_status'
AND UPPER(check_clause) LIKE '%EDITING%'
AND UPPER(check_clause) LIKE '%LOADING%'
AND UPPER(check_clause) LIKE '%COMPLETED%') <> 1 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '智能装柜迁移失败:装柜状态约束不是最终三状态';
END IF;
IF (SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_container_booking'
AND column_name IN ('booking_no', 'cabinet_no', 'warehouse_id', 'bill_of_lading_no',
'vessel_name', 'voyage_no', 'container_type', 'capacity_cbm',
'max_weight_kg', 'terminal_trucking_company')) <> 10 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '智能装柜迁移失败:货柜原型字段缺失';
END IF;
IF (SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_loading_plan_item'
AND column_name IN ('account_id', 'customer_name', 'active_inbound_detail_id', 'frozen_at',
'destination_warehouse_name', 'product_name_zh', 'product_name_en',
'product_image_file_id', 'pallet_count', 'net_weight_kg',
'dimensional_weight_kg', 'product_quantity', 'units_per_carton',
'fba_tracking_no', 'shipment_no', 'shipping_mark', 'version')) <> 17 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_plan_item'
AND index_name = 'uk_loading_item_plan_detail' AND non_unique = 0) <> 2 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '智能装柜迁移失败:计划内唯一活动明细锁缺失';
END IF;
IF (SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = 'baiyue_firstleg'
AND table_name = 'fl_loading_pick_item'
AND column_name IN ('pick_task_id', 'loading_plan_item_id', 'inbound_id',
'inbound_detail_id', 'planned_package_count',
'picked_package_count', 'item_status', 'version')) <> 8 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '智能装柜迁移失败:PDA拣货明细字段缺失';
END IF;
IF (SELECT COUNT(*) FROM information_schema.statistics
WHERE table_schema = 'baiyue_firstleg' AND non_unique = 0
AND index_name IN ('uk_loading_pick_task_plan',
'uk_loading_pick_item_plan_item',
'uk_loading_pick_history_operation')) <> 3 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '智能装柜迁移失败:PDA唯一索引缺失';
END IF;
IF (SELECT COUNT(*) FROM information_schema.key_column_usage
WHERE constraint_schema = 'baiyue_firstleg'
AND table_name = 'fl_loading_pick_item'
AND column_name = 'pick_task_id'
AND referenced_table_name IS NOT NULL) <> 1
OR (SELECT COUNT(*) FROM information_schema.key_column_usage outer_fk
WHERE outer_fk.constraint_schema = 'baiyue_firstleg'
AND outer_fk.table_name = 'fl_loading_pick_item'
AND outer_fk.column_name = 'pick_task_id'
AND outer_fk.referenced_table_name = 'fl_loading_pick_task'
AND outer_fk.referenced_column_name = 'id'
AND (SELECT COUNT(*) FROM information_schema.key_column_usage peer
WHERE peer.constraint_schema = outer_fk.constraint_schema
AND peer.table_name = outer_fk.table_name
AND peer.constraint_name = outer_fk.constraint_name) = 1) <> 1 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '智能装柜迁移失败:柜次PDA明细任务外键不唯一或指向错误';
END IF;
IF (SELECT COUNT(*) FROM information_schema.key_column_usage
WHERE constraint_schema = 'baiyue_firstleg'
AND table_name = 'fl_loading_pick_history'
AND column_name = 'pick_task_id'
AND referenced_table_name IS NOT NULL) <> 1
OR (SELECT COUNT(*) FROM information_schema.key_column_usage outer_fk
WHERE outer_fk.constraint_schema = 'baiyue_firstleg'
AND outer_fk.table_name = 'fl_loading_pick_history'
AND outer_fk.column_name = 'pick_task_id'
AND outer_fk.referenced_table_name = 'fl_loading_pick_task'
AND outer_fk.referenced_column_name = 'id'
AND (SELECT COUNT(*) FROM information_schema.key_column_usage peer
WHERE peer.constraint_schema = outer_fk.constraint_schema
AND peer.table_name = outer_fk.table_name
AND peer.constraint_name = outer_fk.constraint_name) = 1) <> 1 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '智能装柜迁移失败:柜次PDA历史任务外键不唯一或指向错误';
END IF;
IF (SELECT COUNT(*) FROM information_schema.triggers
WHERE trigger_schema = 'baiyue_firstleg'
AND trigger_name IN ('trg_container_booking_before_update',
'trg_loading_plan_before_update', 'trg_loading_plan_after_update',
'trg_loading_item_before_insert', 'trg_loading_item_before_update',
'trg_loading_item_before_delete', 'trg_container_document_before_insert',
'trg_container_document_before_update', 'trg_container_document_before_delete',
'trg_loading_history_before_update', 'trg_loading_history_before_delete',
'trg_loading_pick_task_before_update',
'trg_loading_pick_item_before_update',
'trg_loading_pick_item_before_delete',
'trg_loading_pick_history_before_update',
'trg_loading_pick_history_before_delete')) <> 16 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '智能装柜迁移失败:不可逆状态或冻结触发器缺失';
END IF;
IF (SELECT COUNT(DISTINCT permission_code) FROM baiyue_iam.iam_permission
WHERE tenant_id = 0 AND app_code = 'PLATFORM' AND status = 'ACTIVE' AND deleted = 0
AND permission_code IN (
'platform:firstleg:container:list', 'platform:firstleg:container:view',
'platform:firstleg:container:create', 'platform:firstleg:container:edit',
'platform:firstleg:smart-loading:list', 'platform:firstleg:smart-loading:create',
'platform:firstleg:smart-loading:assign', 'platform:firstleg:smart-loading:delete',
'platform:firstleg:smart-loading:push', 'platform:firstleg:preload:list',
'platform:firstleg:preload:view', 'platform:firstleg:preload:edit',
'platform:firstleg:preload:complete', 'platform:firstleg:preload:confirm',
'platform:firstleg:loading-list:list', 'platform:firstleg:loading-list:view')) <> 16 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '智能装柜迁移失败:平台权限资源未完整创建';
END IF;
IF (SELECT COUNT(DISTINCT CONCAT(role.role_code, ':', permission.permission_code))
FROM baiyue_iam.iam_role role
JOIN baiyue_iam.iam_role_permission grant_row ON grant_row.role_id = role.id
JOIN baiyue_iam.iam_permission permission ON permission.id = grant_row.permission_id
WHERE role.tenant_id = 0 AND role.role_code IN ('PLATFORM_ADMIN', 'ORDER_OPERATOR')
AND permission.tenant_id = 0
AND permission.permission_code IN (
'platform:firstleg:container:list', 'platform:firstleg:container:view',
'platform:firstleg:container:create',
'platform:firstleg:container:edit', 'platform:firstleg:smart-loading:list',
'platform:firstleg:smart-loading:create', 'platform:firstleg:smart-loading:assign',
'platform:firstleg:smart-loading:delete', 'platform:firstleg:smart-loading:push',
'platform:firstleg:preload:list', 'platform:firstleg:preload:view',
'platform:firstleg:preload:edit', 'platform:firstleg:preload:complete',
'platform:firstleg:preload:confirm', 'platform:firstleg:loading-list:list',
'platform:firstleg:loading-list:view')) <> 32 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '智能装柜迁移失败:平台角色授权未完整写入';
END IF;
END$$
DELIMITER ;
-- ============================================================
-- 2026-08-06 平台公共仓库正式主数据:固定作业仓 + FBA 地址库
-- 来源:warehouse/src/main/resources/db/warehouse-public-master-data.sql。必须位于最终 PASS 校验之前,可重复执行。
USE baiyue_warehouse;
-- 正式平台公共仓库主数据:固定作业仓 + FBA 地址库。
-- 来源:C:\Users\BY-OY\Downloads\FBA地址表.xls,只读清洗后生成。
-- 接受 252 条,拒绝 54 条;拒绝清单见 master-data/fba-public-addresses.rejected.csv。
-- 四个固定作业仓按通用占位地址写入;清单见 master-data/platform-fixed-warehouses.csv。
-- FBA 是目的地址主数据,不创建 RECEIVING、STORAGE、PICK_PACK、RETURN 等平台作业能力。
DROP TEMPORARY TABLE IF EXISTS tmp_wh_fixed_platform_warehouse;
CREATE TEMPORARY TABLE tmp_wh_fixed_platform_warehouse (
warehouse_id_seed BIGINT UNSIGNED NOT NULL,
address_id_seed BIGINT UNSIGNED NOT NULL,
warehouse_code VARCHAR(50) NOT NULL,
warehouse_name VARCHAR(120) NOT NULL,
warehouse_type VARCHAR(32) NOT NULL,
country_code CHAR(2) NOT NULL,
timezone VARCHAR(64) NOT NULL,
state_province VARCHAR(100) NOT NULL,
city VARCHAR(100) NOT NULL,
postal_code VARCHAR(20) NOT NULL,
address_line1 VARCHAR(255) NOT NULL,
PRIMARY KEY (warehouse_code),
UNIQUE KEY uk_tmp_fixed_warehouse_id_seed (warehouse_id_seed),
UNIQUE KEY uk_tmp_fixed_address_id_seed (address_id_seed)
) ENGINE=MEMORY DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
INSERT INTO tmp_wh_fixed_platform_warehouse
(warehouse_id_seed,address_id_seed,warehouse_code,warehouse_name,warehouse_type,country_code,timezone,state_province,city,postal_code,address_line1)
VALUES
(193000000000200001,193000000000300001,'CN-YIWU-01','义乌仓','DOMESTIC','CN','UTC+8','浙江省','义乌市','322000','义乌仓通用地址待维护'),
(193000000000200002,193000000000300002,'CN-DONGGUAN-01','东莞仓','DOMESTIC','CN','UTC+8','广东省','东莞市','523000','东莞仓通用地址待维护'),
(193000000000200003,193000000000300003,'US-LAX-01','洛杉矶仓','OVERSEAS','US','UTC-8','CA','Los Angeles','90001','Los Angeles warehouse generic address pending maintenance'),
(193000000000200004,193000000000300004,'US-NYC-01','纽约仓','OVERSEAS','US','UTC-5','NY','New York','10001','New York warehouse generic address pending maintenance');
DROP PROCEDURE IF EXISTS assert_wh_fixed_platform_warehouse_seed;
DELIMITER //
CREATE PROCEDURE assert_wh_fixed_platform_warehouse_seed()
BEGIN
IF (SELECT COUNT(*) FROM tmp_wh_fixed_platform_warehouse) <> 4 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '固定平台仓源数量不是4';
END IF;
IF EXISTS (
SELECT 1
FROM tmp_wh_fixed_platform_warehouse
WHERE warehouse_code <> UPPER(TRIM(warehouse_code))
OR warehouse_type NOT IN ('DOMESTIC', 'OVERSEAS')
OR country_code NOT IN ('CN', 'US')
OR TRIM(timezone) = ''
OR TRIM(city) = ''
OR TRIM(state_province) = ''
OR TRIM(address_line1) = ''
OR TRIM(postal_code) = ''
) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '固定平台仓主数据存在未清洗或必填缺失记录';
END IF;
IF EXISTS (
SELECT 1
FROM wh_warehouse existing
JOIN tmp_wh_fixed_platform_warehouse source
ON existing.id = source.warehouse_id_seed
AND (existing.tenant_id <> 0 OR existing.warehouse_code <> source.warehouse_code OR existing.deleted <> 0)
) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '固定平台仓仓库种子ID与既有数据冲突';
END IF;
IF EXISTS (
SELECT 1
FROM wh_warehouse_address existing
JOIN tmp_wh_fixed_platform_warehouse source
ON existing.id = source.address_id_seed
LEFT JOIN wh_warehouse parent
ON parent.id = existing.warehouse_id
AND parent.tenant_id = existing.tenant_id
WHERE existing.tenant_id <> 0
OR existing.deleted <> 0
OR parent.warehouse_code IS NULL
OR parent.warehouse_code <> source.warehouse_code
) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '固定平台仓地址种子ID与既有数据冲突';
END IF;
IF EXISTS (
SELECT 1
FROM wh_warehouse existing
JOIN tmp_wh_fixed_platform_warehouse source
ON existing.tenant_id = 0
AND existing.warehouse_code = source.warehouse_code
AND existing.deleted = 1
) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '固定平台仓存在同编码已删除仓库,需人工确认后恢复或清理';
END IF;
END//
DELIMITER ;
CALL assert_wh_fixed_platform_warehouse_seed();
DROP PROCEDURE IF EXISTS assert_wh_fixed_platform_warehouse_seed;
INSERT INTO wh_warehouse (
id, tenant_id, warehouse_code, warehouse_name, warehouse_type, country_code, timezone,
contact_name, contact_phone, contact_email, status, remark,
created_by, created_at, updated_by, updated_at, deleted, version
)
SELECT
source.warehouse_id_seed,
0,
source.warehouse_code,
source.warehouse_name,
source.warehouse_type,
source.country_code,
source.timezone,
NULL,
NULL,
NULL,
'ACTIVE',
NULL,
NULL,
CURRENT_TIMESTAMP(3),
NULL,
CURRENT_TIMESTAMP(3),
0,
0
FROM tmp_wh_fixed_platform_warehouse source
ON DUPLICATE KEY UPDATE
warehouse_name = VALUES(warehouse_name),
warehouse_type = VALUES(warehouse_type),
country_code = VALUES(country_code),
timezone = VALUES(timezone),
updated_at = CURRENT_TIMESTAMP(3);
INSERT INTO wh_warehouse_address (
id, tenant_id, warehouse_id, address_type, country_code, state_province, city,
district, postal_code, address_line1, address_line2, latitude, longitude,
created_by, created_at, updated_by, updated_at, deleted, version
)
SELECT
source.address_id_seed,
0,
warehouse.id,
'OPERATING',
source.country_code,
source.state_province,
source.city,
NULL,
source.postal_code,
source.address_line1,
NULL,
NULL,
NULL,
NULL,
CURRENT_TIMESTAMP(3),
NULL,
CURRENT_TIMESTAMP(3),
0,
0
FROM tmp_wh_fixed_platform_warehouse source
JOIN wh_warehouse warehouse
ON warehouse.tenant_id = 0
AND warehouse.warehouse_code = source.warehouse_code
AND warehouse.deleted = 0
ON DUPLICATE KEY UPDATE
tenant_id = VALUES(tenant_id);
-- 国内固定作业仓必须启用收货能力,FBA 目的地址仍不得创建作业能力。
INSERT INTO wh_service_capability (
id, tenant_id, warehouse_id, capability_code, capability_name, enabled, cutoff_time,
description, created_by, created_at, updated_by, updated_at, deleted, version
)
VALUES
(193000000000400001,0,193000000000200001,'RECEIVING','头程收货',1,'16:00:00',
'义乌仓头程订单收货能力',NULL,CURRENT_TIMESTAMP(3),NULL,CURRENT_TIMESTAMP(3),0,0),
(193000000000400002,0,193000000000200002,'RECEIVING','头程收货',1,'16:00:00',
'东莞仓头程订单收货能力',NULL,CURRENT_TIMESTAMP(3),NULL,CURRENT_TIMESTAMP(3),0,0)
ON DUPLICATE KEY UPDATE
capability_name = VALUES(capability_name),
enabled = VALUES(enabled),
cutoff_time = VALUES(cutoff_time),
description = VALUES(description),
deleted = 0,
updated_at = CURRENT_TIMESTAMP(3);
-- 平台托盘初始化主数据;内部状态仅供预占/占用,不作为平台维护字段。
INSERT INTO wh_pallet (
id, tenant_id, warehouse_id, pallet_code, pallet_type, capacity_cbm,
weight_kg, color, status, remark, created_by, updated_by
)
VALUES
(193000000000200101,0,193000000000200001,'TP-001','木托盘',1.200000,8.000,'黄色','AVAILABLE',NULL,0,0),
(193000000000200102,0,193000000000200002,'TP-002','胶托盘',2.400000,12.000,'黑色','AVAILABLE',NULL,0,0)
ON DUPLICATE KEY UPDATE
tenant_id = 0,
warehouse_id = VALUES(warehouse_id),
pallet_type = VALUES(pallet_type),
capacity_cbm = VALUES(capacity_cbm),
weight_kg = VALUES(weight_kg),
color = VALUES(color),
remark = VALUES(remark);
-- 国内作业仓最小库位基线:每仓一个普通 A1 和一个系统共享溢出位 A0。
-- 不初始化预占、占用或任何业务单据。
INSERT INTO wh_location (
id, tenant_id, warehouse_id, zone_code, zone_name, location_code, location_name,
location_type, max_weight_kg, shared_overflow, status, remark, created_by, updated_by
)
VALUES
(193000000000200201,0,193000000000200001,'A','A区','A1','A1 普通库位','PALLET',1000.000,0,'AVAILABLE',NULL,0,0),
(193000000000200202,0,193000000000200001,'A','A区','A0','A0 共享溢出位','PALLET',NULL,1,'AVAILABLE','系统共享临时溢出位',0,0),
(193000000000200203,0,193000000000200002,'A','A区','A1','A1 普通库位','PALLET',1000.000,0,'AVAILABLE',NULL,0,0),
(193000000000200204,0,193000000000200002,'A','A区','A0','A0 共享溢出位','PALLET',NULL,1,'AVAILABLE','系统共享临时溢出位',0,0)
ON DUPLICATE KEY UPDATE
tenant_id = 0,
warehouse_id = VALUES(warehouse_id),
zone_code = VALUES(zone_code),
zone_name = VALUES(zone_name),
location_name = VALUES(location_name),
location_type = VALUES(location_type),
max_weight_kg = VALUES(max_weight_kg),
shared_overflow = VALUES(shared_overflow),
status = 'AVAILABLE',
remark = VALUES(remark),
deleted = 0;
DROP TEMPORARY TABLE IF EXISTS tmp_wh_fba_public_master_data;
CREATE TEMPORARY TABLE tmp_wh_fba_public_master_data (
warehouse_id_seed BIGINT UNSIGNED NOT NULL,
address_id_seed BIGINT UNSIGNED NOT NULL,
warehouse_code VARCHAR(50) NOT NULL,
warehouse_name VARCHAR(120) NOT NULL,
state_province VARCHAR(100) NOT NULL,
city VARCHAR(100) NOT NULL,
postal_code VARCHAR(20) NOT NULL,
timezone VARCHAR(64) NOT NULL,
address_line1 VARCHAR(255) NOT NULL,
source_row INT NOT NULL,
PRIMARY KEY (warehouse_code),
UNIQUE KEY uk_tmp_fba_warehouse_id_seed (warehouse_id_seed),
UNIQUE KEY uk_tmp_fba_address_id_seed (address_id_seed)
) ENGINE=MEMORY DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
INSERT INTO tmp_wh_fba_public_master_data
(warehouse_id_seed,address_id_seed,warehouse_code,warehouse_name,state_province,city,postal_code,timezone,address_line1,source_row)
VALUES
(193000000000000001,193000000000100001,'ABE2','ABE2','PA','Breinigsville','18031','UTC-5','705 Boulder Drive',238),
(193000000000000002,193000000000100002,'ABE3','ABE3','PA','Breinigsville','18031','UTC-5','650 Boulder Drive',237),
(193000000000000003,193000000000100003,'ABE4','ABE4','PA','Easton','18045','UTC-5','1610 Van Buren Road',134),
(193000000000000004,193000000000100004,'ABE5','ABE5','PA','Harrisburg','17112','UTC-5','6455 Allentown Boulevard',231),
(193000000000000005,193000000000100005,'ABE8','ABE8','NJ','Florence','08518','UTC-5','401 Independence Road 08518-2200',125),
(193000000000000006,193000000000100006,'ABQ2','ABQ2','NM','LOS LUNAS','87031','UTC-7','6251 Pioneer Trail NW',99),
(193000000000000007,193000000000100007,'ACY2','ACY2','NJ','BURLINGTON','08016','UTC-5','1101 E PEARL ST',84),
(193000000000000008,193000000000100008,'AFW1','AFW1','TX','FORT WORTH','76131','UTC-6','1851 NE LOOP 820',141),
(193000000000000009,193000000000100009,'AFW5','AFW5','TX','BALCH SPRINGS','75180-1636','UTC-6','12374 LAKE JUNE RD',27),
(193000000000000010,193000000000100010,'AKR1','AKR1','OH','Canton','44705','UTC-5','4747 Rebar Ave NE',148),
(193000000000000011,193000000000100011,'ALB1','ALB1','NY','Castleton','12033-9634','UTC-5','1835 US Route 9',174),
(193000000000000012,193000000000100012,'AMA1','AMA1','TX','Amarillo','79108','UTC-6','8590 NE 24th Avenue',129),
(193000000000000013,193000000000100013,'ATL2N','ATL2N','GA','Carrollton','30117','UTC-5','3101 N. Highway 27',31),
(193000000000000014,193000000000100014,'ATL3','ATL3','GA','Pendergrass','30567','UTC-5','445 Valentine Industrial Parkway',147),
(193000000000000015,193000000000100015,'ATL6','ATL6','GA','East Point','30344','UTC-5','4200 North Commerce',202),
(193000000000000016,193000000000100016,'AVP1','AVP1','PA','Hazle Township','18202-9361','UTC-5','550 Oak Ridge Road',233),
(193000000000000017,193000000000100017,'AVP3','AVP3','PA','GOULDSBORO','18424-9492','UTC-5','298 1ST AVE',232),
(193000000000000018,193000000000100018,'BDL1','BDL1','CT','Windsor','06095','UTC-5','801 Day Hill Rd.',289),
(193000000000000019,193000000000100019,'BFI1','BFI1','WA','Sumner','98390','UTC-8','1800 140th Avenue E.',208),
(193000000000000020,193000000000100020,'BFI3','BFI3','WA','Dupont','98327-9607','UTC-8','2700 Center Drive',207),
(193000000000000021,193000000000100021,'BFI7','BFI7','WA','Sumner','98390','UTC-8','1901 140th Ave E',206),
(193000000000000022,193000000000100022,'BJC1','BJC1','CO','AURORA','80019','UTC-7','6300 N Powhaton Road',11),
(193000000000000023,193000000000100023,'BNA1','BNA1','TN','Lebanon','37090','UTC-6','14840 Central Pike Suite 190',221),
(193000000000000024,193000000000100024,'BNA2','BNA2','TN','Lebanon','37090','UTC-6','500 Duke DR',131),
(193000000000000025,193000000000100025,'BNA3','BNA3','TN','Murfreesboro','37127','UTC-6','2020 Joe B Jackson Pkwy',140),
(193000000000000026,193000000000100026,'BNA4','BNA4','TN','Nashville','37217','UTC-6','50 airways Blvd',220),
(193000000000000027,193000000000100027,'BNA6','BNA6','TN','CLARKSVILLE','37040-5502','UTC-6','3875 GUTHRIE HWY',110),
(193000000000000028,193000000000100028,'BOS1','BOS1','NH','Nashua','03063','UTC-5','10 State St.',243),
(193000000000000029,193000000000100029,'BOS2','BOS2','MA','Drive Stoughton','02072','UTC-5','1000 Tech Centre',193),
(193000000000000030,193000000000100030,'BOS7','BOS7','MA','Fall River','02720','UTC-5','1180 Innovation Way',192),
(193000000000000031,193000000000100031,'BWI1','BWI1','VA','Sterling','20166-2058','UTC-5','45121 Global Plaza',242),
(193000000000000032,193000000000100032,'BWI2','BWI2','MD','Baltimore','21224','UTC-5','2010 Broening Hwy',241),
(193000000000000033,193000000000100033,'BWI4','BWI4','VA','CLEARBROOK','22624','UTC-5','165 Business Blvd',102),
(193000000000000034,193000000000100034,'CAE1','CAE1','SC','West Columbia','29172','UTC-5','4400 12TH STREET EXT',225),
(193000000000000035,193000000000100035,'CHA1','CHA1','TN','Chattanooga','37416','UTC-5','7200 Discovery Drive',223),
(193000000000000036,193000000000100036,'CHA2','CHA2','TN','Charleston','37310','UTC-5','225 Infinity Dr NW',222),
(193000000000000037,193000000000100037,'CHO1','CHO1','VA','FISHERSVILLE','22939','UTC-5','32 TRADER RD',75),
(193000000000000038,193000000000100038,'CLT2','CLT2','NC','Charlotte','28214-8082','UTC-5','10240 Old Dowd Rd',191),
(193000000000000039,193000000000100039,'CLT3','CLT3','NC','CONCORD','28027','UTC-5','6500 Davidson Hwy',114),
(193000000000000040,193000000000100040,'CMH2','CMH2','OH','Groveport','43125-9016','UTC-5','6050 Gateway Court',240),
(193000000000000041,193000000000100041,'CMH3','CMH3','OH','Monroe','45050-1848','UTC-5','700 GATEWAY BLVD',181),
(193000000000000042,193000000000100042,'CVG1','CVG1','KY','Hebron','41048','UTC-5','1155 Worldwide Blvd',262),
(193000000000000043,193000000000100043,'CVG2','CVG2','KY','Hebron','41048','UTC-5','1600 Worldwide Blvd',261),
(193000000000000044,193000000000100044,'CVG3','CVG3','KY','Hebron','41048','UTC-5','3680 Langley Dr.',260),
(193000000000000045,193000000000100045,'CVG5','CVG5','KY','Hebron','41048','UTC-5','2285 Litton Lane',259),
(193000000000000046,193000000000100046,'DCA6','DCA6','MD','BALTIMORE','21219','UTC-5','6001 Bethlehem Blvd',135),
(193000000000000047,193000000000100047,'DEN2','DEN2','CO','Aurora','80019','UTC-7','22205 East 19th Ave',123),
(193000000000000048,193000000000100048,'DEN8','DEN8','CO','Aurora','80018','UTC-7','21000 E 13th Ave',120),
(193000000000000049,193000000000100049,'DET1','DET1','MI','Livonia','48150-1543','UTC-5','39000 Amrhein Road',189),
(193000000000000050,193000000000100050,'DET2','DET2','MI','Shelby Township','48317-1318','UTC-5','50500 Mound Rd',182),
(193000000000000051,193000000000100051,'DFW5S','DFW5S','TX','Lancaster','75146','UTC-6','2500 E Belt Line Rd,',143),
(193000000000000052,193000000000100052,'DFW6','DFW6','TX','Coppell','75019','UTC-6','940 W Bethel Road',219),
(193000000000000053,193000000000100053,'DFW8','DFW8','TX','DFW Airport','75261','UTC-6','2700 Regent Blvd',217),
(193000000000000054,193000000000100054,'EWR4','EWR4','NJ','Robbinsville','08691','UTC-5','50 New Canton Way',256),
(193000000000000055,193000000000100055,'EWR5','EWR5','NJ','Avenel','07001','UTC-5','301 Blair Rd.',255),
(193000000000000056,193000000000100056,'EWR7','EWR7','NJ','Avenel','07001','UTC-5','301 Blair Rd.',254),
(193000000000000057,193000000000100057,'EWR9','EWR9','NJ','Carteret','07008','UTC-5','8003 Industrial Parkway',253),
(193000000000000058,193000000000100058,'FAT2','FAT2','CA','VISALIA','93291-1882','UTC-8','3315 N KELSEYST',117),
(193000000000000059,193000000000100059,'FOE1','FOE1','KS','KANSAS CITY','66109-3665','UTC-6','9400 LEAVENWORTH RD',176),
(193000000000000060,193000000000100060,'FTW1','FTW1','TX','Dallas','75241','UTC-6','33333 Lyndon B Johnson FREEWAY',212),
(193000000000000061,193000000000100061,'FTW2','FTW2','TX','Coppell','75019','UTC-6','2701 West Bethel Road',211),
(193000000000000062,193000000000100062,'FTW3','FTW3','TX','Fort Worth','76177-2517','UTC-6','15201 Heritage Pkwy',89),
(193000000000000063,193000000000100063,'FTW5','FTW5','TX','Forney','75126','UTC-6','1475 AKRON WAY',121),
(193000000000000064,193000000000100064,'FWA4','FWA4','IN','FORT WAYNE','46809-9771','UTC-5','9798 Smith Road',171),
(193000000000000065,193000000000100065,'GEG2','GEG2','WA','SPOKANE','99216','UTC-8','18007 E Garland Ave',151),
(193000000000000066,193000000000100066,'GEU2','GEU2','AZ','Glendale','85307','UTC-7','15301 West Northern Avenue',67),
(193000000000000067,193000000000100067,'GEU3','GEU3','AZ','BUCKEYE','85396','UTC-7','18900 W McDowell Road',78),
(193000000000000068,193000000000100068,'GEU5','GEU5','AZ','GOODYEAR','85395','UTC-7','4660 North Cotton Lane',68),
(193000000000000069,193000000000100069,'GSO1','GSO1','NC','KERNERSVILLE','27284','UTC-5','1656 OLD GREENSBORO RD',164),
(193000000000000070,193000000000100070,'GSP1','GSP1','SC','Spartanburg','29303','UTC-5','402 John Dodd Rd',224),
(193000000000000071,193000000000100071,'GYR2','GYR2','AZ','GOODYEAR','85395-7672','UTC-7','17341 W MINNEZONA AVE',188),
(193000000000000072,193000000000100072,'GYR3','GYR3','AZ','PHOENIX','85043-2356','UTC-7','8181 W ROOSEVELT ST',187),
(193000000000000073,193000000000100073,'HGR6','HGR6','MD','HAGERSTOWN','21740-7301','UTC-5','55 West Oak Ridge Drive',80),
(193000000000000074,193000000000100074,'HIA1','HIA1','PA','MIDDLETOWN','17057','UTC-5','3327 E Harrisburg Pik',87),
(193000000000000075,193000000000100075,'HLI2','HLI2','CA','HOLLISTER','95023','UTC-8','600 Ernie Drive',44),
(193000000000000076,193000000000100076,'HOU1','HOU1','TX','Humble','77338','UTC-6','8120 Humble Westfield Rd',216),
(193000000000000077,193000000000100077,'HOU2','HOU2','TX','Houston','77038-2324','UTC-6','10550 Ella Blvd.',215),
(193000000000000078,193000000000100078,'HOU3','HOU3','TX','Brookshire','77423','UTC-6','31555 Highway 90 E',214),
(193000000000000079,193000000000100079,'HOU8','HOU8','TX','FRESNO','77545','UTC-6','2303 Hurricane Lane',154),
(193000000000000080,193000000000100080,'HSV1','HSV1','AL','Madison','35756','UTC-6','7817 Greenbrier Road',142),
(193000000000000081,193000000000100081,'HSV2','HSV2','AL','MADISON','35756','UTC-6','28869 Fanning Dr NW',126),
(193000000000000082,193000000000100082,'IAH3','IAH3','TX','Houston','77032','UTC-6','15525 Milner Road',155),
(193000000000000083,193000000000100083,'IAZ1','IAZ1','AZ','GOODYEAR','85395-4515','UTC-7','4660 N COTTON LN 85395-4515 - GOODYEAR, AZ - United States',3),
(193000000000000084,193000000000100084,'ICT2','ICT2','KS','PARK CITY','67219','UTC-6','7130 N BROADWAY AVE',136),
(193000000000000085,193000000000100085,'IGA3','IGA3','GA','MACON','31216-6427','UTC-5','7001 Skipper Rd 31216-6427 MACON, GA - United States',2),
(193000000000000086,193000000000100086,'IGQ2','IGQ2','IL','University park','60484','UTC-6','23257 S. Central Avenue',152),
(193000000000000087,193000000000100087,'ILG1','ILG1','DE','NEW CASTLE','19720','UTC-5','780 S. DuPont Highway',119),
(193000000000000088,193000000000100088,'IMO1','IMO1','MO','LIBERTY','64068-8933','UTC-6','2361 S Withers RD',10),
(193000000000000089,193000000000100089,'IMS1','IMS1','MS','OLIVE BRANCH','38654','UTC-6','IMS1-11505 Progress Way,38654-2510,OLIVE BRANCH,MS,US',6),
(193000000000000090,193000000000100090,'INC1','INC1','NC','FAYETTEVILLE','28303-2215','UTC-5','6309 Bragg Blvd 28303-2215 - FAYETTEVILLE, NC - United States',5),
(193000000000000091,193000000000100091,'IND2','IND2','IN','Plainfield','46168-7442','UTC-5','715 Airtech Pkwy',88),
(193000000000000092,193000000000100092,'IND4','IND4','IN','Indianapolis','46231','UTC-5','710 S. Girls School Rd',276),
(193000000000000093,193000000000100093,'IND5','IND5','IN','Plainfield','46168','UTC-5','800 Perry Road',275),
(193000000000000094,193000000000100094,'IND6','IND6','IN','Jeffersonville','47130','UTC-5','1453 E 10th St',274),
(193000000000000095,193000000000100095,'IND9','IND9','IN','GREENWOOD','46143-7830','UTC-5','1151 S GRAHAM RD',173),
(193000000000000096,193000000000100096,'ITX3','ITX3','TX','AMARILLO','79108-7749','UTC-6','ITX3-8590 NE 24th Ave,79108-7749,AMARILLO,TX,US',9),
(193000000000000097,193000000000100097,'IUSF','IUSF','TX','DALLAS','75241','UTC-6','3351 Balmorhea Dr',22),
(193000000000000098,193000000000100098,'IUSJ','IUSJ','CA','BEAUMONT','92223','UTC-8','36900 West 4th Street',69),
(193000000000000099,193000000000100099,'IUSP','IUSP','CA','HESPERIA,','92344','UTC-8','8140 Caliente Rd',74),
(193000000000000100,193000000000100100,'IUSQ','IUSQ','CA','REDLANDS','92374','UTC-8','2125 W San Bernardino Ave',70),
(193000000000000101,193000000000100101,'IUSR','IUSR','SC','COWPENS','29330','UTC-5','1120 Mt Olive Rd',40),
(193000000000000102,193000000000100102,'IUST','IUST','PA','GREENCASTLE','17225','UTC-5','3PL AWD (Greencastle, PA, US) 15651 Greenmount RdGREENCASTLE, PA 17225 United States',39),
(193000000000000103,193000000000100103,'IUSW','IUSW','CA','Hesperia','92344','UTC-8','8130 Caliente Rd 92344 - Hesperia, CA - United States',33),
(193000000000000104,193000000000100104,'IUTE','IUTE','AZ','GOODYEAR','85338','UTC-7','16155 W Elwood St',21),
(193000000000000105,193000000000100105,'IUTJ','IUTJ','CA','Fontana','92335','UTC-8','9253 Dreamland Drive 92335 - Fontana, CA - United',8),
(193000000000000106,193000000000100106,'IUTL','IUTL','CA','Moreno Valley','92551','UTC-8','24405 Krameria Ave 92551 - Moreno Valley, CA - United States',7),
(193000000000000107,193000000000100107,'IWA6','IWA6','WA','PASCO','99301-6161','UTC-8','1202 S Road 40 E 99301-6161 - PASCO, WA - United States',4),
(193000000000000108,193000000000100108,'JOT1','JOT1','CA','SAN BERNARDINO','92408','UTC-8','1494 S WATERMAN AVE',72),
(193000000000000109,193000000000100109,'JVL1','JVL1','WI','BELOIT','53511','UTC-6','1255 Gateway Blvd',150),
(193000000000000110,193000000000100110,'KRB4','KRB4','CA','PERRIS','92571-9722','UTC-8','4501 PATTERSON AVE',178),
(193000000000000111,193000000000100111,'KY1','KY1','KY','Shepherdsville','40165','UTC-5','120 Velocity Way.,',161),
(193000000000000112,193000000000100112,'LAN2','LAN2','MI','LANSING','48917','UTC-5','6500 W Mt Hope Hwy',100),
(193000000000000113,193000000000100113,'LAS1','LAS1','NV','HENDERSON','89044','UTC-8','12300 Bermuda Road',247),
(193000000000000114,193000000000100114,'LAS2','LAS2','NV','Las Vegas','89030','UTC-8','3837 Bay Lake Trail, Suite 111 North',246),
(193000000000000115,193000000000100115,'LAS6','LAS6','NV','Las Vegas','89115','UTC-8','4550 Nexus Way',132),
(193000000000000116,193000000000100116,'LAX2T','LAX2T','CA','CHINO','91708','UTC-8','6730 Kimball Ave',162),
(193000000000000117,193000000000100117,'LAX9','LAX9','CA','FONTANA','92337','UTC-8','11263 Oleander Ave',183),
(193000000000000118,193000000000100118,'LBE1','LBE1','PA','Greensburg','15605','UTC-5','165 Glen Fox Road',86),
(193000000000000119,193000000000100119,'LEX1','LEX1','KY','Lexington','40511','UTC-5','1850 Mercer RD',268),
(193000000000000120,193000000000100120,'LEX2','LEX2','KY','Lexington','40511','UTC-5','172 Trade St.',267),
(193000000000000121,193000000000100121,'LFT1','LFT1','LA','CARENCRO','70520-5976','UTC-6','3550 NE EVANGELINE TRWY',107),
(193000000000000122,193000000000100122,'LGB3','LGB3','CA','Eastvale','91752','UTC-8','4950 Goodman Way',291),
(193000000000000123,193000000000100123,'LGB4','LGB4','CA','Redlands','92374','UTC-8','27517 Pioneer Avenue',292),
(193000000000000124,193000000000100124,'LGB6','LGB6','CA','Riverside','92518-1513','UTC-8','20901 Krameria Ave',293),
(193000000000000125,193000000000100125,'LGB8','LGB8','CA','Rialto','92376','UTC-8','1568 N Linden Ave',294),
(193000000000000126,193000000000100126,'LIT2','LIT2','AR','NORTH LITTLE ROCK','72117','UTC-6','13001 HIGHWAY 70',106),
(193000000000000127,193000000000100127,'MCC1','MCC1','CA','RANCHO CORDOVA','95742','UTC-8','3300 Rancho Cordova Pkwy 95742 - RANCHO CORDOVA, CA - United States',32),
(193000000000000128,193000000000100128,'MCE1','MCE1','CA','TURLOCK','95380','UTC-8','FULKERTH RD',157),
(193000000000000129,193000000000100129,'MCI1','MCI1','KS','Lenexa','66219','UTC-6','113th St & Renner Blvd.',271),
(193000000000000130,193000000000100130,'MCI4','MCI4','MO','KANSAS CITY','64163','UTC-6','MCI4 - 10501 NW 136th Street',12),
(193000000000000131,193000000000100131,'MCO2','MCO2','FL','Deltona','32725-2610','UTC-5','2600 N Normandy Blvd',115),
(193000000000000132,193000000000100132,'MDT1','MDT1','PA','Carlisle','17015','UTC-5','2Ames Drive',230),
(193000000000000133,193000000000100133,'MDT4','MDT4','PA','York','17408','UTC-5','5125 Commerce Drive',128),
(193000000000000134,193000000000100134,'MDW2','MDW2','IL','Joliet','60433-3280','UTC-6','250 Emerald Drive',200),
(193000000000000135,193000000000100135,'MDW6','MDW6','IL','ROMEOVILLE','60446-6529','UTC-6','1125 W REMINGTON BLVD ROMEOVILLE',199),
(193000000000000136,193000000000100136,'MDW7','MDW7','IL','Monee','60449','UTC-6','6605 Monee Manhattan Road',198),
(193000000000000137,193000000000100137,'MDW8','MDW8','IL','Waukegan','60085','UTC-6','1750 Bridge Drive',197),
(193000000000000138,193000000000100138,'MDW9','MDW9','IL','Aurora','60502','UTC-6','2865 Duke Parkway',196),
(193000000000000139,193000000000100139,'MEM1','MEM1','TN','Memphis','38118','UTC-6','3292 E Holmes Rd',163),
(193000000000000140,193000000000100140,'MEM6','MEM6','MS','OLIVE BRANCH','38654','UTC-6','11505 Progress Way',118),
(193000000000000141,193000000000100141,'MGE3','MGE3','GA','Jefferson','30549-2010','UTC-5','808 Hog Mountain Road',201),
(193000000000000142,193000000000100142,'MIT2','MIT2','CA','SHAFTER','93263','UTC-8','5408 Express Avenue',77),
(193000000000000143,193000000000100143,'MKC4','MKC4','KS','Edgerton','66021-9588','UTC-6','19645 Waverly Rd',270),
(193000000000000144,193000000000100144,'MKC6','MKC6','KS','Kansas City','66102','UTC-6','6925 Riverview Avenue',269),
(193000000000000145,193000000000100145,'MKE1','MKE1','WI','Kenosha','53144','UTC-6','3501 120th Ave',203),
(193000000000000146,193000000000100146,'MQJ1','MQJ1','IN','GREENFIELD','46140','UTC-5','4412 W 300 RD N',94),
(193000000000000147,193000000000100147,'MSP1','MSP1','MN','Shakopee','55379','UTC-6','2601 4th Avenue East',190),
(193000000000000148,193000000000100148,'NJ3','NJ3','NJ','Pedricktown','08067','UTC-5','3 Sorbello Way',17),
(193000000000000149,193000000000100149,'OAK3','OAK3','CA','Patterson','95363-8876','UTC-8','255 Park Center Drive',299),
(193000000000000150,193000000000100150,'OAK4','OAK4','CA','Tracy','95304-9370','UTC-8','1555 N. Chrisman Rd',300),
(193000000000000151,193000000000100151,'OKC2','OKC2','OK','OKLAHOMA CITY','73159','UTC-6','8991 S PORTLAND AVENUE',133),
(193000000000000152,193000000000100152,'OLM1','OLM1','WA','OLYMPIA','98516','UTC-8','3300 HOGUM BAY RD NE 98516-3111 - OLYMPIA, WA - United States',19),
(193000000000000153,193000000000100153,'ONT2','ONT2','CA','San Bernardino','92408','UTC-8','1910 E Central Ave',303),
(193000000000000154,193000000000100154,'ONT6','ONT6','CA','Moreno Valley','92551-9561','UTC-8','24208 San Michele Rd',302),
(193000000000000155,193000000000100155,'ONT8','ONT8','CA','Moreno Valley','92551-9534','UTC-8','24300 Nandina Ave',301),
(193000000000000156,193000000000100156,'ONT9','ONT9','CA','Redlands','92374','UTC-8','2125 W. San Bernandino Ave',298),
(193000000000000157,193000000000100157,'ORD2','ORD2','IL','CHANNAHON','60410-8758','UTC-6','23714 W Amoco Rd',103),
(193000000000000158,193000000000100158,'ORD6','ORD6','IL','Wood Dale','60191','UTC-6','1250 N Mittel Blvd',195),
(193000000000000159,193000000000100159,'ORF2','ORF2','VA','CHESAPEAKE','23321-1425','UTC-5','ORF2 - 5045 PORTSMOUTH BLVD',113),
(193000000000000160,193000000000100160,'PBI3','PBI3','FL','Jacksonville','32206','UTC-5','21 Jump Street',81),
(193000000000000161,193000000000100161,'PDX7','PDX7','OR','Salem','97317','UTC-8','4775 Depot Ct SE',127),
(193000000000000162,193000000000100162,'PHL1','PHL1','DE','New Castle','19720','UTC-5','1 Centerpoint Blvd.',288),
(193000000000000163,193000000000100163,'PHL3','PHL3','DE','New Castle','19720','UTC-5','1600 Johnson Way',287),
(193000000000000164,193000000000100164,'PHL4','PHL4','PA','Carlisle','17015','UTC-5','21 Roadway Dr',236),
(193000000000000165,193000000000100165,'PHL5','PHL5','PA','Lewisberry','17339','UTC-5','500 McCarthy Dr',235),
(193000000000000166,193000000000100166,'PHL5S','PHL5S','PA','Greencastle','17225','UTC-5','1915 Ebberts Spg Ct, Greencastle, PA 17225',20),
(193000000000000167,193000000000100167,'PHL6','PHL6','PA','Carlisle','17015','UTC-5','675 Allen Rd.',234),
(193000000000000168,193000000000100168,'PHL7','PHL7','DE','Middletown','19709','UTC-5','560 Merrimac Ave',286),
(193000000000000169,193000000000100169,'PHL8','PHL8','DE','Middletown','19709','UTC-5','727 N Broad St',285),
(193000000000000170,193000000000100170,'PHX3','PHX3','AZ','Phoenix','85043','UTC-7','6835 West Buckeye Road',307),
(193000000000000171,193000000000100171,'PHX5','PHX5','AZ','Goodyear','85338','UTC-7','16920 W Commerce Drive',306),
(193000000000000172,193000000000100172,'PHX6','PHX6','AZ','Phoenix','85043','UTC-7','4750 West Mohave St',305),
(193000000000000173,193000000000100173,'PHX7','PHX7','AZ','Phoenix','85043','UTC-7','800 N 75th Ave',304),
(193000000000000174,193000000000100174,'PIT1','PIT1','PA','Pittsburgh','15205','UTC-5','2250 Roswell Drive',229),
(193000000000000175,193000000000100175,'PIT2','PIT2','PA','IMPERIAL','15126-1002','UTC-5','1200 Westport Rd',158),
(193000000000000176,193000000000100176,'POC1','POC1','CA','FONTANA','92336-1123','UTC-8','4121 LYTLE CREEK RD',46),
(193000000000000177,193000000000100177,'POC2','POC2','CA','ONTARIO','91761','UTC-8','4000 South Hamner Avenue',38),
(193000000000000178,193000000000100178,'POC3','POC3','CA','JURUPA VALLEY','92509','UTC-8','6120 Clinker Drive',45),
(193000000000000179,193000000000100179,'PPO4','PPO4','IN','MERRILLVILLE','46410','UTC-6','9850 Mississippi Street',49),
(193000000000000180,193000000000100180,'PSC2','PSC2','WA','PASCO','99301','UTC-8','1351 S Road 40 E',93),
(193000000000000181,193000000000100181,'PSP3','PSP3','CA','DESERT HOT SPRINGS','92240','UTC-8','64165 19th AVE',28),
(193000000000000182,193000000000100182,'QXY5','QXY5','CA','STOCKTON','95215','UTC-8','4532 NEWCASTLE RD',71),
(193000000000000183,193000000000100183,'QXY8','QXY8','CA','FRESNO','93725-1962','UTC-8','3611 S NORTHPOINTE DR',35),
(193000000000000184,193000000000100184,'QZZ7','QZZ7','AZ','MESA','85212-9608','UTC-7','8560 E ELLIOT RD',14),
(193000000000000185,193000000000100185,'RDU2','RDU2','NC','Smithfield','27577','UTC-5','2150 US HWY 70 Business West',146),
(193000000000000186,193000000000100186,'RDU4','RDU4','NC','FAYETTEVILLE','28303','UTC-5','6309 Bragg Blvd',95),
(193000000000000187,193000000000100187,'RFD2','RFD2','IL','Huntley','60142','UTC-6','11500 Freeman Road',145),
(193000000000000188,193000000000100188,'RFD4','RFD4','IL','HUNTLEY','60142','UTC-6','11400 Venture Court 60142-8075 - HUNTLEY, IL - United States',16),
(193000000000000189,193000000000100189,'RIC1','RIC1','VA','Petersburg','23803','UTC-5','5000 Commerce Way',210),
(193000000000000190,193000000000100190,'RIC2','RIC2','VA','Chester','23836','UTC-5','1901 Meadowville Technology Pkwy',209),
(193000000000000191,193000000000100191,'RMN3','RMN3','VA','Fredericksburg','22406','UTC-5','220 Centreport Parkway',144),
(193000000000000192,193000000000100192,'RNO1','RNO1','NV','Fernley','89408','UTC-8','1600 East Newlands RD',248),
(193000000000000193,193000000000100193,'RNO2','RNO2','NV','Reno','85906','UTC-8','8000 North Virginia Street',245),
(193000000000000194,193000000000100194,'RNO4','RNO4','NV','Reno','89506','UTC-8','8000 North Virginia Street',244),
(193000000000000195,193000000000100195,'RYY2','RYY2','GA','White','30184','UTC-5','1250 Cassville White Road,',73),
(193000000000000196,193000000000100196,'SAT1','SAT1','TX','Schertz','78154-1461','UTC-6','6000 Enterprise Avenue',218),
(193000000000000197,193000000000100197,'SAT4','SAT4','TX','SAN ANTONIO','78245','UTC-6','10384 W US HIGHWAY 90',149),
(193000000000000198,193000000000100198,'SAT6','SAT6','TX','SAN MARCOS','78666','UTC-6','1346 Fortuna Road 78666-0001 - SAN MARCOS, TX - United States',15),
(193000000000000199,193000000000100199,'SAV3','SAV3','GA','Macon','31216-6427','UTC-5','7001 Skipper Rd',98),
(193000000000000200,193000000000100200,'SBD1','SBD1','CA','BLOOMINGTON','92316-3819','UTC-8','3388 S Cactus Ave',185),
(193000000000000201,193000000000100201,'SBD2','SBD2','CA','SAN BERNARDINO','92408-2805','UTC-8','1494 S WATERMAN AVE',179),
(193000000000000202,193000000000100202,'SCK1','SCK1','CA','STOCKTON','95215','UTC-8','4532 NEWCASTLE RD',97),
(193000000000000203,193000000000100203,'SCK3','SCK3','CA','MANTECA','95336','UTC-8','3565 N AIRPORT WAY MANTECA',60),
(193000000000000204,193000000000100204,'SCK4','SCK4','CA','STOCKTON','95215-8354','UTC-8','6001 S AUSTIN RD',186),
(193000000000000205,193000000000100205,'SCK8','SCK8','CA','OAKLEY','94561-2677','UTC-8','4700 WILBUR AVE',104),
(193000000000000206,193000000000100206,'SDF1','SDF1','KY','Campbellsville','42718','UTC-5','1050 South Columbia AVE',266),
(193000000000000207,193000000000100207,'SDF2','SDF2','KY','Watterson Park','40218','UTC-5','4360 Robards Ln',265),
(193000000000000208,193000000000100208,'SDF4','SDF4','KY','Shepherdsville','40165','UTC-5','376 Zappos.com Blvd',264),
(193000000000000209,193000000000100209,'SDF6','SDF6','KY','Shepherdsville','40165','UTC-5','271 Omega Pkwy',263),
(193000000000000210,193000000000100210,'SDF7','SDF7','KY','Shepherdsville','40165','UTC-5','300 Omicron Court',258),
(193000000000000211,193000000000100211,'SDF8','SDF8','IN','Jeffersonville','47130','UTC-5','900 Patrol Rd',273),
(193000000000000212,193000000000100212,'SDF9','SDF9','KY','Shepherdsville','40165','UTC-5','100 W. Thomas P. Echols Lane',257),
(193000000000000213,193000000000100213,'SEA6','SEA6','WA','South Seattle','98144','UTC-8','2646 Rainier Ave.',205),
(193000000000000214,193000000000100214,'SEA8','SEA8','WA','Northeast Bellevue','98005','UTC-8','1475 124th Avenue',204),
(193000000000000215,193000000000100215,'SJC7','SJC7','CA','Tracy','95377-8906','UTC-8','188 Mountain House Parkway',295),
(193000000000000216,193000000000100216,'SLC2','SLC2','UT','West Jordan','84081','UTC-7','7148 W. Old Bingham Hwy',153),
(193000000000000217,193000000000100217,'SLC3','SLC3','UT','SALT LAKE CITY','84116-4413','UTC-7','355 N JOHN GLENN RD',13),
(193000000000000218,193000000000100218,'SMF3','SMF3','CA','Stockton','95206-8202','UTC-8','3923 S B ST',184),
(193000000000000219,193000000000100219,'SMF6','SMF6','CA','SACRAMENTO','95837-9109','UTC-8','4930 ALLBAUGH DR',105),
(193000000000000220,193000000000100220,'SNA4','SNA4','CA','Rialto','92376-3009','UTC-8','2496 W Walnut Ave',296),
(193000000000000221,193000000000100221,'STL3','STL3','MO','REPUBLIC','65738-2779','UTC-6','3200 E SAWYER RD',109),
(193000000000000222,193000000000100222,'STL4','STL4','IL','Edwardsville','62025-2815','UTC-6','3050 GATEWAY COMMERCE CENTER DR S',194),
(193000000000000223,193000000000100223,'SWF1','SWF1','NY','ROCK TAVERN','12575','UTC-5','635 INTERNATIONAL BLVD',122),
(193000000000000224,193000000000100224,'SWF2','SWF2','NY','Hopewell Junction','12533','UTC-5','76 Patriot Way Hopewell Junction',76),
(193000000000000225,193000000000100225,'TCY1','TCY1','CA','STOCKTON','95206','UTC-8','2690 East Arch Airport Road',41),
(193000000000000226,193000000000100226,'TCY2','TCY2','CA','STOCKTON','95215','UTC-8','6201 Newcastle Rd',43),
(193000000000000227,193000000000100227,'TEB3','TEB3','NJ','Logan Township','08085','UTC-5','2651 Oldmans Creek Rd',250),
(193000000000000228,193000000000100228,'TEB4','TEB4','NJ','PENNS GROVE','08069','UTC-5','747 COURSES LANDING RD',130),
(193000000000000229,193000000000100229,'TEB6','TEB6','NJ','Cranbury','08512','UTC-5','22 Hightstown Cranbury State Road',249),
(193000000000000230,193000000000100230,'TEB9','TEB9','NJ','Somerset','08873','UTC-5','601 Randolph Road',156),
(193000000000000231,193000000000100231,'TMB8','TMB8','FL','HOMESTEAD','33032-8597','UTC-5','27505 SW 132nd AVE',65),
(193000000000000232,193000000000100232,'TOL3','TOL3','OH','PERRYSBURG','43551','UTC-5','9240 Fremont Pike 43551-4210 - PERRYSBURG, OH - United States',18),
(193000000000000233,193000000000100233,'TPA1','TPA1','FL','Ruskin','33570','UTC-5','3350 Laurel Ridge Ave.',284),
(193000000000000234,193000000000100234,'TPA2','TPA2','FL','Lakeland','33811','UTC-5','1760 County Line Rd',283),
(193000000000000235,193000000000100235,'TPA3','TPA3','FL','Auburndale','33823','UTC-5','676 C. Fred Jones Boulevard',124),
(193000000000000236,193000000000100236,'TUL1','TUL1','KS','Coffeyville','67337','UTC-6','2654 North Highway 169',272),
(193000000000000237,193000000000100237,'VGT2','VGT2','NV','LAS VEGAS','89115-2013','UTC-8','6401 Howdy Wells Ave',172),
(193000000000000238,193000000000100238,'VUBA','VUBA','PA','Scranton','18512','UTC-5','1000 Keystone Industrial Park',228),
(193000000000000239,193000000000100239,'VUGA','VUGA','PA','Pittston','18643','UTC-5','508 Delaware Avenue West',227),
(193000000000000240,193000000000100240,'WBW2','WBW2','PA','OLYPHANT','18447','UTC-5','1300 Corporate Way',82),
(193000000000000241,193000000000100241,'XAV3','XAV3','GA','PORT WENTWORTH','31407','UTC-5','XAV3-120Crossgate Dr 31407-Port Wentworth, GA- United States',36),
(193000000000000242,193000000000100242,'XEW5','XEW5','NJ','Cinnaminson','08077-2015','UTC-5','995 Taylors Ln 08077-2015',37),
(193000000000000243,193000000000100243,'XIN5','XIN5','IN','Avon','46123-1700','UTC-5','8838 E COUNTY ROAD 100 S',25),
(193000000000000244,193000000000100244,'XLX1','XLX1','MD','HAGERSTOWN','21740','UTC-5','1115 Wesel Blvd HAGERSTOWN,MD 21740,USA',34),
(193000000000000245,193000000000100245,'XLX6','XLX6','NC','PINEVILLE','28134','UTC-5','12220 Carolina Logistics Drive PINEVILLE,NC 28134,USA',23),
(193000000000000246,193000000000100246,'XLX7','XLX7','CA','VICTORVILLE','92394','UTC-8','18580 Gateway Dr,Victorville,CA 92394,USA',63),
(193000000000000247,193000000000100247,'XPB2','XPB2','FL','APOPKA','32712','UTC-5','XPB2 - 4661 Apopka Logistics Pkwy 32712 - Apopka, FL - United States',26),
(193000000000000248,193000000000100248,'XRI3','XRI3','VA','Richmond','23234','UTC-5','4949 Commerce Rd, Richmond, VA 23234',24),
(193000000000000249,193000000000100249,'XUSB','XUSB','TX','Fort Worth','76155','UTC-6','14900 Frye Rd.',213),
(193000000000000250,193000000000100250,'XUSC','XUSC','PA','Carlisle','17013','UTC-5','40 Logistics Drive',226),
(193000000000000251,193000000000100251,'XUSD','XUSD','CA','Stockton','95206','UTC-8','1909 Zephyr St',297),
(193000000000000252,193000000000100252,'XUSE','XUSE','IN','Whitestown','46075','UTC-5','5100 S Indianapolis Rd',280);
DROP PROCEDURE IF EXISTS assert_wh_fba_public_master_data_seed;
DELIMITER //
CREATE PROCEDURE assert_wh_fba_public_master_data_seed()
BEGIN
IF (SELECT COUNT(*) FROM tmp_wh_fba_public_master_data) <> 252 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'FBA公共主数据源数量不是252';
END IF;
IF EXISTS (
SELECT 1
FROM tmp_wh_fba_public_master_data
WHERE warehouse_code <> UPPER(TRIM(warehouse_code))
OR warehouse_code NOT REGEXP '^[A-Z0-9]{3,5}$'
OR TRIM(city) = ''
OR TRIM(state_province) = ''
OR TRIM(address_line1) = ''
OR TRIM(postal_code) = ''
OR TRIM(timezone) = ''
) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'FBA公共主数据存在未清洗或必填缺失记录';
END IF;
IF EXISTS (
SELECT 1
FROM wh_warehouse existing
JOIN tmp_wh_fba_public_master_data source
ON existing.id = source.warehouse_id_seed
AND (existing.tenant_id <> 0 OR existing.warehouse_code <> source.warehouse_code OR existing.deleted <> 0)
) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'FBA公共主数据仓库种子ID与既有数据冲突';
END IF;
IF EXISTS (
SELECT 1
FROM wh_warehouse_address existing
JOIN tmp_wh_fba_public_master_data source
ON existing.id = source.address_id_seed
LEFT JOIN wh_warehouse parent
ON parent.id = existing.warehouse_id
AND parent.tenant_id = existing.tenant_id
WHERE existing.tenant_id <> 0
OR existing.deleted <> 0
OR parent.warehouse_code IS NULL
OR parent.warehouse_code <> source.warehouse_code
) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'FBA公共主数据地址种子ID与既有数据冲突';
END IF;
IF EXISTS (
SELECT 1
FROM wh_warehouse existing
JOIN tmp_wh_fba_public_master_data source
ON existing.tenant_id = 0
AND existing.warehouse_code = source.warehouse_code
AND existing.deleted = 1
) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'FBA公共主数据存在同编码已删除仓库,需人工确认后恢复或清理';
END IF;
END//
DELIMITER ;
CALL assert_wh_fba_public_master_data_seed();
DROP PROCEDURE IF EXISTS assert_wh_fba_public_master_data_seed;
INSERT INTO wh_warehouse (
id, tenant_id, warehouse_code, warehouse_name, warehouse_type, country_code, timezone,
contact_name, contact_phone, contact_email, status, remark,
created_by, created_at, updated_by, updated_at, deleted, version
)
SELECT
source.warehouse_id_seed,
0,
source.warehouse_code,
source.warehouse_name,
'FBA',
'US',
source.timezone,
NULL,
NULL,
NULL,
'ACTIVE',
NULL,
NULL,
CURRENT_TIMESTAMP(3),
NULL,
CURRENT_TIMESTAMP(3),
0,
0
FROM tmp_wh_fba_public_master_data source
ON DUPLICATE KEY UPDATE
warehouse_type = VALUES(warehouse_type),
country_code = VALUES(country_code),
timezone = VALUES(timezone),
updated_at = CURRENT_TIMESTAMP(3);
INSERT INTO wh_warehouse_address (
id, tenant_id, warehouse_id, address_type, country_code, state_province, city,
district, postal_code, address_line1, address_line2, latitude, longitude,
created_by, created_at, updated_by, updated_at, deleted, version
)
SELECT
source.address_id_seed,
0,
warehouse.id,
'OPERATING',
'US',
source.state_province,
source.city,
NULL,
source.postal_code,
source.address_line1,
NULL,
NULL,
NULL,
NULL,
CURRENT_TIMESTAMP(3),
NULL,
CURRENT_TIMESTAMP(3),
0,
0
FROM tmp_wh_fba_public_master_data source
JOIN wh_warehouse warehouse
ON warehouse.tenant_id = 0
AND warehouse.warehouse_code = source.warehouse_code
AND warehouse.deleted = 0
ON DUPLICATE KEY UPDATE
tenant_id = VALUES(tenant_id),
country_code = VALUES(country_code),
state_province = VALUES(state_province),
city = VALUES(city),
postal_code = VALUES(postal_code),
address_line1 = VALUES(address_line1),
updated_at = CURRENT_TIMESTAMP(3);
DROP PROCEDURE IF EXISTS verify_wh_fba_public_master_data;
DELIMITER //
CREATE PROCEDURE verify_wh_fba_public_master_data()
BEGIN
IF (SELECT COUNT(*)
FROM wh_warehouse warehouse
JOIN tmp_wh_fba_public_master_data source
ON source.warehouse_code = warehouse.warehouse_code
WHERE warehouse.tenant_id = 0
AND warehouse.warehouse_type = 'FBA'
AND warehouse.country_code = 'US'
AND warehouse.deleted = 0) <> 252 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'FBA公共仓库写入数量校验失败';
END IF;
IF EXISTS (
SELECT 1
FROM tmp_wh_fba_public_master_data source
LEFT JOIN wh_warehouse warehouse
ON warehouse.tenant_id = 0
AND warehouse.warehouse_code = source.warehouse_code
AND warehouse.deleted = 0
LEFT JOIN wh_warehouse_address address
ON address.tenant_id = 0
AND address.warehouse_id = warehouse.id
AND address.address_type = 'OPERATING'
AND address.deleted = 0
WHERE warehouse.id IS NULL
OR address.id IS NULL
OR address.country_code <> 'US'
OR address.state_province <> source.state_province
OR address.city <> source.city
OR address.postal_code <> source.postal_code
OR address.address_line1 <> source.address_line1
) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'FBA公共地址父子解析或地址字段校验失败';
END IF;
IF EXISTS (
SELECT 1
FROM wh_service_capability capability
JOIN wh_warehouse warehouse
ON warehouse.id = capability.warehouse_id
AND warehouse.tenant_id = capability.tenant_id
WHERE warehouse.tenant_id = 0
AND warehouse.warehouse_type = 'FBA'
AND warehouse.warehouse_code IN (SELECT warehouse_code FROM tmp_wh_fba_public_master_data)
AND capability.deleted = 0
) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'FBA公共地址不得创建平台作业能力';
END IF;
END//
DELIMITER ;
CALL verify_wh_fba_public_master_data();
DROP PROCEDURE IF EXISTS verify_wh_fba_public_master_data;
DROP PROCEDURE IF EXISTS verify_wh_fixed_platform_warehouse;
DELIMITER //
CREATE PROCEDURE verify_wh_fixed_platform_warehouse()
BEGIN
IF (SELECT COUNT(*)
FROM wh_warehouse warehouse
JOIN tmp_wh_fixed_platform_warehouse source
ON source.warehouse_code = warehouse.warehouse_code
WHERE warehouse.tenant_id = 0
AND warehouse.warehouse_type = source.warehouse_type
AND warehouse.country_code = source.country_code
AND warehouse.deleted = 0) <> 4 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '固定平台仓写入数量校验失败';
END IF;
IF EXISTS (
SELECT 1
FROM tmp_wh_fixed_platform_warehouse source
LEFT JOIN wh_warehouse warehouse
ON warehouse.tenant_id = 0
AND warehouse.warehouse_code = source.warehouse_code
AND warehouse.deleted = 0
LEFT JOIN wh_warehouse_address address
ON address.tenant_id = 0
AND address.warehouse_id = warehouse.id
AND address.address_type = 'OPERATING'
AND address.deleted = 0
WHERE warehouse.id IS NULL
OR address.id IS NULL
) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '固定平台仓地址父子解析失败';
END IF;
IF (SELECT COUNT(*)
FROM wh_service_capability capability
WHERE capability.tenant_id = 0
AND capability.warehouse_id IN (193000000000200001,193000000000200002)
AND capability.capability_code = 'RECEIVING'
AND capability.enabled = 1
AND capability.deleted = 0) <> 2 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '国内固定作业仓收货能力写入校验失败';
END IF;
END//
DELIMITER ;
CALL verify_wh_fixed_platform_warehouse();
DROP PROCEDURE IF EXISTS verify_wh_fixed_platform_warehouse;
DROP TEMPORARY TABLE IF EXISTS tmp_wh_fba_public_master_data;
DROP TEMPORARY TABLE IF EXISTS tmp_wh_fixed_platform_warehouse;
-- 全部增量完成后的最终结构强校验;只有通过此段才输出唯一 PASS
-- ============================================================
USE baiyue_firstleg;
DROP PROCEDURE IF EXISTS unified_publish_fee_add_column;
DELIMITER $$
CREATE PROCEDURE unified_publish_fee_add_column(
IN target_table VARCHAR(64), IN target_column VARCHAR(64), IN column_definition TEXT
)
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = target_table AND column_name = target_column
) THEN
SET @ddl = CONCAT('ALTER TABLE `', target_table, '` ADD COLUMN `', target_column, '` ', column_definition);
PREPARE statement FROM @ddl; EXECUTE statement; DEALLOCATE PREPARE statement;
END IF;
END$$
DELIMITER ;
CALL unified_publish_fee_add_column('fl_fee_item', 'fee_code',
"VARCHAR(50) NULL COMMENT '稳定费用编码;系统费用使用固定编码' AFTER trade_scope");
ALTER TABLE fl_fee_item
MODIFY COLUMN tenant_id BIGINT UNSIGNED NOT NULL COMMENT '平台公共费用项目作用域,固定为0;物理字段名沿用 tenant_id',
COMMENT = '平台公共费用项目主数据';
ALTER TABLE fl_inbound_fee_detail
MODIFY COLUMN tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID(客户隔离键);费用明细引用平台公共费用项目';
-- 费用类型.xls(SHA-256 395C9CA71816E79C7745E224E63CF79A4AB782140364F63C8374B396DF38CDB4)
-- 单表费用类型:110条(国内84、国际26);源表无编码列,按受控行序生成稳定编码。
DROP TEMPORARY TABLE IF EXISTS tmp_fee_item_seed;
CREATE TEMPORARY TABLE tmp_fee_item_seed (
seed_no INT NOT NULL PRIMARY KEY,
id BIGINT UNSIGNED NOT NULL,
fee_code VARCHAR(50) NOT NULL,
category VARCHAR(64) NOT NULL,
trade_scope VARCHAR(20) NOT NULL,
fee_type_name VARCHAR(128) NOT NULL,
pickup_payable TINYINT(1) NOT NULL,
pickup_receivable TINYINT(1) NOT NULL,
warehouse_payable TINYINT(1) NOT NULL,
warehouse_receivable TINYINT(1) NOT NULL,
allocation_fee TINYINT(1) NOT NULL,
description VARCHAR(500) NULL,
created_by BIGINT NULL,
created_by_name VARCHAR(64) NULL,
created_at DATETIME(3) NOT NULL,
updated_by BIGINT NULL,
updated_by_name VARCHAR(64) NULL,
updated_at DATETIME(3) NOT NULL,
UNIQUE KEY uk_tmp_fee_seed_code (fee_code),
UNIQUE KEY uk_tmp_fee_seed_semantic (trade_scope, fee_type_name)
) ENGINE=MEMORY;
INSERT INTO tmp_fee_item_seed (seed_no, id, fee_code, category, trade_scope, fee_type_name, pickup_payable, pickup_receivable, warehouse_payable, warehouse_receivable, allocation_fee, description, created_by, created_by_name, created_at, updated_by, updated_by_name, updated_at) VALUES
(1,193000000001000001,'FEE_TYPE_001','洛杉矶仓储成本','INTERNATIONAL','拖车费2',0,0,1,0,0,NULL,NULL,'admin','2023-07-10 00:00:00.000',NULL,'admin','2023-07-10 00:00:00.000'),
(2,193000000001000002,'FEE_TYPE_002','洛杉矶仓储成本','INTERNATIONAL','仓库水电费',0,0,1,0,0,NULL,NULL,'admin','2023-07-08 00:00:00.000',NULL,'admin','2023-07-08 00:00:00.000'),
(3,193000000001000003,'FEE_TYPE_003','洛杉矶仓储成本','INTERNATIONAL','人工费',0,0,1,0,0,'人工工资费用',NULL,'admin','2023-07-08 00:00:00.000',NULL,'admin','2023-07-08 00:00:00.000'),
(4,193000000001000004,'FEE_TYPE_004','洛杉矶仓储成本','INTERNATIONAL','仓库租赁费',0,0,1,0,0,'应付 仓库租赁费',NULL,'admin','2023-07-08 00:00:00.000',NULL,'admin','2023-07-08 00:00:00.000'),
(5,193000000001000005,'FEE_TYPE_005','默认','DOMESTIC','删单费',1,1,0,0,1,NULL,15268360352,'15268360352','2026-05-29 00:00:00.000',15268360352,'15268360352','2026-05-29 00:00:00.000'),
(6,193000000001000006,'FEE_TYPE_006','默认','INTERNATIONAL','换FBA标费用',1,1,1,1,1,NULL,NULL,'zhaoting','2026-03-04 00:00:00.000',NULL,'zhaoting','2026-03-04 00:00:00.000'),
(7,193000000001000007,'FEE_TYPE_007','默认','INTERNATIONAL','换SKU费用',1,1,1,1,1,NULL,NULL,'zhaoting','2026-03-04 00:00:00.000',NULL,'zhaoting','2026-03-04 00:00:00.000'),
(8,193000000001000008,'FEE_TYPE_008','默认','INTERNATIONAL','开箱费',0,1,1,1,1,NULL,NULL,'zhaoting','2026-03-04 00:00:00.000',NULL,'zhaoting','2026-03-04 00:00:00.000'),
(9,193000000001000009,'FEE_TYPE_009','默认','DOMESTIC','天翔八提',1,1,0,0,1,NULL,15268360352,'15268360352','2025-08-19 00:00:00.000',15268360352,'15268360352','2025-08-19 00:00:00.000'),
(10,193000000001000010,'FEE_TYPE_010','默认','DOMESTIC','天翔五提',1,1,0,0,1,NULL,15268360352,'15268360352','2025-08-05 00:00:00.000',15268360352,'15268360352','2025-08-05 00:00:00.000'),
(11,193000000001000011,'FEE_TYPE_011','默认','DOMESTIC','荣盛三提',1,1,0,0,1,NULL,15268360352,'15268360352','2025-08-05 00:00:00.000',15268360352,'15268360352','2025-08-05 00:00:00.000'),
(12,193000000001000012,'FEE_TYPE_012','默认','DOMESTIC','两地费',1,1,0,0,1,NULL,15268360352,'15268360352','2025-08-04 00:00:00.000',15268360352,'15268360352','2025-08-04 00:00:00.000'),
(13,193000000001000013,'FEE_TYPE_013','默认','DOMESTIC','绕路费',1,1,0,0,1,NULL,15268360352,'15268360352','2025-08-04 00:00:00.000',15268360352,'15268360352','2025-08-04 00:00:00.000'),
(14,193000000001000014,'FEE_TYPE_014','默认','DOMESTIC','朝华提',1,1,0,0,1,NULL,15268360352,'15268360352','2025-07-31 00:00:00.000',15268360352,'15268360352','2025-07-31 00:00:00.000'),
(15,193000000001000015,'FEE_TYPE_015','默认','DOMESTIC','长胜提',1,1,0,0,1,NULL,15268360352,'15268360352','2025-07-31 00:00:00.000',15268360352,'15268360352','2025-07-31 00:00:00.000'),
(16,193000000001000016,'FEE_TYPE_016','默认','DOMESTIC','港杂费',1,1,0,0,1,NULL,15268360352,'15268360352','2025-07-24 00:00:00.000',15268360352,'15268360352','2025-07-24 00:00:00.000'),
(17,193000000001000017,'FEE_TYPE_017','默认','DOMESTIC','极偏远费',1,1,1,1,1,NULL,NULL,'zhaoting','2025-06-11 00:00:00.000',NULL,'zhaoting','2025-06-11 00:00:00.000'),
(18,193000000001000018,'FEE_TYPE_018','默认','DOMESTIC','退舱费',1,0,0,0,0,NULL,15690305912,'15690305912','2025-05-22 00:00:00.000',15690305912,'15690305912','2025-05-22 00:00:00.000'),
(19,193000000001000019,'FEE_TYPE_019','默认','DOMESTIC','推送费',1,1,0,0,0,NULL,15690305912,'15690305912','2025-04-07 00:00:00.000',15690305912,'15690305912','2025-04-07 00:00:00.000'),
(20,193000000001000020,'FEE_TYPE_020','默认','DOMESTIC','产品附加费',1,1,1,1,1,NULL,NULL,'zhaoting','2025-04-02 00:00:00.000',NULL,'zhaoting','2025-04-02 00:00:00.000'),
(21,193000000001000021,'FEE_TYPE_021','默认','DOMESTIC','联单费',1,1,0,0,1,NULL,15268360352,'15268360352','2025-04-01 00:00:00.000',15268360352,'15268360352','2025-04-01 00:00:00.000'),
(22,193000000001000022,'FEE_TYPE_022','默认','DOMESTIC','竹木制品小额商检',1,1,0,0,1,NULL,15268360352,'15268360352','2025-04-01 00:00:00.000',15268360352,'15268360352','2025-04-01 00:00:00.000'),
(23,193000000001000023,'FEE_TYPE_023','默认','DOMESTIC','放单费',1,0,0,0,1,NULL,15268360352,'15268360352','2025-03-27 00:00:00.000',15268360352,'15268360352','2025-03-27 00:00:00.000'),
(24,193000000001000024,'FEE_TYPE_024','默认','DOMESTIC','退关费',1,1,1,1,1,NULL,15690305912,'15690305912','2025-03-17 00:00:00.000',15690305912,'15690305912','2025-03-17 00:00:00.000'),
(25,193000000001000025,'FEE_TYPE_025','默认','DOMESTIC','补收税金',0,1,0,0,0,NULL,NULL,'lijiayin','2025-03-07 00:00:00.000',NULL,'lijiayin','2025-03-07 00:00:00.000'),
(26,193000000001000026,'FEE_TYPE_026','默认','INTERNATIONAL','HC',1,1,1,1,1,NULL,15268360352,'15268360352','2025-02-27 00:00:00.000',15268360352,'15268360352','2025-02-27 00:00:00.000'),
(27,193000000001000027,'FEE_TYPE_027','默认','DOMESTIC','舱单数据传输费',1,1,1,1,1,NULL,15268360352,'15268360352','2025-02-27 00:00:00.000',15268360352,'15268360352','2025-02-27 00:00:00.000'),
(28,193000000001000028,'FEE_TYPE_028','默认','DOMESTIC','转船费',1,1,1,1,1,NULL,15268360352,'15268360352','2025-02-27 00:00:00.000',15268360352,'15268360352','2025-02-27 00:00:00.000'),
(29,193000000001000029,'FEE_TYPE_029','默认','DOMESTIC','改配费',1,1,1,1,1,NULL,15268360352,'15268360352','2025-02-27 00:00:00.000',15268360352,'15268360352','2025-02-27 00:00:00.000'),
(30,193000000001000030,'FEE_TYPE_030','默认','INTERNATIONAL','巴拿马附加费',1,1,1,1,1,NULL,15268360352,'15268360352','2025-02-21 00:00:00.000',15268360352,'15268360352','2025-02-21 00:00:00.000'),
(31,193000000001000031,'FEE_TYPE_031','默认','DOMESTIC','报关退点',1,1,0,0,1,NULL,15268360352,'15268360352','2025-02-12 00:00:00.000',15268360352,'15268360352','2025-02-12 00:00:00.000'),
(32,193000000001000032,'FEE_TYPE_032','默认','INTERNATIONAL','滞箱费',1,1,0,0,1,NULL,15268360352,'15268360352','2025-02-11 00:00:00.000',15268360352,'15268360352','2025-02-11 00:00:00.000'),
(33,193000000001000033,'FEE_TYPE_033','默认','DOMESTIC','大榭提',1,0,0,0,1,NULL,15268360352,'15268360352','2025-02-10 00:00:00.000',15268360352,'15268360352','2025-02-10 00:00:00.000'),
(34,193000000001000034,'FEE_TYPE_034','默认','DOMESTIC','进四期',1,0,0,0,1,NULL,15268360352,'15268360352','2025-02-10 00:00:00.000',15268360352,'15268360352','2025-02-10 00:00:00.000'),
(35,193000000001000035,'FEE_TYPE_035','默认','DOMESTIC','入仓费',1,1,1,1,1,NULL,NULL,'zhaoting','2025-01-22 00:00:00.000',NULL,'zhaoting','2025-01-22 00:00:00.000'),
(36,193000000001000036,'FEE_TYPE_036','默认','DOMESTIC','晚截关',1,0,0,0,0,NULL,15268360352,'15268360352','2025-01-16 00:00:00.000',15268360352,'15268360352','2025-01-16 00:00:00.000'),
(37,193000000001000037,'FEE_TYPE_037','默认','DOMESTIC','装柜费',0,1,0,0,1,NULL,15268360352,'15268360352','2024-12-14 00:00:00.000',15268360352,'15268360352','2024-12-14 00:00:00.000'),
(38,193000000001000038,'FEE_TYPE_038','默认','DOMESTIC','亏舱费',0,1,0,0,1,NULL,NULL,'zhaoting','2024-09-23 00:00:00.000',NULL,'zhaoting','2024-09-23 00:00:00.000'),
(39,193000000001000039,'FEE_TYPE_039','默认','INTERNATIONAL','暂扣费用',0,1,0,0,0,NULL,NULL,'lvhonglu','2024-08-08 00:00:00.000',NULL,'lvhonglu','2024-08-08 00:00:00.000'),
(40,193000000001000040,'FEE_TYPE_040','默认','DOMESTIC','改单费',1,1,0,0,1,NULL,NULL,'zhaoting','2024-07-30 00:00:00.000',NULL,'zhaoting','2024-07-30 00:00:00.000'),
(41,193000000001000041,'FEE_TYPE_041','默认','DOMESTIC','叉车费',1,1,0,0,0,NULL,NULL,'zhaoting','2024-07-03 00:00:00.000',NULL,'zhaoting','2024-07-03 00:00:00.000'),
(42,193000000001000042,'FEE_TYPE_042','默认','INTERNATIONAL','全包价',1,0,1,0,1,'提柜+派送',NULL,'huangmengting','2024-06-27 00:00:00.000',NULL,'huangmengting','2024-06-27 00:00:00.000'),
(43,193000000001000043,'FEE_TYPE_043','默认','INTERNATIONAL','快递费',1,0,0,0,0,'快递打单费用',NULL,'lvhonglu','2024-05-10 00:00:00.000',NULL,'lvhonglu','2024-05-10 00:00:00.000'),
(44,193000000001000044,'FEE_TYPE_044','默认','DOMESTIC','超偏远费',0,1,0,0,0,'海派超偏远地区收费(超偏远费+¥50/件)',NULL,'lijiayin','2024-05-10 00:00:00.000',NULL,'lijiayin','2024-05-10 00:00:00.000'),
(45,193000000001000045,'FEE_TYPE_045','默认','DOMESTIC','偏远费',0,1,0,0,0,'海派偏远地区收费(偏远费+¥30/件)',NULL,'lijiayin','2024-05-10 00:00:00.000',NULL,'lijiayin','2024-05-10 00:00:00.000'),
(46,193000000001000046,'FEE_TYPE_046','默认','DOMESTIC','超周长附加费',0,1,0,0,0,'(最短边+次长边)*2+最长边>260CM',NULL,'lijiayin','2024-05-10 00:00:00.000',NULL,'lijiayin','2024-05-10 00:00:00.000'),
(47,193000000001000047,'FEE_TYPE_047','默认','DOMESTIC','超重费',0,1,0,0,0,'海派单箱超22KG',NULL,'lijiayin','2024-05-10 00:00:00.000',NULL,'lijiayin','2024-05-10 00:00:00.000'),
(48,193000000001000048,'FEE_TYPE_048','默认','DOMESTIC','超长费',0,1,0,0,0,'箱子单边超120cm',NULL,'lijiayin','2024-05-10 00:00:00.000',NULL,'lijiayin','2024-05-10 00:00:00.000'),
(49,193000000001000049,'FEE_TYPE_049','默认','INTERNATIONAL','ISF',1,1,1,1,1,NULL,NULL,'lvhonglu','2023-07-21 00:00:00.000',NULL,'lvhonglu','2023-07-21 00:00:00.000'),
(50,193000000001000050,'FEE_TYPE_050','默认','INTERNATIONAL','国外查验费',1,1,0,0,0,NULL,NULL,'lvhonglu','2023-07-21 00:00:00.000',NULL,'lvhonglu','2023-07-21 00:00:00.000'),
(51,193000000001000051,'FEE_TYPE_051','默认','INTERNATIONAL','目的港关税',1,1,0,0,0,NULL,NULL,'lvhonglu','2023-07-21 00:00:00.000',NULL,'lvhonglu','2023-07-21 00:00:00.000'),
(52,193000000001000052,'FEE_TYPE_052','默认','DOMESTIC','停车费',1,1,1,1,1,NULL,NULL,'yingchaoqun','2023-07-14 00:00:00.000',NULL,'yingchaoqun','2023-07-14 00:00:00.000'),
(53,193000000001000053,'FEE_TYPE_053','默认','DOMESTIC','过磅费',1,1,1,1,1,NULL,NULL,'yingchaoqun','2023-07-14 00:00:00.000',NULL,'yingchaoqun','2023-07-14 00:00:00.000'),
(54,193000000001000054,'FEE_TYPE_054','默认','DOMESTIC','提箱费',1,1,1,1,1,NULL,NULL,'yingchaoqun','2023-07-14 00:00:00.000',NULL,'yingchaoqun','2023-07-14 00:00:00.000'),
(55,193000000001000055,'FEE_TYPE_055','默认','DOMESTIC','进港费',1,1,1,1,1,NULL,NULL,'yingchaoqun','2023-07-14 00:00:00.000',NULL,'yingchaoqun','2023-07-14 00:00:00.000'),
(56,193000000001000056,'FEE_TYPE_056','默认','DOMESTIC','保险费',1,1,1,1,1,NULL,NULL,'yingchaoqun','2023-07-14 00:00:00.000',NULL,'yingchaoqun','2023-07-14 00:00:00.000'),
(57,193000000001000057,'FEE_TYPE_057','默认','DOMESTIC','舱单费',1,1,1,1,1,NULL,NULL,'yingchaoqun','2023-07-14 00:00:00.000',NULL,'yingchaoqun','2023-07-14 00:00:00.000'),
(58,193000000001000058,'FEE_TYPE_058','默认','DOMESTIC','EDI',1,1,1,1,1,NULL,NULL,'yingchaoqun','2023-07-14 00:00:00.000',NULL,'yingchaoqun','2023-07-14 00:00:00.000'),
(59,193000000001000059,'FEE_TYPE_059','默认','DOMESTIC','箱单费',1,1,1,1,1,NULL,NULL,'yingchaoqun','2023-07-14 00:00:00.000',NULL,'yingchaoqun','2023-07-14 00:00:00.000'),
(60,193000000001000060,'FEE_TYPE_060','默认','DOMESTIC','文件费',1,1,1,1,1,NULL,NULL,'yingchaoqun','2023-07-14 00:00:00.000',NULL,'yingchaoqun','2023-07-14 00:00:00.000'),
(61,193000000001000061,'FEE_TYPE_061','默认','DOMESTIC','THC',1,1,1,1,1,NULL,NULL,'yingchaoqun','2023-07-14 00:00:00.000',NULL,'yingchaoqun','2023-07-14 00:00:00.000'),
(62,193000000001000062,'FEE_TYPE_062','默认','DOMESTIC','订舱费',1,1,1,1,1,NULL,NULL,'yingchaoqun','2023-07-14 00:00:00.000',NULL,'yingchaoqun','2023-07-14 00:00:00.000'),
(63,193000000001000063,'FEE_TYPE_063','默认','DOMESTIC','包干费',1,1,1,1,1,NULL,NULL,'yingchaoqun','2023-07-14 00:00:00.000',NULL,'yingchaoqun','2023-07-14 00:00:00.000'),
(64,193000000001000064,'FEE_TYPE_064','默认','DOMESTIC','换单费',1,1,1,1,1,NULL,NULL,'yingchaoqun','2023-07-14 00:00:00.000',NULL,'yingchaoqun','2023-07-14 00:00:00.000'),
(65,193000000001000065,'FEE_TYPE_065','默认','DOMESTIC','整柜运费',1,1,1,1,1,NULL,NULL,'lvhonglu','2023-07-13 00:00:00.000',NULL,'lvhonglu','2023-07-13 00:00:00.000'),
(66,193000000001000066,'FEE_TYPE_066','默认','DOMESTIC','查验费',1,1,1,1,0,'国外海外查验',NULL,'zhaoting','2023-02-16 00:00:00.000',NULL,'admin','2023-05-18 00:00:00.000'),
(67,193000000001000067,'FEE_TYPE_067','默认','DOMESTIC','FDA申报费',1,1,1,1,0,'FDA申报费',NULL,'zhaoting','2023-02-16 00:00:00.000',NULL,'admin','2023-05-18 00:00:00.000'),
(68,193000000001000068,'FEE_TYPE_068','默认','DOMESTIC','LACEY ACT申报费',1,1,1,1,0,'LACEY ACT申报费',NULL,'zhaoting','2023-02-16 00:00:00.000',NULL,'zhaoting','2023-02-16 00:00:00.000'),
(69,193000000001000069,'FEE_TYPE_069','默认','DOMESTIC','报关费',1,1,1,1,0,'退税报关',NULL,'zhaoting','2023-02-16 00:00:00.000',NULL,'zhaoting','2023-02-16 00:00:00.000'),
(70,193000000001000070,'FEE_TYPE_070','默认','DOMESTIC','提货费',1,1,1,1,0,'提货费',NULL,'zhaoting','2023-02-16 00:00:00.000',NULL,'admin','2023-07-06 00:00:00.000'),
(71,193000000001000071,'FEE_TYPE_071','默认','INTERNATIONAL','贴标费',1,0,1,1,0,'贴标费',NULL,'admin','2022-11-25 00:00:00.000',NULL,'admin','2023-07-05 00:00:00.000'),
(72,193000000001000072,'FEE_TYPE_072','默认','DOMESTIC','补运费',1,1,1,1,0,'移仓补运费',NULL,'admin','2022-11-23 00:00:00.000',NULL,'admin','2022-11-23 00:00:00.000'),
(73,193000000001000073,'FEE_TYPE_073','默认','DOMESTIC','其他扣费',1,1,1,1,0,'其他扣费',NULL,'admin','2022-11-23 00:00:00.000',NULL,'admin','2022-11-23 00:00:00.000'),
(74,193000000001000074,'FEE_TYPE_074','默认','DOMESTIC','抵扣费用',1,1,1,1,0,'应付-抵扣费用',NULL,'admin','2022-11-23 00:00:00.000',NULL,'admin','2022-11-23 00:00:00.000'),
(75,193000000001000075,'FEE_TYPE_075','默认','INTERNATIONAL','派送费',1,0,1,1,0,'派送费',NULL,'admin','2022-04-16 00:00:00.000',NULL,'admin','2023-07-05 00:00:00.000'),
(76,193000000001000076,'FEE_TYPE_076','默认','DOMESTIC','运费',1,1,1,1,0,'头程运费',NULL,'admin','2022-04-16 00:00:00.000',NULL,'admin','2023-07-06 00:00:00.000'),
(77,193000000001000077,'FEE_TYPE_077','默认','INTERNATIONAL','仓储费',1,0,1,1,0,'仓储费',NULL,'admin','2022-04-16 00:00:00.000',NULL,'admin','2023-07-05 00:00:00.000'),
(78,193000000001000078,'FEE_TYPE_078','集装箱费用','INTERNATIONAL','提柜-额外费用',1,1,1,1,1,NULL,NULL,'jinshanshan','2024-12-05 00:00:00.000',NULL,'jinshanshan','2024-12-05 00:00:00.000'),
(79,193000000001000079,'FEE_TYPE_079','集装箱费用','INTERNATIONAL','移柜费',1,1,1,1,1,NULL,NULL,'jinshanshan','2024-11-22 00:00:00.000',NULL,'jinshanshan','2024-11-22 00:00:00.000'),
(80,193000000001000080,'FEE_TYPE_080','集装箱费用','DOMESTIC','四期提',1,1,1,1,1,NULL,NULL,'yingchaoqun','2024-08-12 00:00:00.000',NULL,'yingchaoqun','2024-08-12 00:00:00.000'),
(81,193000000001000081,'FEE_TYPE_081','集装箱费用','DOMESTIC','进大榭',1,1,1,1,1,NULL,NULL,'yingchaoqun','2024-08-12 00:00:00.000',NULL,'yingchaoqun','2024-08-12 00:00:00.000'),
(82,193000000001000082,'FEE_TYPE_082','集装箱费用','DOMESTIC','过夜费',1,1,1,1,1,NULL,NULL,'yingchaoqun','2024-08-10 00:00:00.000',NULL,'yingchaoqun','2024-08-10 00:00:00.000'),
(83,193000000001000083,'FEE_TYPE_083','集装箱费用','DOMESTIC','搞定费',1,1,1,1,1,NULL,NULL,'yingchaoqun','2024-08-10 00:00:00.000',NULL,'yingchaoqun','2024-08-10 00:00:00.000'),
(84,193000000001000084,'FEE_TYPE_084','集装箱费用','DOMESTIC','梅山提',1,1,1,1,1,NULL,NULL,'yingchaoqun','2024-08-10 00:00:00.000',NULL,'yingchaoqun','2024-08-10 00:00:00.000'),
(85,193000000001000085,'FEE_TYPE_085','集装箱费用','DOMESTIC','进梅山',1,1,1,1,1,NULL,NULL,'yingchaoqun','2024-08-10 00:00:00.000',NULL,'yingchaoqun','2024-08-10 00:00:00.000'),
(86,193000000001000086,'FEE_TYPE_086','集装箱费用','DOMESTIC','舱单录入费',1,1,1,1,1,NULL,NULL,'yingchaoqun','2024-08-10 00:00:00.000',NULL,'yingchaoqun','2024-08-10 00:00:00.000'),
(87,193000000001000087,'FEE_TYPE_087','集装箱费用','DOMESTIC','进洋山',1,1,1,1,1,NULL,NULL,'yingchaoqun','2024-08-10 00:00:00.000',NULL,'yingchaoqun','2024-08-10 00:00:00.000'),
(88,193000000001000088,'FEE_TYPE_088','集装箱费用','DOMESTIC','洋山提',1,1,1,1,1,NULL,NULL,'yingchaoqun','2024-08-10 00:00:00.000',NULL,'yingchaoqun','2024-08-10 00:00:00.000'),
(89,193000000001000089,'FEE_TYPE_089','集装箱费用','DOMESTIC','落箱费',1,1,1,1,1,NULL,NULL,'yingchaoqun','2024-08-10 00:00:00.000',NULL,'yingchaoqun','2024-08-10 00:00:00.000'),
(90,193000000001000090,'FEE_TYPE_090','集装箱费用','DOMESTIC','预进港',1,1,1,1,1,NULL,NULL,'yingchaoqun','2024-08-09 00:00:00.000',NULL,'yingchaoqun','2024-08-09 00:00:00.000'),
(91,193000000001000091,'FEE_TYPE_091','集装箱费用','DOMESTIC','改运抵',1,1,1,1,1,NULL,NULL,'yingchaoqun','2024-08-09 00:00:00.000',NULL,'yingchaoqun','2024-08-09 00:00:00.000'),
(92,193000000001000092,'FEE_TYPE_092','集装箱费用','DOMESTIC','预提费',1,1,1,1,1,NULL,NULL,'yingchaoqun','2024-08-08 00:00:00.000',NULL,'yingchaoqun','2024-08-08 00:00:00.000'),
(93,193000000001000093,'FEE_TYPE_093','集装箱费用','DOMESTIC','设备交接单',1,1,1,1,1,NULL,NULL,'yingchaoqun','2024-08-08 00:00:00.000',NULL,'yingchaoqun','2024-08-08 00:00:00.000'),
(94,193000000001000094,'FEE_TYPE_094','集装箱费用','DOMESTIC','操作费',1,1,1,1,1,NULL,NULL,'yingchaoqun','2024-08-08 00:00:00.000',NULL,'yingchaoqun','2024-08-08 00:00:00.000'),
(95,193000000001000095,'FEE_TYPE_095','集装箱费用','DOMESTIC','VGM',1,1,1,1,1,NULL,NULL,'yingchaoqun','2024-08-08 00:00:00.000',NULL,'yingchaoqun','2024-08-08 00:00:00.000'),
(96,193000000001000096,'FEE_TYPE_096','集装箱费用','INTERNATIONAL','托盘费',1,0,0,0,1,NULL,NULL,'jinshanshan','2023-08-14 00:00:00.000',NULL,'jinshanshan','2023-08-14 00:00:00.000'),
(97,193000000001000097,'FEE_TYPE_097','集装箱费用','INTERNATIONAL','拆柜费',1,0,0,0,1,NULL,NULL,'jinshanshan','2023-08-14 00:00:00.000',NULL,'jinshanshan','2023-08-14 00:00:00.000'),
(98,193000000001000098,'FEE_TYPE_098','集装箱费用','DOMESTIC','加封费',1,1,1,1,1,NULL,NULL,'yingchaoqun','2023-08-03 00:00:00.000',NULL,'yingchaoqun','2023-08-03 00:00:00.000'),
(99,193000000001000099,'FEE_TYPE_099','集装箱费用','DOMESTIC','电子锁',1,1,1,1,1,NULL,NULL,'yingchaoqun','2023-08-03 00:00:00.000',NULL,'yingchaoqun','2023-08-03 00:00:00.000'),
(100,193000000001000100,'FEE_TYPE_100','集装箱费用','DOMESTIC','吊机费',1,1,1,1,1,NULL,NULL,'yingchaoqun','2023-07-19 00:00:00.000',NULL,'yingchaoqun','2023-07-19 00:00:00.000'),
(101,193000000001000101,'FEE_TYPE_101','集装箱费用','DOMESTIC','条码费',1,1,1,1,1,NULL,NULL,'yingchaoqun','2023-07-19 00:00:00.000',NULL,'yingchaoqun','2023-07-19 00:00:00.000'),
(102,193000000001000102,'FEE_TYPE_102','集装箱费用','DOMESTIC','AMS',1,1,1,1,1,NULL,NULL,'yingchaoqun','2023-07-19 00:00:00.000',NULL,'yingchaoqun','2023-07-19 00:00:00.000'),
(103,193000000001000103,'FEE_TYPE_103','集装箱费用','DOMESTIC','海运费',1,1,1,1,1,NULL,NULL,'yingchaoqun','2023-07-19 00:00:00.000',NULL,'yingchaoqun','2023-07-19 00:00:00.000'),
(104,193000000001000104,'FEE_TYPE_104','集装箱费用','DOMESTIC','堆存费',1,1,1,1,1,NULL,NULL,'yingchaoqun','2023-07-19 00:00:00.000',NULL,'yingchaoqun','2023-07-19 00:00:00.000'),
(105,193000000001000105,'FEE_TYPE_105','集装箱费用','DOMESTIC','电子装箱单',1,1,1,1,1,NULL,NULL,'yingchaoqun','2023-07-19 00:00:00.000',NULL,'yingchaoqun','2023-07-19 00:00:00.000'),
(106,193000000001000106,'FEE_TYPE_106','集装箱费用','INTERNATIONAL','提柜费',1,1,1,1,0,'提柜费',NULL,'admin','2023-05-18 00:00:00.000',NULL,'admin','2023-05-18 00:00:00.000'),
(107,193000000001000107,'FEE_TYPE_107','集装箱费用','INTERNATIONAL','清关费',1,1,1,1,0,'清关费',NULL,'admin','2023-05-18 00:00:00.000',NULL,'admin','2023-05-18 00:00:00.000'),
(108,193000000001000108,'FEE_TYPE_108','集装箱费用','DOMESTIC','拖车费',1,1,1,1,0,'拖车费',NULL,'admin','2023-05-18 00:00:00.000',NULL,'admin','2023-07-06 00:00:00.000'),
(109,193000000001000109,'FEE_TYPE_109','集装箱费用','DOMESTIC','加载费',1,1,1,1,0,'加载费',NULL,'admin','2023-05-18 00:00:00.000',NULL,'admin','2023-05-18 00:00:00.000'),
(110,193000000001000110,'FEE_TYPE_110','BOL费用','INTERNATIONAL','装车费',1,1,1,1,1,NULL,NULL,'jinshanshan','2025-01-17 00:00:00.000',NULL,'jinshanshan','2025-01-17 00:00:00.000');
DROP PROCEDURE IF EXISTS unified_publish_assert_fee_item_preflight;
DELIMITER $$
CREATE PROCEDURE unified_publish_assert_fee_item_preflight()
BEGIN
IF (SELECT COUNT(*) FROM tmp_fee_item_seed) <> 110 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '二期费用项目迁移失败:Excel种子总数不正确';
END IF;
IF (SELECT COUNT(*) FROM tmp_fee_item_seed WHERE trade_scope = 'DOMESTIC') <> 84 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '二期费用项目迁移失败:Excel国内种子数量不正确';
END IF;
IF (SELECT COUNT(*) FROM tmp_fee_item_seed WHERE trade_scope = 'INTERNATIONAL') <> 26 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '二期费用项目迁移失败:Excel国际种子数量不正确';
END IF;
IF EXISTS (SELECT fee_code FROM tmp_fee_item_seed GROUP BY fee_code HAVING COUNT(*) > 1) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '二期费用项目迁移失败:Excel种子编码重复';
END IF;
IF EXISTS (SELECT trade_scope, fee_type_name FROM tmp_fee_item_seed GROUP BY trade_scope, fee_type_name HAVING COUNT(*) > 1) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '二期费用项目迁移失败:Excel种子语义键重复';
END IF;
IF EXISTS (
SELECT f.id
FROM fl_fee_item f
JOIN tmp_fee_item_seed s ON s.id = f.id
WHERE f.tenant_id <> 0 OR f.fee_code <> s.fee_code
OR f.category <> s.category OR f.trade_scope <> s.trade_scope
OR f.fee_type_name <> s.fee_type_name
OR f.pickup_payable <> s.pickup_payable OR f.pickup_receivable <> s.pickup_receivable
OR f.warehouse_payable <> s.warehouse_payable OR f.warehouse_receivable <> s.warehouse_receivable
OR f.allocation_fee <> s.allocation_fee OR NOT (f.description <=> s.description)
) THEN
SELECT 'FIXED_ID_CONFLICT' AS conflict_type, f.id, f.tenant_id, f.fee_code,
f.trade_scope, f.fee_type_name
FROM fl_fee_item f JOIN tmp_fee_item_seed s ON s.id = f.id
WHERE f.tenant_id <> 0 OR f.fee_code <> s.fee_code
OR f.category <> s.category OR f.trade_scope <> s.trade_scope
OR f.fee_type_name <> s.fee_type_name
OR f.pickup_payable <> s.pickup_payable OR f.pickup_receivable <> s.pickup_receivable
OR f.warehouse_payable <> s.warehouse_payable OR f.warehouse_receivable <> s.warehouse_receivable
OR f.allocation_fee <> s.allocation_fee OR NOT (f.description <=> s.description);
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '二期费用项目迁移失败:固定主键已被不同费用项目占用';
END IF;
IF EXISTS (
SELECT f.id
FROM fl_fee_item f
JOIN tmp_fee_item_seed s ON s.fee_code = f.fee_code
WHERE f.tenant_id = 0
AND (f.id <> s.id OR f.category <> s.category OR f.trade_scope <> s.trade_scope
OR f.fee_type_name <> s.fee_type_name OR f.pickup_payable <> s.pickup_payable
OR f.pickup_receivable <> s.pickup_receivable OR f.warehouse_payable <> s.warehouse_payable
OR f.warehouse_receivable <> s.warehouse_receivable OR f.allocation_fee <> s.allocation_fee
OR NOT (f.description <=> s.description) OR f.status <> 'ACTIVE' OR f.deleted <> 0)
) THEN
SELECT 'PUBLIC_CODE_CONFLICT' AS conflict_type, f.id, f.tenant_id, f.fee_code,
f.trade_scope, f.fee_type_name, s.id AS expected_id, s.fee_type_name AS expected_name
FROM fl_fee_item f JOIN tmp_fee_item_seed s ON s.fee_code = f.fee_code
WHERE f.tenant_id = 0
AND (f.id <> s.id OR f.category <> s.category OR f.trade_scope <> s.trade_scope
OR f.fee_type_name <> s.fee_type_name OR f.pickup_payable <> s.pickup_payable
OR f.pickup_receivable <> s.pickup_receivable OR f.warehouse_payable <> s.warehouse_payable
OR f.warehouse_receivable <> s.warehouse_receivable OR f.allocation_fee <> s.allocation_fee
OR NOT (f.description <=> s.description) OR f.status <> 'ACTIVE' OR f.deleted <> 0);
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '二期费用项目迁移失败:平台费用编码存在语义冲突';
END IF;
IF EXISTS (
SELECT f.id
FROM fl_fee_item f
JOIN tmp_fee_item_seed s ON s.trade_scope = f.trade_scope AND s.fee_type_name = f.fee_type_name
WHERE f.tenant_id = 0 AND (f.fee_code <> s.fee_code OR f.category <> s.category
OR f.pickup_payable <> s.pickup_payable OR f.pickup_receivable <> s.pickup_receivable
OR f.warehouse_payable <> s.warehouse_payable OR f.warehouse_receivable <> s.warehouse_receivable
OR f.allocation_fee <> s.allocation_fee OR NOT (f.description <=> s.description))
) THEN
SELECT 'PUBLIC_SEMANTIC_CONFLICT' AS conflict_type, f.id, f.tenant_id, f.fee_code,
f.trade_scope, f.fee_type_name, s.fee_code AS expected_code
FROM fl_fee_item f
JOIN tmp_fee_item_seed s ON s.trade_scope = f.trade_scope AND s.fee_type_name = f.fee_type_name
WHERE f.tenant_id = 0 AND (f.fee_code <> s.fee_code OR f.category <> s.category
OR f.pickup_payable <> s.pickup_payable OR f.pickup_receivable <> s.pickup_receivable
OR f.warehouse_payable <> s.warehouse_payable OR f.warehouse_receivable <> s.warehouse_receivable
OR f.allocation_fee <> s.allocation_fee OR NOT (f.description <=> s.description));
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '二期费用项目迁移失败:平台费用语义键存在编码或字段冲突';
END IF;
IF EXISTS (SELECT 1 FROM fl_fee_item WHERE tenant_id = 0 AND (fee_code IS NULL OR TRIM(fee_code) = '')) THEN
SELECT 'PUBLIC_EMPTY_CODE' AS conflict_type, id, tenant_id, fee_code, trade_scope, fee_type_name
FROM fl_fee_item
WHERE tenant_id = 0 AND (fee_code IS NULL OR TRIM(fee_code) = '');
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '二期费用项目迁移失败:平台既有费用项目存在空编码';
END IF;
IF EXISTS (
SELECT f.id
FROM fl_fee_item f
LEFT JOIN tmp_fee_item_seed matched_seed
ON matched_seed.fee_code = NULLIF(TRIM(f.fee_code), '')
OR (matched_seed.trade_scope = f.trade_scope
AND matched_seed.fee_type_name = f.fee_type_name)
WHERE f.tenant_id > 0 AND matched_seed.seed_no IS NULL
) THEN
SELECT 'UNMAPPED_CUSTOMER_ITEM' AS conflict_type, f.id, f.tenant_id, f.fee_code,
f.trade_scope, f.fee_type_name
FROM fl_fee_item f
LEFT JOIN tmp_fee_item_seed matched_seed
ON matched_seed.fee_code = NULLIF(TRIM(f.fee_code), '')
OR (matched_seed.trade_scope = f.trade_scope
AND matched_seed.fee_type_name = f.fee_type_name)
WHERE f.tenant_id > 0 AND matched_seed.seed_no IS NULL;
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '二期费用项目迁移失败:存在无法匹配公共种子的客户费用项目';
END IF;
IF EXISTS (
SELECT f.id
FROM fl_fee_item f
JOIN tmp_fee_item_seed s ON s.fee_code = NULLIF(TRIM(f.fee_code), '')
WHERE f.tenant_id > 0 AND (f.category <> s.category OR f.trade_scope <> s.trade_scope
OR f.fee_type_name <> s.fee_type_name OR f.pickup_payable <> s.pickup_payable
OR f.pickup_receivable <> s.pickup_receivable OR f.warehouse_payable <> s.warehouse_payable
OR f.warehouse_receivable <> s.warehouse_receivable OR f.allocation_fee <> s.allocation_fee
OR NOT (f.description <=> s.description))
) THEN
SELECT 'CUSTOMER_CODE_CONFLICT' AS conflict_type, f.id, f.tenant_id, f.fee_code,
f.trade_scope, f.fee_type_name, s.fee_type_name AS expected_name
FROM fl_fee_item f JOIN tmp_fee_item_seed s ON s.fee_code = NULLIF(TRIM(f.fee_code), '')
WHERE f.tenant_id > 0 AND (f.category <> s.category OR f.trade_scope <> s.trade_scope
OR f.fee_type_name <> s.fee_type_name OR f.pickup_payable <> s.pickup_payable
OR f.pickup_receivable <> s.pickup_receivable OR f.warehouse_payable <> s.warehouse_payable
OR f.warehouse_receivable <> s.warehouse_receivable OR f.allocation_fee <> s.allocation_fee
OR NOT (f.description <=> s.description));
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '二期费用项目迁移失败:客户费用编码存在语义冲突';
END IF;
IF EXISTS (
SELECT f.id
FROM fl_fee_item f
JOIN tmp_fee_item_seed s ON s.trade_scope = f.trade_scope AND s.fee_type_name = f.fee_type_name
WHERE f.tenant_id > 0 AND (NULLIF(TRIM(f.fee_code), '') IS NOT NULL
AND f.fee_code <> s.fee_code AND f.fee_code <> CONCAT('MANUAL_', f.id)
OR f.category <> s.category OR f.pickup_payable <> s.pickup_payable
OR f.pickup_receivable <> s.pickup_receivable OR f.warehouse_payable <> s.warehouse_payable
OR f.warehouse_receivable <> s.warehouse_receivable OR f.allocation_fee <> s.allocation_fee
OR NOT (f.description <=> s.description))
) THEN
SELECT 'CUSTOMER_SEMANTIC_CONFLICT' AS conflict_type, f.id, f.tenant_id, f.fee_code,
f.trade_scope, f.fee_type_name, s.fee_code AS expected_code
FROM fl_fee_item f
JOIN tmp_fee_item_seed s ON s.trade_scope = f.trade_scope AND s.fee_type_name = f.fee_type_name
WHERE f.tenant_id > 0 AND (NULLIF(TRIM(f.fee_code), '') IS NOT NULL
AND f.fee_code <> s.fee_code AND f.fee_code <> CONCAT('MANUAL_', f.id)
OR f.category <> s.category OR f.pickup_payable <> s.pickup_payable
OR f.pickup_receivable <> s.pickup_receivable OR f.warehouse_payable <> s.warehouse_payable
OR f.warehouse_receivable <> s.warehouse_receivable OR f.allocation_fee <> s.allocation_fee
OR NOT (f.description <=> s.description));
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '二期费用项目迁移失败:客户费用语义键存在编码或字段冲突';
END IF;
IF EXISTS (
SELECT d.id
FROM fl_inbound_fee_detail d
JOIN fl_fee_item f ON f.id = d.fee_item_id
WHERE d.tenant_id = 0 OR (f.tenant_id > 0 AND f.tenant_id <> d.tenant_id)
) THEN
SELECT 'CROSS_CUSTOMER_DETAIL_REFERENCE' AS conflict_type, d.id AS fee_detail_id,
d.tenant_id AS detail_customer_id, d.fee_item_id, f.tenant_id AS fee_item_scope
FROM fl_inbound_fee_detail d JOIN fl_fee_item f ON f.id = d.fee_item_id
WHERE d.tenant_id = 0 OR (f.tenant_id > 0 AND f.tenant_id <> d.tenant_id);
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '二期费用项目迁移失败:发现跨客户或平台作用域费用明细引用';
END IF;
END$$
DELIMITER ;
CALL unified_publish_assert_fee_item_preflight();
DROP PROCEDURE IF EXISTS unified_publish_assert_fee_item_preflight;
DROP PROCEDURE IF EXISTS unified_publish_migrate_fee_items;
DELIMITER $$
CREATE PROCEDURE unified_publish_migrate_fee_items()
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
RESIGNAL;
END;
START TRANSACTION;
INSERT INTO fl_fee_item (id, tenant_id, fee_code, category, trade_scope, fee_type_name,
pickup_payable, pickup_receivable, warehouse_payable, warehouse_receivable, allocation_fee,
description, status, created_by, created_by_name, created_at, updated_by, updated_by_name, updated_at)
SELECT s.id, 0, s.fee_code, s.category, s.trade_scope, s.fee_type_name,
s.pickup_payable, s.pickup_receivable, s.warehouse_payable, s.warehouse_receivable, s.allocation_fee,
s.description, 'ACTIVE', s.created_by, s.created_by_name, s.created_at,
s.updated_by, s.updated_by_name, s.updated_at
FROM tmp_fee_item_seed s
WHERE NOT EXISTS (SELECT 1 FROM fl_fee_item f WHERE f.id = s.id);
DROP TEMPORARY TABLE IF EXISTS tmp_fee_item_map;
CREATE TEMPORARY TABLE tmp_fee_item_map (
old_id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
public_id BIGINT UNSIGNED NOT NULL
) ENGINE=MEMORY;
INSERT INTO tmp_fee_item_map (old_id, public_id)
SELECT f.id, s.id
FROM fl_fee_item f
JOIN tmp_fee_item_seed s
ON (s.fee_code = NULLIF(TRIM(f.fee_code), '')
OR (s.trade_scope = f.trade_scope AND s.fee_type_name = f.fee_type_name))
WHERE f.tenant_id > 0;
UPDATE fl_inbound_fee_detail d
JOIN tmp_fee_item_map m ON m.old_id = d.fee_item_id
SET d.fee_item_id = m.public_id;
DELETE f FROM fl_fee_item f JOIN tmp_fee_item_map m ON m.old_id = f.id;
COMMIT;
END$$
DELIMITER ;
CALL unified_publish_migrate_fee_items();
DROP PROCEDURE IF EXISTS unified_publish_migrate_fee_items;
SET @fee_code_ddl = CASE
WHEN
(SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = DATABASE()
AND table_name = 'fl_fee_item' AND index_name = 'uk_fee_item_code'
AND non_unique = 0 AND seq_in_index = 1 AND column_name = 'fee_code') = 1
AND (SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = DATABASE()
AND table_name = 'fl_fee_item' AND index_name = 'uk_fee_item_code') = 1
THEN 'SELECT 1'
WHEN EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema = DATABASE()
AND table_name = 'fl_fee_item' AND index_name = 'uk_fee_item_code')
THEN 'ALTER TABLE fl_fee_item DROP INDEX uk_fee_item_code, ADD UNIQUE KEY uk_fee_item_code (fee_code)'
ELSE 'ALTER TABLE fl_fee_item ADD UNIQUE KEY uk_fee_item_code (fee_code)'
END;
PREPARE statement FROM @fee_code_ddl;
EXECUTE statement;
DEALLOCATE PREPARE statement;
DROP TEMPORARY TABLE IF EXISTS tmp_fee_item_map;
DROP TEMPORARY TABLE IF EXISTS tmp_fee_item_seed;
SET @fee_code_ddl = IF(
(SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = 'fl_fee_item'
AND column_name = 'fee_code' AND is_nullable = 'NO') = 1,
'SELECT 1',
'ALTER TABLE fl_fee_item MODIFY COLUMN fee_code VARCHAR(50) NOT NULL COMMENT ''稳定费用编码;平台公共主数据固定编码'' AFTER trade_scope'
);
PREPARE statement FROM @fee_code_ddl;
EXECUTE statement;
DEALLOCATE PREPARE statement;
CALL unified_publish_fee_add_column('fl_inbound_fee_detail', 'source_type',
"VARCHAR(32) NOT NULL DEFAULT 'MANUAL' COMMENT 'MANUAL或FBA_PRICING' AFTER fee_item_id");
CALL unified_publish_fee_add_column('fl_inbound_fee_detail', 'source_fee_id',
"BIGINT UNSIGNED NULL COMMENT '来源计费快照ID' AFTER source_type");
CALL unified_publish_fee_add_column('fl_inbound_fee_detail', 'source_fee_version',
"INT UNSIGNED NULL COMMENT '来源计费快照版本' AFTER source_fee_id");
CALL unified_publish_fee_add_column('fl_inbound_fee_detail', 'source_fee_no',
"VARCHAR(40) NULL COMMENT '来源费用单号' AFTER source_fee_version");
CALL unified_publish_fee_add_column('fl_inbound_fee_detail', 'customer_id',
"BIGINT UNSIGNED NULL COMMENT '客户ID快照' AFTER business_date");
CALL unified_publish_fee_add_column('fl_inbound_fee_detail', 'customer_name',
"VARCHAR(128) NULL COMMENT '客户公司名称快照' AFTER customer_id");
CALL unified_publish_fee_add_column('fl_inbound_fee_detail', 'invoice_title',
"VARCHAR(256) NULL COMMENT '发票抬头快照' AFTER customer_name");
CALL unified_publish_fee_add_column('fl_inbound_fee_detail', 'chargeable_quantity',
"DECIMAL(18,4) NULL COMMENT '本费用最终计费量' AFTER chargeable_volume_cbm");
CALL unified_publish_fee_add_column('fl_inbound_fee_detail', 'charge_unit',
"VARCHAR(16) NULL COMMENT 'KG或CBM' AFTER chargeable_quantity");
CALL unified_publish_fee_add_column('fl_inbound_fee_detail', 'publication_status',
"VARCHAR(16) NOT NULL DEFAULT 'DRAFT' COMMENT '费用发布状态:DRAFT草稿、PUBLISHED已发布、SUPERSEDED已替代' AFTER review_reason");
CALL unified_publish_fee_add_column('fl_inbound_fee_detail', 'published_by',
"BIGINT NULL COMMENT '发布人ID' AFTER publication_status");
CALL unified_publish_fee_add_column('fl_inbound_fee_detail', 'published_by_name',
"VARCHAR(64) NULL COMMENT '发布人名称快照' AFTER published_by");
CALL unified_publish_fee_add_column('fl_inbound_fee_detail', 'published_at',
"DATETIME(3) NULL COMMENT '发布时间' AFTER published_by_name");
CALL unified_publish_fee_add_column('fl_inbound_fee_detail', 'inbounded_at',
"DATETIME(3) NULL COMMENT '入库完成时间快照' AFTER published_at");
CALL unified_publish_fee_add_column('fl_inbound_fee_detail', 'settlement_included',
"TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否参与结算' AFTER inbounded_at");
DROP PROCEDURE IF EXISTS unified_publish_fee_add_column;
DROP PROCEDURE IF EXISTS unified_publish_fee_add_index;
DELIMITER $$
CREATE PROCEDURE unified_publish_fee_add_index(
IN target_table VARCHAR(64), IN target_index VARCHAR(64), IN index_definition TEXT
)
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.statistics
WHERE table_schema = DATABASE() AND table_name = target_table AND index_name = target_index
) THEN
SET @ddl = CONCAT('ALTER TABLE `', target_table, '` ADD ', index_definition);
PREPARE statement FROM @ddl; EXECUTE statement; DEALLOCATE PREPARE statement;
END IF;
END$$
DELIMITER ;
CALL unified_publish_fee_add_index('fl_fee_item', 'uk_fee_item_code',
'UNIQUE KEY `uk_fee_item_code` (`fee_code`)');
CALL unified_publish_fee_add_index('fl_inbound_fee_detail', 'uk_inbound_fee_source',
'UNIQUE KEY `uk_inbound_fee_source` (`tenant_id`, `source_type`, `source_fee_id`)');
CALL unified_publish_fee_add_index('fl_inbound_fee_detail', 'idx_inbound_fee_published',
'KEY `idx_inbound_fee_published` (`tenant_id`, `inbound_id`, `publication_status`, `source_fee_version`)');
DROP PROCEDURE IF EXISTS unified_publish_fee_add_index;
-- ============================================================================
-- 2026-08-06:三期分柜、预装、拣货、装柜和派送当前基线
-- ============================================================================
USE baiyue_firstleg;
CREATE TABLE IF NOT EXISTS fl_preload_plan (
id BIGINT UNSIGNED NOT NULL COMMENT '预装清单ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '关联入库单逻辑ID',
status VARCHAR(16) NOT NULL DEFAULT 'PENDING' COMMENT '预装状态:PENDING待确认、CONFIRMING确认中、CONFIRMED已确认',
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号,每次更新递增',
items_json JSON NOT NULL COMMENT '可编辑分柜明细JSON快照;人工确认后作为预装依据',
remark VARCHAR(1000) NULL COMMENT '预装备注',
created_by BIGINT UNSIGNED NOT NULL COMMENT '创建预装清单的操作人ID',
created_by_name VARCHAR(64) NULL COMMENT '创建操作人姓名快照',
updated_by BIGINT UNSIGNED NOT NULL COMMENT '最后修改操作人ID',
updated_by_name VARCHAR(64) NULL COMMENT '最后修改操作人姓名快照',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '预装清单创建时间',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '预装清单最后更新时间',
PRIMARY KEY (id),
UNIQUE KEY uk_preload_tenant_inbound (tenant_id, inbound_id),
KEY idx_preload_status (tenant_id, status, updated_at),
CONSTRAINT fk_preload_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id)
) ENGINE=InnoDB COMMENT='订单分柜预装清单';
CREATE TABLE IF NOT EXISTS fl_split_operation (
id BIGINT UNSIGNED NOT NULL COMMENT '幂等记录ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '操作目标入库单逻辑ID',
operator_id BIGINT UNSIGNED NOT NULL COMMENT '执行分柜或预装写操作的平台用户ID',
operation VARCHAR(40) NOT NULL COMMENT '分柜或预装写操作类型',
idempotency_key VARCHAR(128) NOT NULL COMMENT '客户端幂等键,同一操作重试复用',
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 '幂等操作声明创建时间',
PRIMARY KEY (id),
UNIQUE KEY uk_split_operation (tenant_id, inbound_id, operator_id, operation, idempotency_key),
KEY idx_split_operation_time (tenant_id, inbound_id, created_at),
CONSTRAINT fk_split_operation_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id)
) ENGINE=InnoDB COMMENT='分柜操作幂等与审计索引';
CREATE TABLE IF NOT EXISTS fl_inbound_split_detail (
id BIGINT UNSIGNED NOT NULL COMMENT '部分锁定明细ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
parent_inbound_id BIGINT UNSIGNED NOT NULL COMMENT '主入库单ID',
child_inbound_id BIGINT UNSIGNED NULL COMMENT '完成拆单后生成的二级子入库单ID',
inbound_detail_id BIGINT UNSIGNED NOT NULL COMMENT '原订单货物明细ID,不建立跨库外键',
source_package_count INT UNSIGNED NOT NULL COMMENT '拆分前该明细总箱数快照',
locked_package_count INT UNSIGNED NOT NULL COMMENT '本次装柜锁定箱数',
remaining_package_count INT UNSIGNED GENERATED ALWAYS AS (source_package_count - locked_package_count) STORED COMMENT '锁定后剩余箱数,由来源箱数减锁定箱数生成',
status VARCHAR(16) NOT NULL DEFAULT 'LOCKED' COMMENT '分柜明细状态:LOCKED已锁定、COMPLETED已完成拆分',
operation_id VARCHAR(128) NOT NULL COMMENT '部分锁定操作幂等号',
created_by BIGINT UNSIGNED NOT NULL COMMENT '创建部分锁定明细的操作人ID',
created_by_name VARCHAR(64) NULL COMMENT '创建操作人姓名快照',
completed_by BIGINT UNSIGNED NULL COMMENT '完成拆分的操作人ID',
completed_by_name VARCHAR(64) NULL COMMENT '完成操作人姓名快照',
completed_at DATETIME(3) NULL COMMENT '部分拆分完成时间',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '部分锁定明细创建时间',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '部分锁定明细最后更新时间',
version INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '乐观锁版本号,每次更新递增',
PRIMARY KEY (id),
UNIQUE KEY uk_inbound_split_parent_detail (tenant_id, parent_inbound_id, inbound_detail_id),
UNIQUE KEY uk_inbound_split_child_detail (tenant_id, child_inbound_id, inbound_detail_id),
UNIQUE KEY uk_inbound_split_operation (operation_id),
KEY idx_inbound_split_status (tenant_id, parent_inbound_id, status),
CONSTRAINT fk_inbound_split_detail_parent FOREIGN KEY (parent_inbound_id) REFERENCES fl_inbound_order (id),
CONSTRAINT fk_inbound_split_detail_child FOREIGN KEY (child_inbound_id) REFERENCES fl_inbound_order (id),
CONSTRAINT chk_inbound_split_partial CHECK (
source_package_count > 1 AND locked_package_count > 0
AND locked_package_count < source_package_count
),
CONSTRAINT chk_legacy_inbound_split_status CHECK (status IN ('LOCKED', 'COMPLETED')),
CONSTRAINT chk_inbound_split_completion CHECK (
(status = 'LOCKED' AND child_inbound_id IS NULL AND completed_at IS NULL)
OR (status = 'COMPLETED' AND child_inbound_id IS NOT NULL AND completed_at IS NOT NULL)
)
) ENGINE=InnoDB COMMENT='按箱数部分锁定及剩余箱数生成二级子入库单的不可变契约';
DROP TRIGGER IF EXISTS trg_inbound_split_detail_before_insert;
DROP TRIGGER IF EXISTS trg_inbound_split_detail_before_update;
DROP TRIGGER IF EXISTS trg_inbound_split_detail_before_delete;
DELIMITER $$
CREATE TRIGGER trg_inbound_split_detail_before_insert
BEFORE INSERT ON fl_inbound_split_detail
FOR EACH ROW
BEGIN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '旧部分锁定台账已退役,请使用主子分片批次';
END$$
CREATE TRIGGER trg_inbound_split_detail_before_update
BEFORE UPDATE ON fl_inbound_split_detail
FOR EACH ROW
BEGIN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '旧部分锁定台账只读,不允许更新';
END$$
CREATE TRIGGER trg_inbound_split_detail_before_delete
BEFORE DELETE ON fl_inbound_split_detail
FOR EACH ROW
BEGIN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '部分锁定与拆单明细不可删除';
END$$
DELIMITER ;
CREATE TABLE IF NOT EXISTS fl_loading_evidence (
id BIGINT UNSIGNED NOT NULL COMMENT '装柜证据记录ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '关联入库单逻辑ID',
evidence_type VARCHAR(32) NOT NULL COMMENT 'PHOTO_STEP_1空仓/PHOTO_STEP_2半仓/PHOTO_STEP_3满仓/PHOTO_STEP_4锁仓/SEAL带锁码照',
file_id BIGINT UNSIGNED NOT NULL COMMENT 'File 服务逻辑文件ID',
file_name VARCHAR(255) NULL COMMENT '上传时文件名称快照',
seal_no VARCHAR(64) NULL COMMENT '锁码文本;带锁码照必填,客户不可见',
remark VARCHAR(1000) NULL COMMENT '证据补充说明',
status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE' COMMENT '证据状态:ACTIVE有效、VOID已作废;错误照片保留记录不硬删除',
void_reason VARCHAR(500) NULL COMMENT '照片作废原因',
voided_by BIGINT UNSIGNED NULL COMMENT '执行作废的平台操作人ID',
voided_by_name VARCHAR(64) NULL COMMENT '执行作废的平台操作人姓名快照',
voided_at DATETIME(3) NULL COMMENT '照片作废时间',
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号,每次更新递增',
created_by BIGINT UNSIGNED NOT NULL COMMENT '上传证据的操作人ID',
created_by_name VARCHAR(64) NULL COMMENT '上传证据的操作人姓名快照',
updated_by BIGINT UNSIGNED NOT NULL COMMENT '最后修改操作人ID',
updated_by_name VARCHAR(64) NULL COMMENT '最后修改操作人姓名快照',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '证据记录创建时间',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '证据记录最后更新时间',
PRIMARY KEY (id),
KEY idx_loading_evidence_type (tenant_id, inbound_id, evidence_type, status),
KEY idx_loading_evidence_seal (tenant_id, seal_no, status),
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='订单装柜空仓/半仓/满仓/锁仓/带锁码照证据';
CREATE TABLE IF NOT EXISTS fl_loading_operation (
id BIGINT UNSIGNED NOT NULL COMMENT '装柜写操作记录ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '操作目标入库单逻辑ID',
operator_id BIGINT UNSIGNED NOT NULL COMMENT '执行写操作的平台用户ID',
operation VARCHAR(48) 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成功、FAILED失败',
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_loading_operation (tenant_id, inbound_id, operator_id, operation, idempotency_key),
KEY idx_loading_operation_time (tenant_id, inbound_id, created_at),
CONSTRAINT fk_loading_operation_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id)
) ENGINE=InnoDB COMMENT='装柜与派送写操作幂等记录';
CREATE TABLE IF NOT EXISTS fl_inbound_loading_pick_task (
id BIGINT UNSIGNED NOT NULL COMMENT '入库单级PDA拣货任务ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '关联入库单逻辑ID',
status VARCHAR(24) NOT NULL DEFAULT 'PENDING' COMMENT '任务状态:PENDING待拣货、IN_PROGRESS拣货中、COMPLETED已完成',
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号,每次更新递增',
expected_count INT UNSIGNED NULL COMMENT '计划应拣箱数',
picked_count INT UNSIGNED NULL COMMENT '实际已拣箱数',
remark VARCHAR(1000) NULL COMMENT '拣货结果或异常补充说明',
updated_by BIGINT UNSIGNED NOT NULL COMMENT '最后操作人ID',
updated_by_name VARCHAR(64) NULL COMMENT '最后操作人姓名快照',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'PDA拣货任务创建时间',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT 'PDA拣货任务最后更新时间',
PRIMARY KEY (id),
UNIQUE KEY uk_loading_pick_tenant_inbound (tenant_id, inbound_id),
KEY idx_loading_pick_status (tenant_id, status, updated_at),
CONSTRAINT fk_loading_pick_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id)
) ENGINE=InnoDB COMMENT='PDA拣货任务及结果';
USE baiyue_iam;
DROP PROCEDURE IF EXISTS unified_relocate_inbound_loading_permissions;
DELIMITER $$
CREATE PROCEDURE unified_relocate_inbound_loading_permissions()
BEGIN
IF EXISTS (
SELECT 1 FROM iam_permission
WHERE id BETWEEN 5102121 AND 5102129
AND permission_code <> CASE id
WHEN 5102121 THEN 'firstleg:inbound:split'
WHEN 5102122 THEN 'firstleg:inbound:preload'
WHEN 5102123 THEN 'firstleg:inbound:preload-view'
WHEN 5102124 THEN 'platform:firstleg:inbound:loading:edit'
WHEN 5102125 THEN 'platform:firstleg:inbound:loading:view'
WHEN 5102126 THEN 'platform:firstleg:inbound:loading:complete'
WHEN 5102127 THEN 'platform:firstleg:inbound:delivery:complete'
WHEN 5102128 THEN 'platform:firstleg:inbound:picking:edit'
WHEN 5102129 THEN 'platform:firstleg:inbound:picking:view'
END
) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '三期权限迁移失败:5102121-5102129 已被其他权限占用';
END IF;
DROP TEMPORARY TABLE IF EXISTS tmp_phase3_inbound_role_grant;
CREATE TEMPORARY TABLE tmp_phase3_inbound_role_grant (
role_id BIGINT UNSIGNED NOT NULL,
tenant_id BIGINT UNSIGNED NOT NULL,
permission_code VARCHAR(128) NOT NULL,
PRIMARY KEY (role_id, tenant_id, permission_code)
) ENGINE=MEMORY;
INSERT IGNORE INTO tmp_phase3_inbound_role_grant (role_id, tenant_id, permission_code)
SELECT role_permission.role_id, role_permission.tenant_id, permission.permission_code
FROM iam_role_permission role_permission
JOIN iam_permission permission ON permission.id = role_permission.permission_id
WHERE permission.permission_code IN (
'firstleg:inbound:split', 'firstleg:inbound:preload', 'firstleg:inbound:preload-view',
'platform:firstleg:inbound:loading:edit', 'platform:firstleg:inbound:loading:view',
'platform:firstleg:inbound:loading:complete', 'platform:firstleg:inbound:delivery:complete',
'platform:firstleg:inbound:picking:edit', 'platform:firstleg:inbound:picking:view'
);
DELETE role_permission FROM iam_role_permission role_permission
JOIN iam_permission permission ON permission.id = role_permission.permission_id
WHERE permission.permission_code IN (
'firstleg:inbound:split', 'firstleg:inbound:preload', 'firstleg:inbound:preload-view',
'platform:firstleg:inbound:loading:edit', 'platform:firstleg:inbound:loading:view',
'platform:firstleg:inbound:loading:complete', 'platform:firstleg:inbound:delivery:complete',
'platform:firstleg:inbound:picking:edit', 'platform:firstleg:inbound:picking:view'
);
DELETE FROM iam_permission WHERE permission_code IN (
'firstleg:inbound:split', 'firstleg:inbound:preload', 'firstleg:inbound:preload-view',
'platform:firstleg:inbound:loading:edit', 'platform:firstleg:inbound:loading:view',
'platform:firstleg:inbound:loading:complete', 'platform:firstleg:inbound:delivery:complete',
'platform:firstleg:inbound:picking:edit', 'platform:firstleg:inbound:picking:view'
);
END$$
DELIMITER ;
CALL unified_relocate_inbound_loading_permissions();
DROP PROCEDURE unified_relocate_inbound_loading_permissions;
-- 如果旧入库权限占用了 5102091-5102099,上面的语义迁移已释放这些 ID;
-- 此处重新写入智能装柜权限,修复此前 ON DUPLICATE 只改元数据、不改 code 的污染。
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
(5102091, 0, 'PLATFORM', 'BUTTON', 5102090, 'platform:firstleg:container:view', '查看货柜', NULL, 'GET', 10, 'ACTIVE', 1, 0),
(5102092, 0, 'PLATFORM', 'BUTTON', 5102090, 'platform:firstleg:container:create', '建立货柜', NULL, 'POST', 20, 'ACTIVE', 1, 0),
(5102093, 0, 'PLATFORM', 'BUTTON', 5102090, 'platform:firstleg:container:edit', '编辑货柜', NULL, 'PUT', 30, 'ACTIVE', 1, 0),
(5102094, 0, 'PLATFORM', 'MENU', 5102000, 'platform:firstleg:smart-loading:list', '智能装柜', '/first-leg/smart-loading', 'GET', 50, 'ACTIVE', 1, 0),
(5102096, 0, 'PLATFORM', 'BUTTON', 5102094, 'platform:firstleg:smart-loading:create', '创建智能装柜计划', NULL, 'POST', 20, 'ACTIVE', 1, 0),
(5102097, 0, 'PLATFORM', 'BUTTON', 5102094, 'platform:firstleg:smart-loading:assign', '分配装柜明细', NULL, 'POST', 30, 'ACTIVE', 1, 0),
(5102098, 0, 'PLATFORM', 'BUTTON', 5102094, 'platform:firstleg:smart-loading:delete', '删除装柜明细', NULL, 'DELETE', 40, 'ACTIVE', 1, 0),
(5102099, 0, 'PLATFORM', 'BUTTON', 5102094, 'platform:firstleg:smart-loading:push', '推送预装', NULL, 'POST', 50, 'ACTIVE', 1, 0)
ON DUPLICATE KEY UPDATE
permission_type = VALUES(permission_type), parent_id = VALUES(parent_id),
permission_name = VALUES(permission_name), route_path = VALUES(route_path),
http_method = VALUES(http_method), sort_no = VALUES(sort_no), status = 'ACTIVE',
built_in = 1, deleted = 0;
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
(5102121, 0, 'PLATFORM', 'BUTTON', 5102030,
'firstleg:inbound:split', '智能分柜', NULL, NULL, 140, 'ACTIVE', 1, 0),
(5102122, 0, 'PLATFORM', 'BUTTON', 5102030,
'firstleg:inbound:preload', '编辑并确认预装清单', NULL, NULL, 150, 'ACTIVE', 1, 0),
(5102123, 0, 'PLATFORM', 'BUTTON', 5102030,
'firstleg:inbound:preload-view', '查看预装清单', NULL, NULL, 160, 'ACTIVE', 1, 0),
(5102124, 0, 'PLATFORM', 'BUTTON', 5102030,
'platform:firstleg:inbound:loading:edit', '维护装柜证据', NULL, NULL, 170, 'ACTIVE', 1, 0),
(5102125, 0, 'PLATFORM', 'BUTTON', 5102030,
'platform:firstleg:inbound:loading:view', '查看装柜证据', NULL, NULL, 180, 'ACTIVE', 1, 0),
(5102126, 0, 'PLATFORM', 'BUTTON', 5102030,
'platform:firstleg:inbound:loading:complete', '完成装柜', NULL, NULL, 190, 'ACTIVE', 1, 0),
(5102127, 0, 'PLATFORM', 'BUTTON', 5102030,
'platform:firstleg:inbound:delivery:complete', '完成派送', NULL, NULL, 200, 'ACTIVE', 1, 0),
(5102128, 0, 'PLATFORM', 'BUTTON', 5102030,
'platform:firstleg:inbound:picking:edit', '执行拣货', NULL, NULL, 210, 'ACTIVE', 1, 0),
(5102129, 0, 'PLATFORM', 'BUTTON', 5102030,
'platform:firstleg:inbound:picking:view', '查看拣货任务', NULL, NULL, 220, 'ACTIVE', 1, 0)
ON DUPLICATE KEY UPDATE
permission_type = VALUES(permission_type), parent_id = VALUES(parent_id),
permission_name = VALUES(permission_name), route_path = VALUES(route_path),
http_method = VALUES(http_method), sort_no = VALUES(sort_no),
status = 'ACTIVE', built_in = 1, deleted = 0;
INSERT INTO iam_role_permission (role_id, permission_id, tenant_id)
SELECT role.id, permission.id, role.tenant_id
FROM iam_role role
JOIN iam_permission permission ON permission.tenant_id = 0 AND permission.app_code = 'PLATFORM'
AND permission.permission_code IN (
'firstleg:inbound:split',
'firstleg:inbound:preload',
'firstleg:inbound:preload-view',
'platform:firstleg:inbound:loading:edit',
'platform:firstleg:inbound:loading:view',
'platform:firstleg:inbound:loading:complete',
'platform:firstleg:inbound:delivery:complete',
'platform:firstleg:inbound:picking:edit',
'platform:firstleg:inbound:picking:view'
)
WHERE role.tenant_id = 0 AND role.role_code IN ('PLATFORM_ADMIN', 'ORDER_OPERATOR')
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
INSERT IGNORE INTO iam_role_permission (role_id, permission_id, tenant_id)
SELECT grant_row.role_id, permission.id, grant_row.tenant_id
FROM tmp_phase3_inbound_role_grant grant_row
JOIN iam_permission permission ON permission.permission_code = grant_row.permission_code
AND permission.tenant_id = 0 AND permission.app_code = 'PLATFORM';
DROP TEMPORARY TABLE IF EXISTS tmp_phase3_inbound_role_grant;
INSERT IGNORE INTO iam_role_permission (role_id, permission_id, tenant_id)
SELECT role.id, permission.id, role.tenant_id
FROM iam_role role
JOIN iam_permission permission ON permission.tenant_id = 0
AND permission.permission_code IN (
'platform:firstleg:container:view', 'platform:firstleg:container:create',
'platform:firstleg:container:edit', 'platform:firstleg:smart-loading:list',
'platform:firstleg:smart-loading:create', 'platform:firstleg:smart-loading:assign',
'platform:firstleg:smart-loading:delete', 'platform:firstleg:smart-loading:push'
)
WHERE role.tenant_id = 0 AND role.role_code IN ('PLATFORM_ADMIN', 'ORDER_OPERATOR');
-- 污染存量库必须先完成旧入库权限搬迁和两类授权恢复,再校验智能装柜权限。
USE baiyue_firstleg;
CALL assert_phase3_smart_loading_schema();
DROP PROCEDURE IF EXISTS assert_phase3_smart_loading_schema;
-- ============================================================================
-- 兼容已经执行过旧单行配置模型的开发/测试库。仅精确识别旧结构时丢弃旧值并重建;
-- 已是四箱规结构时不做 DDL,避免破坏 Admin 已编辑的数据。
DROP PROCEDURE IF EXISTS unified_upgrade_legacy_customer_dimension_config;
DELIMITER $$
CREATE PROCEDURE unified_upgrade_legacy_customer_dimension_config()
BEGIN
IF (SELECT COUNT(*) FROM information_schema.tables
WHERE table_schema = 'baiyue_firstleg'
AND table_name = 'fl_customer_dimension_config') = 1
AND (SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = 'baiyue_firstleg'
AND table_name = 'fl_customer_dimension_config'
AND column_name = 'box_type') = 0 THEN
IF (SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = 'baiyue_firstleg'
AND table_name = 'fl_customer_dimension_config'
AND column_name IN ('id', 'tenant_id', 'edge_addition_cm', 'remark', 'initialization_key',
'created_by', 'created_at', 'updated_by', 'updated_at', 'deleted', 'version')) <> 11
OR (SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = 'baiyue_firstleg'
AND table_name = 'fl_customer_dimension_config') <> 11
OR (SELECT COUNT(*) FROM information_schema.statistics
WHERE table_schema = 'baiyue_firstleg'
AND table_name = 'fl_customer_dimension_config'
AND index_name = 'uk_customer_dimension_config_tenant'
AND non_unique = 0 AND seq_in_index = 1 AND column_name = 'tenant_id') <> 1 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:客户尺寸配置表不是可识别的旧单行结构';
END IF;
DROP TABLE baiyue_firstleg.fl_customer_dimension_config;
END IF;
END$$
DELIMITER ;
CALL unified_upgrade_legacy_customer_dimension_config();
DROP PROCEDURE IF EXISTS unified_upgrade_legacy_customer_dimension_config;
CREATE TABLE IF NOT EXISTS baiyue_firstleg.fl_customer_dimension_config (
id BIGINT UNSIGNED NOT NULL COMMENT '配置ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户隔离ID',
box_type VARCHAR(24) NOT NULL COMMENT '固定箱规类型:SMALL/MEDIUM/LARGE/EXTRA_LARGE',
inner_length_cm DECIMAL(9,3) NOT NULL COMMENT '箱规内长(厘米)',
inner_width_cm DECIMAL(9,3) NOT NULL COMMENT '箱规内宽(厘米)',
inner_height_cm DECIMAL(9,3) NOT NULL COMMENT '箱规内高(厘米)',
edge_addition_cm DECIMAL(6,3) NOT NULL DEFAULT 2.000 COMMENT '每个尺寸方向的单侧加宽厘米数',
remark VARCHAR(500) NULL COMMENT '备注',
initialization_key VARCHAR(128) NOT NULL COMMENT '创建流程幂等键及补偿归属',
created_by BIGINT UNSIGNED NOT NULL COMMENT '创建人ID',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_by BIGINT UNSIGNED NOT NULL COMMENT '最后修改人ID',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除',
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id),
UNIQUE KEY uk_customer_dimension_config_tenant_box (tenant_id, box_type),
KEY idx_customer_dimension_config_initialization (tenant_id, initialization_key),
CONSTRAINT chk_customer_dimension_box_type
CHECK (box_type IN ('SMALL', 'MEDIUM', 'LARGE', 'EXTRA_LARGE')),
CONSTRAINT chk_customer_dimension_inner_size
CHECK (inner_length_cm >= 0.001 AND inner_width_cm >= 0.001 AND inner_height_cm >= 0.001),
CONSTRAINT chk_customer_dimension_edge_addition
CHECK (edge_addition_cm >= 0.001 AND edge_addition_cm <= 100.000)
) ENGINE=InnoDB COMMENT='客户固定四箱规尺寸配置';
-- 新结构首次部署或旧结构受控重建后,为所有现有企业客户补齐缺失箱规。
-- 复跑命中 (tenant_id, box_type) 唯一键时只做空更新,不覆盖尺寸、备注、版本或审计字段。
INSERT INTO baiyue_firstleg.fl_customer_dimension_config (
id, tenant_id, box_type, inner_length_cm, inner_width_cm, inner_height_cm,
edge_addition_cm, remark, initialization_key, created_by, created_at,
updated_by, updated_at, deleted, version
)
SELECT UUID_SHORT(), tenant.tenant_id, defaults.box_type,
defaults.inner_length_cm, defaults.inner_width_cm, defaults.inner_height_cm,
2.000, defaults.remark,
CONCAT('unified-four-box-default-v1:', tenant.tenant_id),
COALESCE(tenant.created_by, 0), CURRENT_TIMESTAMP(3),
COALESCE(tenant.created_by, 0), CURRENT_TIMESTAMP(3), 0, 0
FROM (
SELECT id AS tenant_id, created_by
FROM baiyue_iam.iam_tenant
WHERE tenant_type = 'MERCHANT' AND deleted = 0 AND id > 0
) tenant
CROSS JOIN (
SELECT 'SMALL' AS box_type, 30.000 AS inner_length_cm, 20.000 AS inner_width_cm,
20.000 AS inner_height_cm, '标准小箱' AS remark
UNION ALL
SELECT 'MEDIUM', 40.000, 30.000, 30.000, '标准中箱'
UNION ALL
SELECT 'LARGE', 50.000, 40.000, 40.000, '标准大箱'
UNION ALL
SELECT 'EXTRA_LARGE', 60.000, 50.000, 50.000, '特大箱'
) defaults
WHERE TRUE
ON DUPLICATE KEY UPDATE id = id;
-- ============================================================================
-- 来源:order/src/main/resources/db/phase3-order-parent-child-schema.sql
SET NAMES utf8mb4;
USE baiyue_order;
DROP PROCEDURE IF EXISTS phase3_order_add_column;
DELIMITER $$
CREATE PROCEDURE phase3_order_add_column(IN p_table VARCHAR(64), IN p_column VARCHAR(64), IN p_definition TEXT)
BEGIN
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE()
AND table_name=p_table AND column_name=p_column) THEN
SET @ddl = CONCAT('ALTER TABLE `', p_table, '` ADD COLUMN `', p_column, '` ', p_definition);
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
END IF;
END$$
DELIMITER ;
CALL phase3_order_add_column('ord_order','order_role',"VARCHAR(12) NOT NULL DEFAULT 'MASTER' COMMENT '订单层级:MASTER主订单、CHILD子订单'");
CALL phase3_order_add_column('ord_order','parent_order_id','BIGINT UNSIGNED NULL COMMENT ''直属父订单ID;禁止子订单嵌套''');
CALL phase3_order_add_column('ord_order','root_order_id','BIGINT UNSIGNED NULL COMMENT ''根主订单ID''');
CALL phase3_order_add_column('ord_order','split_sequence','INT UNSIGNED NULL COMMENT ''同一父订单下的同级顺序''');
DROP PROCEDURE IF EXISTS phase3_order_add_index;
DELIMITER $$
CREATE PROCEDURE phase3_order_add_index(IN p_table VARCHAR(64), IN p_name VARCHAR(64), IN p_definition TEXT)
BEGIN
IF NOT EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE()
AND table_name=p_table AND index_name=p_name) THEN
SET @ddl = CONCAT('ALTER TABLE `',p_table,'` ADD ',p_definition);
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
END IF;
END$$
DELIMITER ;
CALL phase3_order_add_index('ord_order','uk_order_parent_sequence',
'UNIQUE KEY `uk_order_parent_sequence` (`tenant_id`,`parent_order_id`,`split_sequence`)');
CALL phase3_order_add_index('ord_order','idx_order_root_role',
'KEY `idx_order_root_role` (`tenant_id`,`root_order_id`,`order_role`,`created_at`,`id`)');
DROP PROCEDURE IF EXISTS phase3_order_parent_fk;
DELIMITER $$
CREATE PROCEDURE phase3_order_parent_fk()
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.table_constraints WHERE constraint_schema=DATABASE()
AND table_name='ord_order' AND constraint_name='fk_order_parent') THEN
ALTER TABLE ord_order DROP FOREIGN KEY fk_order_parent;
END IF;
ALTER TABLE ord_order ADD CONSTRAINT fk_order_parent FOREIGN KEY (parent_order_id) REFERENCES ord_order(id);
END$$
DELIMITER ;
CALL phase3_order_parent_fk();
DROP PROCEDURE IF EXISTS phase3_order_parent_fk;
CREATE TABLE IF NOT EXISTS ord_order_fulfillment_split (
id BIGINT UNSIGNED NOT NULL COMMENT '订单履约分片台账ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
parent_order_id BIGINT UNSIGNED NOT NULL COMMENT '直属父订单ID;主订单自身作为拆分根',
source_order_id BIGINT UNSIGNED NOT NULL COMMENT '实际被本次分片消费的来源订单ID',
child_order_id BIGINT UNSIGNED NOT NULL COMMENT '本次分片生成或承接的子订单ID',
split_role VARCHAR(12) NOT NULL COMMENT '分片角色:LOADED已装柜部分、REMAINDER剩余部分',
loading_plan_id BIGINT UNSIGNED NULL COMMENT 'Firstleg装柜计划逻辑ID',
container_booking_id BIGINT UNSIGNED NULL COMMENT 'Firstleg柜次逻辑ID',
source_package_count INT UNSIGNED NOT NULL COMMENT '来源订单本次分片前的总包装件数',
loaded_package_count INT UNSIGNED NOT NULL COMMENT '本次已装柜分片的包装件数',
remaining_package_count INT UNSIGNED NOT NULL COMMENT '本次分片后剩余包装件数',
source_detail_json JSON NULL COMMENT '来源货物明细及数量快照;来源订单变化不回写',
operation_id VARCHAR(64) NOT NULL COMMENT '履约分片业务操作ID',
idempotency_key VARCHAR(128) NOT NULL COMMENT '客户端幂等键',
request_hash CHAR(64) NOT NULL COMMENT '规范化请求内容SHA-256摘要',
expected_parent_version INT NOT 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 '台账创建时间',
version INT NOT NULL DEFAULT 0 COMMENT '台账乐观锁版本号',
PRIMARY KEY(id),
UNIQUE KEY uk_order_split_operation (tenant_id,operation_id,split_role),
UNIQUE KEY uk_order_split_idempotency (tenant_id,parent_order_id,idempotency_key,split_role),
UNIQUE KEY uk_order_split_child_role (tenant_id,child_order_id,split_role),
KEY idx_order_split_parent (tenant_id,parent_order_id,created_at,id),
KEY idx_order_split_source (tenant_id,source_order_id,split_role),
KEY idx_order_split_container (tenant_id,container_booking_id,created_at,id),
CONSTRAINT fk_order_split_parent FOREIGN KEY(parent_order_id) REFERENCES ord_order(id),
CONSTRAINT fk_order_split_child FOREIGN KEY(child_order_id) REFERENCES ord_order(id),
CONSTRAINT chk_order_split_quantity CHECK (source_package_count > 0 AND loaded_package_count > 0
AND loaded_package_count + remaining_package_count = source_package_count)
) ENGINE=InnoDB COMMENT='父订单→子订单→柜次数量台账';
DROP TRIGGER IF EXISTS trg_ord_order_parent_insert;
DROP TRIGGER IF EXISTS trg_ord_order_parent_update;
DROP TRIGGER IF EXISTS trg_order_fulfillment_split_insert;
DROP TRIGGER IF EXISTS trg_order_fulfillment_split_update;
DELIMITER $$
CREATE TRIGGER trg_ord_order_parent_insert BEFORE INSERT ON ord_order FOR EACH ROW
BEGIN
IF NEW.order_role='MASTER' THEN
SET NEW.parent_order_id=NULL;
IF NEW.root_order_id IS NULL THEN SET NEW.root_order_id=NEW.id; END IF;
ELSEIF NEW.order_role='CHILD' THEN
IF NEW.parent_order_id IS NULL OR NEW.root_order_id IS NULL OR NEW.split_sequence IS NULL THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='子订单必须有父订单、根订单和顺序号';
END IF;
IF NEW.root_order_id<>NEW.parent_order_id OR EXISTS(
SELECT 1 FROM ord_order p WHERE p.id=NEW.parent_order_id AND p.order_role<>'MASTER') THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='子订单只允许直接挂在主订单下';
END IF;
IF NOT EXISTS(SELECT 1 FROM ord_order p WHERE p.id=NEW.parent_order_id AND p.tenant_id=NEW.tenant_id AND p.deleted=0) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='父订单不存在或客户归属不一致';
END IF;
ELSE
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='订单层级非法';
END IF;
END$$
CREATE TRIGGER trg_ord_order_parent_update BEFORE UPDATE ON ord_order FOR EACH ROW
BEGIN
IF NEW.order_role<>OLD.order_role OR NOT (NEW.parent_order_id<=>OLD.parent_order_id)
OR NOT (NEW.root_order_id<=>OLD.root_order_id) OR NOT (NEW.split_sequence<=>OLD.split_sequence) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='订单父子关系不可修改';
END IF;
END$$
DELIMITER ;
CALL phase3_order_add_column('ord_order_fulfillment_split','split_role',"VARCHAR(12) NOT NULL DEFAULT 'LOADED' COMMENT '分片角色:LOADED已装柜部分、REMAINDER剩余部分'");
CALL phase3_order_add_column('ord_order_fulfillment_split','source_order_id','BIGINT UNSIGNED NULL COMMENT ''实际被消费的来源订单ID''');
CALL phase3_order_add_column('ord_order_fulfillment_split','request_hash',"CHAR(64) NULL COMMENT '请求内容SHA-256;存量记录允许为空'");
-- 旧版台账没有来源字段;当时只允许从根主订单拆分,因此可无歧义回填为父订单。
UPDATE ord_order_fulfillment_split
SET source_order_id = parent_order_id
WHERE source_order_id IS NULL;
-- 旧记录没有请求哈希。生成稳定哨兵哈希可保持非空约束,并让旧幂等键重放明确冲突。
UPDATE ord_order_fulfillment_split
SET request_hash = SHA2(CONCAT('LEGACY:', tenant_id, ':', id), 256)
WHERE request_hash IS NULL OR request_hash = '';
ALTER TABLE ord_order_fulfillment_split
MODIFY source_order_id BIGINT UNSIGNED NOT NULL COMMENT '实际被消费的来源订单ID',
MODIFY request_hash CHAR(64) NOT NULL COMMENT '请求内容SHA-256';
CALL phase3_order_add_index('ord_order_fulfillment_split','idx_order_split_source',
'KEY `idx_order_split_source` (`tenant_id`,`source_order_id`,`split_role`)');
DROP PROCEDURE IF EXISTS phase3_order_drop_split_index;
DELIMITER $$
CREATE PROCEDURE phase3_order_drop_split_index()
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE()
AND table_name='ord_order_fulfillment_split' AND index_name='uk_order_split_operation') THEN
ALTER TABLE ord_order_fulfillment_split DROP INDEX uk_order_split_operation;
END IF;
IF EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE()
AND table_name='ord_order_fulfillment_split' AND index_name='uk_order_split_idempotency') THEN
ALTER TABLE ord_order_fulfillment_split DROP INDEX uk_order_split_idempotency;
END IF;
IF EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE()
AND table_name='ord_order_fulfillment_split' AND index_name='uk_order_split_child') THEN
ALTER TABLE ord_order_fulfillment_split DROP INDEX uk_order_split_child;
END IF;
END$$
DELIMITER ;
CALL phase3_order_drop_split_index();
DROP PROCEDURE IF EXISTS phase3_order_drop_split_index;
CALL phase3_order_add_index('ord_order_fulfillment_split','uk_order_split_operation',
'UNIQUE KEY `uk_order_split_operation` (`tenant_id`,`operation_id`,`split_role`)');
CALL phase3_order_add_index('ord_order_fulfillment_split','uk_order_split_idempotency',
'UNIQUE KEY `uk_order_split_idempotency` (`tenant_id`,`parent_order_id`,`idempotency_key`,`split_role`)');
CALL phase3_order_add_index('ord_order_fulfillment_split','uk_order_split_child_role',
'UNIQUE KEY `uk_order_split_child_role` (`tenant_id`,`child_order_id`,`split_role`)');
DELIMITER $$
CREATE TRIGGER trg_order_fulfillment_split_insert
BEFORE INSERT ON ord_order_fulfillment_split FOR EACH ROW
BEGIN
IF NEW.split_role NOT IN ('LOADED','REMAINDER') THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='订单履约分片角色非法';
END IF;
IF NEW.split_role='LOADED' AND NEW.child_order_id<>NEW.source_order_id THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='实装台账必须由来源订单自身承接';
END IF;
IF NEW.split_role='REMAINDER'
AND (NEW.child_order_id=NEW.source_order_id OR NEW.remaining_package_count=0) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='余量台账必须指向有剩余数量的新子订单';
END IF;
END$$
CREATE TRIGGER trg_order_fulfillment_split_update
BEFORE UPDATE ON ord_order_fulfillment_split FOR EACH ROW
BEGIN
IF NOT (NEW.tenant_id<=>OLD.tenant_id)
OR NOT (NEW.parent_order_id<=>OLD.parent_order_id)
OR NOT (NEW.source_order_id<=>OLD.source_order_id)
OR NOT (NEW.child_order_id<=>OLD.child_order_id)
OR NOT (NEW.split_role<=>OLD.split_role) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='订单履约分片来源、子片与角色不可修改';
END IF;
END$$
DELIMITER ;
DROP PROCEDURE IF EXISTS phase3_order_add_column;
DROP PROCEDURE IF EXISTS phase3_order_add_index;
DROP PROCEDURE IF EXISTS phase3_order_assert;
DELIMITER $$
CREATE PROCEDURE phase3_order_assert()
BEGIN
IF (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema=DATABASE()
AND table_name='ord_order' AND column_name IN ('order_role','parent_order_id','root_order_id','split_sequence'))<>4 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='订单父子字段未完整创建';
END IF;
IF (SELECT COUNT(*) FROM information_schema.tables WHERE table_schema=DATABASE()
AND table_name='ord_order_fulfillment_split')<>1 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='订单拆分台账未创建';
END IF;
IF (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema=DATABASE()
AND table_name='ord_order_fulfillment_split'
AND column_name IN ('source_order_id','request_hash') AND is_nullable='NO')<>2 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='订单拆分来源和请求哈希必须为非空';
END IF;
IF (SELECT COUNT(DISTINCT index_name) FROM information_schema.statistics
WHERE table_schema=DATABASE() AND table_name='ord_order_fulfillment_split'
AND index_name IN ('idx_order_split_source','uk_order_split_operation',
'uk_order_split_idempotency','uk_order_split_child_role'))<>4 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='订单拆分来源或幂等索引未完整创建';
END IF;
IF EXISTS (SELECT 1 FROM information_schema.statistics
WHERE table_schema=DATABASE() AND table_name='ord_order_fulfillment_split'
AND index_name='uk_order_split_child') THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='旧订单拆分子单唯一索引仍阻止跨角色分片';
END IF;
IF (SELECT COUNT(*) FROM information_schema.triggers
WHERE trigger_schema=DATABASE()
AND trigger_name IN ('trg_order_fulfillment_split_insert',
'trg_order_fulfillment_split_update'))<>2 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='订单履约分片写入保护触发器未完整创建';
END IF;
END$$
DELIMITER ;
CALL phase3_order_assert();
DROP PROCEDURE IF EXISTS phase3_order_assert;
-- 来源:firstleg/src/main/resources/db/phase3-inbound-parent-child-schema.sql
SET NAMES utf8mb4;
USE baiyue_firstleg;
/*
* 三期主子入仓单结构迁移。
* 仅建立结构和数据库保护,不自动拆分或回填存量业务数据。
* 新写入采用“来源单承接实装、仅创建余量子单”;LOADED 子单和 loaded_child_inbound_id
* 仅为历史双子单读取保留,不删除、不清洗存量记录。
* tenant_id 继续表示客户隔离键;数量统一使用 package_count。
*/
DROP PROCEDURE IF EXISTS phase3_inbound_add_column;
DELIMITER $$
CREATE PROCEDURE phase3_inbound_add_column(
IN p_table_name VARCHAR(64),
IN p_column_name VARCHAR(64),
IN p_definition TEXT
)
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema = DATABASE()
AND table_name = p_table_name
AND column_name = p_column_name
) THEN
SET @ddl = CONCAT('ALTER TABLE `', p_table_name, '` ADD COLUMN `',
p_column_name, '` ', p_definition);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END IF;
END$$
DELIMITER ;
CALL phase3_inbound_add_column(
'fl_inbound_order', 'inbound_role',
"VARCHAR(16) NOT NULL DEFAULT 'MASTER' COMMENT '入仓单层级:MASTER主单、CHILD子单' AFTER inbound_code");
CALL phase3_inbound_add_column(
'fl_inbound_order', 'parent_inbound_id',
"BIGINT UNSIGNED NULL COMMENT '直属主入仓单ID;主单为空' AFTER inbound_role");
CALL phase3_inbound_add_column(
'fl_inbound_order', 'root_inbound_id',
"BIGINT UNSIGNED NULL COMMENT '根主入仓单ID;存量主单可为空,按自身ID解释' AFTER parent_inbound_id");
CALL phase3_inbound_add_column(
'fl_inbound_order', 'split_sequence',
"INT UNSIGNED NULL COMMENT '同一主单下的子单顺序号' AFTER root_inbound_id");
CALL phase3_inbound_add_column(
'fl_inbound_order', 'split_status',
"VARCHAR(24) NOT NULL DEFAULT 'NOT_SPLIT' COMMENT '拆分状态:NOT_SPLIT、PARTIALLY_SPLIT、FULLY_SPLIT' AFTER split_sequence");
CALL phase3_inbound_add_column(
'fl_inbound_order', 'package_count',
"INT UNSIGNED NULL COMMENT '入仓单箱数;三期唯一数量口径' AFTER total_package_count");
CALL phase3_inbound_add_column(
'fl_inbound_order', 'split_purpose',
"VARCHAR(16) NOT NULL DEFAULT 'MASTER' COMMENT '履约分片用途:MASTER、LOADED、REMAINDER' AFTER package_count");
CALL phase3_inbound_add_column(
'fl_inbound_order', 'master_order_id',
"BIGINT UNSIGNED GENERATED ALWAYS AS (CASE WHEN inbound_role = 'MASTER' AND deleted = 0 THEN order_id ELSE NULL END) STORED COMMENT '仅用于约束有效主单订单唯一性'");
-- 旧实现只生成未装余量子单,可无歧义映射为 REMAINDER。
UPDATE fl_inbound_order
SET split_purpose = 'REMAINDER'
WHERE inbound_role = 'CHILD'
AND split_purpose = 'MASTER';
CALL phase3_inbound_add_column(
'fl_inbound_order', 'active_remainder_root_id',
"BIGINT UNSIGNED GENERATED ALWAYS AS (CASE WHEN inbound_role = 'CHILD' AND split_purpose = 'REMAINDER' AND split_status = 'NOT_SPLIT' AND deleted = 0 THEN root_inbound_id ELSE NULL END) STORED COMMENT '有效余量唯一约束键;历史已完成分片为空'");
DROP PROCEDURE IF EXISTS phase3_inbound_add_column;
DROP PROCEDURE IF EXISTS phase3_inbound_add_index;
DELIMITER $$
CREATE PROCEDURE phase3_inbound_add_index(
IN p_table_name VARCHAR(64),
IN p_index_name VARCHAR(64),
IN p_definition TEXT
)
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.statistics
WHERE table_schema = DATABASE()
AND table_name = p_table_name
AND index_name = p_index_name
) THEN
SET @ddl = CONCAT('ALTER TABLE `', p_table_name, '` ADD ', p_definition);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END IF;
END$$
DELIMITER ;
CALL phase3_inbound_add_index(
'fl_inbound_order', 'uk_inbound_master_order',
'UNIQUE KEY `uk_inbound_master_order` (`tenant_id`, `master_order_id`)');
CALL phase3_inbound_add_index(
'fl_inbound_order', 'idx_inbound_root_role',
'KEY `idx_inbound_root_role` (`tenant_id`, `root_inbound_id`, `inbound_role`, `created_at`, `id`)');
DROP PROCEDURE IF EXISTS phase3_assert_active_remainder_unique_ready;
DELIMITER $$
CREATE PROCEDURE phase3_assert_active_remainder_unique_ready()
BEGIN
IF EXISTS (
SELECT 1
FROM fl_inbound_order
WHERE inbound_role = 'CHILD'
AND split_purpose = 'REMAINDER'
AND split_status = 'NOT_SPLIT'
AND deleted = 0
AND root_inbound_id IS NOT NULL
GROUP BY tenant_id, root_inbound_id
HAVING COUNT(*) > 1
) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '同一根入仓单存在多个有效余量,请先人工核对历史数据';
END IF;
END$$
DELIMITER ;
CALL phase3_assert_active_remainder_unique_ready();
DROP PROCEDURE IF EXISTS phase3_assert_active_remainder_unique_ready;
CALL phase3_inbound_add_index(
'fl_inbound_order', 'uk_inbound_one_active_remainder',
'UNIQUE KEY `uk_inbound_one_active_remainder` (`tenant_id`, `active_remainder_root_id`)');
DROP PROCEDURE IF EXISTS phase3_inbound_drop_legacy_index;
DELIMITER $$
CREATE PROCEDURE phase3_inbound_drop_legacy_index()
BEGIN
IF EXISTS (
SELECT 1 FROM information_schema.statistics
WHERE table_schema = DATABASE()
AND table_name = 'fl_inbound_order'
AND index_name = 'uk_inbound_tenant_order'
) THEN
ALTER TABLE fl_inbound_order DROP INDEX uk_inbound_tenant_order;
END IF;
IF EXISTS (
SELECT 1 FROM information_schema.statistics
WHERE table_schema = DATABASE()
AND table_name = 'fl_inbound_order'
AND index_name = 'uk_inbound_parent_sequence'
) THEN
ALTER TABLE fl_inbound_order DROP INDEX uk_inbound_parent_sequence;
END IF;
IF EXISTS (
SELECT 1 FROM information_schema.statistics
WHERE table_schema = DATABASE()
AND table_name = 'fl_inbound_order'
AND index_name = 'uk_inbound_one_child'
) THEN
ALTER TABLE fl_inbound_order DROP INDEX uk_inbound_one_child;
END IF;
IF EXISTS (
SELECT 1 FROM information_schema.statistics
WHERE table_schema = DATABASE()
AND table_name = 'fl_inbound_order'
AND index_name = 'uk_inbound_one_secondary'
) THEN
ALTER TABLE fl_inbound_order DROP INDEX uk_inbound_one_secondary;
END IF;
END$$
DELIMITER ;
CALL phase3_inbound_drop_legacy_index();
DROP PROCEDURE IF EXISTS phase3_inbound_drop_legacy_index;
CALL phase3_inbound_add_index(
'fl_inbound_order', 'uk_inbound_parent_split_sequence',
'UNIQUE KEY `uk_inbound_parent_split_sequence` (`tenant_id`, `parent_inbound_id`, `split_sequence`)');
DROP PROCEDURE IF EXISTS phase3_inbound_add_index;
CREATE TABLE IF NOT EXISTS fl_inbound_split_batch (
id BIGINT UNSIGNED NOT NULL COMMENT '拆分批次ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
parent_inbound_id BIGINT UNSIGNED NOT NULL COMMENT '主入仓单ID',
operation_id VARCHAR(64) NOT NULL COMMENT '业务操作幂等号',
idempotency_key VARCHAR(128) NOT NULL COMMENT '请求幂等键',
expected_parent_version INT NOT NULL COMMENT '操作时主单乐观锁版本',
package_count INT UNSIGNED NOT NULL COMMENT '本批次拆分箱数',
status VARCHAR(16) NOT NULL DEFAULT 'PREPARED' COMMENT '拆分批次状态:PREPARED已准备、APPLIED已生效、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 '拆分批次创建时间',
applied_at DATETIME(3) NULL COMMENT '批次生效时间',
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号,每次更新递增',
PRIMARY KEY (id),
UNIQUE KEY uk_inbound_split_operation (tenant_id, operation_id),
UNIQUE KEY uk_inbound_split_idempotency (tenant_id, parent_inbound_id, idempotency_key),
KEY idx_inbound_split_parent (tenant_id, parent_inbound_id, created_at, id),
CONSTRAINT chk_inbound_split_package CHECK (package_count > 0),
CONSTRAINT chk_inbound_split_status CHECK (status IN ('PREPARED', 'APPLIED', 'CANCELLED')),
CONSTRAINT fk_inbound_split_parent FOREIGN KEY (parent_inbound_id) REFERENCES fl_inbound_order (id)
) ENGINE=InnoDB COMMENT='主入仓单拆分批次;数量口径仅为箱数';
CREATE TABLE IF NOT EXISTS fl_inbound_split_item (
id BIGINT UNSIGNED NOT NULL COMMENT '拆分明细ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
split_batch_id BIGINT UNSIGNED NOT NULL COMMENT '拆分批次ID',
parent_inbound_id BIGINT UNSIGNED NOT NULL COMMENT '主入仓单ID',
child_inbound_id BIGINT UNSIGNED NOT NULL COMMENT '子入仓单ID',
line_no INT UNSIGNED NOT NULL COMMENT '同一拆分批次内的行号',
source_inbound_detail_id BIGINT UNSIGNED NULL COMMENT '订单货物明细逻辑ID',
source_measurement_id BIGINT UNSIGNED NOT NULL COMMENT '来源测量批次ID',
source_measurement_item_id BIGINT UNSIGNED NOT NULL COMMENT '来源测量箱规明细ID',
package_count INT UNSIGNED NOT NULL COMMENT '分配给子单的箱数',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '拆分数量台账创建时间',
PRIMARY KEY (id),
UNIQUE KEY uk_inbound_split_batch_line (tenant_id, split_batch_id, line_no),
UNIQUE KEY uk_inbound_split_batch_source_child (tenant_id, split_batch_id, source_measurement_item_id, child_inbound_id),
KEY idx_inbound_split_detail (tenant_id, source_inbound_detail_id, created_at),
CONSTRAINT chk_inbound_split_item_package CHECK (package_count > 0),
CONSTRAINT fk_inbound_split_item_batch FOREIGN KEY (split_batch_id) REFERENCES fl_inbound_split_batch (id),
CONSTRAINT fk_inbound_split_item_parent FOREIGN KEY (parent_inbound_id) REFERENCES fl_inbound_order (id),
CONSTRAINT fk_inbound_split_item_child FOREIGN KEY (child_inbound_id) REFERENCES fl_inbound_order (id),
CONSTRAINT fk_inbound_split_item_measurement FOREIGN KEY (source_measurement_id) REFERENCES fl_inbound_measurement (id),
CONSTRAINT fk_inbound_split_item_measure_item FOREIGN KEY (source_measurement_item_id) REFERENCES fl_inbound_measurement_item (id)
) ENGINE=InnoDB COMMENT='主子入仓单拆分数量台账';
CREATE TABLE IF NOT EXISTS fl_inbound_split_history (
id BIGINT UNSIGNED NOT NULL COMMENT '拆分历史ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
split_batch_id BIGINT UNSIGNED NOT NULL COMMENT '拆分批次ID',
operation_id VARCHAR(64) NOT NULL COMMENT '历史操作幂等号',
action_type VARCHAR(24) NOT NULL COMMENT '拆分动作:CREATE创建批次、APPLY生效、CANCEL取消',
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 '操作人名称快照',
operation_reason VARCHAR(500) NULL COMMENT '操作原因',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '不可变拆分历史创建时间',
PRIMARY KEY (id),
UNIQUE KEY uk_inbound_split_history_operation (tenant_id, operation_id),
KEY idx_inbound_split_history (tenant_id, split_batch_id, created_at, id),
CONSTRAINT chk_inbound_split_action CHECK (action_type IN ('CREATE', 'APPLY', 'CANCEL')),
CONSTRAINT fk_inbound_split_history_batch FOREIGN KEY (split_batch_id) REFERENCES fl_inbound_split_batch (id)
) ENGINE=InnoDB COMMENT='不可变主子入仓单拆分审计历史';
DROP PROCEDURE IF EXISTS phase3_inbound_add_column;
DELIMITER $$
CREATE PROCEDURE phase3_inbound_add_column(
IN p_table_name VARCHAR(64),
IN p_column_name VARCHAR(64),
IN p_definition TEXT
)
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema = DATABASE()
AND table_name = p_table_name
AND column_name = p_column_name
) THEN
SET @ddl = CONCAT('ALTER TABLE `', p_table_name, '` ADD COLUMN `',
p_column_name, '` ', p_definition);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END IF;
END$$
DELIMITER ;
CALL phase3_inbound_add_column(
'fl_inbound_measurement', 'split_batch_id',
"BIGINT UNSIGNED NULL COMMENT '产生该测量批次的入仓拆分批次ID' AFTER inbound_id");
CALL phase3_inbound_add_column(
'fl_inbound_measurement', 'source_measurement_id',
"BIGINT UNSIGNED NULL COMMENT '子单继承或复核时的来源测量批次ID' AFTER split_batch_id");
CALL phase3_inbound_add_column(
'fl_inbound_measurement_item', 'split_item_id',
"BIGINT UNSIGNED NULL COMMENT '产生该子单测量明细的拆分台账ID' AFTER measurement_id");
CALL phase3_inbound_add_column(
'fl_inbound_measurement_item', 'source_measurement_item_id',
"BIGINT UNSIGNED NULL COMMENT '被拆分继承的来源测量箱规明细ID' AFTER split_item_id");
CALL phase3_inbound_add_column(
'fl_inbound_split_item', 'line_no',
"INT UNSIGNED NOT NULL DEFAULT 1 COMMENT '同一拆分批次内的行号' AFTER child_inbound_id");
ALTER TABLE fl_inbound_split_item
MODIFY source_measurement_id BIGINT UNSIGNED NOT NULL COMMENT '来源测量批次ID',
MODIFY source_measurement_item_id BIGINT UNSIGNED NOT NULL COMMENT '来源测量箱规明细ID';
CALL phase3_inbound_add_column(
'fl_inbound_split_batch', 'loaded_child_inbound_id',
"BIGINT UNSIGNED NULL COMMENT '本批次实装子单ID' AFTER package_count");
CALL phase3_inbound_add_column(
'fl_inbound_split_batch', 'source_inbound_id',
"BIGINT UNSIGNED NULL COMMENT '本批次实际消耗的MASTER或REMAINDER分片ID' AFTER parent_inbound_id");
CALL phase3_inbound_add_column(
'fl_inbound_split_batch', 'remainder_child_inbound_id',
"BIGINT UNSIGNED NULL COMMENT '本批次余量子单ID' AFTER loaded_child_inbound_id");
CALL phase3_inbound_add_column(
'fl_inbound_split_batch', 'loaded_package_count',
"INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '本批次实装箱数' AFTER remainder_child_inbound_id");
CALL phase3_inbound_add_column(
'fl_inbound_split_batch', 'remaining_package_count',
"INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '本批次未装箱数' AFTER loaded_package_count");
CALL phase3_inbound_add_column(
'fl_inbound_split_batch', 'loading_plan_id',
"BIGINT UNSIGNED NULL COMMENT '关联装柜计划/柜次' AFTER remaining_package_count");
CALL phase3_inbound_add_column(
'fl_inbound_split_batch', 'container_booking_id',
"BIGINT UNSIGNED NULL COMMENT '关联柜次标识(兼容查询)' AFTER loading_plan_id");
DROP PROCEDURE IF EXISTS phase3_inbound_add_column;
DROP PROCEDURE IF EXISTS phase3_inbound_add_relation;
DELIMITER $$
CREATE PROCEDURE phase3_inbound_add_relation(
IN p_table_name VARCHAR(64),
IN p_constraint_name VARCHAR(64),
IN p_definition TEXT
)
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.table_constraints
WHERE constraint_schema = DATABASE()
AND table_name = p_table_name
AND constraint_name = p_constraint_name
) THEN
SET @ddl = CONCAT('ALTER TABLE `', p_table_name, '` ADD CONSTRAINT `',
p_constraint_name, '` ', p_definition);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END IF;
END$$
DELIMITER ;
CALL phase3_inbound_add_relation(
'fl_inbound_order', 'fk_inbound_parent_order',
'FOREIGN KEY (`parent_inbound_id`) REFERENCES `fl_inbound_order` (`id`)');
CALL phase3_inbound_add_relation(
'fl_inbound_order', 'fk_inbound_root_order',
'FOREIGN KEY (`root_inbound_id`) REFERENCES `fl_inbound_order` (`id`)');
CALL phase3_inbound_add_relation(
'fl_inbound_split_batch', 'fk_inbound_split_loaded_child',
'FOREIGN KEY (`loaded_child_inbound_id`) REFERENCES `fl_inbound_order` (`id`)');
CALL phase3_inbound_add_relation(
'fl_inbound_split_batch', 'fk_inbound_split_source',
'FOREIGN KEY (`source_inbound_id`) REFERENCES `fl_inbound_order` (`id`)');
CALL phase3_inbound_add_relation(
'fl_inbound_split_batch', 'fk_inbound_split_remainder_child',
'FOREIGN KEY (`remainder_child_inbound_id`) REFERENCES `fl_inbound_order` (`id`)');
CALL phase3_inbound_add_relation(
'fl_inbound_measurement', 'fk_measure_split_batch',
'FOREIGN KEY (`split_batch_id`) REFERENCES `fl_inbound_split_batch` (`id`)');
CALL phase3_inbound_add_relation(
'fl_inbound_measurement', 'fk_measure_source_batch',
'FOREIGN KEY (`source_measurement_id`) REFERENCES `fl_inbound_measurement` (`id`)');
CALL phase3_inbound_add_relation(
'fl_inbound_measurement_item', 'fk_measure_item_split_item',
'FOREIGN KEY (`split_item_id`) REFERENCES `fl_inbound_split_item` (`id`)');
CALL phase3_inbound_add_relation(
'fl_inbound_measurement_item', 'fk_measure_item_source_item',
'FOREIGN KEY (`source_measurement_item_id`) REFERENCES `fl_inbound_measurement_item` (`id`)');
DROP PROCEDURE IF EXISTS phase3_inbound_add_relation;
DROP PROCEDURE IF EXISTS phase3_inbound_add_index;
DELIMITER $$
CREATE PROCEDURE phase3_inbound_add_index(
IN p_table_name VARCHAR(64),
IN p_index_name VARCHAR(64),
IN p_definition TEXT
)
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.statistics
WHERE table_schema = DATABASE()
AND table_name = p_table_name
AND index_name = p_index_name
) THEN
SET @ddl = CONCAT('ALTER TABLE `', p_table_name, '` ADD ', p_definition);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END IF;
END$$
DELIMITER ;
CALL phase3_inbound_add_index(
'fl_inbound_measurement', 'idx_measure_split_source',
'KEY `idx_measure_split_source` (`tenant_id`, `split_batch_id`, `source_measurement_id`, `created_at`)');
CALL phase3_inbound_add_index(
'fl_inbound_measurement_item', 'idx_measure_item_split_source',
'KEY `idx_measure_item_split_source` (`tenant_id`, `split_item_id`, `source_measurement_item_id`)');
CALL phase3_inbound_add_index(
'fl_inbound_split_item', 'uk_inbound_split_batch_line',
'UNIQUE KEY `uk_inbound_split_batch_line` (`tenant_id`, `split_batch_id`, `line_no`)');
CALL phase3_inbound_add_index(
'fl_inbound_split_batch', 'idx_inbound_split_loading_plan',
'KEY `idx_inbound_split_loading_plan` (`tenant_id`, `loading_plan_id`, `container_booking_id`, `status`, `id`)');
CALL phase3_inbound_add_index(
'fl_inbound_split_batch', 'uk_inbound_split_source_once',
'UNIQUE KEY `uk_inbound_split_source_once` (`tenant_id`, `source_inbound_id`)');
DROP PROCEDURE IF EXISTS phase3_inbound_drop_split_legacy_index;
DELIMITER $$
CREATE PROCEDURE phase3_inbound_drop_split_legacy_index()
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema = DATABASE()
AND table_name = 'fl_inbound_split_item' AND index_name = 'uk_inbound_split_child') THEN
ALTER TABLE fl_inbound_split_item DROP INDEX uk_inbound_split_child;
END IF;
IF EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema = DATABASE()
AND table_name = 'fl_inbound_split_item' AND index_name = 'uk_inbound_split_batch_child') THEN
ALTER TABLE fl_inbound_split_item DROP INDEX uk_inbound_split_batch_child;
END IF;
IF EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema = DATABASE()
AND table_name = 'fl_inbound_split_item' AND index_name = 'uk_inbound_split_batch_source_item') THEN
ALTER TABLE fl_inbound_split_item DROP INDEX uk_inbound_split_batch_source_item;
END IF;
END$$
DELIMITER ;
CALL phase3_inbound_drop_split_legacy_index();
DROP PROCEDURE IF EXISTS phase3_inbound_drop_split_legacy_index;
CALL phase3_inbound_add_index(
'fl_inbound_split_item', 'uk_inbound_split_batch_source_child',
'UNIQUE KEY `uk_inbound_split_batch_source_child` (`tenant_id`, `split_batch_id`, `source_measurement_item_id`, `child_inbound_id`)');
DROP PROCEDURE IF EXISTS phase3_inbound_add_index;
DROP TRIGGER IF EXISTS trg_inbound_order_parent_before_insert;
DROP TRIGGER IF EXISTS trg_inbound_order_parent_before_update;
DROP TRIGGER IF EXISTS trg_inbound_split_item_before_insert;
DROP TRIGGER IF EXISTS trg_inbound_split_batch_before_insert;
DROP TRIGGER IF EXISTS trg_inbound_split_batch_before_update;
DROP TRIGGER IF EXISTS trg_inbound_split_item_before_update;
DROP TRIGGER IF EXISTS trg_inbound_split_item_before_delete;
DROP TRIGGER IF EXISTS trg_inbound_split_history_before_update;
DROP TRIGGER IF EXISTS trg_inbound_split_history_before_delete;
DELIMITER $$
CREATE TRIGGER trg_inbound_order_parent_before_insert
BEFORE INSERT ON fl_inbound_order
FOR EACH ROW
BEGIN
IF NEW.inbound_role NOT IN ('MASTER', 'CHILD') THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '入仓单层级非法';
END IF;
IF NEW.split_status NOT IN ('NOT_SPLIT', 'PARTIALLY_SPLIT', 'FULLY_SPLIT') THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '入仓单拆分状态非法';
END IF;
IF NEW.split_purpose NOT IN ('MASTER', 'LOADED', 'REMAINDER') THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '入仓单分片用途非法';
END IF;
IF NEW.package_count IS NOT NULL AND NEW.package_count = 0 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '入仓单箱数必须大于0';
END IF;
IF NEW.inbound_role = 'MASTER' AND NEW.parent_inbound_id IS NOT NULL THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '主入仓单不能绑定父单';
END IF;
IF NEW.inbound_role = 'MASTER' AND NEW.split_purpose <> 'MASTER' THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '主入仓单用途必须为MASTER';
END IF;
IF NEW.inbound_role = 'CHILD' THEN
IF NEW.parent_inbound_id IS NULL OR NEW.root_inbound_id IS NULL
OR NEW.split_sequence IS NULL OR NEW.package_count IS NULL THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '子入仓单缺少主单、顺序号或箱数';
END IF;
IF NEW.split_purpose <> 'REMAINDER' THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '新建子入仓单只能作为余量分片';
END IF;
IF NOT EXISTS (
SELECT 1 FROM fl_inbound_order parent_order
WHERE parent_order.id = NEW.parent_inbound_id
AND parent_order.tenant_id = NEW.tenant_id
AND parent_order.inbound_role = 'MASTER'
AND parent_order.deleted = 0
) OR NEW.root_inbound_id <> NEW.parent_inbound_id THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '子入仓单必须绑定同一客户的有效主单';
END IF;
END IF;
END$$
CREATE TRIGGER trg_inbound_order_parent_before_update
BEFORE UPDATE ON fl_inbound_order
FOR EACH ROW
BEGIN
IF NOT (OLD.tenant_id <=> NEW.tenant_id)
OR NOT (OLD.inbound_role <=> NEW.inbound_role)
OR NOT (OLD.parent_inbound_id <=> NEW.parent_inbound_id)
OR NOT (OLD.root_inbound_id <=> NEW.root_inbound_id)
OR NOT (OLD.split_sequence <=> NEW.split_sequence)
OR NOT (OLD.split_purpose <=> NEW.split_purpose) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '入仓单主子归属创建后不可修改';
END IF;
IF NEW.split_purpose NOT IN ('MASTER', 'LOADED', 'REMAINDER') THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '入仓单分片用途非法';
END IF;
IF NEW.inbound_role = 'CHILD'
AND (NEW.package_count IS NULL OR NEW.package_count = 0) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '子入仓单箱数必须大于0';
END IF;
IF NEW.split_status NOT IN ('NOT_SPLIT', 'PARTIALLY_SPLIT', 'FULLY_SPLIT') THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '入仓单拆分状态非法';
END IF;
IF (NEW.inbound_role = 'CHILD' AND NEW.split_purpose = 'MASTER')
OR (NEW.inbound_role = 'MASTER' AND NEW.split_purpose <> 'MASTER') THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '入仓单层级与用途不一致';
END IF;
END$$
CREATE TRIGGER trg_inbound_split_item_before_insert
BEFORE INSERT ON fl_inbound_split_item
FOR EACH ROW
BEGIN
IF NEW.source_measurement_id IS NULL OR NEW.source_measurement_item_id IS NULL THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '拆分明细必须绑定真实测量来源';
END IF;
IF NOT EXISTS (
SELECT 1
FROM fl_inbound_split_batch batch
JOIN fl_inbound_order parent_order ON parent_order.id = batch.parent_inbound_id
JOIN fl_inbound_order child_order ON child_order.id = NEW.child_inbound_id
WHERE batch.id = NEW.split_batch_id
AND batch.tenant_id = NEW.tenant_id
AND batch.parent_inbound_id = NEW.parent_inbound_id
AND batch.status = 'PREPARED'
AND batch.loaded_child_inbound_id IS NULL
AND batch.remainder_child_inbound_id IS NOT NULL
AND NEW.child_inbound_id = batch.remainder_child_inbound_id
AND parent_order.tenant_id = NEW.tenant_id
AND parent_order.inbound_role = 'MASTER'
AND child_order.tenant_id = NEW.tenant_id
AND child_order.inbound_role = 'CHILD'
AND child_order.split_purpose = 'REMAINDER'
AND child_order.parent_inbound_id = NEW.parent_inbound_id
AND child_order.package_count >= NEW.package_count
AND child_order.deleted = 0
AND EXISTS (
SELECT 1
FROM fl_inbound_measurement_item source_item
JOIN fl_inbound_measurement source_batch ON source_batch.id = source_item.measurement_id
WHERE source_item.id = NEW.source_measurement_item_id
AND source_item.tenant_id = NEW.tenant_id
AND source_item.measurement_id = NEW.source_measurement_id
AND source_batch.inbound_id = COALESCE(batch.source_inbound_id, batch.parent_inbound_id)
)
) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '拆分明细与批次、主单或子单不一致';
END IF;
END$$
CREATE TRIGGER trg_inbound_split_batch_before_insert
BEFORE INSERT ON fl_inbound_split_batch
FOR EACH ROW
BEGIN
IF NEW.status <> 'PREPARED' OR NEW.applied_at IS NOT NULL THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '拆分批次必须从PREPARED创建';
END IF;
IF NEW.loaded_child_inbound_id IS NOT NULL
OR NEW.loaded_package_count = 0
OR NEW.loaded_package_count + NEW.remaining_package_count <> NEW.package_count
OR (NEW.remaining_package_count = 0 AND NEW.remainder_child_inbound_id IS NOT NULL)
OR (NEW.remaining_package_count > 0 AND NEW.remainder_child_inbound_id IS NULL) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '新拆分批次必须由来源单承接实装且最多创建一个余量子单';
END IF;
IF NOT EXISTS (
SELECT 1 FROM fl_inbound_order parent_order
WHERE parent_order.id = NEW.parent_inbound_id
AND parent_order.tenant_id = NEW.tenant_id
AND parent_order.inbound_role = 'MASTER'
AND parent_order.deleted = 0
) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '拆分批次主单不存在或客户归属不一致';
END IF;
IF NEW.source_inbound_id IS NULL OR NOT EXISTS (
SELECT 1 FROM fl_inbound_order source_order
WHERE source_order.id = NEW.source_inbound_id
AND source_order.tenant_id = NEW.tenant_id
AND source_order.deleted = 0
AND ((source_order.id = NEW.parent_inbound_id
AND source_order.inbound_role = 'MASTER'
AND ((source_order.split_status = 'NOT_SPLIT'
AND COALESCE(source_order.package_count, source_order.total_package_count) = NEW.package_count)
OR (source_order.split_status = 'FULLY_SPLIT'
AND source_order.package_count = NEW.loaded_package_count))) OR
(source_order.inbound_role = 'CHILD'
AND source_order.parent_inbound_id = NEW.parent_inbound_id
AND source_order.split_purpose = 'REMAINDER'
AND ((source_order.split_status = 'NOT_SPLIT'
AND source_order.package_count = NEW.package_count)
OR (source_order.split_status = 'FULLY_SPLIT'
AND source_order.package_count = NEW.loaded_package_count))))
) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '拆分批次来源必须是箱数守恒的根主单或余量分片';
END IF;
IF NEW.loaded_package_count > 0 AND (
NEW.loading_plan_id IS NULL OR NEW.container_booking_id IS NULL OR NOT EXISTS (
SELECT 1 FROM fl_loading_plan plan
WHERE plan.id = NEW.loading_plan_id
AND plan.container_booking_id = NEW.container_booking_id
AND EXISTS (SELECT 1 FROM fl_loading_plan_item plan_item
WHERE plan_item.loading_plan_id = plan.id
AND plan_item.account_id = NEW.tenant_id
AND plan_item.inbound_id = NEW.source_inbound_id
AND plan_item.deleted = 0)
)
) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '实装拆分批次必须绑定匹配的装柜计划和柜次';
END IF;
IF NEW.remainder_child_inbound_id IS NOT NULL AND NOT EXISTS (
SELECT 1 FROM fl_inbound_order remainder_order
WHERE remainder_order.id = NEW.remainder_child_inbound_id
AND remainder_order.tenant_id = NEW.tenant_id
AND remainder_order.parent_inbound_id = NEW.parent_inbound_id
AND remainder_order.root_inbound_id = NEW.parent_inbound_id
AND remainder_order.inbound_role = 'CHILD'
AND remainder_order.split_purpose = 'REMAINDER'
AND remainder_order.split_status = 'NOT_SPLIT'
AND remainder_order.package_count = NEW.remaining_package_count
AND remainder_order.deleted = 0
) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '余量子入仓单与拆分批次数量或根归属不一致';
END IF;
END$$
CREATE TRIGGER trg_inbound_split_batch_before_update
BEFORE UPDATE ON fl_inbound_split_batch
FOR EACH ROW
BEGIN
IF OLD.status IN ('APPLIED', 'CANCELLED') THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '已结束拆分批次不可修改';
END IF;
IF NOT (OLD.parent_inbound_id <=> NEW.parent_inbound_id)
OR NOT (OLD.source_inbound_id <=> NEW.source_inbound_id)
OR NOT (OLD.loaded_child_inbound_id <=> NEW.loaded_child_inbound_id)
OR NOT (OLD.remainder_child_inbound_id <=> NEW.remainder_child_inbound_id)
OR NOT (OLD.package_count <=> NEW.package_count)
OR NOT (OLD.loaded_package_count <=> NEW.loaded_package_count)
OR NOT (OLD.remaining_package_count <=> NEW.remaining_package_count)
OR NOT (OLD.loading_plan_id <=> NEW.loading_plan_id)
OR NOT (OLD.container_booking_id <=> NEW.container_booking_id) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '拆分批次来源、子单、柜次与数量创建后不可修改';
END IF;
IF NEW.status = 'APPLIED' THEN
IF NEW.applied_at IS NULL
OR (NEW.loaded_package_count > 0 AND (
NEW.loading_plan_id IS NULL OR NEW.container_booking_id IS NULL OR NOT EXISTS (
SELECT 1 FROM fl_loading_plan plan
WHERE plan.id = NEW.loading_plan_id
AND plan.container_booking_id = NEW.container_booking_id
AND EXISTS (SELECT 1 FROM fl_loading_plan_item plan_item
WHERE plan_item.loading_plan_id = plan.id
AND plan_item.account_id = NEW.tenant_id
AND plan_item.inbound_id = NEW.source_inbound_id
AND plan_item.deleted = 0))))
OR NOT EXISTS (
SELECT 1 FROM fl_inbound_order parent_order
WHERE parent_order.id = NEW.parent_inbound_id
AND parent_order.tenant_id = NEW.tenant_id
AND parent_order.inbound_role = 'MASTER'
AND parent_order.version > NEW.expected_parent_version
) OR NOT EXISTS (
SELECT 1 FROM fl_inbound_order source_order
WHERE source_order.id = NEW.source_inbound_id
AND source_order.tenant_id = NEW.tenant_id
AND source_order.split_status = 'FULLY_SPLIT'
AND source_order.package_count = NEW.loaded_package_count
) OR
(SELECT COALESCE(SUM(item.package_count), 0)
FROM fl_inbound_split_item item
WHERE item.tenant_id = NEW.tenant_id
AND item.split_batch_id = NEW.id) <> NEW.remaining_package_count OR
(SELECT COUNT(DISTINCT item.child_inbound_id)
FROM fl_inbound_split_item item
WHERE item.tenant_id = NEW.tenant_id
AND item.split_batch_id = NEW.id) <> CASE WHEN NEW.remaining_package_count > 0 THEN 1 ELSE 0 END OR
(NEW.loaded_child_inbound_id IS NOT NULL) OR
NEW.loaded_package_count + NEW.remaining_package_count <> NEW.package_count OR
(NEW.remaining_package_count > 0 AND NEW.remainder_child_inbound_id IS NULL) OR
(NEW.remainder_child_inbound_id IS NOT NULL AND NOT EXISTS (
SELECT 1 FROM fl_inbound_order c WHERE c.id = NEW.remainder_child_inbound_id
AND c.parent_inbound_id = NEW.parent_inbound_id AND c.root_inbound_id = NEW.parent_inbound_id
AND c.split_purpose = 'REMAINDER' AND c.split_status = 'NOT_SPLIT'
AND c.package_count = NEW.remaining_package_count AND c.deleted = 0)) OR
EXISTS (SELECT 1 FROM fl_inbound_split_item item
WHERE item.tenant_id = NEW.tenant_id
AND item.split_batch_id = NEW.id
AND item.child_inbound_id <> NEW.remainder_child_inbound_id) OR
EXISTS (SELECT 1
FROM fl_inbound_split_item item
JOIN fl_inbound_order child_order ON child_order.id = item.child_inbound_id
WHERE item.tenant_id = NEW.tenant_id
AND item.split_batch_id = NEW.id
GROUP BY child_order.id, child_order.package_count
HAVING child_order.package_count <> SUM(item.package_count)) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '二级子入仓单或拆分明细箱数合计不一致';
END IF;
IF EXISTS (
SELECT 1
FROM fl_inbound_split_item split_item
JOIN fl_inbound_measurement_item source_item
ON source_item.id = split_item.source_measurement_item_id
WHERE split_item.tenant_id = NEW.tenant_id
AND split_item.split_batch_id = NEW.id
GROUP BY split_item.source_measurement_item_id, source_item.package_count
HAVING SUM(split_item.package_count) > source_item.package_count
) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '子单承接箱数超过来源测量箱规箱数';
END IF;
IF NOT EXISTS (
SELECT 1
FROM fl_inbound_order root_order
WHERE root_order.id = NEW.parent_inbound_id
AND root_order.tenant_id = NEW.tenant_id
AND root_order.inbound_role = 'MASTER'
AND root_order.deleted = 0
AND root_order.total_package_count =
COALESCE(root_order.package_count, 0) +
COALESCE((SELECT SUM(child.package_count)
FROM fl_inbound_order child
WHERE child.tenant_id = NEW.tenant_id
AND child.root_inbound_id = root_order.id
AND child.inbound_role = 'CHILD'
AND child.deleted = 0), 0)
) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '根入仓单原始箱数与已完成分片及当前余量不守恒';
END IF;
ELSEIF NEW.status = 'CANCELLED' THEN
IF EXISTS (
SELECT 1 FROM fl_inbound_split_item item
WHERE item.tenant_id = NEW.tenant_id
AND item.split_batch_id = NEW.id
) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '已有子单明细的拆分批次不可取消';
END IF;
ELSEIF NEW.status <> 'PREPARED' THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '拆分批次状态流转非法';
END IF;
END$$
CREATE TRIGGER trg_inbound_split_item_before_update
BEFORE UPDATE ON fl_inbound_split_item
FOR EACH ROW
BEGIN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '拆分数量台账不可修改';
END$$
CREATE TRIGGER trg_inbound_split_item_before_delete
BEFORE DELETE ON fl_inbound_split_item
FOR EACH ROW
BEGIN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '拆分数量台账不可删除';
END$$
CREATE TRIGGER trg_inbound_split_history_before_update
BEFORE UPDATE ON fl_inbound_split_history
FOR EACH ROW
BEGIN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '拆分历史不可修改';
END$$
CREATE TRIGGER trg_inbound_split_history_before_delete
BEFORE DELETE ON fl_inbound_split_history
FOR EACH ROW
BEGIN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '拆分历史不可删除';
END$$
DELIMITER ;
DROP PROCEDURE IF EXISTS phase3_assert_inbound_parent_child_schema;
DELIMITER $$
CREATE PROCEDURE phase3_assert_inbound_parent_child_schema()
BEGIN
IF (SELECT COUNT(*) FROM information_schema.tables
WHERE table_schema = DATABASE()
AND table_name IN ('fl_inbound_split_batch', 'fl_inbound_split_item',
'fl_inbound_split_history')) <> 3 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '主子入仓单拆分台账未完整创建';
END IF;
IF (SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = DATABASE()
AND table_name = 'fl_inbound_order'
AND column_name IN ('inbound_role', 'parent_inbound_id', 'root_inbound_id',
'split_sequence', 'split_status', 'package_count',
'master_order_id', 'split_purpose', 'active_remainder_root_id')) <> 9 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '主子入仓单关系字段未完整创建';
END IF;
IF (SELECT COUNT(DISTINCT index_name) FROM information_schema.statistics
WHERE table_schema = DATABASE() AND table_name = 'fl_inbound_order'
AND index_name IN ('uk_inbound_master_order', 'uk_inbound_parent_split_sequence',
'uk_inbound_one_active_remainder')) <> 3
OR (SELECT COUNT(DISTINCT index_name) FROM information_schema.statistics
WHERE table_schema = DATABASE() AND table_name = 'fl_inbound_split_batch'
AND index_name IN ('uk_inbound_split_operation', 'uk_inbound_split_idempotency',
'uk_inbound_split_source_once')) <> 3
OR (SELECT COUNT(DISTINCT index_name) FROM information_schema.statistics
WHERE table_schema = DATABASE() AND table_name = 'fl_inbound_split_item'
AND index_name IN ('uk_inbound_split_batch_line',
'uk_inbound_split_batch_source_child')) <> 2
OR (SELECT COUNT(DISTINCT index_name) FROM information_schema.statistics
WHERE table_schema = DATABASE() AND table_name = 'fl_inbound_split_history'
AND index_name = 'uk_inbound_split_history_operation') <> 1 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '主子入仓单唯一约束未完整创建';
END IF;
IF (SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = 'fl_inbound_split_batch'
AND column_name IN ('source_inbound_id', 'loaded_child_inbound_id',
'remainder_child_inbound_id', 'loaded_package_count',
'remaining_package_count', 'loading_plan_id',
'container_booking_id')) <> 7 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '拆分批次来源、子单、数量或柜次字段未完整创建';
END IF;
IF (SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = 'fl_inbound_split_item'
AND column_name IN ('source_measurement_id', 'source_measurement_item_id')
AND is_nullable = 'NO') <> 2 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '拆分数量台账必须绑定非空测量来源';
END IF;
IF (SELECT COUNT(*) FROM information_schema.table_constraints
WHERE constraint_schema = DATABASE()
AND constraint_name IN ('fk_inbound_parent_order', 'fk_inbound_root_order',
'fk_inbound_split_source', 'fk_inbound_split_loaded_child', 'fk_inbound_split_remainder_child',
'fk_measure_split_batch', 'fk_measure_source_batch',
'fk_measure_item_split_item', 'fk_measure_item_source_item')) <> 9 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '主子入仓单与测量关联约束未完整创建';
END IF;
IF (SELECT COUNT(*) FROM information_schema.triggers
WHERE trigger_schema = DATABASE()
AND trigger_name IN ('trg_inbound_order_parent_before_insert',
'trg_inbound_order_parent_before_update',
'trg_inbound_split_item_before_insert',
'trg_inbound_split_batch_before_insert',
'trg_inbound_split_batch_before_update',
'trg_inbound_split_item_before_update',
'trg_inbound_split_item_before_delete',
'trg_inbound_split_history_before_update',
'trg_inbound_split_history_before_delete')) <> 9 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '主子入仓单数据库保护触发器未完整创建';
END IF;
IF EXISTS (
SELECT 1 FROM information_schema.statistics
WHERE table_schema = DATABASE()
AND table_name = 'fl_inbound_order'
AND index_name IN ('uk_inbound_tenant_order', 'uk_inbound_one_child',
'uk_inbound_one_secondary', 'uk_inbound_parent_sequence')
) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '旧订单唯一索引仍阻止创建子入仓单';
END IF;
IF EXISTS (
SELECT 1 FROM information_schema.statistics
WHERE table_schema = DATABASE()
AND table_name = 'fl_inbound_split_item'
AND index_name IN ('uk_inbound_split_child', 'uk_inbound_split_batch_child',
'uk_inbound_split_batch_source_item')
) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '旧拆分明细唯一索引仍阻止数量守恒分配';
END IF;
END$$
DELIMITER ;
CALL phase3_assert_inbound_parent_child_schema();
DROP PROCEDURE IF EXISTS phase3_assert_inbound_parent_child_schema;
-- 来源:firstleg/src/main/resources/db/phase3-ocean-booking-schema.sql
-- 来源:firstleg/src/main/resources/db/phase3-truck-appointment-schema.sql
-- ============================================================
SET NAMES utf8mb4;
USE baiyue_firstleg;
CREATE TABLE IF NOT EXISTS fl_ocean_booking (
id BIGINT UNSIGNED NOT NULL COMMENT '海运订舱ID',
booking_no VARCHAR(40) NOT NULL COMMENT '平台海运订舱编号',
status VARCHAR(24) NOT NULL DEFAULT 'DRAFT' COMMENT '订舱状态:DRAFT草稿、SUBMITTED已提交、CONFIRMED已确认、RELEASED已放舱、FAILED失败、CHANGE_PENDING船期待变更、CANCELLED已取消',
customer_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
customer_name VARCHAR(128) NOT NULL COMMENT '客户名称快照',
bill_of_lading_no VARCHAR(128) NULL COMMENT '提单号',
shipping_company_id BIGINT UNSIGNED NULL COMMENT '船公司逻辑ID快照',
shipping_company_name VARCHAR(128) NULL COMMENT '船公司名称快照',
booking_agent_id BIGINT UNSIGNED NULL COMMENT '订舱代理逻辑ID快照',
booking_agent_name VARCHAR(128) NULL COMMENT '订舱代理名称快照',
vessel_name VARCHAR(128) NULL COMMENT '当前确认船名快照',
voyage_no VARCHAR(64) NULL COMMENT '当前确认航次号快照',
departure_port_code VARCHAR(32) NULL COMMENT '起运港编码快照',
departure_port_name VARCHAR(128) NULL COMMENT '起运港名称快照',
destination_port_code VARCHAR(32) NULL COMMENT '目的港编码快照',
destination_port_name VARCHAR(128) NULL COMMENT '目的港名称快照',
place_of_delivery VARCHAR(128) NULL COMMENT '最终交货地快照',
service_term VARCHAR(32) NULL COMMENT '运输服务条款快照',
freight_term VARCHAR(32) NULL COMMENT '海运费支付条款快照',
estimated_sailing_at DATETIME(3) NULL COMMENT '当前确认预计开船时间',
estimated_arrival_at DATETIME(3) NULL COMMENT '当前确认预计到港时间',
pending_vessel_name VARCHAR(128) NULL COMMENT '待确认变更船名',
pending_voyage_no VARCHAR(64) NULL COMMENT '待确认变更航次号',
pending_estimated_sailing_at DATETIME(3) NULL COMMENT '待确认变更预计开船时间',
pending_estimated_arrival_at DATETIME(3) NULL COMMENT '待确认变更预计到港时间',
change_reason VARCHAR(1000) NULL COMMENT '船期变更原因',
failure_reason VARCHAR(1000) NULL COMMENT '订舱失败原因',
cancellation_reason VARCHAR(1000) NULL COMMENT '取消订舱原因',
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_ocean_booking_no (booking_no),
KEY idx_ocean_booking_customer (customer_id,status,updated_at,id),
KEY idx_ocean_booking_voyage (vessel_name,voyage_no,updated_at),
CONSTRAINT chk_ocean_booking_customer CHECK (customer_id > 0),
CONSTRAINT chk_ocean_booking_status CHECK (status IN
('DRAFT','SUBMITTED','CONFIRMED','RELEASED','FAILED','CHANGE_PENDING','CANCELLED')),
CONSTRAINT chk_ocean_booking_schedule CHECK
(estimated_sailing_at IS NULL OR estimated_arrival_at IS NULL OR estimated_arrival_at >= estimated_sailing_at),
CONSTRAINT chk_ocean_booking_pending_schedule CHECK
(pending_estimated_sailing_at IS NULL OR pending_estimated_arrival_at IS NULL
OR pending_estimated_arrival_at >= pending_estimated_sailing_at)
) ENGINE=InnoDB COMMENT='独立海运订舱聚合;柜次仍由fl_container_booking承载';
CREATE TABLE IF NOT EXISTS fl_ocean_booking_party (
id BIGINT UNSIGNED NOT NULL COMMENT '订舱提单关系方ID',
ocean_booking_id BIGINT UNSIGNED NOT NULL COMMENT '所属海运订舱ID',
party_type VARCHAR(24) NOT NULL COMMENT '关系方类型:SHIPPER发货人、CONSIGNEE收货人、NOTIFY_PARTY通知人',
party_name VARCHAR(255) NOT NULL COMMENT '关系方名称快照',
address VARCHAR(1000) NULL COMMENT '关系方地址快照',
contact_name VARCHAR(128) NULL COMMENT '关系方联系人快照',
contact_phone VARCHAR(64) NULL COMMENT '关系方联系电话快照',
PRIMARY KEY (id),
UNIQUE KEY uk_ocean_booking_party (ocean_booking_id,party_type),
CONSTRAINT fk_ocean_booking_party FOREIGN KEY (ocean_booking_id) REFERENCES fl_ocean_booking(id),
CONSTRAINT chk_ocean_booking_party_type CHECK (party_type IN ('SHIPPER','CONSIGNEE','NOTIFY_PARTY'))
) ENGINE=InnoDB COMMENT='订舱提单关系方快照';
CREATE TABLE IF NOT EXISTS fl_ocean_booking_container_requirement (
id BIGINT UNSIGNED NOT NULL COMMENT '订舱箱型需求ID',
ocean_booking_id BIGINT UNSIGNED NOT NULL COMMENT '所属海运订舱ID',
container_type VARCHAR(32) NOT NULL COMMENT '所需集装箱尺寸类型编码',
container_kind VARCHAR(32) NOT NULL COMMENT '所需集装箱箱种编码',
capacity_cbm DECIMAL(18,4) NOT NULL COMMENT '该需求柜型可用容量快照m3',
max_weight_kg DECIMAL(18,3) NOT NULL COMMENT '该需求柜型最大承重快照kg',
quantity INT UNSIGNED NOT NULL COMMENT '该箱型箱种所需柜数',
sort_no INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '订舱需求展示顺序',
PRIMARY KEY (id),
KEY idx_ocean_requirement_booking (ocean_booking_id,sort_no,id),
CONSTRAINT fk_ocean_requirement_booking FOREIGN KEY (ocean_booking_id) REFERENCES fl_ocean_booking(id),
CONSTRAINT chk_ocean_requirement_quantity CHECK (quantity > 0)
) ENGINE=InnoDB COMMENT='订舱箱型箱种及数量需求';
CREATE TABLE IF NOT EXISTS fl_ocean_booking_container_allocation (
id BIGINT UNSIGNED NOT NULL COMMENT '订舱柜次分配关系ID',
ocean_booking_id BIGINT UNSIGNED NOT NULL COMMENT '所属海运订舱ID',
requirement_id BIGINT UNSIGNED NOT NULL COMMENT '对应订舱箱型需求ID',
container_booking_id BIGINT UNSIGNED NOT NULL COMMENT '明确引用现有柜次ID',
allocated_by BIGINT UNSIGNED NOT NULL COMMENT '分配操作人ID',
allocated_by_name VARCHAR(64) NULL COMMENT '分配操作人姓名快照',
allocated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '柜次分配时间',
PRIMARY KEY (id),
UNIQUE KEY uk_ocean_allocation_container (container_booking_id),
KEY idx_ocean_allocation_booking (ocean_booking_id,requirement_id,allocated_at),
CONSTRAINT fk_ocean_allocation_booking FOREIGN KEY (ocean_booking_id) REFERENCES fl_ocean_booking(id),
CONSTRAINT fk_ocean_allocation_requirement FOREIGN KEY (requirement_id)
REFERENCES fl_ocean_booking_container_requirement(id),
CONSTRAINT fk_ocean_allocation_container FOREIGN KEY (container_booking_id) REFERENCES fl_container_booking(id)
) ENGINE=InnoDB COMMENT='订舱需求与现有柜次的唯一分配关系';
CREATE TABLE IF NOT EXISTS fl_ocean_booking_status_history (
id BIGINT UNSIGNED NOT NULL COMMENT '订舱状态历史ID',
ocean_booking_id BIGINT UNSIGNED NOT NULL COMMENT '所属海运订舱ID',
from_status VARCHAR(24) NULL COMMENT '变更前订舱状态;创建时为空',
to_status VARCHAR(24) NOT NULL COMMENT '变更后订舱状态',
action VARCHAR(40) 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_ocean_history_booking (ocean_booking_id,created_at,id),
CONSTRAINT fk_ocean_history_booking FOREIGN KEY (ocean_booking_id) REFERENCES fl_ocean_booking(id)
) ENGINE=InnoDB COMMENT='海运订舱不可变状态历史';
CREATE TABLE IF NOT EXISTS fl_ocean_booking_operation (
id BIGINT UNSIGNED NOT NULL COMMENT '订舱写操作幂等记录ID',
ocean_booking_id BIGINT UNSIGNED NULL COMMENT '关联海运订舱ID;创建请求完成前允许为空',
operator_id BIGINT UNSIGNED NOT NULL COMMENT '发起写操作的平台操作人ID',
operation VARCHAR(40) 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_ocean_operation (operator_id,operation,idempotency_key),
KEY idx_ocean_operation_booking (ocean_booking_id,created_at),
CONSTRAINT fk_ocean_operation_booking FOREIGN KEY (ocean_booking_id) REFERENCES fl_ocean_booking(id),
CONSTRAINT chk_ocean_operation_status CHECK (status IN ('PROCESSING','SUCCESS'))
) ENGINE=InnoDB COMMENT='海运订舱写操作幂等与请求哈希';
DROP TRIGGER IF EXISTS trg_ocean_booking_before_update;
DELIMITER $$
CREATE TRIGGER trg_ocean_booking_before_update BEFORE UPDATE ON fl_ocean_booking FOR EACH ROW
BEGIN
IF OLD.status IN ('RELEASED','CANCELLED') THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='已放舱或已取消订舱不可修改';
END IF;
IF OLD.status <> NEW.status AND NOT (
(OLD.status='DRAFT' AND NEW.status IN ('SUBMITTED','CANCELLED'))
OR (OLD.status='SUBMITTED' AND NEW.status IN ('CONFIRMED','FAILED','CANCELLED'))
OR (OLD.status='CONFIRMED' AND NEW.status IN ('RELEASED','CHANGE_PENDING','CANCELLED'))
OR (OLD.status='CHANGE_PENDING' AND NEW.status IN ('CONFIRMED','FAILED','CANCELLED'))
OR (OLD.status='FAILED' AND NEW.status='CANCELLED')) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='海运订舱状态迁移非法';
END IF;
END$$
DELIMITER ;
DROP TRIGGER IF EXISTS trg_ocean_party_before_insert;
DROP TRIGGER IF EXISTS trg_ocean_party_before_update;
DROP TRIGGER IF EXISTS trg_ocean_party_before_delete;
DELIMITER $$
CREATE TRIGGER trg_ocean_party_before_insert BEFORE INSERT ON fl_ocean_booking_party FOR EACH ROW
BEGIN IF (SELECT status FROM fl_ocean_booking WHERE id=NEW.ocean_booking_id) <> 'DRAFT' THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='仅草稿可维护订舱方'; END IF; END$$
CREATE TRIGGER trg_ocean_party_before_update BEFORE UPDATE ON fl_ocean_booking_party FOR EACH ROW
BEGIN IF (SELECT status FROM fl_ocean_booking WHERE id=OLD.ocean_booking_id) <> 'DRAFT' THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='仅草稿可维护订舱方'; END IF; END$$
CREATE TRIGGER trg_ocean_party_before_delete BEFORE DELETE ON fl_ocean_booking_party FOR EACH ROW
BEGIN IF (SELECT status FROM fl_ocean_booking WHERE id=OLD.ocean_booking_id) <> 'DRAFT' THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='仅草稿可维护订舱方'; END IF; END$$
DELIMITER ;
DROP TRIGGER IF EXISTS trg_ocean_requirement_before_insert;
DROP TRIGGER IF EXISTS trg_ocean_requirement_before_update;
DROP TRIGGER IF EXISTS trg_ocean_requirement_before_delete;
DELIMITER $$
CREATE TRIGGER trg_ocean_requirement_before_insert BEFORE INSERT ON fl_ocean_booking_container_requirement FOR EACH ROW
BEGIN IF (SELECT status FROM fl_ocean_booking WHERE id=NEW.ocean_booking_id) <> 'DRAFT' THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='仅草稿可维护箱型需求'; END IF; END$$
CREATE TRIGGER trg_ocean_requirement_before_update BEFORE UPDATE ON fl_ocean_booking_container_requirement FOR EACH ROW
BEGIN IF (SELECT status FROM fl_ocean_booking WHERE id=OLD.ocean_booking_id) <> 'DRAFT' THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='仅草稿可维护箱型需求'; END IF; END$$
CREATE TRIGGER trg_ocean_requirement_before_delete BEFORE DELETE ON fl_ocean_booking_container_requirement FOR EACH ROW
BEGIN IF (SELECT status FROM fl_ocean_booking WHERE id=OLD.ocean_booking_id) <> 'DRAFT' THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='仅草稿可维护箱型需求'; END IF; END$$
DELIMITER ;
DROP TRIGGER IF EXISTS trg_ocean_allocation_before_insert;
DELIMITER $$
CREATE TRIGGER trg_ocean_allocation_before_insert BEFORE INSERT ON fl_ocean_booking_container_allocation FOR EACH ROW
BEGIN
IF (SELECT ocean_booking_id FROM fl_ocean_booking_container_requirement WHERE id=NEW.requirement_id) <> NEW.ocean_booking_id THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='箱型需求不属于当前订舱';
END IF;
IF (SELECT status FROM fl_ocean_booking WHERE id=NEW.ocean_booking_id) NOT IN
('SUBMITTED','CONFIRMED','CHANGE_PENDING') THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='当前订舱状态不可分配柜次';
END IF;
END$$
DELIMITER ;
DROP TRIGGER IF EXISTS trg_ocean_history_before_update;
DROP TRIGGER IF EXISTS trg_ocean_history_before_delete;
DELIMITER $$
CREATE TRIGGER trg_ocean_history_before_update BEFORE UPDATE ON fl_ocean_booking_status_history FOR EACH ROW
BEGIN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='订舱状态历史不可修改'; END$$
CREATE TRIGGER trg_ocean_history_before_delete BEFORE DELETE ON fl_ocean_booking_status_history FOR EACH ROW
BEGIN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='订舱状态历史不可删除'; END$$
DELIMITER ;
USE baiyue_iam;
DROP PROCEDURE IF EXISTS assert_ocean_booking_permission_ids_available;
DELIMITER $$
CREATE PROCEDURE assert_ocean_booking_permission_ids_available()
BEGIN
IF EXISTS (
SELECT 1 FROM iam_permission
WHERE (id BETWEEN 5102140 AND 5102149
AND permission_code <> CASE id
WHEN 5102140 THEN 'platform:firstleg:ocean-booking:list'
WHEN 5102141 THEN 'platform:firstleg:ocean-booking:view'
WHEN 5102142 THEN 'platform:firstleg:ocean-booking:create'
WHEN 5102143 THEN 'platform:firstleg:ocean-booking:edit'
WHEN 5102144 THEN 'platform:firstleg:ocean-booking:submit'
WHEN 5102145 THEN 'platform:firstleg:ocean-booking:confirm'
WHEN 5102146 THEN 'platform:firstleg:ocean-booking:release'
WHEN 5102147 THEN 'platform:firstleg:ocean-booking:cancel'
WHEN 5102148 THEN 'platform:firstleg:ocean-booking:change'
WHEN 5102149 THEN 'platform:firstleg:ocean-booking:allocate'
END)
OR (permission_code LIKE 'platform:firstleg:ocean-booking:%'
AND (tenant_id <> 0 OR app_code <> 'PLATFORM' OR id NOT BETWEEN 5102140 AND 5102149))
) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '三期订舱权限迁移失败:5102140-5102149 ID 或权限码被其他语义占用';
END IF;
END$$
DELIMITER ;
CALL assert_ocean_booking_permission_ids_available();
DROP PROCEDURE IF EXISTS assert_ocean_booking_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
(5102140,0,'PLATFORM','MENU',5102000,'platform:firstleg:ocean-booking:list','订舱管理','/first-leg/ocean-bookings','GET',80,'ACTIVE',1,0),
(5102141,0,'PLATFORM','BUTTON',5102140,'platform:firstleg:ocean-booking:view','查看订舱',NULL,'GET',10,'ACTIVE',1,0),
(5102142,0,'PLATFORM','BUTTON',5102140,'platform:firstleg:ocean-booking:create','创建订舱',NULL,'POST',20,'ACTIVE',1,0),
(5102143,0,'PLATFORM','BUTTON',5102140,'platform:firstleg:ocean-booking:edit','编辑订舱',NULL,'PUT',30,'ACTIVE',1,0),
(5102144,0,'PLATFORM','BUTTON',5102140,'platform:firstleg:ocean-booking:submit','提交订舱',NULL,'POST',40,'ACTIVE',1,0),
(5102145,0,'PLATFORM','BUTTON',5102140,'platform:firstleg:ocean-booking:confirm','确认订舱',NULL,'POST',50,'ACTIVE',1,0),
(5102146,0,'PLATFORM','BUTTON',5102140,'platform:firstleg:ocean-booking:release','确认放舱',NULL,'POST',60,'ACTIVE',1,0),
(5102147,0,'PLATFORM','BUTTON',5102140,'platform:firstleg:ocean-booking:cancel','取消订舱',NULL,'POST',70,'ACTIVE',1,0),
(5102148,0,'PLATFORM','BUTTON',5102140,'platform:firstleg:ocean-booking:change','变更船期',NULL,'POST',80,'ACTIVE',1,0),
(5102149,0,'PLATFORM','BUTTON',5102140,'platform:firstleg:ocean-booking:allocate','分配柜次',NULL,'POST',90,'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.permission_code LIKE 'platform:firstleg:ocean-booking:%'
WHERE role.tenant_id=0 AND role.role_code IN ('PLATFORM_ADMIN','ORDER_OPERATOR');
USE baiyue_firstleg;
SET NAMES utf8mb4;
CREATE TABLE IF NOT EXISTS fl_truck_appointment (
id BIGINT UNSIGNED NOT NULL COMMENT '拖车预约ID', appointment_no VARCHAR(40) NOT NULL COMMENT '平台拖车预约编号', outbound_order_id BIGINT UNSIGNED NULL COMMENT '关联头程订单逻辑ID;不跨库建外键',
ocean_booking_id BIGINT UNSIGNED NULL COMMENT '关联海运订舱逻辑ID', container_booking_id BIGINT UNSIGNED NULL COMMENT '关联柜次逻辑ID;拖车完成供装柜门禁校验', loading_plan_id BIGINT UNSIGNED NULL COMMENT '关联装柜计划逻辑ID',
warehouse_id BIGINT UNSIGNED NULL COMMENT '预约到仓的国内收货仓逻辑ID', customer_id BIGINT UNSIGNED NULL COMMENT '客户ID和数据隔离键', customer_name VARCHAR(128) NULL COMMENT '客户名称快照',
carrier_id BIGINT UNSIGNED NULL COMMENT '拖车服务商逻辑ID', carrier_name VARCHAR(128) NULL COMMENT '拖车服务商名称快照', fleet_contact VARCHAR(64) NULL COMMENT '车队联系人', fleet_phone VARCHAR(32) NULL COMMENT '车队联系电话',
driver_name VARCHAR(64) NULL COMMENT '司机姓名', driver_phone VARCHAR(32) NULL COMMENT '司机联系电话', driver_id_card VARCHAR(32) NULL COMMENT '司机身份证号;平台内部敏感信息', vehicle_no VARCHAR(32) NULL COMMENT '车辆车牌号',
store_name VARCHAR(128) NULL COMMENT '到仓装柜地点名称快照', store_contact VARCHAR(64) NULL COMMENT '到仓地点联系人快照', store_phone VARCHAR(32) NULL COMMENT '到仓地点联系电话快照', store_address VARCHAR(255) NULL COMMENT '到仓装柜地点地址快照',
store_city VARCHAR(64) NULL COMMENT '到仓地点城市快照', store_zipcode VARCHAR(16) NULL COMMENT '到仓地点邮编快照', package_count INT UNSIGNED NULL COMMENT '预约货物包装件数快照', gross_weight_kg DECIMAL(18,3) NULL COMMENT '预约货物毛重,单位千克',
volume_cbm DECIMAL(18,4) NULL COMMENT '预约货物体积,单位立方米', pallet_count INT UNSIGNED NULL COMMENT '预约托盘数量', po_no VARCHAR(255) NULL COMMENT '关联采购单号或客户PO号', planned_loading_at DATETIME(3) NULL COMMENT '预约到仓装柜时间',
actual_arrived_warehouse_at DATETIME(3) NULL COMMENT '车辆实际到达收货仓时间', actual_loading_at DATETIME(3) NULL COMMENT '实际开始装柜时间', actual_departed_at DATETIME(3) NULL COMMENT '车辆实际离仓时间;DEPARTED后供LOADED门禁使用', transit_minutes INT UNSIGNED NULL COMMENT '从到仓到离仓的运输作业耗时,单位分钟', payable_amount DECIMAL(18,2) NULL COMMENT '应付拖车服务商金额;币种由currency指定', receivable_amount DECIMAL(18,2) NULL COMMENT '向客户收取的拖车金额;币种由currency指定',
currency VARCHAR(8) NOT NULL DEFAULT 'RMB' COMMENT '金额币种;首期默认RMB', status VARCHAR(32) NOT NULL DEFAULT 'DRAFT' COMMENT '拖车预约状态:DRAFT草稿、PENDING_DISPATCH待派车、DISPATCHED已派车、PICKUP_IN_PROGRESS提货中、ARRIVED_WAREHOUSE已到仓、LOADING装柜中、DEPARTED已离仓、COMPLETED已完成、RESCHEDULED已改约、EXCEPTION异常、CANCELLED已取消', revision INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '拖车预约业务修订号;每次状态或改约递增',
version INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '乐观锁版本号', remark VARCHAR(1000) NULL COMMENT '平台内部备注或异常说明', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '拖车预约创建时间',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '拖车预约最后更新时间', PRIMARY KEY(id), UNIQUE KEY uk_truck_appointment_no(appointment_no),
KEY idx_truck_appointment_status(status,updated_at), KEY idx_truck_appointment_container(container_booking_id), KEY idx_truck_appointment_customer(customer_id),
CONSTRAINT chk_truck_appointment_status CHECK(status IN ('DRAFT','PENDING_DISPATCH','DISPATCHED','PICKUP_IN_PROGRESS','ARRIVED_WAREHOUSE','LOADING','DEPARTED','COMPLETED','RESCHEDULED','EXCEPTION','CANCELLED'))
) ENGINE=InnoDB COMMENT='三期拖车预约派车聚合';
CREATE TABLE IF NOT EXISTS fl_truck_appointment_history (
id BIGINT UNSIGNED NOT NULL COMMENT '拖车预约历史ID', appointment_id BIGINT UNSIGNED NOT NULL COMMENT '所属拖车预约ID', revision INT UNSIGNED NOT NULL COMMENT '该预约业务修订号', action VARCHAR(32) NOT NULL COMMENT '触发状态或改约的业务动作',
from_status VARCHAR(32) NULL COMMENT '变更前拖车状态;创建时为空', to_status VARCHAR(32) NULL COMMENT '变更后拖车状态;改约时可保持原状态', reason VARCHAR(500) NULL COMMENT '状态或改约原因', operator_id BIGINT UNSIGNED NOT NULL COMMENT '执行操作的平台用户ID',
operator_name VARCHAR(64) NULL COMMENT '操作人姓名快照', request_id VARCHAR(128) NULL COMMENT '请求链路中的请求ID', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '历史记录创建时间', PRIMARY KEY(id), KEY idx_truck_history(appointment_id,revision)
) ENGINE=InnoDB COMMENT='拖车状态与改约历史';
CREATE TABLE IF NOT EXISTS fl_truck_appointment_attachment (
id BIGINT UNSIGNED NOT NULL COMMENT '拖车附件关系ID', appointment_id BIGINT UNSIGNED NOT NULL COMMENT '所属拖车预约ID', file_id BIGINT UNSIGNED NOT NULL COMMENT 'File服务文件逻辑ID', attachment_type VARCHAR(32) NOT NULL COMMENT '附件类型编码;由业务动作约定',
file_name VARCHAR(255) NULL COMMENT '上传时文件名快照', file_hash CHAR(64) NULL COMMENT '文件内容SHA-256摘要', visibility VARCHAR(24) NOT NULL DEFAULT 'INTERNAL' COMMENT '可见范围:INTERNAL仅平台内部、CLIENT客户可见', uploaded_by BIGINT UNSIGNED NOT NULL COMMENT '上传操作人ID',
uploaded_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '附件上传时间', PRIMARY KEY(id), UNIQUE KEY uk_truck_attachment(appointment_id,file_id), KEY idx_truck_attachment(appointment_id)
) ENGINE=InnoDB COMMENT='拖车附件文件引用';
CREATE TABLE IF NOT EXISTS fl_truck_appointment_operation (
id BIGINT UNSIGNED NOT NULL COMMENT '拖车写操作幂等记录ID', appointment_id BIGINT UNSIGNED NOT NULL COMMENT '所属拖车预约ID', idempotency_key VARCHAR(128) NOT NULL COMMENT '客户端写操作幂等键', request_hash CHAR(64) NOT NULL COMMENT '规范化请求内容SHA-256摘要;用于识别同键不同请求',
action VARCHAR(32) NOT NULL COMMENT '执行的拖车业务动作', response_snapshot JSON NULL COMMENT '成功响应JSON快照;供相同幂等请求重放', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '幂等记录创建时间', PRIMARY KEY(id), UNIQUE KEY uk_truck_operation(idempotency_key), KEY idx_truck_operation_appointment(appointment_id)
) ENGINE=InnoDB COMMENT='拖车操作幂等记录';
DROP PROCEDURE IF EXISTS phase3_truck_add_column;
DELIMITER $$
CREATE PROCEDURE phase3_truck_add_column(
IN p_column_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_truck_appointment'
AND column_name = p_column_name
) THEN
SET @phase3_truck_ddl = CONCAT(
'ALTER TABLE fl_truck_appointment ADD COLUMN `', p_column_name, '` ', p_definition
);
PREPARE phase3_truck_stmt FROM @phase3_truck_ddl;
EXECUTE phase3_truck_stmt;
DEALLOCATE PREPARE phase3_truck_stmt;
END IF;
END$$
DELIMITER ;
CALL phase3_truck_add_column(
'actual_arrived_warehouse_at',
'DATETIME(3) NULL AFTER planned_loading_at'
);
CALL phase3_truck_add_column(
'actual_departed_at',
'DATETIME(3) NULL AFTER actual_loading_at'
);
DROP PROCEDURE IF EXISTS phase3_truck_add_column;
USE baiyue_iam;
DROP PROCEDURE IF EXISTS assert_truck_appointment_permission_ids_available;
DELIMITER $$
CREATE PROCEDURE assert_truck_appointment_permission_ids_available()
BEGIN
IF EXISTS (
SELECT 1 FROM iam_permission
WHERE (id BETWEEN 5102150 AND 5102154
AND permission_code <> CASE id
WHEN 5102150 THEN 'platform:firstleg:truck-appointment:list'
WHEN 5102151 THEN 'platform:firstleg:truck-appointment:view'
WHEN 5102152 THEN 'platform:firstleg:truck-appointment:create'
WHEN 5102153 THEN 'platform:firstleg:truck-appointment:edit'
WHEN 5102154 THEN 'platform:firstleg:truck-appointment:operate'
END)
OR (permission_code LIKE 'platform:firstleg:truck-appointment:%'
AND (tenant_id <> 0 OR app_code <> 'PLATFORM' OR id NOT BETWEEN 5102150 AND 5102154))
) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '三期拖车权限迁移失败:5102150-5102154 ID 或权限码被其他语义占用';
END IF;
END$$
DELIMITER ;
CALL assert_truck_appointment_permission_ids_available();
DROP PROCEDURE IF EXISTS assert_truck_appointment_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
(5102150,0,'PLATFORM','MENU',5102000,'platform:firstleg:truck-appointment:list','拖车预约','/first-leg/truck-appointments','GET',90,'ACTIVE',1,0),
(5102151,0,'PLATFORM','BUTTON',5102150,'platform:firstleg:truck-appointment:view','查看拖车预约',NULL,'GET',10,'ACTIVE',1,0),
(5102152,0,'PLATFORM','BUTTON',5102150,'platform:firstleg:truck-appointment:create','创建拖车预约',NULL,'POST',20,'ACTIVE',1,0),
(5102153,0,'PLATFORM','BUTTON',5102150,'platform:firstleg:truck-appointment:edit','编辑拖车预约',NULL,'PUT',30,'ACTIVE',1,0),
(5102154,0,'PLATFORM','BUTTON',5102150,'platform:firstleg:truck-appointment:operate','操作拖车预约',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:truck-appointment:%'
WHERE role.tenant_id=0 AND role.role_code IN ('PLATFORM_ADMIN','ORDER_OPERATOR');
USE baiyue_firstleg;
DROP PROCEDURE IF EXISTS assert_phase3_service_center_schema;
DELIMITER $$
CREATE PROCEDURE assert_phase3_service_center_schema()
BEGIN
IF (SELECT COUNT(*) FROM information_schema.tables
WHERE table_schema='baiyue_firstleg'
AND table_name IN ('fl_ocean_booking','fl_ocean_booking_party',
'fl_ocean_booking_container_requirement','fl_ocean_booking_container_allocation',
'fl_ocean_booking_status_history','fl_ocean_booking_operation',
'fl_truck_appointment','fl_truck_appointment_history',
'fl_truck_appointment_attachment','fl_truck_appointment_operation')) <> 10 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 5102140 AND 5102149
AND permission_code LIKE 'platform:firstleg:ocean-booking:%') <> 10
OR (SELECT COUNT(*) FROM baiyue_iam.iam_permission
WHERE tenant_id=0 AND app_code='PLATFORM' AND id BETWEEN 5102150 AND 5102154
AND permission_code LIKE 'platform:firstleg:truck-appointment:%') <> 5 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:ocean-booking:%') <> 20
OR (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:truck-appointment:%') <> 10 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='统一迁移失败:订舱或拖车预约角色授权未完整写入';
END IF;
END$$
DELIMITER ;
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 COMMENT '国内出仓调度记录ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
order_id BIGINT UNSIGNED NOT NULL COMMENT 'Order服务订单逻辑ID,不复制订单主数据',
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '关联入库单逻辑ID',
container_booking_id BIGINT UNSIGNED NOT NULL COMMENT '关联柜次逻辑ID',
status VARCHAR(32) NOT NULL DEFAULT 'READY_FOR_DISPATCH' COMMENT '出仓状态:READY_FOR_DISPATCH待确认出仓、WAITING_FOR_LOADING等待装柜',
version INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '乐观锁版本号,每次更新递增',
confirmed_by BIGINT UNSIGNED NULL COMMENT '确认国内出仓的操作人ID',
confirmed_by_name VARCHAR(64) NULL COMMENT '确认国内出仓的操作人姓名快照',
confirmed_at DATETIME(3) NULL COMMENT '国内出仓确认时间',
reversed_by BIGINT UNSIGNED NULL COMMENT '撤销国内出仓的操作人ID',
reversed_by_name VARCHAR(64) NULL COMMENT '撤销国内出仓的操作人姓名快照',
reversed_at DATETIME(3) NULL COMMENT '国内出仓撤销时间',
remark VARCHAR(500) NULL COMMENT '国内出仓调度备注',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '出仓调度记录创建时间',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '出仓调度记录最后更新时间',
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 COMMENT '出仓状态历史记录ID',
dispatch_id BIGINT UNSIGNED NOT NULL COMMENT '关联国内出仓调度记录ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '关联入库单逻辑ID快照',
container_booking_id BIGINT UNSIGNED NOT NULL COMMENT '关联柜次逻辑ID快照',
action VARCHAR(32) NOT NULL COMMENT '出仓状态变更动作',
from_status VARCHAR(32) NULL COMMENT '变更前出仓状态;首次创建时为空',
to_status VARCHAR(32) NOT NULL COMMENT '变更后出仓状态',
remark VARCHAR(500) 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_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 COMMENT '出仓幂等操作记录ID',
dispatch_id BIGINT UNSIGNED NULL COMMENT '首次声明幂等键时允许为空',
operator_id BIGINT UNSIGNED NOT NULL COMMENT '执行写操作的平台用户ID',
operation VARCHAR(32) 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成功',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '幂等声明创建时间',
completed_at DATETIME(3) NULL COMMENT '幂等操作完成时间',
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 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;
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 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_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='报关代理提交批次及冻结文件版本';
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;
-- Phase 3 loading evidence hardening for existing databases. Evidence is append-only:
-- multiple active/void records of the same type are allowed; voiding is an audit action.
USE baiyue_firstleg;
DROP PROCEDURE IF EXISTS phase3_loading_evidence_add_column;
DELIMITER $$
CREATE PROCEDURE phase3_loading_evidence_add_column(IN p_column VARCHAR(64), IN p_definition VARCHAR(255))
BEGIN
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.columns
WHERE table_schema=DATABASE() AND table_name='fl_loading_evidence' AND column_name=p_column),
CONCAT('ALTER TABLE fl_loading_evidence ADD COLUMN `', p_column, '` ', p_definition),
'SELECT 1');
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
END$$
DELIMITER ;
CALL phase3_loading_evidence_add_column('status', 'VARCHAR(16) NOT NULL DEFAULT ''ACTIVE'' COMMENT ''ACTIVE/VOID''');
CALL phase3_loading_evidence_add_column('void_reason', 'VARCHAR(500) NULL');
CALL phase3_loading_evidence_add_column('voided_by', 'BIGINT UNSIGNED NULL');
CALL phase3_loading_evidence_add_column('voided_by_name', 'VARCHAR(64) NULL');
CALL phase3_loading_evidence_add_column('voided_at', 'DATETIME(3) NULL');
DROP PROCEDURE IF EXISTS phase3_loading_evidence_add_column;
UPDATE fl_loading_evidence SET status='ACTIVE' WHERE status IS NULL OR status='';
UPDATE fl_loading_evidence SET seal_no=NULL WHERE evidence_type<>'SEAL' AND seal_no IS NOT NULL;
SET @ddl = IF(
EXISTS(SELECT 1 FROM information_schema.statistics
WHERE table_schema=DATABASE() AND table_name='fl_loading_evidence'
AND index_name='uk_loading_evidence_type'),
'ALTER TABLE fl_loading_evidence DROP INDEX uk_loading_evidence_type', 'SELECT 1');
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @ddl = IF(
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 DROP INDEX uk_loading_evidence_seal', 'SELECT 1');
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @ddl = IF(
NOT EXISTS(SELECT 1 FROM information_schema.statistics
WHERE table_schema=DATABASE() AND table_name='fl_loading_evidence'
AND index_name='idx_loading_evidence_type'),
'ALTER TABLE fl_loading_evidence ADD KEY idx_loading_evidence_type (tenant_id,inbound_id,evidence_type,status)',
'SELECT 1');
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @ddl = IF(
NOT EXISTS(SELECT 1 FROM information_schema.statistics
WHERE table_schema=DATABASE() AND table_name='fl_loading_evidence'
AND index_name='idx_loading_evidence_seal'),
'ALTER TABLE fl_loading_evidence ADD KEY idx_loading_evidence_seal (tenant_id,seal_no,status)',
'SELECT 1');
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
/* Phase 3: link inbound preload plans to one or more container loading plans. */
CREATE TABLE IF NOT EXISTS fl_preload_loading_plan_link (
id BIGINT UNSIGNED NOT NULL COMMENT '关联ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '入库单客户隔离ID',
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID',
preload_plan_id BIGINT UNSIGNED NOT NULL COMMENT '入库单级预装计划ID',
loading_plan_id BIGINT UNSIGNED NOT NULL COMMENT '柜次级装柜计划ID',
idempotency_key VARCHAR(128) NOT NULL COMMENT '建立关联幂等键',
request_hash CHAR(64) NOT NULL COMMENT '建立关联请求SHA-256',
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_preload_loading_pair (tenant_id, inbound_id, loading_plan_id),
UNIQUE KEY uk_preload_loading_idempotency (created_by, idempotency_key),
KEY idx_preload_loading_preload (preload_plan_id, loading_plan_id),
KEY idx_preload_loading_plan (loading_plan_id, tenant_id, inbound_id),
CONSTRAINT fk_preload_loading_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id),
CONSTRAINT fk_preload_loading_preload FOREIGN KEY (preload_plan_id) REFERENCES fl_preload_plan (id),
CONSTRAINT fk_preload_loading_plan FOREIGN KEY (loading_plan_id) REFERENCES fl_loading_plan (id),
CONSTRAINT chk_preload_loading_tenant CHECK (tenant_id > 0)
) ENGINE=InnoDB COMMENT='入库单预装与柜次装柜计划多对多关联';
INSERT IGNORE INTO fl_preload_loading_plan_link
(id, tenant_id, inbound_id, preload_plan_id, loading_plan_id, idempotency_key,
request_hash, created_by, created_by_name, created_at)
SELECT CAST(CONV(SUBSTRING(SHA2(CONCAT('PRELOAD_LOADING|', item.account_id, '|', item.inbound_id,
'|', item.loading_plan_id), 256), 1, 15), 16, 10) AS UNSIGNED),
item.account_id, item.inbound_id, preload.id, item.loading_plan_id,
CONCAT('BACKFILL-', item.loading_plan_id, '-', item.inbound_id),
SHA2(CONCAT('BACKFILL|', item.account_id, '|', item.inbound_id, '|', item.loading_plan_id), 256),
preload.created_by, preload.created_by_name, LEAST(preload.created_at, plan.created_at)
FROM (SELECT DISTINCT account_id, inbound_id, loading_plan_id
FROM fl_loading_plan_item WHERE deleted = 0) item
JOIN fl_preload_plan preload ON preload.tenant_id = item.account_id
AND preload.inbound_id = item.inbound_id
JOIN fl_loading_plan plan ON plan.id = item.loading_plan_id;
/* Phase 3: PDA assignment, immutable task operations and review records. */
CREATE TABLE IF NOT EXISTS fl_loading_pick_assignment (
pick_task_id BIGINT UNSIGNED NOT NULL COMMENT '柜次PDA拣货任务ID;同一任务仅保留一名作业员',
assignee_id BIGINT UNSIGNED NOT NULL COMMENT '当前唯一作业员ID',
assignee_name VARCHAR(64) NOT NULL COMMENT '当前作业员姓名快照',
assigned_by BIGINT UNSIGNED NOT NULL COMMENT '领取任务的作业员或执行分配的平台用户ID',
assigned_by_name VARCHAR(64) NULL COMMENT '领取或分配操作人姓名快照',
assignment_type VARCHAR(16) NOT NULL COMMENT '分配方式:CLAIMED作业员领取、ASSIGNED平台指派',
version INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '乐观锁版本号,防止多人并发领取或覆盖',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '任务领取或分配时间',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '作业员分配最后更新时间',
PRIMARY KEY (pick_task_id),
KEY idx_loading_pick_assignee (assignee_id, updated_at),
CONSTRAINT fk_loading_pick_assignment_task FOREIGN KEY (pick_task_id) REFERENCES fl_loading_pick_task (id),
CONSTRAINT chk_loading_pick_assignment_type CHECK (assignment_type IN ('CLAIMED', 'ASSIGNED'))
) ENGINE=InnoDB COMMENT='柜次PDA任务领取与作业员分配';
CREATE TABLE IF NOT EXISTS fl_loading_pick_review (
pick_task_id BIGINT UNSIGNED NOT NULL COMMENT '已完成的柜次PDA拣货任务ID',
review_status VARCHAR(16) NOT NULL DEFAULT 'APPROVED' COMMENT '复核状态:APPROVED已通过;记录创建后不可修改或删除',
reviewed_by BIGINT UNSIGNED NOT NULL COMMENT '执行完成复核的平台用户ID',
reviewed_by_name VARCHAR(64) NULL COMMENT '复核人姓名快照',
remark VARCHAR(500) NULL COMMENT 'PDA任务复核备注',
version INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '复核记录版本号',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '复核通过时间',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '复核记录最后更新时间;正常业务不允许更新',
PRIMARY KEY (pick_task_id),
KEY idx_loading_pick_reviewer (reviewed_by, created_at),
CONSTRAINT fk_loading_pick_review_task FOREIGN KEY (pick_task_id) REFERENCES fl_loading_pick_task (id),
CONSTRAINT chk_loading_pick_review_status CHECK (review_status = 'APPROVED')
) ENGINE=InnoDB COMMENT='柜次PDA任务完成复核';
CREATE TABLE IF NOT EXISTS fl_loading_pick_operation (
id BIGINT UNSIGNED NOT NULL COMMENT 'PDA任务操作审计记录ID',
pick_task_id BIGINT UNSIGNED NOT NULL COMMENT '关联柜次PDA拣货任务ID',
operation_id VARCHAR(128) NOT NULL COMMENT '全局唯一操作ID,用于幂等重放',
request_hash CHAR(64) NOT NULL COMMENT '完整批量请求或任务操作内容SHA-256摘要',
action_type VARCHAR(16) NOT NULL COMMENT '动作类型:CLAIM领取、ASSIGN分配、REVIEW复核、BATCH批量提交',
operator_id BIGINT UNSIGNED NOT NULL COMMENT '执行PDA任务操作的用户ID',
operator_name VARCHAR(64) NULL COMMENT '操作人姓名快照',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '不可变操作审计创建时间',
PRIMARY KEY (id),
UNIQUE KEY uk_loading_pick_operation_id (operation_id),
KEY idx_loading_pick_operation_task (pick_task_id, created_at),
CONSTRAINT fk_loading_pick_operation_task FOREIGN KEY (pick_task_id) REFERENCES fl_loading_pick_task (id),
CONSTRAINT chk_loading_pick_operation_action CHECK (action_type IN ('CLAIM', 'ASSIGN', 'ITEM_CLAIM', 'ITEM_ASSIGN', 'REVIEW', 'BATCH', 'COMPLETE_LOADING'))
) ENGINE=InnoDB COMMENT='柜次PDA任务级幂等审计';
DROP TRIGGER IF EXISTS trg_loading_pick_review_before_update;
DELIMITER $$
CREATE TRIGGER trg_loading_pick_review_before_update BEFORE UPDATE ON fl_loading_pick_review
FOR EACH ROW BEGIN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='PDA复核记录不可修改'; END$$
DELIMITER ;
DROP TRIGGER IF EXISTS trg_loading_pick_review_before_delete;
DELIMITER $$
CREATE TRIGGER trg_loading_pick_review_before_delete BEFORE DELETE ON fl_loading_pick_review
FOR EACH ROW BEGIN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='PDA复核记录不可删除'; END$$
DELIMITER ;
DROP TRIGGER IF EXISTS trg_loading_pick_operation_before_update;
DELIMITER $$
CREATE TRIGGER trg_loading_pick_operation_before_update BEFORE UPDATE ON fl_loading_pick_operation
FOR EACH ROW BEGIN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='PDA任务操作历史不可修改'; END$$
DELIMITER ;
DROP TRIGGER IF EXISTS trg_loading_pick_operation_before_delete;
DELIMITER $$
CREATE TRIGGER trg_loading_pick_operation_before_delete BEFORE DELETE ON fl_loading_pick_operation
FOR EACH ROW BEGIN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='PDA任务操作历史不可删除'; END$$
DELIMITER ;
USE baiyue_iam;
DROP PROCEDURE IF EXISTS assert_phase3_pda_permission_ids;
DELIMITER $$
CREATE PROCEDURE assert_phase3_pda_permission_ids()
BEGIN
IF EXISTS (SELECT 1 FROM iam_permission WHERE id BETWEEN 5102190 AND 5102193
AND permission_code NOT IN ('platform:firstleg:pda:view','platform:firstleg:pda:assign',
'platform:firstleg:pda:work','platform:firstleg:pda:review')) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='PDA权限ID已被其他资源占用';
END IF;
END$$
DELIMITER ;
CALL assert_phase3_pda_permission_ids();
DROP PROCEDURE IF EXISTS assert_phase3_pda_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
(5102190,0,'PLATFORM','MENU',5102094,'platform:firstleg:pda:view','PDA装柜拣货','/first-leg/pda-loading','GET',10,'ACTIVE',1,0),
(5102191,0,'PLATFORM','BUTTON',5102190,'platform:firstleg:pda:assign','分配PDA任务',NULL,'POST',20,'ACTIVE',1,0),
(5102192,0,'PLATFORM','BUTTON',5102190,'platform:firstleg:pda:work','执行PDA拣货',NULL,'POST',30,'ACTIVE',1,0),
(5102193,0,'PLATFORM','BUTTON',5102190,'platform:firstleg:pda:review','复核PDA任务',NULL,'POST',40,'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),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 r.id,p.id,0 FROM iam_role r JOIN iam_permission p ON p.id BETWEEN 5102190 AND 5102193
WHERE r.tenant_id=0 AND r.role_code IN ('PLATFORM_ADMIN','ORDER_OPERATOR') AND p.tenant_id=0 AND p.deleted=0;
USE baiyue_firstleg;
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',
'fl_preload_loading_plan_link','fl_loading_pick_assignment','fl_loading_pick_review',
'fl_loading_pick_operation')) <> 18 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='统一迁移失败:三期出仓、报关或船期表未完整创建';
END IF;
IF (SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema='baiyue_firstleg' AND table_name='fl_loading_evidence'
AND column_name IN ('status','void_reason','voided_by','voided_by_name','voided_at')) <> 5
OR (SELECT COUNT(*) FROM information_schema.statistics
WHERE table_schema='baiyue_firstleg' AND table_name='fl_loading_evidence'
AND index_name='uk_loading_evidence_type') <> 0
OR (SELECT COUNT(*) FROM information_schema.statistics
WHERE table_schema='baiyue_firstleg' AND table_name='fl_loading_evidence'
AND index_name='uk_loading_evidence_seal') <> 0
OR (SELECT COUNT(*) FROM information_schema.statistics
WHERE table_schema='baiyue_firstleg' AND table_name='fl_loading_evidence'
AND index_name='idx_loading_evidence_type' AND non_unique=1
AND ((seq_in_index=1 AND column_name='tenant_id')
OR (seq_in_index=2 AND column_name='inbound_id')
OR (seq_in_index=3 AND column_name='evidence_type')
OR (seq_in_index=4 AND column_name='status'))) <> 4
OR (SELECT COUNT(*) FROM information_schema.statistics
WHERE table_schema='baiyue_firstleg' AND table_name='fl_loading_evidence'
AND index_name='idx_loading_evidence_seal' AND non_unique=1
AND ((seq_in_index=1 AND column_name='tenant_id')
OR (seq_in_index=2 AND column_name='seal_no')
OR (seq_in_index=3 AND column_name='status'))) <> 3
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','fk_preload_loading_inbound','fk_preload_loading_preload',
'fk_preload_loading_plan','fk_loading_pick_assignment_task','fk_loading_pick_review_task',
'fk_loading_pick_operation_task')) <> 19 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',
'trg_loading_pick_review_before_update','trg_loading_pick_review_before_delete',
'trg_loading_pick_operation_before_update','trg_loading_pick_operation_before_delete')) <> 8 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(*) FROM baiyue_iam.iam_permission WHERE tenant_id=0 AND app_code='PLATFORM'
AND id BETWEEN 5102190 AND 5102193 AND permission_code LIKE 'platform:firstleg:pda:%') <> 4 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='统一迁移失败:PDA权限未完整写入';
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:%'
OR p.permission_code LIKE 'platform:firstleg:pda:%')) <> 38 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='统一迁移失败:三期出仓、报关或船期角色授权未完整写入';
END IF;
END$$
DELIMITER ;
CALL assert_phase3_final_schema();
DROP PROCEDURE IF EXISTS assert_phase3_final_schema;
-- PDA 部分拣货释放策略:NULL/AUTO 按比例释放,MANUAL + [] 表示人工明确不释放。
USE baiyue_firstleg;
DROP PROCEDURE IF EXISTS phase3_pda_add_column;
DELIMITER $$
CREATE PROCEDURE phase3_pda_add_column(
IN p_table_name VARCHAR(64), IN p_column_name VARCHAR(64), IN p_definition TEXT
)
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = p_table_name AND column_name = p_column_name
) THEN
SET @phase3_pda_column_ddl = CONCAT(
'ALTER TABLE `', p_table_name, '` ADD COLUMN `', p_column_name, '` ', p_definition
);
PREPARE phase3_pda_column_stmt FROM @phase3_pda_column_ddl;
EXECUTE phase3_pda_column_stmt;
DEALLOCATE PREPARE phase3_pda_column_stmt;
END IF;
END$$
DELIMITER ;
CALL phase3_pda_add_column(
'fl_loading_pick_item', 'release_mode',
"VARCHAR(10) NULL COMMENT 'AUTO按比例释放,MANUAL按人工选择释放' AFTER item_status"
);
CALL phase3_pda_add_column(
'fl_loading_pick_item', 'release_location_ids',
"JSON NULL COMMENT '人工选择释放库位ID;空数组表示明确释放0个' AFTER release_mode"
);
CALL phase3_pda_add_column(
'fl_inbound_pallet_allocation', 'reservation_id',
"BIGINT UNSIGNED NULL COMMENT '仓库预占资源唯一ID,A0共享场景用于区分托盘' AFTER location_code"
);
DROP PROCEDURE IF EXISTS phase3_pda_add_column;
-- PDA 多人协作:领取粒度由整柜任务下沉为单条拣货明细。
CREATE TABLE IF NOT EXISTS fl_loading_pick_item_assignment (
pick_item_id BIGINT UNSIGNED NOT NULL COMMENT 'PDA拣货明细ID,同时作为本表主键',
assignee_id BIGINT UNSIGNED NOT NULL COMMENT '当前领取或被分配的作业员ID',
assignee_name VARCHAR(64) NOT NULL COMMENT '当前作业员姓名快照',
assigned_by BIGINT UNSIGNED NOT NULL COMMENT '执行领取或分配操作的用户ID',
assigned_by_name VARCHAR(64) NULL COMMENT '执行领取或分配操作的用户姓名快照',
assignment_type VARCHAR(16) NOT NULL COMMENT '分配类型:CLAIMED作业员领取、ASSIGNED管理员分配',
version INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '乐观锁版本号,每次更新递增',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '明细分配记录创建时间',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '明细分配记录最后更新时间',
PRIMARY KEY (pick_item_id),
KEY idx_loading_pick_item_assignee (assignee_id, updated_at),
CONSTRAINT fk_loading_pick_item_assignment_item FOREIGN KEY (pick_item_id) REFERENCES fl_loading_pick_item (id),
CONSTRAINT chk_loading_pick_item_assignment_type CHECK (assignment_type IN ('CLAIMED', 'ASSIGNED'))
) ENGINE=InnoDB COMMENT='PDA拣货明细领取与作业员分配';
DROP PROCEDURE IF EXISTS phase3_pda_replace_operation_check;
DELIMITER $$
CREATE PROCEDURE phase3_pda_replace_operation_check()
BEGIN
IF EXISTS (
SELECT 1 FROM information_schema.table_constraints
WHERE constraint_schema = DATABASE()
AND table_name = 'fl_loading_pick_operation'
AND constraint_name = 'chk_loading_pick_operation_action'
AND constraint_type = 'CHECK'
) THEN
ALTER TABLE fl_loading_pick_operation DROP CHECK chk_loading_pick_operation_action;
END IF;
ALTER TABLE fl_loading_pick_operation
ADD CONSTRAINT chk_loading_pick_operation_action
CHECK (action_type IN ('CLAIM', 'ASSIGN', 'ITEM_CLAIM', 'ITEM_ASSIGN', 'REVIEW', 'BATCH', 'COMPLETE_LOADING'));
END$$
DELIMITER ;
CALL phase3_pda_replace_operation_check();
DROP PROCEDURE IF EXISTS phase3_pda_replace_operation_check;
DROP PROCEDURE IF EXISTS assert_phase3_pda_item_assignment_schema;
DELIMITER $$
CREATE PROCEDURE assert_phase3_pda_item_assignment_schema()
BEGIN
IF (SELECT COUNT(*) FROM information_schema.tables
WHERE table_schema='baiyue_firstleg' AND table_name='fl_loading_pick_item_assignment') <> 1
OR (SELECT COUNT(*) FROM information_schema.statistics
WHERE table_schema='baiyue_firstleg' AND table_name='fl_loading_pick_item_assignment'
AND index_name='idx_loading_pick_item_assignee') <> 2
OR (SELECT COUNT(*) FROM information_schema.referential_constraints
WHERE constraint_schema='baiyue_firstleg'
AND constraint_name='fk_loading_pick_item_assignment_item') <> 1 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='PDA明细级多人分配表创建失败';
END IF;
END$$
DELIMITER ;
CALL assert_phase3_pda_item_assignment_schema();
DROP PROCEDURE IF EXISTS assert_phase3_pda_item_assignment_schema;
-- 来源:firstleg/src/main/resources/db/phase3-carrier-schedule-query-schema.sql
SET NAMES utf8mb4;
USE baiyue_firstleg;
CREATE TABLE IF NOT EXISTS fl_carrier_schedule_query (
id BIGINT UNSIGNED NOT NULL COMMENT '船司官网查询任务ID',
query_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;整票订舱查询时为空',
container_scope_id BIGINT UNSIGNED GENERATED ALWAYS AS (COALESCE(container_booking_id,0)) STORED COMMENT '柜次唯一作用域计算值;整票订舱查询固定为0',
carrier_code VARCHAR(16) NOT NULL COMMENT '船公司编码:MATSON美森、SML森罗、ZIM以星、COSCO中远海运、EMC长荣、HEDE合德',
tracking_type VARCHAR(32) NOT NULL COMMENT '官网跟踪类型:BOOKING_NO订舱号、BILL_OF_LADING提单号、CONTAINER_NO箱号',
tracking_no VARCHAR(128) NOT NULL COMMENT '提交至船公司官网的跟踪号码',
official_url VARCHAR(500) NOT NULL COMMENT '船公司官网查询入口地址快照',
access_mode VARCHAR(32) NOT NULL COMMENT '官网访问方式:PUBLIC_FORM公开表单、HCAPTCHA人机校验、DYNAMIC_IFRAME动态页面',
status VARCHAR(24) NOT NULL COMMENT '任务状态:READY可查询、MANUAL_REQUIRED需人工处理、SUCCESS已取得有效结果',
window_started_at DATETIME(3) NOT NULL COMMENT '当前三天查询窗口起始时间',
attempt_count INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '当前查询窗口已执行次数;最多3次',
next_allowed_at DATETIME(3) NOT NULL COMMENT '频控规则允许的下次查询时间',
last_attempt_at DATETIME(3) NULL COMMENT '最近一次查询尝试时间',
last_success_at DATETIME(3) NULL COMMENT '最近一次取得有效结果时间',
source_updated_at DATETIME(3) NULL COMMENT '船公司官网展示的结果更新时间',
expires_at DATETIME(3) NULL COMMENT '当前最近有效结果过期时间;到期后仍保留但标记过期',
result_port_schedule_id BIGINT UNSIGNED NULL COMMENT '最近有效结果对应的船期记录ID',
last_attempt_status VARCHAR(24) NULL COMMENT '最近查询尝试状态快照',
manual_reason VARCHAR(1000) NULL COMMENT '需要人工查询或录入的原因',
last_error_code VARCHAR(64) NULL COMMENT '最近查询失败错误码',
last_error_message VARCHAR(1000) NULL COMMENT '最近查询失败错误说明',
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_carrier_schedule_query_no (query_no),
UNIQUE KEY uk_carrier_schedule_query_scope
(customer_id,ocean_booking_id,container_scope_id,carrier_code,tracking_type,tracking_no),
KEY idx_carrier_schedule_query_due (status,next_allowed_at,id),
KEY idx_carrier_schedule_query_customer (customer_id,updated_at,id),
KEY idx_carrier_schedule_query_expiry (expires_at,id),
CONSTRAINT fk_carrier_schedule_query_booking FOREIGN KEY (ocean_booking_id) REFERENCES fl_ocean_booking(id),
CONSTRAINT fk_carrier_schedule_query_container FOREIGN KEY (container_booking_id) REFERENCES fl_container_booking(id),
CONSTRAINT fk_carrier_schedule_query_result FOREIGN KEY (result_port_schedule_id) REFERENCES fl_port_schedule(id),
CONSTRAINT chk_carrier_schedule_query_customer CHECK (customer_id > 0),
CONSTRAINT chk_carrier_schedule_query_carrier CHECK
(carrier_code IN ('MATSON','SML','ZIM','COSCO','EMC','HEDE')),
CONSTRAINT chk_carrier_schedule_query_tracking CHECK
(tracking_type IN ('BOOKING_NO','BILL_OF_LADING','CONTAINER_NO')),
CONSTRAINT chk_carrier_schedule_query_access CHECK
(access_mode IN ('PUBLIC_FORM','HCAPTCHA','DYNAMIC_IFRAME')),
CONSTRAINT chk_carrier_schedule_query_status CHECK
(status IN ('READY','MANUAL_REQUIRED','SUCCESS')),
CONSTRAINT chk_carrier_schedule_query_attempt CHECK (attempt_count BETWEEN 0 AND 3)
) ENGINE=InnoDB COMMENT='六家船公司官网查询任务与三天窗口频控';
CREATE TABLE IF NOT EXISTS fl_carrier_schedule_query_attempt (
id BIGINT UNSIGNED NOT NULL COMMENT '船司官网查询尝试ID',
query_id BIGINT UNSIGNED NOT NULL COMMENT '所属船司官网查询任务ID',
window_attempt_no INT UNSIGNED NOT NULL COMMENT '当前三天窗口内的尝试序号;取值1至3',
carrier_code VARCHAR(16) NOT NULL COMMENT '本次尝试的船公司编码快照',
access_mode VARCHAR(32) NOT NULL COMMENT '本次尝试的官网访问方式快照',
official_url VARCHAR(500) NOT NULL COMMENT '本次尝试使用的官网地址快照',
attempt_status VARCHAR(24) NOT NULL COMMENT '尝试结果:MANUAL_REQUIRED需人工处理、SUCCESS成功、FAILED失败',
coverage_gap TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否存在自动化覆盖缺口:0否、1是',
coverage_gap_code VARCHAR(64) NULL COMMENT '自动化覆盖缺口编码',
manual_reason VARCHAR(1000) NULL COMMENT '转人工处理原因',
error_code VARCHAR(64) NULL COMMENT '查询失败错误码',
error_message VARCHAR(1000) 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_carrier_schedule_attempt_query (query_id,created_at,id),
CONSTRAINT fk_carrier_schedule_attempt_query FOREIGN KEY (query_id) REFERENCES fl_carrier_schedule_query(id),
CONSTRAINT chk_carrier_schedule_attempt_no CHECK (window_attempt_no BETWEEN 1 AND 3),
CONSTRAINT chk_carrier_schedule_attempt_status CHECK
(attempt_status IN ('MANUAL_REQUIRED','SUCCESS','FAILED'))
) ENGINE=InnoDB COMMENT='船司官网查询尝试不可变审计';
CREATE TABLE IF NOT EXISTS fl_carrier_schedule_query_history (
id BIGINT UNSIGNED NOT NULL COMMENT '船司查询任务历史ID',
query_id BIGINT UNSIGNED NOT NULL COMMENT '所属船司官网查询任务ID',
action VARCHAR(24) NOT NULL COMMENT '历史动作:CREATE创建、ATTEMPT执行查询、MANUAL_RESULT录入人工结果',
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_carrier_schedule_history_query (query_id,created_at,id),
CONSTRAINT fk_carrier_schedule_history_query FOREIGN KEY (query_id) REFERENCES fl_carrier_schedule_query(id),
CONSTRAINT chk_carrier_schedule_history_action CHECK (action IN ('CREATE','ATTEMPT','MANUAL_RESULT'))
) ENGINE=InnoDB COMMENT='船司查询任务不可变历史';
CREATE TABLE IF NOT EXISTS fl_carrier_schedule_query_operation (
id BIGINT UNSIGNED NOT NULL COMMENT '船司查询写操作幂等记录ID',
query_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_carrier_schedule_operation (operator_id,idempotency_key),
KEY idx_carrier_schedule_operation_query (query_id,created_at,id),
CONSTRAINT fk_carrier_schedule_operation_query FOREIGN KEY (query_id) REFERENCES fl_carrier_schedule_query(id),
CONSTRAINT chk_carrier_schedule_operation_status CHECK (status IN ('PROCESSING','SUCCESS'))
) ENGINE=InnoDB COMMENT='船司查询写操作幂等与请求摘要';
DROP TRIGGER IF EXISTS trg_carrier_schedule_query_before_insert;
DELIMITER $$
CREATE TRIGGER trg_carrier_schedule_query_before_insert BEFORE INSERT ON fl_carrier_schedule_query FOR EACH ROW
BEGIN
IF NOT EXISTS (SELECT 1 FROM fl_ocean_booking booking
WHERE booking.id=NEW.ocean_booking_id AND booking.customer_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 allocation
WHERE allocation.ocean_booking_id=NEW.ocean_booking_id
AND allocation.container_booking_id=NEW.container_booking_id
) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='船司查询柜次未分配至订舱';
END IF;
END$$
DELIMITER ;
DROP TRIGGER IF EXISTS trg_carrier_schedule_attempt_before_update;
DROP TRIGGER IF EXISTS trg_carrier_schedule_attempt_before_delete;
DROP TRIGGER IF EXISTS trg_carrier_schedule_history_before_update;
DROP TRIGGER IF EXISTS trg_carrier_schedule_history_before_delete;
DELIMITER $$
CREATE TRIGGER trg_carrier_schedule_attempt_before_update BEFORE UPDATE ON fl_carrier_schedule_query_attempt
FOR EACH ROW BEGIN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='船司查询尝试不可修改'; END$$
CREATE TRIGGER trg_carrier_schedule_attempt_before_delete BEFORE DELETE ON fl_carrier_schedule_query_attempt
FOR EACH ROW BEGIN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='船司查询尝试不可删除'; END$$
CREATE TRIGGER trg_carrier_schedule_history_before_update BEFORE UPDATE ON fl_carrier_schedule_query_history
FOR EACH ROW BEGIN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='船司查询历史不可修改'; END$$
CREATE TRIGGER trg_carrier_schedule_history_before_delete BEFORE DELETE ON fl_carrier_schedule_query_history
FOR EACH ROW BEGIN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='船司查询历史不可删除'; END$$
DELIMITER ;
USE baiyue_iam;
DROP PROCEDURE IF EXISTS assert_carrier_schedule_query_permission_ids;
DELIMITER $$
CREATE PROCEDURE assert_carrier_schedule_query_permission_ids()
BEGIN
IF EXISTS (
SELECT 1 FROM iam_permission
WHERE (id BETWEEN 5102184 AND 5102188
AND permission_code <> CASE id
WHEN 5102184 THEN 'platform:firstleg:port-schedule-query:list'
WHEN 5102185 THEN 'platform:firstleg:port-schedule-query:view'
WHEN 5102186 THEN 'platform:firstleg:port-schedule-query:create'
WHEN 5102187 THEN 'platform:firstleg:port-schedule-query:attempt'
WHEN 5102188 THEN 'platform:firstleg:port-schedule-query:manual'
END)
OR (permission_code LIKE 'platform:firstleg:port-schedule-query:%'
AND (tenant_id<>0 OR app_code<>'PLATFORM' OR id NOT BETWEEN 5102184 AND 5102188))
) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT='三期船司查询权限迁移失败:5102184-5102188 ID或权限码被占用';
END IF;
END$$
DELIMITER ;
CALL assert_carrier_schedule_query_permission_ids();
DROP PROCEDURE IF EXISTS assert_carrier_schedule_query_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
(5102184,0,'PLATFORM','BUTTON',5102180,'platform:firstleg:port-schedule-query:list','查询船司任务',NULL,'GET',40,'ACTIVE',1,0),
(5102185,0,'PLATFORM','BUTTON',5102180,'platform:firstleg:port-schedule-query:view','查看船司任务',NULL,'GET',50,'ACTIVE',1,0),
(5102186,0,'PLATFORM','BUTTON',5102180,'platform:firstleg:port-schedule-query:create','创建船司任务',NULL,'POST',60,'ACTIVE',1,0),
(5102187,0,'PLATFORM','BUTTON',5102180,'platform:firstleg:port-schedule-query:attempt','执行船司查询',NULL,'POST',70,'ACTIVE',1,0),
(5102188,0,'PLATFORM','BUTTON',5102180,'platform:firstleg:port-schedule-query:manual','录入人工结果',NULL,'POST',80,'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-query:%'
WHERE role.tenant_id=0 AND role.role_code IN ('PLATFORM_ADMIN','ORDER_OPERATOR');
USE baiyue_firstleg;
DROP PROCEDURE IF EXISTS assert_carrier_schedule_query_schema;
DELIMITER $$
CREATE PROCEDURE assert_carrier_schedule_query_schema()
BEGIN
IF (SELECT COUNT(*) FROM information_schema.tables
WHERE table_schema='baiyue_firstleg' AND table_name IN
('fl_carrier_schedule_query','fl_carrier_schedule_query_attempt',
'fl_carrier_schedule_query_history','fl_carrier_schedule_query_operation')) <> 4 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='船司查询任务表未完整创建';
END IF;
IF (SELECT COUNT(*) FROM baiyue_iam.iam_permission
WHERE tenant_id=0 AND id BETWEEN 5102184 AND 5102188
AND permission_code LIKE 'platform:firstleg:port-schedule-query:%') <> 5 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='船司查询任务权限未完整创建';
END IF;
IF (SELECT COUNT(*)
FROM baiyue_iam.iam_role_permission role_permission
JOIN baiyue_iam.iam_role role ON role.id=role_permission.role_id
JOIN baiyue_iam.iam_permission permission ON permission.id=role_permission.permission_id
WHERE role.tenant_id=0 AND role.role_code IN ('PLATFORM_ADMIN','ORDER_OPERATOR')
AND permission.tenant_id=0
AND permission.permission_code LIKE 'platform:firstleg:port-schedule-query:%') <> 10 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='船司查询任务角色授权未完整创建';
END IF;
END$$
DELIMITER ;
CALL assert_carrier_schedule_query_schema();
DROP PROCEDURE IF EXISTS assert_carrier_schedule_query_schema;
-- 来源:iam/src/main/resources/db/phase3-warehouse-roles.sql
-- 三期仓库常用系统角色:仓库操作员、仓库拣货员、仓库理货员。
-- 本脚本只组合已经存在的权限资源,需在三期 Firstleg/IAM 权限脚本之后执行,可重复部署。
USE baiyue_iam;
SET NAMES utf8mb4;
DROP PROCEDURE IF EXISTS assert_phase3_warehouse_role_ids_available;
DELIMITER $$
CREATE PROCEDURE assert_phase3_warehouse_role_ids_available()
BEGIN
IF EXISTS (
SELECT 1
FROM iam_role
WHERE (id = 4000004 AND (tenant_id <> 0 OR app_code <> 'PLATFORM' OR role_code <> 'WAREHOUSE_OPERATOR'))
OR (id = 4000005 AND (tenant_id <> 0 OR app_code <> 'PLATFORM' OR role_code <> 'WAREHOUSE_PICKER'))
OR (id = 4000006 AND (tenant_id <> 0 OR app_code <> 'PLATFORM' OR role_code <> 'WAREHOUSE_TALLY'))
OR (tenant_id = 0 AND app_code = 'PLATFORM' AND role_code = 'WAREHOUSE_OPERATOR' AND id <> 4000004)
OR (tenant_id = 0 AND app_code = 'PLATFORM' AND role_code = 'WAREHOUSE_PICKER' AND id <> 4000005)
OR (tenant_id = 0 AND app_code = 'PLATFORM' AND role_code = 'WAREHOUSE_TALLY' AND id <> 4000006)
) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '仓库角色初始化失败:4000004-4000006 或角色编码已被占用';
END IF;
IF EXISTS (
SELECT 1
FROM iam_data_scope
WHERE (id = 6000004 AND role_id <> 4000004)
OR (id = 6000005 AND role_id <> 4000005)
OR (id = 6000006 AND role_id <> 4000006)
OR (role_id = 4000004 AND id <> 6000004)
OR (role_id = 4000005 AND id <> 6000005)
OR (role_id = 4000006 AND id <> 6000006)
) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '仓库角色初始化失败:6000004-6000006 数据范围ID已被占用';
END IF;
END$$
DELIMITER ;
CALL assert_phase3_warehouse_role_ids_available();
DROP PROCEDURE assert_phase3_warehouse_role_ids_available;
INSERT INTO iam_role (
id, tenant_id, app_code, role_code, role_name, role_type, status,
description, deleted, version
) VALUES
(4000004, 0, 'PLATFORM', 'WAREHOUSE_OPERATOR', '仓库操作员', 'SYSTEM', 'ACTIVE',
'负责查看入仓业务并在后台维护装柜证据', 0, 0),
(4000005, 0, 'PLATFORM', 'WAREHOUSE_PICKER', '仓库拣货员', 'SYSTEM', 'ACTIVE',
'负责使用PDA执行柜次拣货和现场拍照', 0, 0),
(4000006, 0, 'PLATFORM', 'WAREHOUSE_TALLY', '仓库理货员', 'SYSTEM', 'ACTIVE',
'负责入库测量、PDA复核和装柜证据维护', 0, 0)
ON DUPLICATE KEY UPDATE
role_name = VALUES(role_name), role_type = 'SYSTEM', status = 'ACTIVE',
description = VALUES(description), deleted = 0;
DROP TEMPORARY TABLE IF EXISTS tmp_phase3_warehouse_role_permission;
CREATE TEMPORARY TABLE tmp_phase3_warehouse_role_permission (
role_code VARCHAR(64) NOT NULL,
permission_code VARCHAR(128) NOT NULL,
PRIMARY KEY (role_code, permission_code)
) ENGINE=MEMORY;
-- 操作员:查看入仓业务并维护后台装柜证据,不授予完成装柜、任务分派或出仓动作。
INSERT INTO tmp_phase3_warehouse_role_permission (role_code, permission_code) VALUES
('WAREHOUSE_OPERATOR', 'platform:order'),
('WAREHOUSE_OPERATOR', 'platform:firstleg:inbound:list'),
('WAREHOUSE_OPERATOR', 'platform:firstleg:inbound:view'),
('WAREHOUSE_OPERATOR', 'platform:firstleg:inbound:loading:view'),
('WAREHOUSE_OPERATOR', 'platform:firstleg:inbound:loading:edit');
-- 拣货员:只允许PDA任务查询、作业和现场照片,不授予后台页面、任务分派或复核权限。
INSERT INTO tmp_phase3_warehouse_role_permission (role_code, permission_code) VALUES
('WAREHOUSE_PICKER', 'platform:firstleg:pda:view'),
('WAREHOUSE_PICKER', 'platform:firstleg:pda:work');
-- 理货员:负责到仓测量、装柜证据维护和PDA复核,不允许完成装柜或确认出仓。
INSERT INTO tmp_phase3_warehouse_role_permission (role_code, permission_code) VALUES
('WAREHOUSE_TALLY', 'platform:order'),
('WAREHOUSE_TALLY', 'platform:firstleg:inbound:list'),
('WAREHOUSE_TALLY', 'platform:firstleg:inbound:view'),
('WAREHOUSE_TALLY', 'platform:firstleg:inbound:measure:view'),
('WAREHOUSE_TALLY', 'platform:firstleg:inbound:measure:edit'),
('WAREHOUSE_TALLY', 'platform:firstleg:inbound:measure:submit'),
('WAREHOUSE_TALLY', 'platform:firstleg:inbound:weight:edit'),
('WAREHOUSE_TALLY', 'platform:firstleg:inbound:weight:confirm'),
('WAREHOUSE_TALLY', 'platform:firstleg:pda:view'),
('WAREHOUSE_TALLY', 'platform:firstleg:pda:review'),
('WAREHOUSE_TALLY', 'platform:firstleg:inbound:loading:view'),
('WAREHOUSE_TALLY', 'platform:firstleg:inbound:loading:edit');
DROP PROCEDURE IF EXISTS assert_phase3_warehouse_role_permissions_available;
DELIMITER $$
CREATE PROCEDURE assert_phase3_warehouse_role_permissions_available()
BEGIN
DECLARE operator_permission_count INT DEFAULT 0;
DECLARE picker_permission_count INT DEFAULT 0;
DECLARE tally_permission_count INT DEFAULT 0;
SELECT COALESCE(SUM(role_code = 'WAREHOUSE_OPERATOR'), 0),
COALESCE(SUM(role_code = 'WAREHOUSE_PICKER'), 0),
COALESCE(SUM(role_code = 'WAREHOUSE_TALLY'), 0)
INTO operator_permission_count, picker_permission_count, tally_permission_count
FROM tmp_phase3_warehouse_role_permission;
IF operator_permission_count <> 5
OR picker_permission_count <> 2
OR tally_permission_count <> 12 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '仓库角色初始化失败:角色权限组合数量不符合预期';
END IF;
IF EXISTS (
SELECT 1
FROM tmp_phase3_warehouse_role_permission expected
LEFT JOIN iam_permission permission
ON permission.tenant_id = 0
AND permission.app_code = 'PLATFORM'
AND permission.permission_code = expected.permission_code
AND permission.status = 'ACTIVE'
AND permission.deleted = 0
WHERE permission.id IS NULL
) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '仓库角色初始化失败:依赖的三期平台权限尚未完整部署';
END IF;
END$$
DELIMITER ;
CALL assert_phase3_warehouse_role_permissions_available();
DROP PROCEDURE assert_phase3_warehouse_role_permissions_available;
-- 三个系统角色的权限集合由迁移维护。重复执行时移除历史版本或人工误授的越权项,
-- 避免最小权限角色因脚本升级只增不减。
DELETE role_permission
FROM iam_role_permission role_permission
JOIN iam_role role
ON role.id = role_permission.role_id
AND role.tenant_id = role_permission.tenant_id
JOIN iam_permission permission
ON permission.id = role_permission.permission_id
AND permission.tenant_id = role_permission.tenant_id
LEFT JOIN tmp_phase3_warehouse_role_permission expected
ON expected.role_code = role.role_code
AND expected.permission_code = permission.permission_code
WHERE role.tenant_id = 0
AND role.app_code = 'PLATFORM'
AND role.role_code IN ('WAREHOUSE_OPERATOR', 'WAREHOUSE_PICKER', 'WAREHOUSE_TALLY')
AND expected.permission_code IS NULL;
INSERT IGNORE INTO iam_role_permission (role_id, permission_id, tenant_id)
SELECT role.id, permission.id, 0
FROM tmp_phase3_warehouse_role_permission expected
JOIN iam_role role
ON role.tenant_id = 0
AND role.app_code = 'PLATFORM'
AND role.role_code = expected.role_code
AND role.status = 'ACTIVE'
AND role.deleted = 0
JOIN iam_permission permission
ON permission.tenant_id = 0
AND permission.app_code = 'PLATFORM'
AND permission.permission_code = expected.permission_code
AND permission.status = 'ACTIVE'
AND permission.deleted = 0;
INSERT INTO iam_data_scope (id, role_id, tenant_id, scope_type, scope_expression) VALUES
(6000004, 4000004, 0, 'TENANT', NULL),
(6000005, 4000005, 0, 'TENANT', NULL),
(6000006, 4000006, 0, 'TENANT', NULL)
ON DUPLICATE KEY UPDATE
tenant_id = 0, scope_type = 'TENANT', scope_expression = NULL;
DROP PROCEDURE IF EXISTS assert_phase3_warehouse_roles_ready;
DELIMITER $$
CREATE PROCEDURE assert_phase3_warehouse_roles_ready()
BEGIN
IF (SELECT COUNT(*)
FROM iam_role
WHERE tenant_id = 0
AND app_code = 'PLATFORM'
AND role_type = 'SYSTEM'
AND status = 'ACTIVE'
AND deleted = 0
AND role_code IN ('WAREHOUSE_OPERATOR', 'WAREHOUSE_PICKER', 'WAREHOUSE_TALLY')) <> 3 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '仓库角色初始化失败:系统角色未完整启用';
END IF;
IF (SELECT COUNT(*)
FROM tmp_phase3_warehouse_role_permission expected
JOIN iam_role role
ON role.tenant_id = 0
AND role.app_code = 'PLATFORM'
AND role.role_code = expected.role_code
JOIN iam_permission permission
ON permission.tenant_id = 0
AND permission.app_code = 'PLATFORM'
AND permission.permission_code = expected.permission_code
JOIN iam_role_permission grant_row
ON grant_row.role_id = role.id
AND grant_row.permission_id = permission.id
AND grant_row.tenant_id = 0) <> 19 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '仓库角色初始化失败:最小权限授权未完整写入';
END IF;
IF (SELECT COUNT(*)
FROM iam_data_scope scope_row
JOIN iam_role role ON role.id = scope_row.role_id
WHERE role.tenant_id = 0
AND role.app_code = 'PLATFORM'
AND role.role_code IN ('WAREHOUSE_OPERATOR', 'WAREHOUSE_PICKER', 'WAREHOUSE_TALLY')
AND scope_row.tenant_id = 0
AND scope_row.scope_type = 'TENANT') <> 3 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '仓库角色初始化失败:数据范围未完整写入';
END IF;
END$$
DELIMITER ;
CALL assert_phase3_warehouse_roles_ready();
DROP PROCEDURE assert_phase3_warehouse_roles_ready;
DROP TEMPORARY TABLE IF EXISTS tmp_phase3_warehouse_role_permission;
-- BEGIN FINANCE FEE SOURCE MODULE MIGRATIONS
-- 来源:finance/src/main/resources/db/finance.sql
CREATE DATABASE IF NOT EXISTS baiyue_finance
DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
USE baiyue_finance;
CREATE TABLE IF NOT EXISTS fin_fee_source (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
source_system VARCHAR(32) NOT NULL DEFAULT 'FIRSTLEG' COMMENT '费用来源系统,当前FIRSTLEG表示头程服务',
source_fee_id BIGINT UNSIGNED NOT NULL COMMENT '来源系统费用记录ID',
source_fee_version INT UNSIGNED NOT NULL COMMENT '来源费用版本号,用于固化不可变来源版本',
customer_id BIGINT UNSIGNED NOT NULL COMMENT '费用来源所属客户ID',
source_type VARCHAR(32) NOT NULL COMMENT '来源费用类型:INBOUND_FEE入库单费用、CONTAINER_FEE柜子费用',
source_no VARCHAR(64) NULL COMMENT '来源入库单号或柜号快照,可为空',
direction VARCHAR(16) NOT NULL COMMENT '费用方向:RECEIVABLE应收、PAYABLE应付',
order_id BIGINT UNSIGNED NULL COMMENT '来源头程订单ID,可为空',
inbound_id BIGINT UNSIGNED NULL COMMENT '来源入库单ID,可为空',
container_id BIGINT UNSIGNED NULL COMMENT '来源Firstleg装柜计划ID,可为空',
fee_item_code VARCHAR(64) NOT NULL COMMENT '来源费用项目编码快照',
fee_item_name VARCHAR(200) NOT NULL COMMENT '来源费用项目名称快照',
amount DECIMAL(20,6) NOT NULL COMMENT '来源费用金额,精度6位小数',
currency_code CHAR(3) NOT NULL DEFAULT 'CNY' COMMENT 'ISO 4217三位币种代码,首期仅CNY',
source_hash CHAR(64) NOT NULL COMMENT 'Firstleg费用来源完整快照SHA-256哈希',
source_snapshot JSON NOT NULL COMMENT 'Firstleg费用来源完整不可变JSON快照',
status VARCHAR(16) NOT NULL DEFAULT 'AVAILABLE' COMMENT '费用来源状态:AVAILABLE可结算、SUPERSEDED已被新版本替代、VOIDED已作废',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '来源快照入池时间',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '来源状态最后更新时间,数据修改时自动刷新',
PRIMARY KEY (id),
UNIQUE KEY uk_fin_fee_source (source_system, source_fee_id, source_fee_version),
KEY idx_fin_fee_source_customer (customer_id, status),
CONSTRAINT chk_fin_fee_source_currency CHECK (currency_code = 'CNY'),
CONSTRAINT chk_fin_fee_source_amount CHECK (amount >= 0)
) ENGINE=InnoDB COMMENT='Firstleg费用来源快照池;账单人工选择并固化明细';
CREATE TABLE IF NOT EXISTS fin_inbox_event (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
event_id VARCHAR(128) NOT NULL COMMENT '来源系统事件唯一ID',
source_system VARCHAR(32) NOT NULL COMMENT '事件来源系统标识',
event_type VARCHAR(64) NOT NULL COMMENT '来源事件类型',
payload JSON NOT NULL COMMENT '来源事件原始JSON载荷快照',
payload_hash CHAR(64) NOT NULL COMMENT '规范化事件内容SHA-256哈希,用于同事件ID冲突检测',
result_resource_id BIGINT UNSIGNED NULL COMMENT '成功处理生成或定位的费用来源快照ID',
error_code VARCHAR(64) NULL COMMENT '处理失败时的稳定FIN错误码',
status VARCHAR(16) NOT NULL DEFAULT 'RECEIVED' COMMENT '事件处理状态:RECEIVED已接收、PROCESSED已完成、FAILED处理失败',
received_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '事件接收时间',
processed_at DATETIME(3) NULL COMMENT '事件处理完成时间,未完成时为空',
PRIMARY KEY (id),
UNIQUE KEY uk_fin_inbox_event (source_system, event_id)
) ENGINE=InnoDB COMMENT='Firstleg费用事件幂等接收记录';
CREATE TABLE IF NOT EXISTS fin_subject (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
subject_code VARCHAR(64) NOT NULL COMMENT '平台财务主体唯一编码',
subject_name VARCHAR(200) NOT NULL COMMENT '平台收付款及核算主体名称',
status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE' COMMENT '财务主体状态,ACTIVE表示启用',
version INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
created_by BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建操作人ID,0表示系统初始化',
updated_by BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '最后修改操作人ID,0表示系统初始化',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '记录创建时间',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '最后更新时间,数据修改时自动刷新',
PRIMARY KEY (id),
UNIQUE KEY uk_fin_subject_code (subject_code)
) ENGINE=InnoDB COMMENT='平台财务主体;首期仅一个有效主体';
CREATE TABLE IF NOT EXISTS fin_customer_settlement_profile (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
customer_name_snapshot VARCHAR(200) NOT NULL COMMENT '客户名称快照,客户主数据后续变更不追改',
credit_days INT UNSIGNED NOT NULL DEFAULT 30 COMMENT '客户账期自然日数,默认30天',
status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE' COMMENT '客户结算配置状态,ACTIVE表示启用',
version INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
created_by BIGINT UNSIGNED NOT NULL COMMENT '创建操作人ID',
updated_by BIGINT UNSIGNED NOT NULL COMMENT '最后修改操作人ID',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '记录创建时间',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '最后更新时间,数据修改时自动刷新',
PRIMARY KEY (id),
UNIQUE KEY uk_fin_customer_profile (tenant_id),
CONSTRAINT chk_fin_customer_profile_tenant CHECK (tenant_id > 0),
CONSTRAINT chk_fin_customer_profile_days CHECK (credit_days <= 3650)
) ENGINE=InnoDB COMMENT='客户结算配置;一客户一个结算对象';
CREATE TABLE IF NOT EXISTS fin_bill (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
bill_no VARCHAR(64) NOT NULL COMMENT '客户范围内唯一账单编号',
subject_id BIGINT UNSIGNED NOT NULL COMMENT '平台财务主体ID',
customer_name_snapshot VARCHAR(200) NOT NULL COMMENT '出账时客户名称快照',
currency_code CHAR(3) NOT NULL DEFAULT 'CNY' COMMENT 'ISO 4217三位币种代码,首期仅CNY',
bill_date DATE NOT NULL COMMENT '账单日期',
due_date DATE NOT NULL COMMENT '到期日期,由账单日期加客户账期计算',
status VARCHAR(24) NOT NULL DEFAULT 'DRAFT' COMMENT '账单状态:DRAFT草稿、ISSUED已出账、PARTIALLY_PAID部分收款、PAID已收清、VOIDED已作废',
total_amount DECIMAL(20,6) NOT NULL DEFAULT 0 COMMENT '账单全部明细总金额,精度6位小数',
received_amount DECIMAL(20,6) NOT NULL DEFAULT 0 COMMENT '账单累计已收金额,精度6位小数',
outstanding_amount DECIMAL(20,6) NOT NULL DEFAULT 0 COMMENT '账单未收余额,精度6位小数',
remark VARCHAR(500) NULL COMMENT '平台账单内部备注',
issued_at DATETIME(3) NULL COMMENT '出账时间,未出账时为空',
issued_by BIGINT UNSIGNED NULL COMMENT '出账操作人ID,未出账时为空',
voided_at DATETIME(3) NULL COMMENT '作废时间,未作废时为空',
voided_by BIGINT UNSIGNED NULL COMMENT '作废操作人ID,未作废时为空',
void_reason VARCHAR(500) NULL COMMENT '账单作废原因',
version INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
created_by BIGINT UNSIGNED NOT NULL COMMENT '创建操作人ID',
updated_by BIGINT UNSIGNED NOT NULL COMMENT '最后修改操作人ID',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '记录创建时间',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '最后更新时间,数据修改时自动刷新',
PRIMARY KEY (id),
UNIQUE KEY uk_fin_bill_no (tenant_id, bill_no),
KEY idx_fin_bill_customer_status_date (tenant_id, status, bill_date),
CONSTRAINT chk_fin_bill_tenant CHECK (tenant_id > 0),
CONSTRAINT chk_fin_bill_currency CHECK (currency_code = 'CNY'),
CONSTRAINT chk_fin_bill_amounts CHECK (total_amount >= 0 AND received_amount >= 0 AND outstanding_amount >= 0)
) ENGINE=InnoDB COMMENT='客户CNY账单;首期账单即应收台账';
CREATE TABLE IF NOT EXISTS fin_bill_line (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
bill_id BIGINT UNSIGNED NOT NULL COMMENT '所属账单ID',
source_type VARCHAR(32) NOT NULL DEFAULT 'FIRSTLEG_FEE' COMMENT '费用来源类型,FIRSTLEG_FEE表示头程费用',
source_id BIGINT UNSIGNED NOT NULL COMMENT '来源费用ID',
source_version INT UNSIGNED NOT NULL COMMENT '出账固化的来源费用版本号',
source_no VARCHAR(64) NULL COMMENT '来源费用单号快照,可为空',
order_id BIGINT UNSIGNED NULL COMMENT '来源头程订单ID,可为空',
inbound_id BIGINT UNSIGNED NULL COMMENT '来源入库单ID,可为空',
fee_item_code VARCHAR(64) NOT NULL COMMENT '出账时费用项目编码快照',
fee_item_name VARCHAR(200) NOT NULL COMMENT '出账时费用项目名称快照',
amount DECIMAL(20,6) NOT NULL COMMENT '账单明细金额,精度6位小数',
adjustment_reason VARCHAR(500) NULL COMMENT '最近一次账单调整行原因;完整历史保存在修订与审计记录',
currency_code CHAR(3) NOT NULL DEFAULT 'CNY' COMMENT 'ISO 4217三位币种代码,首期仅CNY',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '账单明细创建时间',
PRIMARY KEY (id),
UNIQUE KEY uk_fin_bill_line_source (source_type, source_id, source_version),
KEY idx_fin_bill_line_bill (tenant_id, bill_id),
CONSTRAINT chk_fin_bill_line_tenant CHECK (tenant_id > 0),
CONSTRAINT chk_fin_bill_line_currency CHECK (currency_code = 'CNY'),
CONSTRAINT chk_fin_bill_line_amount CHECK (amount >= 0)
) ENGINE=InnoDB COMMENT='账单费用来源快照明细';
SET @fin_bill_line_adjustment_reason_sql = IF(
EXISTS(SELECT 1 FROM information_schema.columns
WHERE table_schema='baiyue_finance' AND table_name='fin_bill_line' AND column_name='adjustment_reason'),
'SELECT 1',
'ALTER TABLE baiyue_finance.fin_bill_line ADD COLUMN adjustment_reason VARCHAR(500) NULL COMMENT ''最近一次账单调整行原因;完整历史保存在修订与审计记录'' AFTER amount'
);
PREPARE fin_bill_line_adjustment_reason_stmt FROM @fin_bill_line_adjustment_reason_sql;
EXECUTE fin_bill_line_adjustment_reason_stmt;
DEALLOCATE PREPARE fin_bill_line_adjustment_reason_stmt;
CREATE TABLE IF NOT EXISTS fin_receipt (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
receipt_no VARCHAR(64) NOT NULL COMMENT '客户范围内唯一收款编号',
subject_id BIGINT UNSIGNED NOT NULL COMMENT '实际收款的平台财务主体ID',
currency_code CHAR(3) NOT NULL DEFAULT 'CNY' COMMENT 'ISO 4217三位币种代码,首期仅CNY',
received_amount DECIMAL(20,6) NOT NULL COMMENT '本次实际到账金额,精度6位小数',
received_at DATETIME(3) NOT NULL COMMENT '实际到账时间',
payment_method VARCHAR(32) NULL COMMENT '收款方式编码',
voucher_no VARCHAR(128) NULL COMMENT '银行流水号或线下收款凭证号',
voucher_file_ids JSON NOT NULL COMMENT 'File服务财务凭证文件ID字符串数组JSON',
status VARCHAR(16) NOT NULL DEFAULT 'CONFIRMED' COMMENT '收款状态:CONFIRMED已确认、VOIDED已作废',
remark VARCHAR(500) NULL COMMENT '平台收款内部备注',
voided_at DATETIME(3) NULL COMMENT '收款作废时间',
voided_by BIGINT UNSIGNED NULL COMMENT '收款作废操作人ID',
void_reason VARCHAR(500) NULL COMMENT '收款作废原因',
version INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
created_by BIGINT UNSIGNED NOT NULL COMMENT '创建操作人ID',
updated_by BIGINT UNSIGNED NOT NULL COMMENT '最后修改操作人ID',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '记录创建时间',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '最后更新时间,数据修改时自动刷新',
PRIMARY KEY (id),
UNIQUE KEY uk_fin_receipt_no (tenant_id, receipt_no),
KEY idx_fin_receipt_customer_time (tenant_id, received_at),
CONSTRAINT chk_fin_receipt_tenant CHECK (tenant_id > 0),
CONSTRAINT chk_fin_receipt_currency CHECK (currency_code = 'CNY'),
CONSTRAINT chk_fin_receipt_amount CHECK (received_amount > 0)
) ENGINE=InnoDB COMMENT='平台财务人工登记收款及凭证';
CREATE TABLE IF NOT EXISTS fin_receipt_bill (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
receipt_id BIGINT UNSIGNED NOT NULL COMMENT '收款记录ID',
bill_id BIGINT UNSIGNED NOT NULL COMMENT '被分配收款的账单ID',
applied_amount DECIMAL(20,6) NOT NULL COMMENT '本笔收款分配到该账单的金额,精度6位小数',
created_by BIGINT UNSIGNED NOT NULL COMMENT '分配操作人ID',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '收款分配创建时间',
PRIMARY KEY (id),
UNIQUE KEY uk_fin_receipt_bill_pair (receipt_id, bill_id),
KEY idx_fin_receipt_bill_bill (tenant_id, bill_id),
CONSTRAINT chk_fin_receipt_bill_tenant CHECK (tenant_id > 0),
CONSTRAINT chk_fin_receipt_bill_amount CHECK (applied_amount > 0)
) ENGINE=InnoDB COMMENT='人工收款对应账单金额;UI不使用复杂核销术语';
CREATE TABLE IF NOT EXISTS fin_cost (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
cost_no VARCHAR(64) NOT NULL COMMENT '客户范围内唯一成本编号',
cost_type VARCHAR(24) NOT NULL COMMENT '成本归属类型:SINGLE_ORDER单票成本、CONTAINER_SHARED整柜公共成本',
order_id BIGINT UNSIGNED NULL COMMENT '成本归属的头程订单ID,单票成本必填',
inbound_id BIGINT UNSIGNED NULL COMMENT '成本关联的入库单ID,可为空',
container_id BIGINT UNSIGNED NULL COMMENT 'Firstleg装柜计划ID,整柜公共成本必填',
supplier_id BIGINT UNSIGNED NULL COMMENT 'Warehouse公共往来单位或供应商引用ID',
supplier_name_snapshot VARCHAR(200) NULL COMMENT '登记成本时供应商或成本对象名称快照',
fee_item_code VARCHAR(64) NOT NULL COMMENT '成本费用项目编码快照',
fee_item_name VARCHAR(200) NOT NULL COMMENT '成本费用项目名称快照',
currency_code CHAR(3) NOT NULL DEFAULT 'CNY' COMMENT 'ISO 4217三位币种代码,首期仅CNY',
cost_amount DECIMAL(20,6) NOT NULL COMMENT '成本总金额,精度6位小数',
voucher_file_ids JSON NOT NULL COMMENT 'File服务财务凭证文件ID字符串数组JSON',
status VARCHAR(16) NOT NULL DEFAULT 'DRAFT' COMMENT '成本状态:DRAFT草稿、CONFIRMED已确认、VOIDED已作废',
remark VARCHAR(500) NULL COMMENT '平台成本内部备注',
confirmed_at DATETIME(3) NULL COMMENT '成本确认时间',
confirmed_by BIGINT UNSIGNED NULL COMMENT '成本确认操作人ID',
voided_at DATETIME(3) NULL COMMENT '成本作废时间',
voided_by BIGINT UNSIGNED NULL COMMENT '成本作废操作人ID',
void_reason VARCHAR(500) NULL COMMENT '成本作废原因',
version INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
created_by BIGINT UNSIGNED NOT NULL COMMENT '创建操作人ID',
updated_by BIGINT UNSIGNED NOT NULL COMMENT '最后修改操作人ID',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '记录创建时间',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '最后更新时间,数据修改时自动刷新',
PRIMARY KEY (id),
UNIQUE KEY uk_fin_cost_no (tenant_id, cost_no),
KEY idx_fin_cost_ticket (tenant_id, order_id, inbound_id),
KEY idx_fin_cost_container (tenant_id, container_id),
CONSTRAINT chk_fin_cost_tenant CHECK (tenant_id > 0),
CONSTRAINT chk_fin_cost_currency CHECK (currency_code = 'CNY'),
CONSTRAINT chk_fin_cost_amount CHECK (cost_amount > 0),
CONSTRAINT chk_fin_cost_target CHECK (
(cost_type = 'SINGLE_ORDER' AND order_id IS NOT NULL AND container_id IS NULL)
OR (cost_type = 'CONTAINER_SHARED' AND container_id IS NOT NULL)
)
) ENGINE=InnoDB COMMENT='人工成本;单票直接归属,整柜按体积分摊';
CREATE TABLE IF NOT EXISTS fin_cost_allocation (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
cost_id BIGINT UNSIGNED NOT NULL COMMENT '整柜公共成本ID',
order_id BIGINT UNSIGNED NOT NULL COMMENT '参与分摊的头程订单ID',
inbound_id BIGINT UNSIGNED NULL COMMENT '参与分摊订单关联的入库单ID,可为空',
volume_cbm DECIMAL(20,6) NOT NULL COMMENT '该订单分摊时有效体积快照,单位CBM、精度6位',
total_volume_cbm DECIMAL(20,6) NOT NULL COMMENT '柜内有效订单总体积快照,单位CBM、精度6位',
allocation_rate DECIMAL(20,10) NOT NULL COMMENT '订单体积占总体积的分摊比例,精度10位',
allocated_amount DECIMAL(20,6) NOT NULL COMMENT '分摊到该订单的成本金额,精度6位',
rounding_adjustment TINYINT NOT NULL DEFAULT 0 COMMENT '尾差归集标记:0未归集、1该行为实际尾差归集行',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '不可变分摊快照创建时间',
PRIMARY KEY (id),
UNIQUE KEY uk_fin_cost_allocation_order (cost_id, order_id),
KEY idx_fin_cost_allocation_query (tenant_id, order_id),
CONSTRAINT chk_fin_cost_allocation_volume CHECK (volume_cbm >= 0 AND total_volume_cbm > 0),
CONSTRAINT chk_fin_cost_allocation_amount CHECK (allocated_amount >= 0)
) ENGINE=InnoDB COMMENT='整柜成本按有效体积分摊的可审计结果';
CREATE TABLE IF NOT EXISTS fin_bill_revision (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
bill_id BIGINT UNSIGNED NOT NULL COMMENT '被调整账单ID',
version INT UNSIGNED NOT NULL COMMENT '调整前账单乐观锁版本号',
reason VARCHAR(500) NULL COMMENT '本次账单调整原因',
bill_snapshot JSON NOT NULL COMMENT '调整前账单主记录不可变JSON快照',
line_snapshot JSON NOT NULL COMMENT '调整前全部账单明细不可变JSON数组快照',
created_by BIGINT UNSIGNED NOT NULL COMMENT '执行调整的操作人ID',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '修订快照创建时间',
PRIMARY KEY (id),
UNIQUE KEY uk_fin_bill_revision (bill_id, version),
KEY idx_fin_bill_revision_tenant (tenant_id, bill_id)
) ENGINE=InnoDB COMMENT='出账后调整前的不可变账单及明细版本快照';
CREATE TABLE IF NOT EXISTS fin_idempotency_record (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
operator_id BIGINT UNSIGNED NOT NULL COMMENT '发起幂等写操作的操作人ID',
operation 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 COMMENT '幂等处理状态:PROCESSING处理中、SUCCEEDED成功、FAILED失败',
result_resource_id BIGINT UNSIGNED NULL COMMENT '成功操作生成或更新的业务资源ID',
error_code VARCHAR(64) NULL COMMENT '失败操作的稳定业务错误码',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '幂等请求首次登记时间',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '幂等处理状态最后更新时间',
PRIMARY KEY (id),
UNIQUE KEY uk_fin_idempotency (tenant_id, operator_id, operation, idempotency_key)
) ENGINE=InnoDB COMMENT='Finance人工写操作幂等记录';
CREATE TABLE IF NOT EXISTS fin_audit_log (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
resource_type VARCHAR(32) NOT NULL COMMENT '被审计财务资源类型',
resource_id BIGINT UNSIGNED NOT NULL COMMENT '被审计财务资源ID',
action VARCHAR(64) NOT NULL COMMENT '审计业务动作类型',
reason VARCHAR(500) NULL COMMENT '操作原因或说明',
before_data JSON NULL COMMENT '操作前业务数据JSON快照',
after_data JSON NULL COMMENT '操作后业务数据JSON快照',
operator_id BIGINT UNSIGNED NOT NULL COMMENT '操作人ID',
operator_name_snapshot VARCHAR(128) NOT NULL COMMENT '操作发生时的操作人名称快照',
request_id VARCHAR(128) NULL COMMENT '入口请求ID,用于问题定位',
trace_id VARCHAR(128) NULL COMMENT '分布式调用链追踪ID',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '不可变审计记录创建时间',
PRIMARY KEY (id),
KEY idx_fin_audit_resource (tenant_id, resource_type, resource_id, created_at),
KEY idx_fin_audit_operator (operator_id, created_at)
) ENGINE=InnoDB COMMENT='Finance不可变业务审计明细';
USE baiyue_firstleg;
-- 来源:firstleg/src/main/resources/db/finance-fee-source-migration.sql
-- Firstleg -> Finance 费用来源事件和柜次费用模型(MySQL 8,可重复执行)
DROP PROCEDURE IF EXISTS firstleg_finance_add_column;
DELIMITER $$
CREATE PROCEDURE firstleg_finance_add_column(
IN target_table VARCHAR(64), IN target_column VARCHAR(64), IN column_definition TEXT
)
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = target_table AND column_name = target_column
) THEN
SET @ddl = CONCAT('ALTER TABLE `', target_table, '` ADD COLUMN `', target_column, '` ', column_definition);
PREPARE statement FROM @ddl;
EXECUTE statement;
DEALLOCATE PREPARE statement;
END IF;
END$$
DELIMITER ;
CALL firstleg_finance_add_column('fl_inbound_outbox', 'event_key',
"VARCHAR(160) NULL COMMENT '费用来源事件幂等键;历史事件允许为空' AFTER event_type");
CALL firstleg_finance_add_column('fl_inbound_outbox', 'event_version',
"INT UNSIGNED NOT NULL DEFAULT 1 COMMENT '事件信封版本' AFTER event_key");
CALL firstleg_finance_add_column('fl_inbound_outbox', 'aggregate_sequence',
"INT UNSIGNED NULL COMMENT '同一费用来源的单调版本序号' AFTER event_version");
CALL firstleg_finance_add_column('fl_inbound_outbox', 'payload_hash',
"CHAR(64) NULL COMMENT '短payload规范化JSON的SHA-256' AFTER aggregate_sequence");
CALL firstleg_finance_add_column('fl_inbound_outbox', 'next_retry_at',
"DATETIME(3) NULL COMMENT '下一次允许投递时间' AFTER retry_count");
CALL firstleg_finance_add_column('fl_inbound_outbox', 'published_at',
"DATETIME(3) NULL COMMENT 'Finance确认接收时间' AFTER next_retry_at");
CALL firstleg_finance_add_column('fl_inbound_outbox', 'locked_at',
"DATETIME(3) NULL COMMENT 'Relay抢占租约时间' AFTER published_at");
CALL firstleg_finance_add_column('fl_inbound_outbox', 'locked_by',
"VARCHAR(64) NULL COMMENT 'Relay抢占实例ID' AFTER locked_at");
CALL firstleg_finance_add_column('fl_inbound_outbox', 'last_error',
"VARCHAR(500) NULL COMMENT '最近一次投递失败摘要,不记录令牌' AFTER locked_by");
DROP PROCEDURE IF EXISTS firstleg_finance_add_index;
DELIMITER $$
CREATE PROCEDURE firstleg_finance_add_index(
IN target_table VARCHAR(64), IN target_index VARCHAR(64), IN index_definition TEXT
)
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.statistics
WHERE table_schema = DATABASE() AND table_name = target_table AND index_name = target_index
) THEN
SET @ddl = CONCAT('ALTER TABLE `', target_table, '` ADD ', index_definition);
PREPARE statement FROM @ddl;
EXECUTE statement;
DEALLOCATE PREPARE statement;
END IF;
END$$
DELIMITER ;
CALL firstleg_finance_add_index('fl_inbound_outbox', 'uk_inbound_outbox_event_key',
'UNIQUE KEY `uk_inbound_outbox_event_key` (`event_key`)');
CALL firstleg_finance_add_index('fl_inbound_outbox', 'idx_inbound_outbox_finance_relay',
'KEY `idx_inbound_outbox_finance_relay` (`publish_status`, `next_retry_at`, `created_at`, `id`)');
DROP PROCEDURE IF EXISTS firstleg_finance_add_index;
CREATE TABLE IF NOT EXISTS fl_container_fee_detail (
id BIGINT UNSIGNED NOT NULL COMMENT '柜次费用来源ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
loading_plan_id BIGINT UNSIGNED NOT NULL COMMENT '柜次ID,对应fl_loading_plan.id',
container_booking_id BIGINT UNSIGNED NULL COMMENT '订舱柜ID快照',
container_no VARCHAR(64) NULL COMMENT '柜号快照',
fee_item_id BIGINT UNSIGNED NOT NULL COMMENT '平台公共费用项目ID',
fee_item_code_snapshot VARCHAR(50) NOT NULL COMMENT '费用项目稳定编码快照',
fee_item_name_snapshot VARCHAR(128) NOT NULL COMMENT '费用项目名称快照',
direction VARCHAR(16) NOT NULL COMMENT '费用方向:RECEIVABLE应收、PAYABLE应付',
partner_id BIGINT UNSIGNED NULL COMMENT '应付往来单位ID快照;应收为空',
partner_name VARCHAR(200) NULL COMMENT '应付往来单位名称快照;应收为空',
business_date DATE NOT NULL COMMENT '费用业务日期',
amount DECIMAL(18,2) NOT NULL COMMENT 'CNY费用金额',
currency CHAR(3) NOT NULL DEFAULT 'CNY' COMMENT '币种,首期固定CNY',
settlement_included TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否参与财务结算来源池',
review_status VARCHAR(16) NOT NULL DEFAULT 'PENDING' COMMENT '审核状态:PENDING待审核、APPROVED通过、REJECTED驳回',
source_status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE' COMMENT '来源状态:ACTIVE有效、VOIDED作废',
source_version INT UNSIGNED NOT NULL DEFAULT 1 COMMENT '费用来源版本;调整递增,作废携带当前版本',
remark VARCHAR(500) NULL COMMENT '人工备注',
reviewer_id BIGINT UNSIGNED NULL COMMENT '审核人ID',
reviewer_name VARCHAR(64) NULL COMMENT '审核人名称快照',
reviewed_at DATETIME(3) NULL COMMENT '审核时间',
review_reason VARCHAR(500) NULL COMMENT '审核驳回原因',
created_by BIGINT UNSIGNED NOT NULL COMMENT '创建人ID',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_by BIGINT UNSIGNED NOT NULL COMMENT '最后更新人ID',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '最后更新时间',
PRIMARY KEY (id),
KEY idx_container_fee_customer_plan (tenant_id, loading_plan_id, source_status),
KEY idx_container_fee_settlement (tenant_id, review_status, settlement_included, source_status),
CONSTRAINT chk_container_fee_direction CHECK (direction IN ('RECEIVABLE', 'PAYABLE')),
CONSTRAINT chk_container_fee_currency CHECK (currency = 'CNY'),
CONSTRAINT chk_container_fee_amount CHECK (amount >= 0),
CONSTRAINT chk_container_fee_review CHECK (review_status IN ('PENDING', 'APPROVED', 'REJECTED')),
CONSTRAINT chk_container_fee_source CHECK (source_status IN ('ACTIVE', 'VOIDED'))
) ENGINE=InnoDB COMMENT='柜次级人工应收应付费用来源;审核通过且参与结算后同步Finance';
CALL firstleg_finance_add_column('fl_container_fee_detail', 'partner_id',
"BIGINT UNSIGNED NULL COMMENT '应付往来单位ID快照;应收为空' AFTER direction");
CALL firstleg_finance_add_column('fl_container_fee_detail', 'partner_name',
"VARCHAR(200) NULL COMMENT '应付往来单位名称快照;应收为空' AFTER partner_id");
DROP PROCEDURE IF EXISTS firstleg_finance_add_column;
DROP PROCEDURE IF EXISTS firstleg_finance_assert_schema;
DELIMITER $$
CREATE PROCEDURE firstleg_finance_assert_schema()
BEGIN
IF (SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = 'fl_inbound_outbox'
AND column_name IN ('event_key', 'event_version', 'aggregate_sequence', 'payload_hash',
'next_retry_at', 'published_at', 'locked_at', 'locked_by', 'last_error')) <> 9 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Firstleg Finance迁移失败:Outbox事件信封字段不完整';
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.tables
WHERE table_schema = DATABASE() AND table_name = 'fl_container_fee_detail') THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Firstleg Finance迁移失败:柜次费用表不存在';
END IF;
IF (SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = 'fl_container_fee_detail'
AND column_name IN ('partner_id', 'partner_name')) <> 2 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Firstleg Finance迁移失败:柜次费用往来单位快照字段不完整';
END IF;
END$$
DELIMITER ;
CALL firstleg_finance_assert_schema();
DROP PROCEDURE IF EXISTS firstleg_finance_assert_schema;
-- 来源:finance/src/main/resources/db/fee-source-event-migration.sql
-- Firstleg费用来源事件消费模型存量升级(MySQL 8,可重复执行)
USE baiyue_finance;
DROP PROCEDURE IF EXISTS finance_fee_source_add_column;
DELIMITER $$
CREATE PROCEDURE finance_fee_source_add_column(
IN target_table VARCHAR(64), IN target_column VARCHAR(64), IN column_definition TEXT
)
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = target_table AND column_name = target_column
) THEN
SET @ddl = CONCAT('ALTER TABLE `', target_table, '` ADD COLUMN `', target_column, '` ', column_definition);
PREPARE statement FROM @ddl;
EXECUTE statement;
DEALLOCATE PREPARE statement;
END IF;
END$$
DELIMITER ;
CALL finance_fee_source_add_column('fin_fee_source', 'source_type',
"VARCHAR(32) NULL COMMENT '来源费用类型:INBOUND_FEE入库单费用、CONTAINER_FEE柜子费用' AFTER customer_id");
CALL finance_fee_source_add_column('fin_fee_source', 'source_no',
"VARCHAR(64) NULL COMMENT '来源入库单号或柜号快照,可为空' AFTER source_type");
CALL finance_fee_source_add_column('fin_fee_source', 'direction',
"VARCHAR(16) NULL COMMENT '费用方向:RECEIVABLE应收、PAYABLE应付' AFTER source_no");
CALL finance_fee_source_add_column('fin_fee_source', 'container_id',
"BIGINT UNSIGNED NULL COMMENT '来源Firstleg装柜计划ID,可为空' AFTER inbound_id");
CALL finance_fee_source_add_column('fin_fee_source', 'fee_item_code',
"VARCHAR(64) NULL COMMENT '来源费用项目编码快照' AFTER container_id");
CALL finance_fee_source_add_column('fin_fee_source', 'source_hash',
"CHAR(64) NULL COMMENT 'Firstleg费用来源完整快照SHA-256哈希' AFTER currency_code");
CALL finance_fee_source_add_column('fin_fee_source', 'source_snapshot',
"JSON NULL COMMENT 'Firstleg费用来源完整不可变JSON快照' AFTER source_hash");
CALL finance_fee_source_add_column('fin_fee_source', 'review_status',
"VARCHAR(16) NOT NULL DEFAULT 'PENDING' COMMENT 'Finance费用审核状态:PENDING待审核、APPROVED已通过、REJECTED已驳回' AFTER source_snapshot");
CALL finance_fee_source_add_column('fin_fee_source', 'review_reason',
"VARCHAR(500) NULL COMMENT 'Finance费用审核说明或驳回原因' AFTER review_status");
CALL finance_fee_source_add_column('fin_fee_source', 'settlement_included',
"TINYINT NOT NULL DEFAULT 0 COMMENT '是否已人工确认参与结算:0否、1是' AFTER review_reason");
CALL finance_fee_source_add_column('fin_fee_source', 'settlement_reason',
"VARCHAR(500) NULL COMMENT '参与或禁止结算的人工说明' AFTER settlement_included");
CALL finance_fee_source_add_column('fin_fee_source', 'operation_version',
"INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '费用审核和结算参与操作版本号' AFTER settlement_reason");
CALL finance_fee_source_add_column('fin_fee_source', 'updated_at',
"DATETIME(3) NULL COMMENT '来源状态最后更新时间,数据修改时自动刷新' AFTER created_at");
CALL finance_fee_source_add_column('fin_inbox_event', 'payload_hash',
"CHAR(64) NULL COMMENT '规范化事件内容SHA-256哈希,用于同事件ID冲突检测' AFTER payload");
CALL finance_fee_source_add_column('fin_inbox_event', 'result_resource_id',
"BIGINT UNSIGNED NULL COMMENT '成功处理生成或定位的费用来源快照ID' AFTER payload_hash");
CALL finance_fee_source_add_column('fin_inbox_event', 'error_code',
"VARCHAR(64) NULL COMMENT '处理失败时的稳定FIN错误码' AFTER result_resource_id");
DROP PROCEDURE IF EXISTS finance_fee_source_add_column;
-- 产品确认无历史财务数据;以下兼容值仅用于空环境重复迁移时保证结构收敛。
UPDATE fin_fee_source
SET source_type = COALESCE(source_type, 'INBOUND_FEE'),
direction = COALESCE(direction, 'RECEIVABLE'),
fee_item_code = COALESCE(fee_item_code, 'LEGACY'),
source_hash = COALESCE(source_hash, SHA2(CONCAT_WS('|', source_system, source_fee_id, source_fee_version), 256)),
source_snapshot = COALESCE(source_snapshot, JSON_OBJECT('migrated', TRUE)),
updated_at = COALESCE(updated_at, created_at),
review_status = COALESCE(review_status, 'PENDING'),
settlement_included = COALESCE(settlement_included, 0),
operation_version = COALESCE(operation_version, 0);
UPDATE fin_inbox_event
SET payload_hash = COALESCE(payload_hash, SHA2(CAST(payload AS CHAR), 256));
ALTER TABLE fin_fee_source
MODIFY COLUMN source_type VARCHAR(32) NOT NULL COMMENT '来源费用类型:INBOUND_FEE入库单费用、CONTAINER_FEE柜子费用',
MODIFY COLUMN direction VARCHAR(16) NOT NULL COMMENT '费用方向:RECEIVABLE应收、PAYABLE应付',
MODIFY COLUMN fee_item_code VARCHAR(64) NOT NULL COMMENT '来源费用项目编码快照',
MODIFY COLUMN source_hash CHAR(64) NOT NULL COMMENT 'Firstleg费用来源完整快照SHA-256哈希',
MODIFY COLUMN source_snapshot JSON NOT NULL COMMENT 'Firstleg费用来源完整不可变JSON快照',
MODIFY COLUMN status VARCHAR(16) NOT NULL DEFAULT 'AVAILABLE' COMMENT '费用来源状态:AVAILABLE可结算、SUPERSEDED已被新版本替代、VOIDED已作废',
MODIFY COLUMN updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '来源状态最后更新时间,数据修改时自动刷新';
ALTER TABLE fin_inbox_event
MODIFY COLUMN payload_hash CHAR(64) NOT NULL COMMENT '规范化事件内容SHA-256哈希,用于同事件ID冲突检测',
MODIFY COLUMN status VARCHAR(16) NOT NULL DEFAULT 'RECEIVED' COMMENT '事件处理状态:RECEIVED已接收、PROCESSED已完成、FAILED处理失败';
DROP PROCEDURE IF EXISTS finance_fee_source_assert_schema;
DELIMITER $$
CREATE PROCEDURE finance_fee_source_assert_schema()
BEGIN
IF (SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = 'fin_fee_source'
AND column_name IN ('source_type','source_no','direction','container_id','fee_item_code',
'source_hash','source_snapshot','review_status','review_reason',
'settlement_included','settlement_reason','operation_version','updated_at')) <> 13 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Finance费用来源迁移失败:来源快照字段不完整';
END IF;
IF (SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = 'fin_inbox_event'
AND column_name IN ('payload_hash','result_resource_id','error_code')) <> 3 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Finance费用来源迁移失败:Inbox幂等字段不完整';
END IF;
END$$
DELIMITER ;
CALL finance_fee_source_assert_schema();
DROP PROCEDURE IF EXISTS finance_fee_source_assert_schema;
-- END FINANCE FEE SOURCE MODULE MIGRATIONS
-- 九库中文注释存量升级。
-- MySQL DDL 会隐式提交,以下过程不能依赖事务回滚。所有目标先只读校验并排入临时队列,
-- 再按 database.table 聚合为每表至多一次 ALTER TABLE;字段 DDL 前后均校验不含注释的完整结构签名。
-- 不使用 GROUP_CONCAT,LONGTEXT 由游标逐项拼接,不受 group_concat_max_len 截断影响。
USE baiyue_firstleg;
DROP TEMPORARY TABLE IF EXISTS tmp_table_comment_target;
CREATE TEMPORARY TABLE tmp_table_comment_target (
schema_name VARCHAR(64) NOT NULL,
table_name VARCHAR(64) NOT NULL,
desired_comment VARCHAR(2048) NOT NULL,
current_comment VARCHAR(2048) NOT NULL,
needs_change TINYINT(1) NOT NULL,
PRIMARY KEY (schema_name,table_name)
) ENGINE=InnoDB;
DROP TEMPORARY TABLE IF EXISTS tmp_column_comment_target;
CREATE TEMPORARY TABLE tmp_column_comment_target (
schema_name VARCHAR(64) NOT NULL,
table_name VARCHAR(64) NOT NULL,
column_name VARCHAR(64) NOT NULL,
desired_comment VARCHAR(2048) NOT NULL,
definition_sql LONGTEXT NOT NULL,
signature_before CHAR(64) NOT NULL,
needs_change TINYINT(1) NOT NULL,
PRIMARY KEY (schema_name,table_name,column_name)
) ENGINE=InnoDB;
DROP PROCEDURE IF EXISTS queue_table_comment;
DELIMITER $$
CREATE PROCEDURE queue_table_comment(
IN p_schema_name VARCHAR(64), IN p_table_name VARCHAR(64), IN p_table_comment VARCHAR(2048)
)
BEGIN
DECLARE v_found TINYINT DEFAULT 1;
DECLARE v_current_comment LONGTEXT;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_found=0;
IF p_schema_name NOT IN ('baiyue_auth','baiyue_iam','baiyue_order','baiyue_warehouse',
'baiyue_firstleg','baiyue_file','baiyue_audit','baiyue_event_bridge','baiyue_finance')
OR p_table_name NOT REGEXP '^[a-z0-9_]+$'
OR p_table_comment IS NULL OR p_table_comment='' THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='表注释升级参数无效';
END IF;
SELECT table_comment INTO v_current_comment FROM information_schema.tables
WHERE table_schema=p_schema_name AND table_name=p_table_name AND table_type='BASE TABLE';
IF v_found=0 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='表注释升级目标不存在'; END IF;
INSERT INTO tmp_table_comment_target
(schema_name,table_name,desired_comment,current_comment,needs_change)
VALUES (p_schema_name,p_table_name,p_table_comment,v_current_comment,
NOT (BINARY v_current_comment=BINARY p_table_comment))
ON DUPLICATE KEY UPDATE desired_comment=VALUES(desired_comment),
current_comment=VALUES(current_comment),needs_change=VALUES(needs_change);
END$$
DELIMITER ;
DROP PROCEDURE IF EXISTS queue_column_comment;
DELIMITER $$
CREATE PROCEDURE queue_column_comment(
IN p_schema_name VARCHAR(64), IN p_table_name VARCHAR(64),
IN p_column_name VARCHAR(64), IN p_column_comment VARCHAR(2048)
)
BEGIN
DECLARE v_found TINYINT DEFAULT 1;
DECLARE v_column_type LONGTEXT;
DECLARE v_is_nullable VARCHAR(3);
DECLARE v_column_default LONGTEXT;
DECLARE v_extra VARCHAR(255);
DECLARE v_data_type VARCHAR(64);
DECLARE v_character_set VARCHAR(64);
DECLARE v_collation VARCHAR(64);
DECLARE v_generation_expression LONGTEXT;
DECLARE v_current_comment LONGTEXT;
DECLARE v_default_sql LONGTEXT DEFAULT '';
DECLARE v_extra_sql LONGTEXT DEFAULT '';
DECLARE v_definition_sql LONGTEXT;
DECLARE v_signature_before CHAR(64);
DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_found=0;
IF p_schema_name NOT IN ('baiyue_auth','baiyue_iam','baiyue_order','baiyue_warehouse',
'baiyue_firstleg','baiyue_file','baiyue_audit','baiyue_event_bridge','baiyue_finance')
OR p_table_name NOT REGEXP '^[a-z0-9_]+$' OR p_column_name NOT REGEXP '^[a-z0-9_]+$'
OR p_column_comment IS NULL OR p_column_comment='' THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='字段注释升级参数无效';
END IF;
SELECT column_type,is_nullable,column_default,extra,data_type,character_set_name,
collation_name,generation_expression,column_comment
INTO v_column_type,v_is_nullable,v_column_default,v_extra,v_data_type,v_character_set,
v_collation,v_generation_expression,v_current_comment
FROM information_schema.columns
WHERE table_schema=p_schema_name AND table_name=p_table_name AND column_name=p_column_name;
IF v_found=0 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='字段注释升级目标不存在'; END IF;
IF v_data_type NOT IN ('bigint','binary','bit','blob','boolean','char','date','datetime',
'decimal','double','enum','float','int','integer','json','longblob','longtext',
'mediumblob','mediumint','mediumtext','numeric','real','set','smallint','text','time',
'timestamp','tinyblob','tinyint','tinytext','varbinary','varchar','year') THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='字段注释升级遇到未知数据类型';
END IF;
SET v_signature_before=SHA2(CONCAT_WS(CHAR(31),v_column_type,v_is_nullable,
COALESCE(v_column_default,'<NULL>'),COALESCE(v_extra,''),COALESCE(v_character_set,''),
COALESCE(v_collation,''),COALESCE(v_generation_expression,'')),256);
-- MySQL 8.0/8.4 的 information_schema 会把生成表达式单引号返回为反斜杠转义形式。
-- 结构签名使用原值;仅在重建 DDL 时恢复为可执行的表达式文本。
SET v_generation_expression=REPLACE(v_generation_expression,CONCAT(CHAR(92),CHAR(39)),CHAR(39));
SET v_extra_sql=TRIM(REPLACE(COALESCE(v_extra,''),'DEFAULT_GENERATED',''));
IF COALESCE(v_generation_expression,'')<>'' THEN
IF UPPER(v_extra_sql) NOT IN ('STORED GENERATED','VIRTUAL GENERATED')
OR v_column_default IS NOT NULL THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='字段注释升级遇到未知生成列结构';
END IF;
SET v_definition_sql=CONCAT(v_column_type,
IF(v_character_set IS NULL,'',CONCAT(' CHARACTER SET ',v_character_set)),
IF(v_collation IS NULL,'',CONCAT(' COLLATE ',v_collation)),
' GENERATED ALWAYS AS (',v_generation_expression,') ',
IF(UPPER(v_extra_sql)='STORED GENERATED','STORED','VIRTUAL'));
ELSE
IF UPPER(v_extra_sql) LIKE '%GENERATED%' THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='字段注释升级遇到缺失表达式的生成列';
END IF;
IF v_column_default IS NULL THEN
IF v_is_nullable='YES' THEN SET v_default_sql=' DEFAULT NULL'; END IF;
ELSEIF UPPER(v_column_default) REGEXP '^CURRENT_TIMESTAMP(\\([0-9]+\\))?$'
OR v_column_default REGEXP '^b''[01]+''$'
OR (v_data_type IN ('tinyint','smallint','mediumint','int','integer','bigint','decimal',
'numeric','float','double','real','boolean')
AND v_column_default REGEXP '^-?[0-9]+(\\.[0-9]+)?$') THEN
SET v_default_sql=CONCAT(' DEFAULT ',v_column_default);
ELSEIF COALESCE(v_extra,'') LIKE '%DEFAULT_GENERATED%'
AND v_column_default REGEXP '^\\(.*\\)$' THEN
SET v_default_sql=CONCAT(' DEFAULT ',v_column_default);
ELSE
SET v_default_sql=CONCAT(' DEFAULT ',QUOTE(v_column_default));
END IF;
IF v_extra_sql<>''
AND LOWER(v_extra_sql) NOT REGEXP '^(auto_increment|on update current_timestamp(\\([0-9]+\\))?)$' THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='字段注释升级遇到未知EXTRA定义';
END IF;
SET v_definition_sql=CONCAT(v_column_type,
IF(v_character_set IS NULL,'',CONCAT(' CHARACTER SET ',v_character_set)),
IF(v_collation IS NULL,'',CONCAT(' COLLATE ',v_collation)),
IF(v_is_nullable='YES',' NULL',' NOT NULL'),v_default_sql,
IF(v_extra_sql='','',CONCAT(' ',v_extra_sql)));
END IF;
INSERT INTO tmp_column_comment_target
(schema_name,table_name,column_name,desired_comment,definition_sql,signature_before,needs_change)
VALUES (p_schema_name,p_table_name,p_column_name,p_column_comment,v_definition_sql,
v_signature_before,NOT (BINARY v_current_comment=BINARY p_column_comment))
ON DUPLICATE KEY UPDATE desired_comment=VALUES(desired_comment),definition_sql=VALUES(definition_sql),
signature_before=VALUES(signature_before),needs_change=VALUES(needs_change);
END$$
DELIMITER ;
DROP PROCEDURE IF EXISTS apply_one_table_comments;
DELIMITER $$
CREATE PROCEDURE apply_one_table_comments(IN p_schema_name VARCHAR(64), IN p_table_name VARCHAR(64))
apply_one: BEGIN
DECLARE v_done TINYINT DEFAULT 0;
DECLARE v_column_name VARCHAR(64);
DECLARE v_comment VARCHAR(2048);
DECLARE v_definition LONGTEXT;
DECLARE v_sql LONGTEXT;
DECLARE v_separator VARCHAR(2) DEFAULT '';
DECLARE column_cursor CURSOR FOR
SELECT column_name,desired_comment,definition_sql
FROM tmp_column_comment_target
WHERE schema_name=p_schema_name AND table_name=p_table_name AND needs_change=1
ORDER BY column_name;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_done=1;
SET v_sql=CONCAT('ALTER TABLE `',p_schema_name,'`.`',p_table_name,'` ');
IF EXISTS (SELECT 1 FROM tmp_table_comment_target
WHERE schema_name=p_schema_name AND table_name=p_table_name AND needs_change=1) THEN
SELECT desired_comment INTO v_comment FROM tmp_table_comment_target
WHERE schema_name=p_schema_name AND table_name=p_table_name;
SET v_sql=CONCAT(v_sql,'COMMENT = ',QUOTE(v_comment));
SET v_separator=', ';
END IF;
OPEN column_cursor;
column_loop: LOOP
FETCH column_cursor INTO v_column_name,v_comment,v_definition;
IF v_done=1 THEN LEAVE column_loop; END IF;
SET v_sql=CONCAT(v_sql,v_separator,'MODIFY COLUMN `',v_column_name,'` ',v_definition,
' COMMENT ',QUOTE(v_comment));
SET v_separator=', ';
END LOOP;
CLOSE column_cursor;
IF v_separator='' THEN LEAVE apply_one; END IF;
SET @table_comment_batch_sql=v_sql;
PREPARE table_comment_batch_stmt FROM @table_comment_batch_sql;
EXECUTE table_comment_batch_stmt;
DEALLOCATE PREPARE table_comment_batch_stmt;
IF EXISTS (
SELECT 1 FROM tmp_table_comment_target target
LEFT JOIN information_schema.tables actual
ON actual.table_schema=target.schema_name AND actual.table_name=target.table_name
AND actual.table_type='BASE TABLE'
WHERE target.schema_name=p_schema_name AND target.table_name=p_table_name
AND target.needs_change=1
AND (actual.table_name IS NULL OR BINARY actual.table_comment<>BINARY target.desired_comment)
) THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='表注释批量升级结果校验失败'; END IF;
IF EXISTS (
SELECT 1 FROM tmp_column_comment_target target
LEFT JOIN information_schema.columns actual
ON actual.table_schema=target.schema_name AND actual.table_name=target.table_name
AND actual.column_name=target.column_name
WHERE target.schema_name=p_schema_name AND target.table_name=p_table_name
AND target.needs_change=1
AND (actual.column_name IS NULL OR BINARY actual.column_comment<>BINARY target.desired_comment
OR BINARY SHA2(CONCAT_WS(CHAR(31),actual.column_type,actual.is_nullable,
COALESCE(actual.column_default,'<NULL>'),COALESCE(actual.extra,''),
COALESCE(actual.character_set_name,''),COALESCE(actual.collation_name,''),
COALESCE(actual.generation_expression,'')),256)<>BINARY target.signature_before)
) THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='字段注释批量升级改变了结构签名'; END IF;
END$$
DELIMITER ;
DROP PROCEDURE IF EXISTS apply_queued_comments;
DELIMITER $$
CREATE PROCEDURE apply_queued_comments()
BEGIN
DECLARE v_done TINYINT DEFAULT 0;
DECLARE v_schema_name VARCHAR(64);
DECLARE v_table_name VARCHAR(64);
DECLARE table_cursor CURSOR FOR
SELECT schema_name,table_name FROM (
SELECT schema_name,table_name FROM tmp_table_comment_target WHERE needs_change=1
UNION
SELECT schema_name,table_name FROM tmp_column_comment_target WHERE needs_change=1
) changed_tables ORDER BY schema_name,table_name;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_done=1;
OPEN table_cursor;
table_loop: LOOP
FETCH table_cursor INTO v_schema_name,v_table_name;
IF v_done=1 THEN LEAVE table_loop; END IF;
CALL apply_one_table_comments(v_schema_name,v_table_name);
END LOOP;
CLOSE table_cursor;
END$$
DELIMITER ;
-- BEGIN GENERATED NINE-SCHEMA COMMENT UPGRADE CALLS
CALL queue_table_comment('baiyue_audit', 'sys_audit_dead_letter', '审计消息死信补偿表');
CALL queue_table_comment('baiyue_audit', 'sys_audit_log', '统一业务审计日志表:追加记录敏感操作的主体、租户、资源、结果及前后快照;不同于消息Outbox和消费幂等表');
CALL queue_table_comment('baiyue_auth', 'auth_account', '统一登录账号表:保存平台端和商户端共用的认证凭据及账号安全状态');
CALL queue_table_comment('baiyue_auth', 'auth_refresh_token', '刷新令牌表:保存令牌摘要、平台或客户作用域、有效期和轮换/吊销状态');
CALL queue_table_comment('baiyue_auth', 'sys_audit_outbox', '可靠审计事件本地发送箱');
CALL queue_table_comment('baiyue_event_bridge', 'bridge_delivery_attempt', '跨区域事件投递尝试记录');
CALL queue_table_comment('baiyue_event_bridge', 'bridge_inbox', '跨区域事件已接收箱');
CALL queue_table_comment('baiyue_event_bridge', 'bridge_outbox', '跨区域事件待投递箱');
CALL queue_table_comment('baiyue_file', 'file_download_log', '文件下载审计日志表');
CALL queue_table_comment('baiyue_file', 'file_download_token', '临时下载授权令牌表');
CALL queue_table_comment('baiyue_file', 'file_export_outbox', '导出命令事务Outbox表');
CALL queue_table_comment('baiyue_file', 'file_export_task', '公共异步导出任务表');
CALL queue_table_comment('baiyue_file', 'file_export_task_guard', '导出任务创建并发保护表:低频创建入口使用单行锁保证重复提交及活动上限');
CALL queue_table_comment('baiyue_file', 'file_object', '文件元数据表');
CALL queue_table_comment('baiyue_finance', 'fin_audit_log', 'Finance不可变业务审计明细');
CALL queue_table_comment('baiyue_finance', 'fin_bill', '客户CNY账单;首期账单即应收台账');
CALL queue_table_comment('baiyue_finance', 'fin_bill_line', '账单费用来源快照明细');
CALL queue_table_comment('baiyue_finance', 'fin_bill_revision', '出账后调整前的不可变账单及明细版本快照');
CALL queue_table_comment('baiyue_finance', 'fin_cost', '人工成本;单票直接归属,整柜按体积分摊');
CALL queue_table_comment('baiyue_finance', 'fin_cost_allocation', '整柜成本按有效体积分摊的可审计结果');
CALL queue_table_comment('baiyue_finance', 'fin_customer_settlement_profile', '客户结算配置;一客户一个结算对象');
CALL queue_table_comment('baiyue_finance', 'fin_fee_source', 'Firstleg费用来源快照池;账单人工选择并固化明细');
CALL queue_table_comment('baiyue_finance', 'fin_idempotency_record', 'Finance人工写操作幂等记录');
CALL queue_table_comment('baiyue_finance', 'fin_inbox_event', 'Firstleg费用事件幂等接收记录');
CALL queue_table_comment('baiyue_finance', 'fin_receipt', '平台财务人工登记收款及凭证');
CALL queue_table_comment('baiyue_finance', 'fin_receipt_bill', '人工收款对应账单金额;UI不使用复杂核销术语');
CALL queue_table_comment('baiyue_finance', 'fin_subject', '平台财务主体;首期仅一个有效主体');
CALL queue_table_comment('baiyue_firstleg', 'fl_assignment_history', '头程任务指派历史表:不可变记录每次负责人变更及原因,用于追踪和审计');
CALL queue_table_comment('baiyue_firstleg', 'fl_carrier_fba_rate', '船司FBA目的仓价格版本');
CALL queue_table_comment('baiyue_firstleg', 'fl_carrier_schedule_query', '六家船公司官网查询任务与三天窗口频控');
CALL queue_table_comment('baiyue_firstleg', 'fl_carrier_schedule_query_attempt', '船司官网查询尝试不可变审计');
CALL queue_table_comment('baiyue_firstleg', 'fl_carrier_schedule_query_history', '船司查询任务不可变历史');
CALL queue_table_comment('baiyue_firstleg', 'fl_carrier_schedule_query_operation', '船司查询写操作幂等与请求摘要');
CALL queue_table_comment('baiyue_firstleg', 'fl_consumed_event', '头程消息消费幂等表:记录已处理事件并按事件ID去重,防止RocketMQ重复投递造成重复建单;不是消息发布表');
CALL queue_table_comment('baiyue_firstleg', 'fl_container_booking', '平台货柜预订资料;预装状态唯一来源为装柜计划');
CALL queue_table_comment('baiyue_firstleg', 'fl_container_document', '平台货柜清关、DO与PU文件关联');
CALL queue_table_comment('baiyue_firstleg', 'fl_container_fee_detail', '柜次级人工应收应付费用来源;审核通过且参与结算后同步Finance');
CALL queue_table_comment('baiyue_firstleg', 'fl_customer_charge_rule', '客户头程计费规则版本');
CALL queue_table_comment('baiyue_firstleg', 'fl_customer_dimension_config', '客户固定四箱规尺寸配置');
CALL queue_table_comment('baiyue_firstleg', 'fl_customs_case', '三期订单报关协同聚合');
CALL queue_table_comment('baiyue_firstleg', 'fl_customs_checklist_version', '三期报关资料清单不可变版本');
CALL queue_table_comment('baiyue_firstleg', 'fl_customs_document', '报关客户文件版本引用');
CALL queue_table_comment('baiyue_firstleg', 'fl_customs_history', '报关协同稳定历史');
CALL queue_table_comment('baiyue_firstleg', 'fl_customs_operation', '报关协同命令幂等记录');
CALL queue_table_comment('baiyue_firstleg', 'fl_customs_receipt', '报关代理回执版本与客户可见范围');
CALL queue_table_comment('baiyue_firstleg', 'fl_customs_submission_batch', '报关代理提交批次及冻结文件版本');
CALL queue_table_comment('baiyue_firstleg', 'fl_customs_supplement_request', '报关补件请求');
CALL queue_table_comment('baiyue_firstleg', 'fl_fee_item', '平台公共费用项目主数据');
CALL queue_table_comment('baiyue_firstleg', 'fl_fulfillment_task', '头程履约任务表:接收订单事件后形成独立履约任务,管理指派、执行和完成状态');
CALL queue_table_comment('baiyue_firstleg', 'fl_inbound_carton_label', '首次打印前冻结的实际箱号与订单货物明细映射');
CALL queue_table_comment('baiyue_firstleg', 'fl_inbound_destination_snapshot', '入库单目的仓地址和联系人快照');
CALL queue_table_comment('baiyue_firstleg', 'fl_inbound_fee', '入库头程费用计算和确认快照');
CALL queue_table_comment('baiyue_firstleg', 'fl_inbound_fee_detail', '入库单应收应付费用明细');
CALL queue_table_comment('baiyue_firstleg', 'fl_inbound_fee_item', '入库头程费用项目明细');
CALL queue_table_comment('baiyue_firstleg', 'fl_inbound_file', '入库单与文件中心对象的分类关系');
CALL queue_table_comment('baiyue_firstleg', 'fl_inbound_hold_record', '入库单扣货和解除扣货不可变历史');
CALL queue_table_comment('baiyue_firstleg', 'fl_inbound_idempotency', '第三方测量、审核、费用发送和入库写操作幂等记录');
CALL queue_table_comment('baiyue_firstleg', 'fl_inbound_internal_note', '入库单客服内部备注历史');
CALL queue_table_comment('baiyue_firstleg', 'fl_inbound_loading_pick_task', 'PDA拣货任务及结果');
CALL queue_table_comment('baiyue_firstleg', 'fl_inbound_measurement', '入库长宽高测量批次和第三方原始数据');
CALL queue_table_comment('baiyue_firstleg', 'fl_inbound_measurement_item', '入库长宽高和箱数测量明细');
CALL queue_table_comment('baiyue_firstleg', 'fl_inbound_order', '二期头程入库单聚合根和列表查询快照');
CALL queue_table_comment('baiyue_firstleg', 'fl_inbound_outbox', '入库事务Outbox事件表');
CALL queue_table_comment('baiyue_firstleg', 'fl_inbound_pallet_allocation', '头程侧托盘、库位和仓库预占结果引用');
CALL queue_table_comment('baiyue_firstleg', 'fl_inbound_pickup_snapshot', '入库单提货信息不可变快照');
CALL queue_table_comment('baiyue_firstleg', 'fl_inbound_split_batch', '主入仓单拆分批次;数量口径仅为箱数');
CALL queue_table_comment('baiyue_firstleg', 'fl_inbound_split_detail', '按箱数部分锁定及剩余箱数生成二级子入库单的不可变契约');
CALL queue_table_comment('baiyue_firstleg', 'fl_inbound_split_history', '不可变主子入仓单拆分审计历史');
CALL queue_table_comment('baiyue_firstleg', 'fl_inbound_split_item', '主子入仓单拆分数量台账');
CALL queue_table_comment('baiyue_firstleg', 'fl_inbound_status_history', '入库单流程状态不可变历史');
CALL queue_table_comment('baiyue_firstleg', 'fl_inbound_transport_snapshot', '入库单关联运输信息只读快照');
CALL queue_table_comment('baiyue_firstleg', 'fl_inbound_weight_result', '称重图片OCR原值、人工修正值和确认重量');
CALL queue_table_comment('baiyue_firstleg', 'fl_inbound_weight_revision', 'OCR重量人工修正和确认的不可变版本历史');
CALL queue_table_comment('baiyue_firstleg', 'fl_loading_evidence', '订单装柜空仓/半仓/满仓/锁仓/带锁码照证据');
CALL queue_table_comment('baiyue_firstleg', 'fl_loading_operation', '装柜与派送写操作幂等记录');
CALL queue_table_comment('baiyue_firstleg', 'fl_loading_pick_assignment', '柜次PDA任务领取与作业员分配');
CALL queue_table_comment('baiyue_firstleg', 'fl_loading_pick_history', 'PDA拣货幂等和操作审计历史');
CALL queue_table_comment('baiyue_firstleg', 'fl_loading_pick_item', 'PDA装柜拣货明细');
CALL queue_table_comment('baiyue_firstleg', 'fl_loading_pick_item_assignment', 'PDA拣货明细领取与作业员分配');
CALL queue_table_comment('baiyue_firstleg', 'fl_loading_pick_operation', '柜次PDA任务级幂等审计');
CALL queue_table_comment('baiyue_firstleg', 'fl_loading_pick_review', '柜次PDA任务完成复核');
CALL queue_table_comment('baiyue_firstleg', 'fl_loading_pick_task', 'PDA装柜拣货任务');
CALL queue_table_comment('baiyue_firstleg', 'fl_loading_plan', '平台智能装柜计划与唯一预装状态源');
CALL queue_table_comment('baiyue_firstleg', 'fl_loading_plan_history', '智能装柜追加式不可变操作历史');
CALL queue_table_comment('baiyue_firstleg', 'fl_loading_plan_item', '智能装柜客户货物明细;客户归属仅在明细层');
CALL queue_table_comment('baiyue_firstleg', 'fl_measure_review_record', '测量审核、驳回和异常放行不可变记录');
CALL queue_table_comment('baiyue_firstleg', 'fl_mojia_measurement_receipt', '墨家模拟或未匹配入库单的原始测量收件箱');
CALL queue_table_comment('baiyue_firstleg', 'fl_ocean_booking', '独立海运订舱聚合;柜次仍由fl_container_booking承载');
CALL queue_table_comment('baiyue_firstleg', 'fl_ocean_booking_container_allocation', '订舱需求与现有柜次的唯一分配关系');
CALL queue_table_comment('baiyue_firstleg', 'fl_ocean_booking_container_requirement', '订舱箱型箱种及数量需求');
CALL queue_table_comment('baiyue_firstleg', 'fl_ocean_booking_operation', '海运订舱写操作幂等与请求哈希');
CALL queue_table_comment('baiyue_firstleg', 'fl_ocean_booking_party', '订舱提单关系方快照');
CALL queue_table_comment('baiyue_firstleg', 'fl_ocean_booking_status_history', '海运订舱不可变状态历史');
CALL queue_table_comment('baiyue_firstleg', 'fl_outbound_dispatch', '三期国内出仓派车状态,不重复创建出仓单');
CALL queue_table_comment('baiyue_firstleg', 'fl_outbound_dispatch_history', '国内出仓追加式审计历史');
CALL queue_table_comment('baiyue_firstleg', 'fl_outbound_dispatch_operation', '国内出仓写操作幂等声明');
CALL queue_table_comment('baiyue_firstleg', 'fl_port_schedule', '独立港务船期来源聚合,不替代订舱快照');
CALL queue_table_comment('baiyue_firstleg', 'fl_port_schedule_history', '港务船期不可变变更历史');
CALL queue_table_comment('baiyue_firstleg', 'fl_port_schedule_operation', '港务船期写操作幂等与请求哈希');
CALL queue_table_comment('baiyue_firstleg', 'fl_preload_loading_plan_link', '入库单预装与柜次装柜计划多对多关联');
CALL queue_table_comment('baiyue_firstleg', 'fl_preload_plan', '订单分柜预装清单');
CALL queue_table_comment('baiyue_firstleg', 'fl_price_book_channel', '价格版本渠道及船期快照');
CALL queue_table_comment('baiyue_firstleg', 'fl_price_book_version', '平台公共船期价格整表版本');
CALL queue_table_comment('baiyue_firstleg', 'fl_price_channel_catalog', '平台公共船期价格固定渠道目录');
CALL queue_table_comment('baiyue_firstleg', 'fl_price_origin_alias', '价格表起运仓名称别名映射');
CALL queue_table_comment('baiyue_firstleg', 'fl_price_warehouse_mapping', '价格表来源仓与固定平台公共仓映射');
CALL queue_table_comment('baiyue_firstleg', 'fl_split_operation', '分柜操作幂等与审计索引');
CALL queue_table_comment('baiyue_firstleg', 'fl_third_party_call_log', '第三方测量开放接口原始请求、签名主体和处理结果留档');
CALL queue_table_comment('baiyue_firstleg', 'fl_truck_appointment', '三期拖车预约派车聚合');
CALL queue_table_comment('baiyue_firstleg', 'fl_truck_appointment_attachment', '拖车附件文件引用');
CALL queue_table_comment('baiyue_firstleg', 'fl_truck_appointment_history', '拖车状态与改约历史');
CALL queue_table_comment('baiyue_firstleg', 'fl_truck_appointment_operation', '拖车操作幂等记录');
CALL queue_table_comment('baiyue_firstleg', 'sys_audit_outbox', '可靠审计事件本地发送箱');
CALL queue_table_comment('baiyue_iam', 'iam_data_scope', '角色数据范围表:在功能权限之外限制角色可查看和操作的平台或客户范围内数据集合');
CALL queue_table_comment('baiyue_iam', 'iam_membership', '客户成员关系表:表示用户在客户企业中的成员身份,是分配企业客户端角色的主体');
CALL queue_table_comment('baiyue_iam', 'iam_membership_role', '成员角色关联表:将客户成员分配到对应应用端的一个或多个角色');
CALL queue_table_comment('baiyue_iam', 'iam_permission', '统一权限资源表:支持商户端和平台后台两套菜单、页面、按钮及API权限,权限最细可控制到按钮');
CALL queue_table_comment('baiyue_iam', 'iam_role', '角色表:按客户或平台作用域及应用端隔离企业客户端、平台后台两套角色体系');
CALL queue_table_comment('baiyue_iam', 'iam_role_permission', '角色权限关联表:定义角色可访问的菜单、页面、按钮和API资源');
CALL queue_table_comment('baiyue_iam', 'iam_tenant', '客户企业主档表:保存企业资料、审核结果和启停状态;ID同时作为客户数据隔离键');
CALL queue_table_comment('baiyue_iam', 'iam_tenant_address', '客户地址表:保存企业注册地址、办公地址等客户资料');
CALL queue_table_comment('baiyue_iam', 'iam_tenant_qualification', '客户资质表:保存企业证照、有效期、文件地址和审核状态');
CALL queue_table_comment('baiyue_iam', 'iam_tenant_review', '客户审核记录表:保存企业注册、资料变更等审核过程和审核意见');
CALL queue_table_comment('baiyue_iam', 'iam_user', 'IAM用户资料表:连接统一认证账号与业务身份资料,不保存密码');
CALL queue_table_comment('baiyue_iam', 'sys_audit_outbox', '可靠审计事件本地发送箱');
CALL queue_table_comment('baiyue_order', 'ord_idempotency_record', '订单写接口持久化幂等记录');
CALL queue_table_comment('baiyue_order', 'ord_order', '头程订单主表:保存商户委托、目的仓、运输方式和订单当前状态');
CALL queue_table_comment('baiyue_order', 'ord_order_cargo', '订单货物明细表:记录件数、重量、体积、申报价值和危险品属性');
CALL queue_table_comment('baiyue_order', 'ord_order_fulfillment_split', '父订单→子订单→柜次数量台账');
CALL queue_table_comment('baiyue_order', 'ord_order_internal_note', '订单内部备注表:保存平台运营或履约人员的内部协作记录,不向商户客户展示');
CALL queue_table_comment('baiyue_order', 'ord_order_party', '订单收发货方快照表:固化下单时发货人和收货人信息,避免主数据变化影响历史订单');
CALL queue_table_comment('baiyue_order', 'ord_order_status_history', '订单状态不可变历史表:按时间记录每次状态迁移、动作、原因和操作人');
CALL queue_table_comment('baiyue_order', 'ord_outbox_event', '订单事务Outbox表:与订单业务变更在同一本地事务写入,供后台发布器可靠投递RocketMQ,保证数据库状态与领域事件最终一致;不是普通操作日志');
CALL queue_table_comment('baiyue_order', 'sys_audit_outbox', '可靠审计事件本地发送箱');
CALL queue_table_comment('baiyue_warehouse', 'sys_audit_outbox', '可靠审计事件本地发送箱');
CALL queue_table_comment('baiyue_warehouse', 'wh_express_company', '平台公共快递公司主数据,固定归属 tenant_id=0');
CALL queue_table_comment('baiyue_warehouse', 'wh_location', '二期库位基础资料,默认一托盘对应一个库位且不作容量校验');
CALL queue_table_comment('baiyue_warehouse', 'wh_location_occupancy', '已确认的一托一库位占用事实记录');
CALL queue_table_comment('baiyue_warehouse', 'wh_location_reservation', '跨服务入库Saga的托盘和库位临时预占');
CALL queue_table_comment('baiyue_warehouse', 'wh_overseas_storage_fee_rule', '客户海外仓费用规则');
CALL queue_table_comment('baiyue_warehouse', 'wh_overseas_storage_fee_tier', '客户海外仓费用阶梯');
CALL queue_table_comment('baiyue_warehouse', 'wh_pallet', '二期托盘基础资料和物理托盘实例');
CALL queue_table_comment('baiyue_warehouse', 'wh_pallet_location_release_operation', 'PDA已拣满托盘库位精确释放的持久化幂等操作记录');
CALL queue_table_comment('baiyue_warehouse', 'wh_pallet_location_settlement_operation', 'PDA拣货资源原子结算的持久化幂等操作记录');
CALL queue_table_comment('baiyue_warehouse', 'wh_pallet_location_transfer_operation', '托盘库位归属转移的持久化幂等操作记录');
CALL queue_table_comment('baiyue_warehouse', 'wh_partner', '平台统一维护、供全部客户共享的往来单位主数据');
CALL queue_table_comment('baiyue_warehouse', 'wh_partner_type', '平台共享往来单位稳定业务类型');
CALL queue_table_comment('baiyue_warehouse', 'wh_service_capability', '仓库服务能力表:声明仓库可提供的收货、仓储、拣包、退货等能力及截单时间');
CALL queue_table_comment('baiyue_warehouse', 'wh_warehouse', '平台公共仓库主数据表:统一归属平台作用域 tenant_id=0');
CALL queue_table_comment('baiyue_warehouse', 'wh_warehouse_address', '仓库地址表:保存运营、退货等不同用途的地址及可选经纬度');
CALL queue_column_comment('baiyue_audit', 'sys_audit_dead_letter', 'created_at', '死信记录创建时间');
CALL queue_column_comment('baiyue_audit', 'sys_audit_dead_letter', 'event_id', '审计事件全局唯一ID');
CALL queue_column_comment('baiyue_audit', 'sys_audit_dead_letter', 'id', '死信记录自增ID');
CALL queue_column_comment('baiyue_audit', 'sys_audit_dead_letter', 'last_error', '最近一次补偿错误');
CALL queue_column_comment('baiyue_audit', 'sys_audit_dead_letter', 'lease_owner', '当前补偿租约持有实例');
CALL queue_column_comment('baiyue_audit', 'sys_audit_dead_letter', 'lease_until', '当前补偿租约截止时间');
CALL queue_column_comment('baiyue_audit', 'sys_audit_dead_letter', 'next_retry_at', '下次允许补偿时间');
CALL queue_column_comment('baiyue_audit', 'sys_audit_dead_letter', 'payload_text', 'RocketMQ死信原始载荷');
CALL queue_column_comment('baiyue_audit', 'sys_audit_dead_letter', 'processed_at', '补偿处理完成时间');
CALL queue_column_comment('baiyue_audit', 'sys_audit_dead_letter', 'retry_count', '死信重试次数');
CALL queue_column_comment('baiyue_audit', 'sys_audit_dead_letter', 'status', '状态:PENDING待补偿、PROCESSING处理中、PROCESSED已处理、DEAD永久失败');
CALL queue_column_comment('baiyue_audit', 'sys_audit_log', 'after_data', '变更后数据快照(已脱敏)');
CALL queue_column_comment('baiyue_audit', 'sys_audit_log', 'app_code', '操作来源应用端');
CALL queue_column_comment('baiyue_audit', 'sys_audit_log', 'before_data', '变更前数据快照(已脱敏)');
CALL queue_column_comment('baiyue_audit', 'sys_audit_log', 'client_ip', '客户端IP');
CALL queue_column_comment('baiyue_audit', 'sys_audit_log', 'client_type', '客户端类型');
CALL queue_column_comment('baiyue_audit', 'sys_audit_log', 'created_at', '审计服务落库时间');
CALL queue_column_comment('baiyue_audit', 'sys_audit_log', 'description', '操作说明');
CALL queue_column_comment('baiyue_audit', 'sys_audit_log', 'duration_ms', '请求耗时(毫秒)');
CALL queue_column_comment('baiyue_audit', 'sys_audit_log', 'error_code', '失败时的错误码');
CALL queue_column_comment('baiyue_audit', 'sys_audit_log', 'error_message', '脱敏后的失败信息');
CALL queue_column_comment('baiyue_audit', 'sys_audit_log', 'event_id', '审计事件全局唯一ID');
CALL queue_column_comment('baiyue_audit', 'sys_audit_log', 'event_type', '审计事件编码');
CALL queue_column_comment('baiyue_audit', 'sys_audit_log', 'http_method', 'HTTP请求方法');
CALL queue_column_comment('baiyue_audit', 'sys_audit_log', 'id', '审计日志自增ID');
CALL queue_column_comment('baiyue_audit', 'sys_audit_log', 'occurred_at', '业务操作发生时间');
CALL queue_column_comment('baiyue_audit', 'sys_audit_log', 'operator_tenant_id', '操作主体所属客户ID;平台操作可为空或为0');
CALL queue_column_comment('baiyue_audit', 'sys_audit_log', 'request_id', '请求ID,用于单次请求追踪');
CALL queue_column_comment('baiyue_audit', 'sys_audit_log', 'request_uri', '请求URI');
CALL queue_column_comment('baiyue_audit', 'sys_audit_log', 'resource_id', '被操作资源ID');
CALL queue_column_comment('baiyue_audit', 'sys_audit_log', 'resource_type', '被操作资源类型');
CALL queue_column_comment('baiyue_audit', 'sys_audit_log', 'service_name', '产生日志的服务名');
CALL queue_column_comment('baiyue_audit', 'sys_audit_log', 'success', '操作是否成功');
CALL queue_column_comment('baiyue_audit', 'sys_audit_log', 'tenant_id', '客户ID和数据隔离键;平台级审计可为空或为0');
CALL queue_column_comment('baiyue_audit', 'sys_audit_log', 'trace_id', '分布式链路追踪ID');
CALL queue_column_comment('baiyue_audit', 'sys_audit_log', 'user_id', '操作用户ID');
CALL queue_column_comment('baiyue_audit', 'sys_audit_log', 'username', '操作用户名快照');
CALL queue_column_comment('baiyue_auth', 'auth_account', 'account_status', '账号状态:ACTIVE正常、DISABLED停用、LOCKED锁定');
CALL queue_column_comment('baiyue_auth', 'auth_account', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_auth', 'auth_account', 'default_app_code', '默认应用端:MERCHANT企业客户端、PLATFORM平台后台');
CALL queue_column_comment('baiyue_auth', 'auth_account', 'default_membership_id', '默认客户成员关系ID;平台账号可为空');
CALL queue_column_comment('baiyue_auth', 'auth_account', 'default_tenant_id', '默认业务作用域ID:0为平台,正数为客户ID');
CALL queue_column_comment('baiyue_auth', 'auth_account', 'deleted', '逻辑删除标记');
CALL queue_column_comment('baiyue_auth', 'auth_account', 'display_name', '登录后展示名称');
CALL queue_column_comment('baiyue_auth', 'auth_account', 'failed_login_count', '连续登录失败次数');
CALL queue_column_comment('baiyue_auth', 'auth_account', 'id', '统一登录账号ID(雪花ID)');
CALL queue_column_comment('baiyue_auth', 'auth_account', 'last_login_at', '最近一次成功登录时间');
CALL queue_column_comment('baiyue_auth', 'auth_account', 'locked_until', '临时锁定截止时间');
CALL queue_column_comment('baiyue_auth', 'auth_account', 'operator_tenant_id', '操作主体作用域ID:0为平台,正数为客户ID,用于平台端与客户端身份隔离');
CALL queue_column_comment('baiyue_auth', 'auth_account', 'password_change_required', '管理员重置后是否强制下次登录改密');
CALL queue_column_comment('baiyue_auth', 'auth_account', 'password_hash', 'BCrypt密码哈希,禁止保存明文密码');
CALL queue_column_comment('baiyue_auth', 'auth_account', 'token_version', '令牌版本,递增后可使历史令牌失效');
CALL queue_column_comment('baiyue_auth', 'auth_account', 'updated_at', '更新时间');
CALL queue_column_comment('baiyue_auth', 'auth_account', 'username', '全局唯一登录名');
CALL queue_column_comment('baiyue_auth', 'auth_refresh_token', 'app_code', '令牌所属应用端:MERCHANT企业客户端、PLATFORM平台后台');
CALL queue_column_comment('baiyue_auth', 'auth_refresh_token', 'created_at', '签发时间');
CALL queue_column_comment('baiyue_auth', 'auth_refresh_token', 'expires_at', '到期时间');
CALL queue_column_comment('baiyue_auth', 'auth_refresh_token', 'id', '刷新令牌记录ID(雪花ID)');
CALL queue_column_comment('baiyue_auth', 'auth_refresh_token', 'operator_tenant_id', '令牌操作主体作用域ID:0为平台,正数为客户ID');
CALL queue_column_comment('baiyue_auth', 'auth_refresh_token', 'revoked_at', '吊销时间,非空表示不可继续使用');
CALL queue_column_comment('baiyue_auth', 'auth_refresh_token', 'tenant_id', '令牌当前业务作用域ID:0为平台,正数为客户ID');
CALL queue_column_comment('baiyue_auth', 'auth_refresh_token', 'token_hash', '刷新令牌SHA-256摘要,不保存令牌明文');
CALL queue_column_comment('baiyue_auth', 'auth_refresh_token', 'user_id', '认证账号ID');
CALL queue_column_comment('baiyue_auth', 'sys_audit_outbox', 'attempt_count', '当前周期发送次数');
CALL queue_column_comment('baiyue_auth', 'sys_audit_outbox', 'compensation_count', '自动补偿次数');
CALL queue_column_comment('baiyue_auth', 'sys_audit_outbox', 'created_at', '记录创建时间');
CALL queue_column_comment('baiyue_auth', 'sys_audit_outbox', 'dead_at', '进入死信时间');
CALL queue_column_comment('baiyue_auth', 'sys_audit_outbox', 'event_id', '审计事件全局唯一ID');
CALL queue_column_comment('baiyue_auth', 'sys_audit_outbox', 'event_type', '审计事件类型');
CALL queue_column_comment('baiyue_auth', 'sys_audit_outbox', 'last_error', '最近一次发送错误信息');
CALL queue_column_comment('baiyue_auth', 'sys_audit_outbox', 'lease_owner', '当前租约持有者');
CALL queue_column_comment('baiyue_auth', 'sys_audit_outbox', 'lease_until', '发送租约截止时间');
CALL queue_column_comment('baiyue_auth', 'sys_audit_outbox', 'next_retry_at', '下一次重试时间');
CALL queue_column_comment('baiyue_auth', 'sys_audit_outbox', 'payload_json', '脱敏后的审计事件');
CALL queue_column_comment('baiyue_auth', 'sys_audit_outbox', 'resource_id', '资源ID');
CALL queue_column_comment('baiyue_auth', 'sys_audit_outbox', 'resource_type', '资源类型');
CALL queue_column_comment('baiyue_auth', 'sys_audit_outbox', 'sent_at', '成功发送时间');
CALL queue_column_comment('baiyue_auth', 'sys_audit_outbox', 'service_name', '来源服务');
CALL queue_column_comment('baiyue_auth', 'sys_audit_outbox', 'status', '发送状态:PENDING待发送、SENDING发送中、SENT已发送、DEAD进入死信');
CALL queue_column_comment('baiyue_auth', 'sys_audit_outbox', 'updated_at', '记录更新时间');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_delivery_attempt', 'attempt_no', '投递序号');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_delivery_attempt', 'error_message', '错误信息');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_delivery_attempt', 'event_id', '事件全局标识');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_delivery_attempt', 'finished_at', '完成时间');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_delivery_attempt', 'id', '主键');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_delivery_attempt', 'response_code', '对端响应码');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_delivery_attempt', 'source_region', '来源区域');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_delivery_attempt', 'started_at', '开始时间');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_delivery_attempt', 'status', '投递结果');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_delivery_attempt', 'target_region', '目标区域');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_inbox', 'aggregate_id', '聚合标识');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_inbox', 'aggregate_type', '聚合类型');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_inbox', 'aggregate_version', '聚合版本');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_inbox', 'causation_id', '上游事件标识');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_inbox', 'correlation_id', '业务关联标识');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_inbox', 'event_id', '事件全局标识');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_inbox', 'event_type', '事件类型');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_inbox', 'event_version', '事件契约版本');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_inbox', 'handover_version', '跨区交接版本');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_inbox', 'idempotency_key', '接收幂等键');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_inbox', 'last_error', '最后错误');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_inbox', 'lease_owner', '消费租约持有者');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_inbox', 'lease_until', '消费租约截止时间');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_inbox', 'next_retry_at', '下次消费时间');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_inbox', 'occurred_at', '业务事实发生时间');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_inbox', 'payload_hash', '规范化负载摘要');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_inbox', 'payload_json', '事件负载');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_inbox', 'processed_at', '消费完成时间');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_inbox', 'received_at', '接收时间');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_inbox', 'retry_count', '消费重试次数');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_inbox', 'source_region', '来源区域');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_inbox', 'status', '消费状态');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_inbox', 'target_region', '目标区域');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_inbox', 'tenant_id', '客户或业务隔离标识;跨区域事件沿用来源系统口径');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_inbox', 'trace_id', '链路标识');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_outbox', 'aggregate_id', '聚合标识');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_outbox', 'aggregate_type', '聚合类型');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_outbox', 'aggregate_version', '聚合版本');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_outbox', 'causation_id', '上游事件标识');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_outbox', 'correlation_id', '业务关联标识');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_outbox', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_outbox', 'delivered_at', '投递成功时间');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_outbox', 'event_id', '事件全局标识');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_outbox', 'event_type', '事件类型');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_outbox', 'event_version', '事件契约版本');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_outbox', 'handover_version', '跨区交接版本');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_outbox', 'idempotency_key', '发送幂等键');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_outbox', 'last_error', '最后错误');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_outbox', 'lease_owner', '租约持有者');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_outbox', 'lease_until', '租约截止时间');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_outbox', 'next_retry_at', '下次重试时间');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_outbox', 'occurred_at', '业务事实发生时间');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_outbox', 'payload_hash', '规范化负载摘要');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_outbox', 'payload_json', '事件负载');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_outbox', 'retry_count', '已重试次数');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_outbox', 'source_region', '来源区域');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_outbox', 'status', '投递状态');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_outbox', 'target_region', '目标区域');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_outbox', 'tenant_id', '客户或业务隔离标识;跨区域事件沿用来源系统口径');
CALL queue_column_comment('baiyue_event_bridge', 'bridge_outbox', 'trace_id', '链路标识');
CALL queue_column_comment('baiyue_file', 'file_download_log', 'client_ip', '客户端IP');
CALL queue_column_comment('baiyue_file', 'file_download_log', 'downloaded_at', '下载时间');
CALL queue_column_comment('baiyue_file', 'file_download_log', 'failure_reason', '失败原因');
CALL queue_column_comment('baiyue_file', 'file_download_log', 'file_id', '文件逻辑ID');
CALL queue_column_comment('baiyue_file', 'file_download_log', 'id', '下载日志ID');
CALL queue_column_comment('baiyue_file', 'file_download_log', 'request_id', '请求ID');
CALL queue_column_comment('baiyue_file', 'file_download_log', 'result', '下载结果');
CALL queue_column_comment('baiyue_file', 'file_download_log', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_file', 'file_download_log', 'user_id', '下载用户ID');
CALL queue_column_comment('baiyue_file', 'file_download_token', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_file', 'file_download_token', 'created_by', '创建人ID');
CALL queue_column_comment('baiyue_file', 'file_download_token', 'download_count', '已下载次数');
CALL queue_column_comment('baiyue_file', 'file_download_token', 'expires_at', '过期时间');
CALL queue_column_comment('baiyue_file', 'file_download_token', 'file_id', '文件逻辑ID');
CALL queue_column_comment('baiyue_file', 'file_download_token', 'id', '下载令牌ID');
CALL queue_column_comment('baiyue_file', 'file_download_token', 'max_downloads', '最大下载次数');
CALL queue_column_comment('baiyue_file', 'file_download_token', 'revoked_at', '撤销时间');
CALL queue_column_comment('baiyue_file', 'file_download_token', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_file', 'file_download_token', 'token_hash', '下载令牌SHA-256摘要');
CALL queue_column_comment('baiyue_file', 'file_export_outbox', 'attempt_count', '累计发送尝试次数');
CALL queue_column_comment('baiyue_file', 'file_export_outbox', 'created_at', 'Outbox记录创建时间');
CALL queue_column_comment('baiyue_file', 'file_export_outbox', 'dispatch_version', '任务派发版本');
CALL queue_column_comment('baiyue_file', 'file_export_outbox', 'event_id', '投递意图唯一标识');
CALL queue_column_comment('baiyue_file', 'file_export_outbox', 'id', 'Outbox记录ID');
CALL queue_column_comment('baiyue_file', 'file_export_outbox', 'last_error', '最后发送错误');
CALL queue_column_comment('baiyue_file', 'file_export_outbox', 'lease_owner', '领取实例标识');
CALL queue_column_comment('baiyue_file', 'file_export_outbox', 'lease_until', '领取租约截止时间');
CALL queue_column_comment('baiyue_file', 'file_export_outbox', 'message_key', '消息Key');
CALL queue_column_comment('baiyue_file', 'file_export_outbox', 'next_retry_at', '允许发送的最早时间');
CALL queue_column_comment('baiyue_file', 'file_export_outbox', 'payload_json', '命令消息体');
CALL queue_column_comment('baiyue_file', 'file_export_outbox', 'sent_at', '发送成功时间');
CALL queue_column_comment('baiyue_file', 'file_export_outbox', 'status', '状态:PENDING待发送、CLAIMED已领取、SENT已发送、QUARANTINED隔离');
CALL queue_column_comment('baiyue_file', 'file_export_outbox', 'tag', '消息Tag');
CALL queue_column_comment('baiyue_file', 'file_export_outbox', 'task_id', '关联导出任务ID');
CALL queue_column_comment('baiyue_file', 'file_export_outbox', 'topic', '目标Topic');
CALL queue_column_comment('baiyue_file', 'file_export_outbox', 'updated_at', 'Outbox记录更新时间');
CALL queue_column_comment('baiyue_file', 'file_export_task', 'app_code', '申请端:PLATFORM或MERCHANT');
CALL queue_column_comment('baiyue_file', 'file_export_task', 'attempt_count', '已执行尝试次数');
CALL queue_column_comment('baiyue_file', 'file_export_task', 'completed_at', '完成时间');
CALL queue_column_comment('baiyue_file', 'file_export_task', 'content_type', '文件类型');
CALL queue_column_comment('baiyue_file', 'file_export_task', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_file', 'file_export_task', 'created_by', '创建人ID');
CALL queue_column_comment('baiyue_file', 'file_export_task', 'deleted', '逻辑删除标记');
CALL queue_column_comment('baiyue_file', 'file_export_task', 'dispatch_version', '派发版本,仅执行重试时递增');
CALL queue_column_comment('baiyue_file', 'file_export_task', 'execution_deadline_at', '本次执行截止时间');
CALL queue_column_comment('baiyue_file', 'file_export_task', 'expires_at', '文件过期时间');
CALL queue_column_comment('baiyue_file', 'file_export_task', 'export_type', '导出类型');
CALL queue_column_comment('baiyue_file', 'file_export_task', 'failure_message', '失败原因');
CALL queue_column_comment('baiyue_file', 'file_export_task', 'file_name', '下载文件名');
CALL queue_column_comment('baiyue_file', 'file_export_task', 'format', '导出格式');
CALL queue_column_comment('baiyue_file', 'file_export_task', 'id', '导出任务ID(雪花ID)');
CALL queue_column_comment('baiyue_file', 'file_export_task', 'last_heartbeat_at', '最后心跳时间');
CALL queue_column_comment('baiyue_file', 'file_export_task', 'lease_owner', '当前执行实例标识');
CALL queue_column_comment('baiyue_file', 'file_export_task', 'lease_until', '执行租约截止时间');
CALL queue_column_comment('baiyue_file', 'file_export_task', 'max_attempts', '自动重试上限');
CALL queue_column_comment('baiyue_file', 'file_export_task', 'next_retry_at', '允许执行的最早时间');
CALL queue_column_comment('baiyue_file', 'file_export_task', 'object_key', '存储对象键');
CALL queue_column_comment('baiyue_file', 'file_export_task', 'parameters_json', '筛选条件快照');
CALL queue_column_comment('baiyue_file', 'file_export_task', 'processed_rows', '已处理行数');
CALL queue_column_comment('baiyue_file', 'file_export_task', 'requested_at', '申请时间');
CALL queue_column_comment('baiyue_file', 'file_export_task', 'requested_by', '申请用户ID');
CALL queue_column_comment('baiyue_file', 'file_export_task', 'requested_username', '申请账号快照');
CALL queue_column_comment('baiyue_file', 'file_export_task', 'size_bytes', '文件字节数');
CALL queue_column_comment('baiyue_file', 'file_export_task', 'started_at', '开始时间');
CALL queue_column_comment('baiyue_file', 'file_export_task', 'status', '状态:PENDING待处理、RUNNING执行中、SUCCEEDED成功、FAILED失败、EXPIRED已过期');
CALL queue_column_comment('baiyue_file', 'file_export_task', 'task_name', '任务名称');
CALL queue_column_comment('baiyue_file', 'file_export_task', 'task_no', '导出任务编号');
CALL queue_column_comment('baiyue_file', 'file_export_task', 'tenant_id', '客户ID和数据隔离键;平台任务固定为0');
CALL queue_column_comment('baiyue_file', 'file_export_task', 'total_rows', '预计总行数');
CALL queue_column_comment('baiyue_file', 'file_export_task', 'updated_at', '更新时间');
CALL queue_column_comment('baiyue_file', 'file_export_task', 'updated_by', '最后修改人ID');
CALL queue_column_comment('baiyue_file', 'file_export_task', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_file', 'file_export_task_guard', 'guard_key', '任务创建串行化锁键');
CALL queue_column_comment('baiyue_file', 'file_export_task_guard', 'updated_at', '锁记录最后更新时间');
CALL queue_column_comment('baiyue_file', 'file_object', 'bucket_name', '存储桶名称');
CALL queue_column_comment('baiyue_file', 'file_object', 'business_id', '关联业务逻辑ID');
CALL queue_column_comment('baiyue_file', 'file_object', 'business_type', '关联业务类型');
CALL queue_column_comment('baiyue_file', 'file_object', 'content_type', '文件内容类型');
CALL queue_column_comment('baiyue_file', 'file_object', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_file', 'file_object', 'created_by', '创建人ID');
CALL queue_column_comment('baiyue_file', 'file_object', 'deleted', '逻辑删除标记');
CALL queue_column_comment('baiyue_file', 'file_object', 'id', '文件ID(雪花ID)');
CALL queue_column_comment('baiyue_file', 'file_object', 'object_key', '对象存储键');
CALL queue_column_comment('baiyue_file', 'file_object', 'original_name', '原始文件名');
CALL queue_column_comment('baiyue_file', 'file_object', 'sha256', '文件SHA-256摘要');
CALL queue_column_comment('baiyue_file', 'file_object', 'size_bytes', '文件字节数');
CALL queue_column_comment('baiyue_file', 'file_object', 'status', '文件状态');
CALL queue_column_comment('baiyue_file', 'file_object', 'storage_provider', '存储提供方');
CALL queue_column_comment('baiyue_file', 'file_object', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_file', 'file_object', 'updated_at', '更新时间');
CALL queue_column_comment('baiyue_file', 'file_object', 'updated_by', '最后修改人ID');
CALL queue_column_comment('baiyue_file', 'file_object', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_finance', 'fin_audit_log', 'action', '审计业务动作类型');
CALL queue_column_comment('baiyue_finance', 'fin_audit_log', 'after_data', '操作后业务数据JSON快照');
CALL queue_column_comment('baiyue_finance', 'fin_audit_log', 'before_data', '操作前业务数据JSON快照');
CALL queue_column_comment('baiyue_finance', 'fin_audit_log', 'created_at', '不可变审计记录创建时间');
CALL queue_column_comment('baiyue_finance', 'fin_audit_log', 'id', '主键ID');
CALL queue_column_comment('baiyue_finance', 'fin_audit_log', 'operator_id', '操作人ID');
CALL queue_column_comment('baiyue_finance', 'fin_audit_log', 'operator_name_snapshot', '操作发生时的操作人名称快照');
CALL queue_column_comment('baiyue_finance', 'fin_audit_log', 'reason', '操作原因或说明');
CALL queue_column_comment('baiyue_finance', 'fin_audit_log', 'request_id', '入口请求ID,用于问题定位');
CALL queue_column_comment('baiyue_finance', 'fin_audit_log', 'resource_id', '被审计财务资源ID');
CALL queue_column_comment('baiyue_finance', 'fin_audit_log', 'resource_type', '被审计财务资源类型');
CALL queue_column_comment('baiyue_finance', 'fin_audit_log', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_finance', 'fin_audit_log', 'trace_id', '分布式调用链追踪ID');
CALL queue_column_comment('baiyue_finance', 'fin_bill', 'bill_date', '账单日期');
CALL queue_column_comment('baiyue_finance', 'fin_bill', 'bill_no', '客户范围内唯一账单编号');
CALL queue_column_comment('baiyue_finance', 'fin_bill', 'created_at', '记录创建时间');
CALL queue_column_comment('baiyue_finance', 'fin_bill', 'created_by', '创建操作人ID');
CALL queue_column_comment('baiyue_finance', 'fin_bill', 'currency_code', 'ISO 4217三位币种代码,首期仅CNY');
CALL queue_column_comment('baiyue_finance', 'fin_bill', 'customer_name_snapshot', '出账时客户名称快照');
CALL queue_column_comment('baiyue_finance', 'fin_bill', 'due_date', '到期日期,由账单日期加客户账期计算');
CALL queue_column_comment('baiyue_finance', 'fin_bill', 'id', '主键ID');
CALL queue_column_comment('baiyue_finance', 'fin_bill', 'issued_at', '出账时间,未出账时为空');
CALL queue_column_comment('baiyue_finance', 'fin_bill', 'issued_by', '出账操作人ID,未出账时为空');
CALL queue_column_comment('baiyue_finance', 'fin_bill', 'outstanding_amount', '账单未收余额,精度6位小数');
CALL queue_column_comment('baiyue_finance', 'fin_bill', 'received_amount', '账单累计已收金额,精度6位小数');
CALL queue_column_comment('baiyue_finance', 'fin_bill', 'remark', '平台账单内部备注');
CALL queue_column_comment('baiyue_finance', 'fin_bill', 'status', '账单状态:DRAFT草稿、ISSUED已出账、PARTIALLY_PAID部分收款、PAID已收清、VOIDED已作废');
CALL queue_column_comment('baiyue_finance', 'fin_bill', 'subject_id', '平台财务主体ID');
CALL queue_column_comment('baiyue_finance', 'fin_bill', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_finance', 'fin_bill', 'total_amount', '账单全部明细总金额,精度6位小数');
CALL queue_column_comment('baiyue_finance', 'fin_bill', 'updated_at', '最后更新时间,数据修改时自动刷新');
CALL queue_column_comment('baiyue_finance', 'fin_bill', 'updated_by', '最后修改操作人ID');
CALL queue_column_comment('baiyue_finance', 'fin_bill', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_finance', 'fin_bill', 'void_reason', '账单作废原因');
CALL queue_column_comment('baiyue_finance', 'fin_bill', 'voided_at', '作废时间,未作废时为空');
CALL queue_column_comment('baiyue_finance', 'fin_bill', 'voided_by', '作废操作人ID,未作废时为空');
CALL queue_column_comment('baiyue_finance', 'fin_bill_line', 'amount', '账单明细金额,精度6位小数');
CALL queue_column_comment('baiyue_finance', 'fin_bill_line', 'adjustment_reason', '最近一次账单调整行原因;完整历史保存在修订与审计记录');
CALL queue_column_comment('baiyue_finance', 'fin_bill_line', 'bill_id', '所属账单ID');
CALL queue_column_comment('baiyue_finance', 'fin_bill_line', 'created_at', '账单明细创建时间');
CALL queue_column_comment('baiyue_finance', 'fin_bill_line', 'currency_code', 'ISO 4217三位币种代码,首期仅CNY');
CALL queue_column_comment('baiyue_finance', 'fin_bill_line', 'fee_item_code', '出账时费用项目编码快照');
CALL queue_column_comment('baiyue_finance', 'fin_bill_line', 'fee_item_name', '出账时费用项目名称快照');
CALL queue_column_comment('baiyue_finance', 'fin_bill_line', 'id', '主键ID');
CALL queue_column_comment('baiyue_finance', 'fin_bill_line', 'inbound_id', '来源入库单ID,可为空');
CALL queue_column_comment('baiyue_finance', 'fin_bill_line', 'order_id', '来源头程订单ID,可为空');
CALL queue_column_comment('baiyue_finance', 'fin_bill_line', 'source_id', '来源费用ID');
CALL queue_column_comment('baiyue_finance', 'fin_bill_line', 'source_no', '来源费用单号快照,可为空');
CALL queue_column_comment('baiyue_finance', 'fin_bill_line', 'source_type', '费用来源类型,FIRSTLEG_FEE表示头程费用');
CALL queue_column_comment('baiyue_finance', 'fin_bill_line', 'source_version', '出账固化的来源费用版本号');
CALL queue_column_comment('baiyue_finance', 'fin_bill_line', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_finance', 'fin_bill_revision', 'bill_id', '被调整账单ID');
CALL queue_column_comment('baiyue_finance', 'fin_bill_revision', 'bill_snapshot', '调整前账单主记录不可变JSON快照');
CALL queue_column_comment('baiyue_finance', 'fin_bill_revision', 'created_at', '修订快照创建时间');
CALL queue_column_comment('baiyue_finance', 'fin_bill_revision', 'created_by', '执行调整的操作人ID');
CALL queue_column_comment('baiyue_finance', 'fin_bill_revision', 'id', '主键ID');
CALL queue_column_comment('baiyue_finance', 'fin_bill_revision', 'line_snapshot', '调整前全部账单明细不可变JSON数组快照');
CALL queue_column_comment('baiyue_finance', 'fin_bill_revision', 'reason', '本次账单调整原因');
CALL queue_column_comment('baiyue_finance', 'fin_bill_revision', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_finance', 'fin_bill_revision', 'version', '调整前账单乐观锁版本号');
CALL queue_column_comment('baiyue_finance', 'fin_cost', 'confirmed_at', '成本确认时间');
CALL queue_column_comment('baiyue_finance', 'fin_cost', 'confirmed_by', '成本确认操作人ID');
CALL queue_column_comment('baiyue_finance', 'fin_cost', 'container_id', 'Firstleg装柜计划ID,整柜公共成本必填');
CALL queue_column_comment('baiyue_finance', 'fin_cost', 'cost_amount', '成本总金额,精度6位小数');
CALL queue_column_comment('baiyue_finance', 'fin_cost', 'cost_no', '客户范围内唯一成本编号');
CALL queue_column_comment('baiyue_finance', 'fin_cost', 'cost_type', '成本归属类型:SINGLE_ORDER单票成本、CONTAINER_SHARED整柜公共成本');
CALL queue_column_comment('baiyue_finance', 'fin_cost', 'created_at', '记录创建时间');
CALL queue_column_comment('baiyue_finance', 'fin_cost', 'created_by', '创建操作人ID');
CALL queue_column_comment('baiyue_finance', 'fin_cost', 'currency_code', 'ISO 4217三位币种代码,首期仅CNY');
CALL queue_column_comment('baiyue_finance', 'fin_cost', 'fee_item_code', '成本费用项目编码快照');
CALL queue_column_comment('baiyue_finance', 'fin_cost', 'fee_item_name', '成本费用项目名称快照');
CALL queue_column_comment('baiyue_finance', 'fin_cost', 'id', '主键ID');
CALL queue_column_comment('baiyue_finance', 'fin_cost', 'inbound_id', '成本关联的入库单ID,可为空');
CALL queue_column_comment('baiyue_finance', 'fin_cost', 'order_id', '成本归属的头程订单ID,单票成本必填');
CALL queue_column_comment('baiyue_finance', 'fin_cost', 'remark', '平台成本内部备注');
CALL queue_column_comment('baiyue_finance', 'fin_cost', 'status', '成本状态:DRAFT草稿、CONFIRMED已确认、VOIDED已作废');
CALL queue_column_comment('baiyue_finance', 'fin_cost', 'supplier_id', 'Warehouse公共往来单位或供应商引用ID');
CALL queue_column_comment('baiyue_finance', 'fin_cost', 'supplier_name_snapshot', '登记成本时供应商或成本对象名称快照');
CALL queue_column_comment('baiyue_finance', 'fin_cost', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_finance', 'fin_cost', 'updated_at', '最后更新时间,数据修改时自动刷新');
CALL queue_column_comment('baiyue_finance', 'fin_cost', 'updated_by', '最后修改操作人ID');
CALL queue_column_comment('baiyue_finance', 'fin_cost', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_finance', 'fin_cost', 'void_reason', '成本作废原因');
CALL queue_column_comment('baiyue_finance', 'fin_cost', 'voided_at', '成本作废时间');
CALL queue_column_comment('baiyue_finance', 'fin_cost', 'voided_by', '成本作废操作人ID');
CALL queue_column_comment('baiyue_finance', 'fin_cost', 'voucher_file_ids', 'File服务财务凭证文件ID字符串数组JSON');
CALL queue_column_comment('baiyue_finance', 'fin_cost_allocation', 'allocated_amount', '分摊到该订单的成本金额,精度6位');
CALL queue_column_comment('baiyue_finance', 'fin_cost_allocation', 'allocation_rate', '订单体积占总体积的分摊比例,精度10位');
CALL queue_column_comment('baiyue_finance', 'fin_cost_allocation', 'cost_id', '整柜公共成本ID');
CALL queue_column_comment('baiyue_finance', 'fin_cost_allocation', 'created_at', '不可变分摊快照创建时间');
CALL queue_column_comment('baiyue_finance', 'fin_cost_allocation', 'id', '主键ID');
CALL queue_column_comment('baiyue_finance', 'fin_cost_allocation', 'inbound_id', '参与分摊订单关联的入库单ID,可为空');
CALL queue_column_comment('baiyue_finance', 'fin_cost_allocation', 'order_id', '参与分摊的头程订单ID');
CALL queue_column_comment('baiyue_finance', 'fin_cost_allocation', 'rounding_adjustment', '尾差归集标记:0未归集、1该行为实际尾差归集行');
CALL queue_column_comment('baiyue_finance', 'fin_cost_allocation', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_finance', 'fin_cost_allocation', 'total_volume_cbm', '柜内有效订单总体积快照,单位CBM、精度6位');
CALL queue_column_comment('baiyue_finance', 'fin_cost_allocation', 'volume_cbm', '该订单分摊时有效体积快照,单位CBM、精度6位');
CALL queue_column_comment('baiyue_finance', 'fin_customer_settlement_profile', 'created_at', '记录创建时间');
CALL queue_column_comment('baiyue_finance', 'fin_customer_settlement_profile', 'created_by', '创建操作人ID');
CALL queue_column_comment('baiyue_finance', 'fin_customer_settlement_profile', 'credit_days', '客户账期自然日数,默认30天');
CALL queue_column_comment('baiyue_finance', 'fin_customer_settlement_profile', 'customer_name_snapshot', '客户名称快照,客户主数据后续变更不追改');
CALL queue_column_comment('baiyue_finance', 'fin_customer_settlement_profile', 'id', '主键ID');
CALL queue_column_comment('baiyue_finance', 'fin_customer_settlement_profile', 'status', '客户结算配置状态,ACTIVE表示启用');
CALL queue_column_comment('baiyue_finance', 'fin_customer_settlement_profile', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_finance', 'fin_customer_settlement_profile', 'updated_at', '最后更新时间,数据修改时自动刷新');
CALL queue_column_comment('baiyue_finance', 'fin_customer_settlement_profile', 'updated_by', '最后修改操作人ID');
CALL queue_column_comment('baiyue_finance', 'fin_customer_settlement_profile', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_finance', 'fin_fee_source', 'amount', '来源费用金额,精度6位小数');
CALL queue_column_comment('baiyue_finance', 'fin_fee_source', 'container_id', '来源Firstleg装柜计划ID,可为空');
CALL queue_column_comment('baiyue_finance', 'fin_fee_source', 'created_at', '来源快照入池时间');
CALL queue_column_comment('baiyue_finance', 'fin_fee_source', 'currency_code', 'ISO 4217三位币种代码,首期仅CNY');
CALL queue_column_comment('baiyue_finance', 'fin_fee_source', 'customer_id', '费用来源所属客户ID');
CALL queue_column_comment('baiyue_finance', 'fin_fee_source', 'direction', '费用方向:RECEIVABLE应收、PAYABLE应付');
CALL queue_column_comment('baiyue_finance', 'fin_fee_source', 'fee_item_code', '来源费用项目编码快照');
CALL queue_column_comment('baiyue_finance', 'fin_fee_source', 'fee_item_name', '来源费用项目名称快照');
CALL queue_column_comment('baiyue_finance', 'fin_fee_source', 'id', '主键ID');
CALL queue_column_comment('baiyue_finance', 'fin_fee_source', 'inbound_id', '来源入库单ID,可为空');
CALL queue_column_comment('baiyue_finance', 'fin_fee_source', 'order_id', '来源头程订单ID,可为空');
CALL queue_column_comment('baiyue_finance', 'fin_fee_source', 'source_fee_id', '来源系统费用记录ID');
CALL queue_column_comment('baiyue_finance', 'fin_fee_source', 'source_fee_version', '来源费用版本号,用于固化不可变来源版本');
CALL queue_column_comment('baiyue_finance', 'fin_fee_source', 'source_hash', 'Firstleg费用来源完整快照SHA-256哈希');
CALL queue_column_comment('baiyue_finance', 'fin_fee_source', 'source_no', '来源入库单号或柜号快照,可为空');
CALL queue_column_comment('baiyue_finance', 'fin_fee_source', 'source_snapshot', 'Firstleg费用来源完整不可变JSON快照');
CALL queue_column_comment('baiyue_finance', 'fin_fee_source', 'source_system', '费用来源系统,当前FIRSTLEG表示头程服务');
CALL queue_column_comment('baiyue_finance', 'fin_fee_source', 'source_type', '来源费用类型:INBOUND_FEE入库单费用、CONTAINER_FEE柜子费用');
CALL queue_column_comment('baiyue_finance', 'fin_fee_source', 'status', '费用来源状态:AVAILABLE可结算、SUPERSEDED已被新版本替代、VOIDED已作废');
CALL queue_column_comment('baiyue_finance', 'fin_fee_source', 'updated_at', '来源状态最后更新时间,数据修改时自动刷新');
CALL queue_column_comment('baiyue_finance', 'fin_idempotency_record', 'created_at', '幂等请求首次登记时间');
CALL queue_column_comment('baiyue_finance', 'fin_idempotency_record', 'error_code', '失败操作的稳定业务错误码');
CALL queue_column_comment('baiyue_finance', 'fin_idempotency_record', 'id', '主键ID');
CALL queue_column_comment('baiyue_finance', 'fin_idempotency_record', 'idempotency_key', '调用方提供的幂等键');
CALL queue_column_comment('baiyue_finance', 'fin_idempotency_record', 'operation', '幂等隔离的财务操作类型');
CALL queue_column_comment('baiyue_finance', 'fin_idempotency_record', 'operator_id', '发起幂等写操作的操作人ID');
CALL queue_column_comment('baiyue_finance', 'fin_idempotency_record', 'request_hash', '规范化完整请求的SHA-256十六进制哈希');
CALL queue_column_comment('baiyue_finance', 'fin_idempotency_record', 'result_resource_id', '成功操作生成或更新的业务资源ID');
CALL queue_column_comment('baiyue_finance', 'fin_idempotency_record', 'status', '幂等处理状态:PROCESSING处理中、SUCCEEDED成功、FAILED失败');
CALL queue_column_comment('baiyue_finance', 'fin_idempotency_record', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_finance', 'fin_idempotency_record', 'updated_at', '幂等处理状态最后更新时间');
CALL queue_column_comment('baiyue_finance', 'fin_inbox_event', 'error_code', '处理失败时的稳定FIN错误码');
CALL queue_column_comment('baiyue_finance', 'fin_inbox_event', 'event_id', '来源系统事件唯一ID');
CALL queue_column_comment('baiyue_finance', 'fin_inbox_event', 'event_type', '来源事件类型');
CALL queue_column_comment('baiyue_finance', 'fin_inbox_event', 'id', '主键ID');
CALL queue_column_comment('baiyue_finance', 'fin_inbox_event', 'payload', '来源事件原始JSON载荷快照');
CALL queue_column_comment('baiyue_finance', 'fin_inbox_event', 'payload_hash', '规范化事件内容SHA-256哈希,用于同事件ID冲突检测');
CALL queue_column_comment('baiyue_finance', 'fin_inbox_event', 'processed_at', '事件处理完成时间,未完成时为空');
CALL queue_column_comment('baiyue_finance', 'fin_inbox_event', 'received_at', '事件接收时间');
CALL queue_column_comment('baiyue_finance', 'fin_inbox_event', 'result_resource_id', '成功处理生成或定位的费用来源快照ID');
CALL queue_column_comment('baiyue_finance', 'fin_inbox_event', 'source_system', '事件来源系统标识');
CALL queue_column_comment('baiyue_finance', 'fin_inbox_event', 'status', '事件处理状态:RECEIVED已接收、PROCESSED已完成、FAILED处理失败');
CALL queue_column_comment('baiyue_finance', 'fin_receipt', 'created_at', '记录创建时间');
CALL queue_column_comment('baiyue_finance', 'fin_receipt', 'created_by', '创建操作人ID');
CALL queue_column_comment('baiyue_finance', 'fin_receipt', 'currency_code', 'ISO 4217三位币种代码,首期仅CNY');
CALL queue_column_comment('baiyue_finance', 'fin_receipt', 'id', '主键ID');
CALL queue_column_comment('baiyue_finance', 'fin_receipt', 'payment_method', '收款方式编码');
CALL queue_column_comment('baiyue_finance', 'fin_receipt', 'receipt_no', '客户范围内唯一收款编号');
CALL queue_column_comment('baiyue_finance', 'fin_receipt', 'received_amount', '本次实际到账金额,精度6位小数');
CALL queue_column_comment('baiyue_finance', 'fin_receipt', 'received_at', '实际到账时间');
CALL queue_column_comment('baiyue_finance', 'fin_receipt', 'remark', '平台收款内部备注');
CALL queue_column_comment('baiyue_finance', 'fin_receipt', 'status', '收款状态:CONFIRMED已确认、VOIDED已作废');
CALL queue_column_comment('baiyue_finance', 'fin_receipt', 'subject_id', '实际收款的平台财务主体ID');
CALL queue_column_comment('baiyue_finance', 'fin_receipt', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_finance', 'fin_receipt', 'updated_at', '最后更新时间,数据修改时自动刷新');
CALL queue_column_comment('baiyue_finance', 'fin_receipt', 'updated_by', '最后修改操作人ID');
CALL queue_column_comment('baiyue_finance', 'fin_receipt', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_finance', 'fin_receipt', 'void_reason', '收款作废原因');
CALL queue_column_comment('baiyue_finance', 'fin_receipt', 'voided_at', '收款作废时间');
CALL queue_column_comment('baiyue_finance', 'fin_receipt', 'voided_by', '收款作废操作人ID');
CALL queue_column_comment('baiyue_finance', 'fin_receipt', 'voucher_file_ids', 'File服务财务凭证文件ID字符串数组JSON');
CALL queue_column_comment('baiyue_finance', 'fin_receipt', 'voucher_no', '银行流水号或线下收款凭证号');
CALL queue_column_comment('baiyue_finance', 'fin_receipt_bill', 'applied_amount', '本笔收款分配到该账单的金额,精度6位小数');
CALL queue_column_comment('baiyue_finance', 'fin_receipt_bill', 'bill_id', '被分配收款的账单ID');
CALL queue_column_comment('baiyue_finance', 'fin_receipt_bill', 'created_at', '收款分配创建时间');
CALL queue_column_comment('baiyue_finance', 'fin_receipt_bill', 'created_by', '分配操作人ID');
CALL queue_column_comment('baiyue_finance', 'fin_receipt_bill', 'id', '主键ID');
CALL queue_column_comment('baiyue_finance', 'fin_receipt_bill', 'receipt_id', '收款记录ID');
CALL queue_column_comment('baiyue_finance', 'fin_receipt_bill', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_finance', 'fin_subject', 'created_at', '记录创建时间');
CALL queue_column_comment('baiyue_finance', 'fin_subject', 'created_by', '创建操作人ID,0表示系统初始化');
CALL queue_column_comment('baiyue_finance', 'fin_subject', 'id', '主键ID');
CALL queue_column_comment('baiyue_finance', 'fin_subject', 'status', '财务主体状态,ACTIVE表示启用');
CALL queue_column_comment('baiyue_finance', 'fin_subject', 'subject_code', '平台财务主体唯一编码');
CALL queue_column_comment('baiyue_finance', 'fin_subject', 'subject_name', '平台收付款及核算主体名称');
CALL queue_column_comment('baiyue_finance', 'fin_subject', 'updated_at', '最后更新时间,数据修改时自动刷新');
CALL queue_column_comment('baiyue_finance', 'fin_subject', 'updated_by', '最后修改操作人ID,0表示系统初始化');
CALL queue_column_comment('baiyue_finance', 'fin_subject', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_firstleg', 'fl_assignment_history', 'assignee_id', '新负责人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_assignment_history', 'assignee_name', '新负责人名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_assignment_history', 'created_at', '指派发生时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_assignment_history', 'id', '指派历史ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_assignment_history', 'operator_id', '执行指派的操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_assignment_history', 'previous_assignee_id', '原负责人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_assignment_history', 'reason', '指派或改派原因');
CALL queue_column_comment('baiyue_firstleg', 'fl_assignment_history', 'task_id', '所属履约任务ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_assignment_history', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_fba_rate', 'additional_fee', '固定附加费');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_fba_rate', 'business_type', '业务或计费类型');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_fba_rate', 'carrier_id', '船司逻辑ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_fba_rate', 'carrier_name', '历史承运商名称;渠道价格可为空');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_fba_rate', 'channel_code', '渠道编码');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_fba_rate', 'charge_unit', '计费单位:KG或CBM');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_fba_rate', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_fba_rate', 'created_by', '创建人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_fba_rate', 'currency', '币种');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_fba_rate', 'deleted', '逻辑删除标记');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_fba_rate', 'destination_code', 'FBA目的仓编码;区域阶梯模型为空');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_fba_rate', 'destination_name', '目的仓名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_fba_rate', 'destination_type', '目的地类型:FBA或OVERSEAS;REGION_WEIGHT_TIER区域阶梯行为空');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_fba_rate', 'destination_warehouse_id', '平台海外仓稳定ID;不存环境专属ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_fba_rate', 'effective_from', '生效开始时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_fba_rate', 'effective_to', '生效结束时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_fba_rate', 'id', '船司FBA价格ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_fba_rate', 'origin_warehouse_code', '起运仓稳定编码');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_fba_rate', 'origin_warehouse_id', '确认前可为空;确认后绑定平台公共起运仓逻辑ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_fba_rate', 'origin_warehouse_name', '起运仓名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_fba_rate', 'price_book_version_id', '整表价格版本ID;历史兼容价格可为空');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_fba_rate', 'price_model', '价格模型');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_fba_rate', 'pricing_tax_mode', '报价包税维度;独立于订单tax_mode');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_fba_rate', 'rate_code', '平台公共价格编码');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_fba_rate', 'rate_version', '价格版本号');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_fba_rate', 'region_code', '区域阶梯模型区域编码');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_fba_rate', 'remark', '价格备注');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_fba_rate', 'source_destination_text', '原始组合仓库或区域文本');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_fba_rate', 'source_row_number', '原始Excel行号');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_fba_rate', 'source_sheet_name', '原始Excel Sheet');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_fba_rate', 'status', '价格状态:PENDING_CONFIRMATION、ACTIVE或SUPERSEDED;分别表示待确认、已生效、已被替代;FAILED表示解析失败');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_fba_rate', 'tenant_id', '平台价格作用域ID,船司价格固定为0');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_fba_rate', 'unit_price', '基础单价');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_fba_rate', 'updated_at', '更新时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_fba_rate', 'updated_by', '最后修改人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_fba_rate', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_fba_rate', 'weight_from_kg', '区域阶梯起始计费重量KG');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query', 'access_mode', '官网访问方式:PUBLIC_FORM公开表单、HCAPTCHA人机校验、DYNAMIC_IFRAME动态页面');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query', 'attempt_count', '当前查询窗口已执行次数;最多3次');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query', 'carrier_code', '船公司编码:MATSON美森、SML森罗、ZIM以星、COSCO中远海运、EMC长荣、HEDE合德');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query', 'container_booking_id', '指定查询的柜次ID;整票订舱查询时为空');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query', 'container_scope_id', '柜次唯一作用域计算值;整票订舱查询固定为0');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query', 'created_at', '任务创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query', 'created_by', '创建操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query', 'created_by_name', '创建操作人姓名快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query', 'customer_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query', 'expires_at', '当前最近有效结果过期时间;到期后仍保留但标记过期');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query', 'id', '船司官网查询任务ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query', 'last_attempt_at', '最近一次查询尝试时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query', 'last_attempt_status', '最近查询尝试状态快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query', 'last_error_code', '最近查询失败错误码');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query', 'last_error_message', '最近查询失败错误说明');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query', 'last_success_at', '最近一次取得有效结果时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query', 'manual_reason', '需要人工查询或录入的原因');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query', 'next_allowed_at', '频控规则允许的下次查询时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query', 'ocean_booking_id', '关联海运订舱ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query', 'official_url', '船公司官网查询入口地址快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query', 'query_no', '船司官网查询任务编号');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query', 'remark', '平台内部备注');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query', 'result_port_schedule_id', '最近有效结果对应的船期记录ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query', 'source_updated_at', '船公司官网展示的结果更新时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query', 'status', '任务状态:READY可查询、MANUAL_REQUIRED需人工处理、SUCCESS已取得有效结果');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query', 'tracking_no', '提交至船公司官网的跟踪号码');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query', 'tracking_type', '官网跟踪类型:BOOKING_NO订舱号、BILL_OF_LADING提单号、CONTAINER_NO箱号');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query', 'updated_at', '任务最后更新时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query', 'updated_by', '最后修改操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query', 'updated_by_name', '最后修改操作人姓名快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query', 'window_started_at', '当前三天查询窗口起始时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query_attempt', 'access_mode', '本次尝试的官网访问方式快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query_attempt', 'attempt_status', '尝试结果:MANUAL_REQUIRED需人工处理、SUCCESS成功、FAILED失败');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query_attempt', 'carrier_code', '本次尝试的船公司编码快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query_attempt', 'coverage_gap', '是否存在自动化覆盖缺口:0否、1是');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query_attempt', 'coverage_gap_code', '自动化覆盖缺口编码');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query_attempt', 'created_at', '尝试记录创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query_attempt', 'error_code', '查询失败错误码');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query_attempt', 'error_message', '查询失败错误说明');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query_attempt', 'id', '船司官网查询尝试ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query_attempt', 'manual_reason', '转人工处理原因');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query_attempt', 'official_url', '本次尝试使用的官网地址快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query_attempt', 'operator_id', '执行本次尝试的操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query_attempt', 'operator_name', '执行操作人姓名快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query_attempt', 'query_id', '所属船司官网查询任务ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query_attempt', 'window_attempt_no', '当前三天窗口内的尝试序号;取值1至3');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query_history', 'action', '历史动作:CREATE创建、ATTEMPT执行查询、MANUAL_RESULT录入人工结果');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query_history', 'after_snapshot', '变更后任务数据快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query_history', 'before_snapshot', '变更前任务数据快照;创建时为空');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query_history', 'created_at', '历史记录创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query_history', 'from_version', '变更前任务版本;创建时为空');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query_history', 'id', '船司查询任务历史ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query_history', 'operator_id', '操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query_history', 'operator_name', '操作人姓名快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query_history', 'query_id', '所属船司官网查询任务ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query_history', 'reason', '操作原因或说明');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query_history', 'to_version', '变更后任务版本');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query_operation', 'completed_at', '写操作成功完成时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query_operation', 'created_at', '幂等记录创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query_operation', 'id', '船司查询写操作幂等记录ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query_operation', 'idempotency_key', '客户端写操作幂等键');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query_operation', 'operation', '幂等写操作类型');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query_operation', 'operator_id', '发起写操作的平台操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query_operation', 'query_id', '关联查询任务ID;创建任务落库前允许为空');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query_operation', 'request_hash', '规范化请求内容SHA-256摘要;用于识别同键不同请求');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query_operation', 'result_json', '成功结果JSON快照;供幂等重放返回');
CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query_operation', 'status', '处理状态:PROCESSING处理中、SUCCESS成功');
CALL queue_column_comment('baiyue_firstleg', 'fl_consumed_event', 'consumed_at', '首次成功消费时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_consumed_event', 'event_id', '已消费领域事件ID,与生产端Outbox事件ID一致');
CALL queue_column_comment('baiyue_firstleg', 'fl_consumed_event', 'event_type', '已消费事件类型');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_booking', 'actual_port_arrival_at', '实际到港时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_booking', 'actual_sailing_at', '实际开船时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_booking', 'actual_warehouse_arrival_at', '实际到仓时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_booking', 'bill_of_lading_no', '提单号');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_booking', 'booking_no', '平台唯一货柜预订单号');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_booking', 'cabinet_no', '平台唯一内部柜号');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_booking', 'capacity_cbm', '货柜容量m3');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_booking', 'container_no', '集装箱号;确认前允许修订');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_booking', 'container_type', '箱型,例如20GP、40GP、40HQ');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_booking', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_booking', 'created_by', '创建人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_booking', 'created_by_name', '创建人名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_booking', 'departure_port_code', '起运港编码快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_booking', 'departure_port_name', '起运港名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_booking', 'destination_port_code', '目的港编码快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_booking', 'destination_port_name', '目的港名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_booking', 'destination_warehouse_code', '目的仓编码快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_booking', 'destination_warehouse_name', '目的仓名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_booking', 'estimated_port_arrival_at', '预计到港时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_booking', 'estimated_sailing_at', '预计开船时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_booking', 'estimated_warehouse_arrival_at', '预计到仓时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_booking', 'id', '货柜预订ID(雪花ID)');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_booking', 'loading_at', '计划或实际装柜时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_booking', 'max_weight_kg', '货柜最大承重kg');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_booking', 'shipping_company_id', '船司ID快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_booking', 'shipping_company_name', '船司名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_booking', 'terminal_trucking_company', '码头拖车公司');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_booking', 'updated_at', '货柜资料最后更新时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_booking', 'updated_by', '最后修改人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_booking', 'updated_by_name', '最后修改人名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_booking', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_booking', 'vessel_name', '船名快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_booking', 'voyage_no', '航次;确认前允许修订');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_booking', 'warehouse_id', '装柜仓库ID;平台公共资源');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_booking', 'warehouse_name', '装柜仓库名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_document', 'container_booking_id', '货柜预订ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_document', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_document', 'document_type', '文件类型:DESTINATION_CLEARANCE目的港清关、CUSTOMS_DECLARATION出口报关、DELIVERY_ORDER提货单、PICKUP_ORDER取货单');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_document', 'file_id', 'File服务文件ID,不建立跨库外键');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_document', 'file_name', '文件名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_document', 'file_url', '文件访问地址快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_document', 'id', '货柜文件关联ID(雪花ID)');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_document', 'updated_at', '货柜文件关联最后更新时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_document', 'uploaded_by', '上传操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_document', 'uploaded_by_name', '上传操作人名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_document', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_fee_detail', 'amount', 'CNY费用金额');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_fee_detail', 'business_date', '费用业务日期');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_fee_detail', 'container_booking_id', '订舱柜ID快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_fee_detail', 'container_no', '柜号快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_fee_detail', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_fee_detail', 'created_by', '创建人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_fee_detail', 'currency', '币种,首期固定CNY');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_fee_detail', 'direction', '费用方向:RECEIVABLE应收、PAYABLE应付');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_fee_detail', 'partner_id', '应付往来单位ID快照;应收为空');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_fee_detail', 'partner_name', '应付往来单位名称快照;应收为空');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_fee_detail', 'fee_item_code_snapshot', '费用项目稳定编码快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_fee_detail', 'fee_item_id', '平台公共费用项目ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_fee_detail', 'fee_item_name_snapshot', '费用项目名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_fee_detail', 'id', '柜次费用来源ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_fee_detail', 'loading_plan_id', '柜次ID,对应fl_loading_plan.id');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_fee_detail', 'remark', '人工备注');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_fee_detail', 'review_reason', '审核驳回原因');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_fee_detail', 'review_status', '审核状态:PENDING待审核、APPROVED通过、REJECTED驳回');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_fee_detail', 'reviewed_at', '审核时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_fee_detail', 'reviewer_id', '审核人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_fee_detail', 'reviewer_name', '审核人名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_fee_detail', 'settlement_included', '是否参与财务结算来源池');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_fee_detail', 'source_status', '来源状态:ACTIVE有效、VOIDED作废');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_fee_detail', 'source_version', '费用来源版本;调整递增,作废携带当前版本');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_fee_detail', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_fee_detail', 'updated_at', '最后更新时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_container_fee_detail', 'updated_by', '最后更新人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_charge_rule', 'business_type', '业务类型');
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_charge_rule', 'charge_mode', '计费口径:KG或CBM');
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_charge_rule', 'conversion_factor', '重量体积换算系数');
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_charge_rule', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_charge_rule', 'created_by', '创建人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_charge_rule', 'currency', '默认币种');
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_charge_rule', 'customer_id', '客户逻辑ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_charge_rule', 'customer_name', '客户名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_charge_rule', 'deleted', '逻辑删除标记');
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_charge_rule', 'effective_from', '生效开始时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_charge_rule', 'effective_to', '生效结束时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_charge_rule', 'id', '客户计费规则ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_charge_rule', 'remark', '规则备注');
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_charge_rule', 'rounding_mode', '舍入方式');
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_charge_rule', 'rounding_scale', '结果保留小数位数');
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_charge_rule', 'rule_code', '客户内唯一计费规则编码');
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_charge_rule', 'rule_version', '规则版本号');
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_charge_rule', 'status', '规则状态:ACTIVE启用、DISABLED停用');
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_charge_rule', 'tax_mode', '包税或不包税类型');
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_charge_rule', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_charge_rule', 'updated_at', '更新时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_charge_rule', 'updated_by', '最后修改人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_charge_rule', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_dimension_config', 'box_type', '固定箱规类型:SMALL/MEDIUM/LARGE/EXTRA_LARGE');
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_dimension_config', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_dimension_config', 'created_by', '创建人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_dimension_config', 'deleted', '逻辑删除');
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_dimension_config', 'edge_addition_cm', '每个尺寸方向的单侧加宽厘米数');
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_dimension_config', 'id', '配置ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_dimension_config', 'initialization_key', '创建流程幂等键及补偿归属');
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_dimension_config', 'inner_height_cm', '箱规内高(厘米)');
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_dimension_config', 'inner_length_cm', '箱规内长(厘米)');
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_dimension_config', 'inner_width_cm', '箱规内宽(厘米)');
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_dimension_config', 'remark', '备注');
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_dimension_config', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_dimension_config', 'updated_at', '更新时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_dimension_config', 'updated_by', '最后修改人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_customer_dimension_config', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_case', 'checklist_code', '创建协同单时的清单编码快照;源清单变化不回写');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_case', 'checklist_version_id', '本单冻结使用的报关资料清单版本ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_case', 'checklist_version_no', '创建协同单时的清单版本号快照;源清单变化不回写');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_case', 'container_booking_id', '关联柜次逻辑ID;0表示创建时尚未关联柜次');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_case', 'created_at', '协同单创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_case', 'created_by', '创建协同单的平台操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_case', 'created_by_name', '创建协同单时的平台操作人名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_case', 'customs_no', '全局唯一报关协同单号');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_case', 'id', '报关协同单逻辑ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_case', 'inbound_id', 'Firstleg入库单逻辑ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_case', 'order_id', 'Order服务订单逻辑ID;跨库不建立外键');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_case', 'remark', '报关协同单内部业务备注');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_case', 'status', '协同状态:DOCUMENTS_PENDING待补资料、REVIEWING审核中、SUPPLEMENT_REQUIRED需补件、READY_TO_SUBMIT待提交代理、RECEIPT_PENDING待回执、RECEIPT_AVAILABLE已有回执、CLEARANCE_COMPLETED已放行、CANCELLED已取消');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_case', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_case', 'updated_at', '协同单最后修改时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_case', 'updated_by', '最后修改协同单的平台操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_case', 'updated_by_name', '最后修改时的平台操作人名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_case', 'version', '乐观锁版本号;每次状态变更递增');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_checklist_version', 'cargo_type', '清单适用的货物类型编码');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_checklist_version', 'channel_code', '清单适用的运输渠道编码');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_checklist_version', 'checklist_code', '报关资料清单编码;同一清单的多个版本共用');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_checklist_version', 'country_code', '清单适用的目的国家或地区编码');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_checklist_version', 'created_at', '清单版本创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_checklist_version', 'created_by', '创建清单版本的平台操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_checklist_version', 'created_by_name', '创建清单版本时的平台操作人名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_checklist_version', 'id', '报关资料清单版本逻辑ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_checklist_version', 'items_json', '不可变清单项目数组;元素包含code项目编码、name项目名称和required是否必填');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_checklist_version', 'status', '清单版本状态:ACTIVE启用、RETIRED停用');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_checklist_version', 'version_no', '清单业务版本号');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_document', 'checklist_item_code', '对应冻结清单中的资料项目编码');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_document', 'customs_case_id', '所属报关协同单ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_document', 'document_version', '同一协同单和清单项目下的文件版本序号');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_document', 'file_hash', '文件内容SHA-256摘要;用于版本审计和幂等校验');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_document', 'file_id', 'File服务文件逻辑ID;跨库不建立外键');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_document', 'file_name', '上传或引用时的文件名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_document', 'id', '报关资料文件版本逻辑ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_document', 'replaces_document_id', '被本版本替换的上一资料版本ID;首个版本为空');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_document', 'review_reason', '审核退回原因或审核备注');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_document', 'reviewed_at', '资料审核时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_document', 'reviewed_by', '审核资料的平台操作人ID;未审核时为空');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_document', 'reviewed_by_name', '审核时的平台操作人名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_document', 'status', '资料状态:UPLOADED待审核、APPROVED审核通过、RETURNED已退回、SUPERSEDED已被替换、FROZEN已随代理提交批次冻结');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_document', 'uploaded_at', '资料上传或引用时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_document', 'uploaded_by', '上传或引用资料的操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_document', 'uploaded_by_type', '上传主体类型:PLATFORM平台操作人、MERCHANT客户操作人');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_document', 'visibility', '资料可见范围:PLATFORM仅平台可见、CUSTOMER客户可见');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_history', 'action', '业务动作编码,如CREATE、UPLOAD_DOCUMENT、REPLACE_DOCUMENT、REVIEW_DOCUMENT_APPROVE、REVIEW_DOCUMENT_RETURN、SUBMIT_AGENT_BATCH或UPLOAD_RECEIPT');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_history', 'created_at', '历史记录创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_history', 'customs_case_id', '所属报关协同单ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_history', 'from_status', '动作发生前的协同状态;创建动作时为空');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_history', 'id', '报关协同历史逻辑ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_history', 'note', '动作原因、资料项目编码或业务备注快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_history', 'operator_id', '执行业务动作的操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_history', 'operator_name', '执行动作时的操作人名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_history', 'operator_type', '操作主体类型:PLATFORM平台操作人、MERCHANT客户操作人');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_history', 'resource_id', '动作关联资源逻辑ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_history', 'resource_type', '动作关联资源类型:CASE协同单、DOCUMENT资料、SUBMISSION_BATCH提交批次或RECEIPT回执');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_history', 'to_status', '动作完成后的协同状态');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_operation', 'action', '幂等业务动作编码,包含平台或客户动作及资料、审核、提交、回执类型');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_operation', 'completed_at', '写命令成功完成时间;处理中为空');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_operation', 'created_at', '幂等记录创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_operation', 'id', '报关写命令幂等记录逻辑ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_operation', 'idempotency_key', '客户端提供的幂等键');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_operation', 'operator_id', '发起写命令的操作人ID;与动作和幂等键共同限定重放范围');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_operation', 'request_hash', '规范化完整请求内容的SHA-256摘要;同键不同请求时拒绝');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_operation', 'result_id', '成功结果资源逻辑ID;处理中为空');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_operation', 'result_type', '成功结果资源类型:CHECKLIST、CASE、DOCUMENT、BATCH或RECEIPT');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_operation', 'status', '幂等处理状态:PROCESSING处理中、SUCCESS处理成功');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_receipt', 'customs_case_id', '所属报关协同单ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_receipt', 'file_hash', '回执文件内容SHA-256摘要;用于版本审计和幂等校验');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_receipt', 'file_id', 'File服务回执文件逻辑ID;跨库不建立外键');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_receipt', 'file_name', '上传或引用时的回执文件名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_receipt', 'id', '报关回执文件版本逻辑ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_receipt', 'note', '回执业务备注');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_receipt', 'receipt_type', '回执类型:RECEIVED已收件、DECLARED已申报、REJECTED已拒绝、INSPECTION查验通知、RELEASED放行回执、OTHER其他回执');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_receipt', 'receipt_version', '同一提交批次和回执类型下的版本序号');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_receipt', 'replaces_receipt_id', '被本版本替换的上一回执版本ID;首个版本为空');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_receipt', 'submission_batch_id', '产生本回执的报关代理提交批次ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_receipt', 'uploaded_at', '回执上传或引用时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_receipt', 'uploaded_by', '上传或引用回执的平台操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_receipt', 'uploaded_by_name', '上传回执时的平台操作人名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_receipt', 'visibility', '回执可见范围:PLATFORM仅平台可见、CUSTOMER客户可查询下载');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_submission_batch', 'batch_no', '全局唯一代理提交批次号');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_submission_batch', 'batch_version', '同一报关协同单内的提交批次版本序号');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_submission_batch', 'customs_agent_name', '本批次接收报关资料的报关代理名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_submission_batch', 'customs_case_id', '所属报关协同单ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_submission_batch', 'external_reference', '报关代理或外部系统返回的业务参考号');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_submission_batch', 'file_snapshot_json', '提交时冻结的已审核资料数组;元素包含documentId、fileId、itemCode、documentVersion和fileHash');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_submission_batch', 'id', '报关代理提交批次逻辑ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_submission_batch', 'note', '提交报关代理时的平台备注');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_submission_batch', 'status', '代理提交状态:SUBMITTED已提交、ACKNOWLEDGED代理已确认、REJECTED代理已拒绝、COMPLETED代理处理完成');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_submission_batch', 'submitted_at', '提交报关代理时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_submission_batch', 'submitted_by', '提交报关代理的平台操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_submission_batch', 'submitted_by_name', '提交时的平台操作人名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_supplement_request', 'checklist_item_code', '需要补充的报关资料项目编码');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_supplement_request', 'customs_case_id', '所属报关协同单ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_supplement_request', 'due_at', '期望完成补件时间;未设置时为空');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_supplement_request', 'id', '报关补件请求逻辑ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_supplement_request', 'reason', '平台审核人员填写的补件原因');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_supplement_request', 'requested_at', '补件请求发起时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_supplement_request', 'requested_by', '发起补件的平台操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_supplement_request', 'requested_by_name', '发起补件时的平台操作人名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_supplement_request', 'resolved_at', '补件请求解决时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_supplement_request', 'resolved_by', '提交解决资料的操作人ID;未解决时为空');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_supplement_request', 'resolved_document_id', '解决本次补件的新资料版本ID;未解决时为空');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_supplement_request', 'source_document_id', '触发本次补件的被退回资料版本ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_customs_supplement_request', 'status', '补件状态:OPEN待补件、RESOLVED已由新资料解决、CANCELLED已取消');
CALL queue_column_comment('baiyue_firstleg', 'fl_fee_item', 'allocation_fee', '平摊费用是否参与');
CALL queue_column_comment('baiyue_firstleg', 'fl_fee_item', 'category', '费用分类');
CALL queue_column_comment('baiyue_firstleg', 'fl_fee_item', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_fee_item', 'created_by', '创建人ID;系统初始化时可为空');
CALL queue_column_comment('baiyue_firstleg', 'fl_fee_item', 'created_by_name', '创建人名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_fee_item', 'deleted', '逻辑删除标记:0未删除、1已删除');
CALL queue_column_comment('baiyue_firstleg', 'fl_fee_item', 'description', '说明');
CALL queue_column_comment('baiyue_firstleg', 'fl_fee_item', 'fee_code', '稳定费用编码;系统费用使用固定编码');
CALL queue_column_comment('baiyue_firstleg', 'fl_fee_item', 'fee_type_name', '费用类型名称');
CALL queue_column_comment('baiyue_firstleg', 'fl_fee_item', 'id', '费用项目ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_fee_item', 'pickup_payable', '提柜应付是否参与');
CALL queue_column_comment('baiyue_firstleg', 'fl_fee_item', 'pickup_receivable', '提柜应收是否参与');
CALL queue_column_comment('baiyue_firstleg', 'fl_fee_item', 'status', '状态:ACTIVE启用、DISABLED停用');
CALL queue_column_comment('baiyue_firstleg', 'fl_fee_item', 'tenant_id', '平台公共费用项目作用域,固定为0;物理字段名沿用 tenant_id');
CALL queue_column_comment('baiyue_firstleg', 'fl_fee_item', 'trade_scope', 'DOMESTIC国内、INTERNATIONAL国际');
CALL queue_column_comment('baiyue_firstleg', 'fl_fee_item', 'updated_at', '最后更新时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_fee_item', 'updated_by', '最后修改人ID;系统初始化时可为空');
CALL queue_column_comment('baiyue_firstleg', 'fl_fee_item', 'updated_by_name', '最后修改人名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_fee_item', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_firstleg', 'fl_fee_item', 'warehouse_payable', '仓储应付是否参与');
CALL queue_column_comment('baiyue_firstleg', 'fl_fee_item', 'warehouse_receivable', '仓储应收是否参与');
CALL queue_column_comment('baiyue_firstleg', 'fl_fulfillment_task', 'assigned_at', '最近指派时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_fulfillment_task', 'assignee_id', '当前负责人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_fulfillment_task', 'assignee_name', '当前负责人名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_fulfillment_task', 'completed_at', '履约完成时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_fulfillment_task', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_fulfillment_task', 'created_by', '创建人或系统主体ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_fulfillment_task', 'deleted', '逻辑删除标记');
CALL queue_column_comment('baiyue_firstleg', 'fl_fulfillment_task', 'id', '履约任务ID(雪花ID)');
CALL queue_column_comment('baiyue_firstleg', 'fl_fulfillment_task', 'order_id', '订单服务中的订单逻辑ID,不建立跨库外键');
CALL queue_column_comment('baiyue_firstleg', 'fl_fulfillment_task', 'order_no', '订单号快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_fulfillment_task', 'order_snapshot_json', '创建履约任务时所需的订单业务快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_fulfillment_task', 'started_at', '开始履约时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_fulfillment_task', 'status', '履约状态:PENDING_ASSIGNMENT待指派、ASSIGNED已指派、PROCESSING处理中、COMPLETED已完成、CANCELLED已取消');
CALL queue_column_comment('baiyue_firstleg', 'fl_fulfillment_task', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_firstleg', 'fl_fulfillment_task', 'updated_at', '更新时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_fulfillment_task', 'updated_by', '最后修改人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_fulfillment_task', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_carton_label', 'box_code', '固定二维码内容:入仓码#总箱数-箱下标');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_carton_label', 'box_specification', '订单箱规或货物明细描述快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_carton_label', 'cargo_item_no', '订单货物明细序号快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_carton_label', 'cargo_name', '货物名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_carton_label', 'carton_index', '整单从1开始的稳定箱下标');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_carton_label', 'created_at', '首次分配时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_carton_label', 'detail_package_count', '该订单货物明细箱数快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_carton_label', 'height_cm', '下单箱规高度cm快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_carton_label', 'id', '实际箱唛头映射ID(雪花ID)');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_carton_label', 'inbound_code', '入仓码快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_carton_label', 'inbound_detail_id', '订单货物明细ID,不建立跨库外键');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_carton_label', 'inbound_id', '入库单ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_carton_label', 'length_cm', '下单箱规长度cm快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_carton_label', 'order_id', '订单服务订单ID快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_carton_label', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_carton_label', 'total_carton_count', '首次分配时整单总箱数');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_carton_label', 'width_cm', '下单箱规宽度cm快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_destination_snapshot', 'address_line', '详细地址');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_destination_snapshot', 'city', '城市');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_destination_snapshot', 'contact_name', '联系人');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_destination_snapshot', 'contact_phone', '联系人电话');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_destination_snapshot', 'country_code', '国家二字码');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_destination_snapshot', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_destination_snapshot', 'destination_code', '目的仓编码');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_destination_snapshot', 'destination_name', '目的仓名称');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_destination_snapshot', 'destination_type', '目的仓类型');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_destination_snapshot', 'id', '目的仓地址快照ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_destination_snapshot', 'inbound_id', '入库单ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_destination_snapshot', 'postal_code', '邮编');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_destination_snapshot', 'state_province', '省或州');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_destination_snapshot', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'actual_volume_cbm', '试算时实际体积m3');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'actual_weight_kg', '试算时实际重量kg');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'additional_amount', '附加费金额');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'business_type', '业务类型快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'calculated_at', '试算时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'calculated_by', '试算人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'carrier_id', '历史承运商ID;渠道价格可为空');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'carrier_name', '历史承运商名称;渠道价格可为空');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'carrier_rate_id', '船司FBA价格ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'carrier_rate_version', '船司价格版本快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'channel_code', '渠道编码快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'charge_mode', '计费口径:KG或CBM');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'charge_rule_id', '历史客户计费规则ID;渠道价格可为空');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'charge_rule_version', '历史客户规则版本快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'charge_unit', '计费单位');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'chargeable_quantity', '最终计费量');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'confirmed_at', '确认时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'confirmed_by', '确认人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'conversion_factor', '历史客户规则使用的换算系数快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'converted_quantity', '换算后的重量或体积');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'currency', '币种');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'customer_id', '客户ID快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'customer_name', '客户名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'destination_code', 'FBA仓编码快照;海外仓可为空');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'destination_name', '目的仓名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'destination_type', '目的地类型快照:FBA或OVERSEAS');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'destination_warehouse_id', '海外仓或FBA目的仓ID快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'exception_release', '是否基于测量异常放行生成');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'fee_no', '客户内唯一费用单号');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'fee_status', '费用状态:CALCULATED已计算、SUPERSEDED已被新版本替代');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'fee_version', '费用快照版本');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'id', '入库费用ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'inbound_id', '入库单ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'origin_warehouse_id', '起运仓ID快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'origin_warehouse_name', '起运仓名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'price_book_version_id', '整表价格版本ID快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'pricing_tax_mode', '报价包税维度快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'rate_code', '船司价格编码快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'rate_effective_from', '价格生效开始快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'rate_effective_to', '价格生效结束快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'review_status', '费用审核状态:NOT_SUBMITTED未提交、PENDING待审核、APPROVED已通过、REJECTED已驳回、CONFIRMED已确认');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'rounding_mode', '历史客户规则舍入方式快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'rounding_scale', '历史客户规则计费量保留位数快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'rule_code', '历史客户规则编码快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'rule_effective_from', '历史客户规则生效开始快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'rule_effective_to', '客户规则生效结束快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'sent_at', '发送客户时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'sent_by', '发送人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'subtotal_amount', '基础费用金额');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'tax_mode', '订单买单或退税模式快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'total_amount', '总金额');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee', 'updated_at', '更新时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'actual_gross_weight_kg', '仓库实测毛重快照,单位千克');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'actual_volume_cbm', '实际体积快照,单位立方米');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'amount', '该费用明细金额,币种由currency字段指定');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'business_date', '业务发生日');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'channel_code', '渠道编码快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'channel_name', '渠道名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'charge_unit', 'KG或CBM');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'chargeable_quantity', '本费用最终计费量');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'chargeable_volume_cbm', '费用计算采用的计费体积,单位立方米');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'chargeable_weight_kg', '费用计算采用的计费重量,单位千克');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'created_by', '创建人或系统主体ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'currency', '金额币种三字码,如CNY');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'customer_id', '客户ID快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'customer_name', '客户公司名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'deleted', '逻辑删除标记:0未删除、1已删除');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'destination_code', '目的仓编码快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'destination_name', '目的仓名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'direction', 'RECEIVABLE应收、PAYABLE应付');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'fee_category_snapshot', '费用分类快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'fee_item_id', '绑定费用项目ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'fee_type_name_snapshot', '费用类型名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'id', '入库费用明细ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'inbound_code', '入仓码快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'inbound_id', '入库单ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'inbounded_at', '入库完成时间快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'invoice_title', '发票抬头快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'package_count', '件数快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'publication_status', '费用发布状态:DRAFT草稿、PUBLISHED已发布、SUPERSEDED已替代');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'published_at', '发布时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'published_by', '发布人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'published_by_name', '发布人名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'remark', '备注');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'review_reason', '审核驳回原因');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'review_status', '审核状态:PENDING待审核、APPROVED已通过、REJECTED已驳回');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'reviewed_at', '审核时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'reviewer_id', '审核人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'reviewer_name', '审核人名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'settlement_included', '是否参与结算');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'source_fee_id', '来源计费快照ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'source_fee_no', '来源费用单号');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'source_fee_version', '来源计费快照版本');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'source_type', 'MANUAL或FBA_PRICING');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'tenant_id', '客户ID(客户隔离键);费用明细引用平台公共费用项目');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'trade_scope_snapshot', '国内国际快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'unit_price', '费用单价;计价单位由对应费用项目规则确定');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'updated_at', '最后更新时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'updated_by', '最后修改人或系统主体ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'volume_weight_kg', '材积重快照,单位千克');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_item', 'amount', '金额');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_item', 'automatic_flag', '是否自动计算');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_item', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_item', 'currency', '币种');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_item', 'fee_code', '费用项目编码');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_item', 'fee_id', '费用头ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_item', 'fee_name', '费用项目名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_item', 'id', '费用明细ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_item', 'item_no', '费用单内序号');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_item', 'quantity', '计费数量');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_item', 'remark', '费用备注');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_item', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_item', 'unit', '计费单位');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_item', 'unit_price', '单价');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_file', 'deleted', '逻辑删除标记');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_file', 'file_id', '文件服务文件ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_file', 'file_name_snapshot', '文件名快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_file', 'file_type', '文件类型:RELATED、MARK、PC_ORDER、MEASURE或WEIGHT');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_file', 'file_version', '业务文件版本');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_file', 'id', '入库单文件关系ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_file', 'inbound_id', '入库单ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_file', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_file', 'uploaded_at', '上传时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_file', 'uploaded_by', '上传人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_file', 'visibility', '可见范围');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_hold_record', 'evidence_file_id', '证据文件ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_hold_record', 'hold_action', '动作:HOLD或RELEASE');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_hold_record', 'id', '扣货记录ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_hold_record', 'inbound_id', '入库单ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_hold_record', 'operated_at', '操作时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_hold_record', 'operator_id', '操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_hold_record', 'operator_name', '操作人名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_hold_record', 'reason', '扣货或解除原因');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_hold_record', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_idempotency', 'completed_at', '完成时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_idempotency', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_idempotency', 'id', '入库写操作幂等记录ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_idempotency', 'idempotency_key', '幂等键');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_idempotency', 'inbound_id', '入库单ID,创建前可为0');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_idempotency', 'operation', '操作类型');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_idempotency', 'operator_id', '操作者或外部应用ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_idempotency', 'request_hash', '请求内容摘要');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_idempotency', 'result_json', '成功结果摘要');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_idempotency', 'result_resource_id', '成功结果资源ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_idempotency', 'status', '幂等处理状态:PROCESSING处理中、SUCCEEDED已成功、FAILED已失败');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_idempotency', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_idempotency', 'updated_at', '更新时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_internal_note', 'content', '仅内部可见备注');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_internal_note', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_internal_note', 'created_by', '创建人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_internal_note', 'created_by_name', '创建人名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_internal_note', 'id', '客服内部备注ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_internal_note', 'inbound_id', '入库单ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_internal_note', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_loading_pick_task', 'created_at', 'PDA拣货任务创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_loading_pick_task', 'expected_count', '计划应拣箱数');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_loading_pick_task', 'id', '入库单级PDA拣货任务ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_loading_pick_task', 'inbound_id', '关联入库单逻辑ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_loading_pick_task', 'picked_count', '实际已拣箱数');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_loading_pick_task', 'remark', '拣货结果或异常补充说明');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_loading_pick_task', 'status', '任务状态:PENDING待拣货、IN_PROGRESS拣货中、COMPLETED已完成');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_loading_pick_task', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_loading_pick_task', 'updated_at', 'PDA拣货任务最后更新时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_loading_pick_task', 'updated_by', '最后操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_loading_pick_task', 'updated_by_name', '最后操作人姓名快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_loading_pick_task', 'version', '乐观锁版本号,每次更新递增');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement', 'actual_goods_name', '实际品名');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement', 'created_by', '创建人或系统主体ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement', 'device_code', '测量设备编码');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement', 'external_event_no', '第三方事件号');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement', 'external_request_hash', '第三方data原文和图片内容联合请求哈希');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement', 'id', '测量批次ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement', 'inbound_id', '入库单ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement', 'measured_at', '设备测量时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement', 'original_document_no', '原单据号码快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement', 'original_gross_weight_kg', '原单据毛重kg');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement', 'original_volume_cbm', '原单据体积或材积m3');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement', 'raw_payload_json', '第三方原始报文');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement', 'source_code', '第三方应用或来源编码');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement', 'source_measurement_id', '子单继承或复核时的来源测量批次ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement', 'source_type', '数据来源:THIRD_PARTY或MANUAL');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement', 'split_batch_id', '产生该测量批次的入仓拆分批次ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement', 'status', '测量批次状态:DRAFT草稿、RECEIVED已接收、SUBMITTED已提交、APPROVED已通过、REJECTED已驳回');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement', 'total_package_count', '本批次总箱数');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement', 'total_volume_cbm', '本批次业务总体积m3');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement', 'total_volume_raw_cbm', '本批次原始总体积m3');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement', 'updated_at', '更新时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement', 'volume_weight_kg', '本批次总材积重kg');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement', 'warehouse_code', '第三方回传仓库编码');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'box_code', '柏悦唛头箱号;设备单箱测量时同一客户内唯一');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'box_specification', '箱规标识');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'calculation_rule_version', '体积计算和舍入规则版本');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'device_measured_at', '本次设备测量时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'external_detail_no', '第三方测量明细唯一号,平台手工明细可为空');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'girth_cm', '周长cm,规则为长+2*(宽+高)');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'height_cm', '原始高度cm');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'id', '测量明细ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'inbound_detail_id', '绑定的订单货物明细ID,平台手工明细可为空');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'item_no', '批次内明细号');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'length_cm', '原始长度cm');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'machine_no', '本次测量设备编号');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'measurement_id', '测量批次ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'outer_height_cm', '外箱规高度cm');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'outer_length_cm', '外箱规长度cm');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'outer_width_cm', '外箱规宽度cm');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'over_limit_warning', '超线提醒快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'package_count', '相同箱规箱数');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'remark', '测量明细备注');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'scanned_shipment_id', '设备从Amazon FBA箱标实扫的Shipment ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'source_image_file_id', '测量设备图片文件ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'source_measurement_item_id', '被拆分继承的来源测量箱规明细ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'split_item_id', '产生该子单测量明细的拆分台账ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'total_volume_cbm', '本箱规总体积m3');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'total_weight_kg', '本箱规总重量kg');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'unit_volume_cbm', '单箱业务体积m3');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'unit_weight_kg', '单箱重量kg,第三方仅回传尺寸时可为空');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'updated_at', '最后更新时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'volume_cbm', '业务体积m3');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'volume_raw_cbm', '原始体积m3');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'volume_weight_kg', '本箱规材积重kg');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'width_cm', '原始宽度cm');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'active_remainder_root_id', '有效余量唯一约束键;历史已完成分片为空');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'actual_gross_weight_kg', '确认后的实际总毛重kg');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'actual_net_weight_kg', '总净重kg');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'actual_volume_cbm', '确认后的实际总体积m3');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'business_type', '业务类型');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'carrier_id', '计费船司逻辑ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'carrier_name', '计费船司名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'channel_code', '渠道编码');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'channel_name', '渠道名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'chargeable_volume_cbm', '当前费用快照的计费体积m3');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'chargeable_weight_kg', '当前费用快照的计费重量kg');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'company_id', '运营公司逻辑ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'company_name', '运营公司名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'container_mode', '整柜或散货模式');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'created_by', '创建人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'created_by_name', '创建人名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'current_hold_reason', '当前扣货理由快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'customer_id', '客户逻辑ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'customer_name', '客户名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'customs_inspection', '是否商检');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'deleted', '逻辑删除标记');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'delivery_warehouse_id', '交货仓逻辑ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'delivery_warehouse_name', '交货仓名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'destination_code', '目的仓编码,例如FBA仓编码');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'destination_name', '目的仓名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'destination_type', '目的仓类型');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'destination_warehouse_id', '目的仓逻辑ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'expected_arrival_at', '预计到达时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'express_company_code', '快递公司编码快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'express_company_name', '快递公司名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'express_tracking_no', '快递单号快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'fee_review_status', '费用审核状态:NOT_SUBMITTED未提交、PENDING待审核、APPROVED已通过、REJECTED已驳回、CONFIRMED已确认');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'fee_status', '费用状态:UNPRICED未计费、CALCULATED已计算、CONFIRMED已确认、SENT已发送、VOIDED已作废');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'hold_flag', '当前是否扣货');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'id', '入库单ID(雪花ID)');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'inbound_code', '客户内唯一入仓码');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'inbound_role', '入仓单层级:MASTER主单、CHILD子单');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'inbounded_at', '入库完成时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'master_order_id', '仅用于约束有效主单订单唯一性');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'measure_completed_at', '测量完成时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'measure_review_status', '测量审核状态:NOT_SUBMITTED未提交、PENDING待审核、APPROVED已通过、REJECTED已驳回');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'notification_status', '客户通知状态:NOT_CREATED未创建、PENDING待通知');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'order_id', '订单服务订单ID,不建立跨库外键');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'order_no', '订单号快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'order_status_snapshot', '订单状态快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'package_count', '入仓单箱数;三期唯一数量口径');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'pallet_count', '已分配总托盘数快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'parent_inbound_id', '直属主入仓单ID;主单为空');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'pricing_tax_mode', '报价包税维度:TAX_INCLUDED/TAX_EXCLUDED');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'process_status', '入库流程状态:WAIT_MEASURE待测量、MEASURING测量中、MEASURE_PENDING待审核、MEASURE_REJECTED测量驳回、WAIT_PRICING待计费、FEE_CONFIRMING费用确认中、INBOUNDING入库收口中、INBOUNDED已入库、WAIT_SPLIT历史待分柜');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'public_remark', '客户可见备注');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'root_inbound_id', '根主入仓单ID;存量主单可为空,按自身ID解释');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'split_purpose', '履约分片用途:MASTER、LOADED、REMAINDER');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'split_sequence', '同一主单下的子单顺序号');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'split_status', '拆分状态:NOT_SPLIT、PARTIALLY_SPLIT、FULLY_SPLIT');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'task_id', '头程履约任务ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'tax_mode', '买单或退税模式');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'total_package_count', '确认后的总箱数');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'updated_at', '更新时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'updated_by', '最后修改人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'volume_weight_kg', '总材积重kg');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'warehouse_id', '实际作业仓库ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'warehouse_name', '实际作业仓库名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_outbox', 'aggregate_id', '入库单ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_outbox', 'aggregate_sequence', '同一费用来源的单调版本序号');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_outbox', 'aggregate_type', '聚合类型');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_outbox', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_outbox', 'event_key', '费用来源事件幂等键;历史事件允许为空');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_outbox', 'event_type', '事件类型');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_outbox', 'event_version', '事件信封版本');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_outbox', 'id', '领域事件全局ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_outbox', 'last_error', '最近一次投递失败摘要,不记录令牌');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_outbox', 'locked_at', 'Relay抢占租约时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_outbox', 'locked_by', 'Relay抢占实例ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_outbox', 'next_retry_at', '下次重试时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_outbox', 'payload_hash', '短payload规范化JSON的SHA-256');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_outbox', 'payload_json', '事件负载快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_outbox', 'publish_status', '事件发布状态:PENDING待发布、PUBLISHED已发布、FAILED发布失败');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_outbox', 'published_at', '发布时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_outbox', 'retry_count', '重试次数');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_outbox', 'tenant_id', '事件所属客户ID和数据隔离键');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_pallet_allocation', 'allocated_package_count', '本托盘分配箱数');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_pallet_allocation', 'allocated_volume_cbm', '本托盘分配体积');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_pallet_allocation', 'allocation_no', '入库单内分配序号');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_pallet_allocation', 'allocation_status', '托盘库位分配状态:RESERVED已预占、CONFIRMED已确认、TRANSFERRED已转移、RELEASED已释放');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_pallet_allocation', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_pallet_allocation', 'id', '头程托盘分配引用ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_pallet_allocation', 'inbound_id', '入库单ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_pallet_allocation', 'location_code', '库位编码快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_pallet_allocation', 'location_id', '仓库服务库位ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_pallet_allocation', 'pallet_capacity_cbm', '托盘可用容积快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_pallet_allocation', 'pallet_code', '托盘编码快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_pallet_allocation', 'pallet_id', '仓库服务托盘ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_pallet_allocation', 'picked_package_count', '已拣货箱数');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_pallet_allocation', 'reservation_id', '仓库预占资源唯一ID,A0共享场景用于区分托盘');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_pallet_allocation', 'reservation_no', '仓库库位预占单号');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_pallet_allocation', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_pallet_allocation', 'updated_at', '更新时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_pallet_allocation', 'warehouse_id', '仓库服务仓库ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_pallet_allocation', 'warehouse_resource_tenant_id', '平台公共仓储资源固定使用 0;tenant_id 仍是客户隔离键');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_pickup_snapshot', 'address_line', '提货详细地址');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_pickup_snapshot', 'city', '城市');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_pickup_snapshot', 'contact_email', '提货联系人邮箱');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_pickup_snapshot', 'contact_name', '提货联系人');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_pickup_snapshot', 'contact_phone', '提货联系人电话');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_pickup_snapshot', 'country_code', '国家二字码');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_pickup_snapshot', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_pickup_snapshot', 'district', '区县');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_pickup_snapshot', 'id', '提货信息快照ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_pickup_snapshot', 'inbound_id', '入库单ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_pickup_snapshot', 'pickup_at', '提货时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_pickup_snapshot', 'pickup_method', '提货方式');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_pickup_snapshot', 'postal_code', '提货邮编');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_pickup_snapshot', 'province', '省或州');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_pickup_snapshot', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_batch', 'applied_at', '批次生效时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_batch', 'container_booking_id', '关联柜次标识(兼容查询)');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_batch', 'created_at', '拆分批次创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_batch', 'created_by', '创建人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_batch', 'created_by_name', '创建人名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_batch', 'expected_parent_version', '操作时主单乐观锁版本');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_batch', 'id', '拆分批次ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_batch', 'idempotency_key', '请求幂等键');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_batch', 'loaded_child_inbound_id', '本批次实装子单ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_batch', 'loaded_package_count', '本批次实装箱数');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_batch', 'loading_plan_id', '关联装柜计划/柜次');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_batch', 'operation_id', '业务操作幂等号');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_batch', 'package_count', '本批次拆分箱数');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_batch', 'parent_inbound_id', '主入仓单ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_batch', 'remainder_child_inbound_id', '本批次余量子单ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_batch', 'remaining_package_count', '本批次未装箱数');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_batch', 'remark', '拆分原因或备注');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_batch', 'source_inbound_id', '本批次实际消耗的MASTER或REMAINDER分片ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_batch', 'status', '拆分批次状态:PREPARED已准备、APPLIED已生效、CANCELLED已取消');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_batch', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_batch', 'version', '乐观锁版本号,每次更新递增');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_detail', 'child_inbound_id', '完成拆单后生成的二级子入库单ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_detail', 'completed_at', '部分拆分完成时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_detail', 'completed_by', '完成拆分的操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_detail', 'completed_by_name', '完成操作人姓名快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_detail', 'created_at', '部分锁定明细创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_detail', 'created_by', '创建部分锁定明细的操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_detail', 'created_by_name', '创建操作人姓名快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_detail', 'id', '部分锁定明细ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_detail', 'inbound_detail_id', '原订单货物明细ID,不建立跨库外键');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_detail', 'locked_package_count', '本次装柜锁定箱数');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_detail', 'operation_id', '部分锁定操作幂等号');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_detail', 'parent_inbound_id', '主入库单ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_detail', 'remaining_package_count', '锁定后剩余箱数,由来源箱数减锁定箱数生成');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_detail', 'source_package_count', '拆分前该明细总箱数快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_detail', 'status', '分柜明细状态:LOCKED已锁定、COMPLETED已完成拆分');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_detail', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_detail', 'updated_at', '部分锁定明细最后更新时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_detail', 'version', '乐观锁版本号,每次更新递增');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_history', 'action_type', '拆分动作:CREATE创建批次、APPLY生效、CANCEL取消');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_history', 'after_snapshot', '操作后快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_history', 'before_snapshot', '操作前快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_history', 'created_at', '不可变拆分历史创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_history', 'id', '拆分历史ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_history', 'operation_id', '历史操作幂等号');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_history', 'operation_reason', '操作原因');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_history', 'operator_id', '操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_history', 'operator_name', '操作人名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_history', 'split_batch_id', '拆分批次ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_history', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_item', 'child_inbound_id', '子入仓单ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_item', 'created_at', '拆分数量台账创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_item', 'id', '拆分明细ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_item', 'line_no', '同一拆分批次内的行号');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_item', 'package_count', '分配给子单的箱数');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_item', 'parent_inbound_id', '主入仓单ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_item', 'source_inbound_detail_id', '订单货物明细逻辑ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_item', 'source_measurement_id', '来源测量批次ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_item', 'source_measurement_item_id', '来源测量箱规明细ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_item', 'split_batch_id', '拆分批次ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_split_item', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_status_history', 'action', '触发动作');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_status_history', 'from_status', '变更前状态');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_status_history', 'id', '入库状态历史ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_status_history', 'inbound_id', '入库单ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_status_history', 'occurred_at', '发生时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_status_history', 'operator_id', '操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_status_history', 'operator_name', '操作人名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_status_history', 'reason', '变更原因');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_status_history', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_status_history', 'to_status', '变更后状态');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_transport_snapshot', 'cabinet_no', '柜号或内部柜单号');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_transport_snapshot', 'container_no', '集装箱号');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_transport_snapshot', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_transport_snapshot', 'id', '运输信息快照ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_transport_snapshot', 'inbound_id', '入库单ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_transport_snapshot', 'loading_status', '配载状态快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_transport_snapshot', 'port_arrival_at', '到港时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_transport_snapshot', 'sailing_at', '开船时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_transport_snapshot', 'source_updated_at', '上游数据更新时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_transport_snapshot', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_transport_snapshot', 'updated_at', '更新时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_transport_snapshot', 'vessel_name', '船名');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_transport_snapshot', 'voyage_no', '航次');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_weight_result', 'adjusted_at', '修正时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_weight_result', 'adjusted_by', '修正人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_weight_result', 'adjusted_weight_unit', '人工修正后的单位');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_weight_result', 'adjusted_weight_value', '人工修正后的原单位重量');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_weight_result', 'adjustment_reason', '人工修正原因');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_weight_result', 'confidence', '识别置信度');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_weight_result', 'confirmed_at', '确认时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_weight_result', 'confirmed_by', '确认人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_weight_result', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_weight_result', 'created_by', '创建人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_weight_result', 'deleted', '逻辑删除标记');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_weight_result', 'file_id', '称重图片文件ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_weight_result', 'final_weight_kg', '确认并统一换算后的重量kg');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_weight_result', 'id', 'OCR重量结果ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_weight_result', 'inbound_id', '入库单ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_weight_result', 'ocr_provider', 'OCR服务商编码');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_weight_result', 'ocr_raw_text', 'OCR原始文本');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_weight_result', 'ocr_weight_unit', 'OCR识别原始单位');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_weight_result', 'ocr_weight_value', 'OCR识别原始重量值');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_weight_result', 'recognition_status', '重量识别状态:PENDING待识别、PENDING_CONFIRM待确认、NEEDS_MANUAL需人工录入、CONFIRMED已确认');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_weight_result', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_weight_result', 'updated_at', '更新时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_weight_result', 'updated_by', '最后修改人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_weight_result', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_weight_revision', 'action', '动作:CORRECT或CONFIRM');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_weight_revision', 'after_weight_unit', '变更后单位');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_weight_revision', 'after_weight_value', '变更后原单位重量');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_weight_revision', 'before_weight_unit', '变更前单位');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_weight_revision', 'before_weight_value', '变更前原单位重量');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_weight_revision', 'final_weight_kg', '确认时统一换算后的重量kg');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_weight_revision', 'id', 'OCR重量修订记录ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_weight_revision', 'inbound_id', '入库单ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_weight_revision', 'operated_at', '操作时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_weight_revision', 'operator_id', '操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_weight_revision', 'operator_name', '操作人名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_weight_revision', 'reason', '人工修正原因');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_weight_revision', 'revision_no', '结果版本号,与乐观锁版本一致');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_weight_revision', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_weight_revision', 'weight_result_id', 'OCR重量结果ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_evidence', 'created_at', '证据记录创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_evidence', 'created_by', '上传证据的操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_evidence', 'created_by_name', '上传证据的操作人姓名快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_evidence', 'evidence_type', 'PHOTO_STEP_1空仓/PHOTO_STEP_2半仓/PHOTO_STEP_3满仓/PHOTO_STEP_4锁仓/SEAL带锁码照');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_evidence', 'file_id', 'File 服务逻辑文件ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_evidence', 'file_name', '上传时文件名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_evidence', 'id', '装柜证据记录ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_evidence', 'inbound_id', '关联入库单逻辑ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_evidence', 'remark', '证据补充说明');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_evidence', 'seal_no', '锁码文本;带锁码照必填,客户不可见');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_evidence', 'status', '证据状态:ACTIVE有效、VOID已作废;错误照片保留记录不硬删除');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_evidence', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_evidence', 'updated_at', '证据记录最后更新时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_evidence', 'updated_by', '最后修改操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_evidence', 'updated_by_name', '最后修改操作人姓名快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_evidence', 'version', '乐观锁版本号,每次更新递增');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_evidence', 'void_reason', '照片作废原因');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_evidence', 'voided_at', '照片作废时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_evidence', 'voided_by', '执行作废的平台操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_evidence', 'voided_by_name', '执行作废的平台操作人姓名快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_operation', 'completed_at', '操作完成时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_operation', 'created_at', '操作声明创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_operation', 'id', '装柜写操作记录ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_operation', 'idempotency_key', '客户端幂等键,同一操作重试复用');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_operation', 'inbound_id', '操作目标入库单逻辑ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_operation', 'operation', '装柜或证据写操作类型');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_operation', 'operator_id', '执行写操作的平台用户ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_operation', 'request_hash', '规范化请求内容SHA-256摘要');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_operation', 'result_json', '成功结果或失败信息JSON快照,用于幂等重放');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_operation', 'status', '幂等处理状态:PROCESSING处理中、SUCCESS成功、FAILED失败');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_operation', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_assignment', 'assigned_by', '领取任务的作业员或执行分配的平台用户ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_assignment', 'assigned_by_name', '领取或分配操作人姓名快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_assignment', 'assignee_id', '当前唯一作业员ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_assignment', 'assignee_name', '当前作业员姓名快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_assignment', 'assignment_type', '分配方式:CLAIMED作业员领取、ASSIGNED平台指派');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_assignment', 'created_at', '任务领取或分配时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_assignment', 'pick_task_id', '柜次PDA拣货任务ID;同一任务仅保留一名作业员');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_assignment', 'updated_at', '作业员分配最后更新时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_assignment', 'version', '乐观锁版本号,防止多人并发领取或覆盖');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_history', 'action_type', '拣货动作:PICK正常拣货、PARTIAL部分拣货、SKIP跳拣');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_history', 'created_at', '不可变拣货操作历史创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_history', 'id', 'PDA拣货操作历史ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_history', 'operation_id', '客户端幂等操作号');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_history', 'operation_reason', '部分拣货或跳拣原因');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_history', 'operator_id', '执行PDA拣货操作的作业员ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_history', 'operator_name', '作业员姓名快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_history', 'pick_item_id', 'PDA拣货明细ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_history', 'pick_task_id', 'PDA拣货任务ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_history', 'picked_package_count', '本次拣货箱数');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_history', 'request_hash', '完整批量请求内容SHA-256摘要');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_item', 'created_at', 'PDA拣货明细创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_item', 'id', 'PDA拣货明细ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_item', 'inbound_detail_id', '订单货物明细ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_item', 'inbound_id', '父入库单ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_item', 'item_status', '拣货明细状态:PENDING待拣、PICKING拣货中、PICKED已拣完、PARTIAL部分拣货、SKIPPED跳拣');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_item', 'loading_plan_item_id', '装柜计划明细ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_item', 'pick_task_id', 'PDA拣货任务ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_item', 'picked_package_count', '已拣货箱数');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_item', 'planned_package_count', '计划拣货箱数;上层拆分按业务规则向上取整');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_item', 'release_location_ids', '人工选择释放库位ID;空数组表示明确释放0个');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_item', 'release_mode', 'AUTO按比例释放,MANUAL按人工选择释放');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_item', 'updated_at', 'PDA拣货明细最后更新时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_item', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_item_assignment', 'assigned_by', '执行领取或分配操作的用户ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_item_assignment', 'assigned_by_name', '执行领取或分配操作的用户姓名快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_item_assignment', 'assignee_id', '当前领取或被分配的作业员ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_item_assignment', 'assignee_name', '当前作业员姓名快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_item_assignment', 'assignment_type', '分配类型:CLAIMED作业员领取、ASSIGNED管理员分配');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_item_assignment', 'created_at', '明细分配记录创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_item_assignment', 'pick_item_id', 'PDA拣货明细ID,同时作为本表主键');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_item_assignment', 'updated_at', '明细分配记录最后更新时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_item_assignment', 'version', '乐观锁版本号,每次更新递增');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_operation', 'action_type', '动作类型:CLAIM领取、ASSIGN分配、REVIEW复核、BATCH批量提交、COMPLETE_LOADING人工确认装柜完成');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_operation', 'created_at', '不可变操作审计创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_operation', 'id', 'PDA任务操作审计记录ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_operation', 'operation_id', '全局唯一操作ID,用于幂等重放');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_operation', 'operator_id', '执行PDA任务操作的用户ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_operation', 'operator_name', '操作人姓名快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_operation', 'pick_task_id', '关联柜次PDA拣货任务ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_operation', 'request_hash', '完整批量请求或任务操作内容SHA-256摘要');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_review', 'created_at', '复核通过时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_review', 'pick_task_id', '已完成的柜次PDA拣货任务ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_review', 'remark', 'PDA任务复核备注');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_review', 'review_status', '复核状态:APPROVED已通过;记录创建后不可修改或删除');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_review', 'reviewed_by', '执行完成复核的平台用户ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_review', 'reviewed_by_name', '复核人姓名快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_review', 'updated_at', '复核记录最后更新时间;正常业务不允许更新');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_review', 'version', '复核记录版本号');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_task', 'completed_at', '完成时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_task', 'completed_by', '完成装柜人');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_task', 'completed_by_name', '完成装柜人名称');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_task', 'container_no', '集装箱号快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_task', 'id', 'PDA拣货任务ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_task', 'loading_plan_id', '装柜计划ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_task', 'started_at', '开始时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_task', 'started_by', '开始装柜人');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_task', 'started_by_name', '开始装柜人名称');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_task', 'task_status', 'PDA任务状态:LOADING拣货装柜中、COMPLETED已完成');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_pick_task', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan', 'confirmed_at', '确认时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan', 'confirmed_by', '确认操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan', 'confirmed_by_name', '确认操作人名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan', 'container_booking_id', '货柜预订ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan', 'created_by', '创建人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan', 'created_by_name', '创建人名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan', 'id', '智能装柜计划ID(雪花ID)');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan', 'plan_no', '平台唯一装柜计划号');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan', 'plan_status', '装柜计划状态:EDITING编辑中、LOADING装柜中、COMPLETED装柜完成;建议须经人工确认');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan', 'preload_completed_at', '预装完成时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan', 'preload_completed_by', '预装完成操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan', 'preload_completed_by_name', '预装完成操作人名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan', 'preload_pushed_at', '开始装柜并进入LOADING的时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan', 'preload_pushed_by', '推送预装操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan', 'preload_pushed_by_name', '推送预装操作人名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan', 'push_idempotency_key', '推送预装幂等键');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan', 'remark', '平台装柜计划备注');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan', 'updated_at', '装柜计划最后更新时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan', 'updated_by', '最后修改人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan', 'updated_by_name', '最后修改人名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_history', 'action_type', '动作类型:CREATE_PLAN创建计划、ADD_ITEM新增明细、ADJUST_ITEM调整明细、REMOVE_ITEM移除明细、START_LOADING开始装柜、COMPLETE_LOADING完成装柜、PICK_ITEM拣货');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_history', 'after_snapshot', '变更后快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_history', 'before_snapshot', '变更前快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_history', 'created_at', '操作时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_history', 'from_status', '变更前状态');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_history', 'id', '装柜操作历史ID(雪花ID)');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_history', 'loading_plan_id', '智能装柜计划ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_history', 'loading_plan_item_id', '装柜计划明细ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_history', 'operation_id', '操作幂等ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_history', 'operation_reason', '操作原因');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_history', 'operator_id', '操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_history', 'operator_name', '操作人名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_history', 'to_status', '变更后状态');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'account_id', '客户账号ID;装柜业务唯一客户归属字段');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'active_inbound_detail_id', '未删除明细唯一锁;删除后返回NULL释放分配资格');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'created_by', '创建人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'created_by_name', '创建人名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'customer_name', '客户名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'customer_remark', '客户备注快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'deleted', '推送前逻辑删除标记;冻结后不可修改');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'destination_warehouse_code', '目的仓编码快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'destination_warehouse_id', '目的仓ID快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'destination_warehouse_name', '目的仓名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'dimensional_weight_kg', '本次装柜材积重kg');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'fba_tracking_no', 'FBA追踪号快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'frozen_at', '装柜完成后的永久冻结时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'frozen_by', '冻结操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'gross_weight_kg', '本次装柜毛重kg');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'id', '装柜计划明细ID(雪花ID)');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'inbound_code', '入仓码快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'inbound_detail_id', '订单货物或入库货物明细ID,不建立跨库外键');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'inbound_id', 'Firstleg入库单ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'internal_remark', '平台内部备注');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'load_sequence', '装柜顺序');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'loading_plan_id', '智能装柜计划ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'net_weight_kg', '本次装柜净重kg');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'order_id', '订单服务订单ID快照,不建立跨库外键');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'order_no', '订单号快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'package_count', '本次装柜箱数');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'pallet_count', '托盘数');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'position_snapshot', '智能装柜位置与摆放结果快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'product_image_file_id', '产品图片File对象ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'product_image_url', '产品图片访问地址快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'product_name_en', '英文品名快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'product_name_zh', '中文品名快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'product_quantity', '产品总数量');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'removed_at', '推送前逻辑删除时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'removed_by', '推送前逻辑删除操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'removed_by_name', '推送前逻辑删除操作人名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'shipment_no', '货件编号快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'shipping_mark', '唛头快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'sku_code', 'SKU编码快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'units_per_carton', '每箱产品数');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'updated_at', '装柜明细最后更新时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'updated_by', '最后修改人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'updated_by_name', '最后修改人名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'volume_cbm', '本次装柜体积m3');
CALL queue_column_comment('baiyue_firstleg', 'fl_loading_plan_item', 'warehouse_remark', '仓库备注快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_measure_review_record', 'allow_rejected_config', '操作时Nacos异常放行开关值');
CALL queue_column_comment('baiyue_firstleg', 'fl_measure_review_record', 'exception_release', '是否异常放行');
CALL queue_column_comment('baiyue_firstleg', 'fl_measure_review_record', 'id', '测量审核记录ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_measure_review_record', 'inbound_id', '入库单ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_measure_review_record', 'measurement_id', '被审核的测量批次ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_measure_review_record', 'release_reason', '异常放行原因');
CALL queue_column_comment('baiyue_firstleg', 'fl_measure_review_record', 'review_reason', '审核或驳回原因');
CALL queue_column_comment('baiyue_firstleg', 'fl_measure_review_record', 'review_result', '审核结果');
CALL queue_column_comment('baiyue_firstleg', 'fl_measure_review_record', 'reviewed_at', '审核时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_measure_review_record', 'reviewer_id', '审核人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_measure_review_record', 'reviewer_name', '审核人名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_measure_review_record', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_firstleg', 'fl_mojia_measurement_receipt', 'box_code', '设备扫描的原始箱码');
CALL queue_column_comment('baiyue_firstleg', 'fl_mojia_measurement_receipt', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_mojia_measurement_receipt', 'height_cm', '设备实测高度cm');
CALL queue_column_comment('baiyue_firstleg', 'fl_mojia_measurement_receipt', 'id', '墨家联调收件记录ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_mojia_measurement_receipt', 'image_file_id', 'File服务中的墨家联调图片ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_mojia_measurement_receipt', 'length_cm', '设备实测长度cm');
CALL queue_column_comment('baiyue_firstleg', 'fl_mojia_measurement_receipt', 'machine_no', '设备编号');
CALL queue_column_comment('baiyue_firstleg', 'fl_mojia_measurement_receipt', 'match_failure_code', '正式业务匹配失败码');
CALL queue_column_comment('baiyue_firstleg', 'fl_mojia_measurement_receipt', 'match_failure_reason', '脱敏后的匹配失败原因');
CALL queue_column_comment('baiyue_firstleg', 'fl_mojia_measurement_receipt', 'measured_at', '设备测量时间或服务接收时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_mojia_measurement_receipt', 'request_id', '请求链路ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_mojia_measurement_receipt', 'scanned_shipment_id', '设备从Amazon FBA箱标实扫的Shipment ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_mojia_measurement_receipt', 'status', '测量回执匹配状态:UNMATCHED未匹配;当前首版仅记录未匹配回执');
CALL queue_column_comment('baiyue_firstleg', 'fl_mojia_measurement_receipt', 'subject_id', '设备应用操作主体ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_mojia_measurement_receipt', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_firstleg', 'fl_mojia_measurement_receipt', 'trace_id', '调用链ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_mojia_measurement_receipt', 'updated_at', '更新时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_mojia_measurement_receipt', 'weight_kg', '设备实测重量kg');
CALL queue_column_comment('baiyue_firstleg', 'fl_mojia_measurement_receipt', 'width_cm', '设备实测宽度cm');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking', 'bill_of_lading_no', '提单号');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking', 'booking_agent_id', '订舱代理逻辑ID快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking', 'booking_agent_name', '订舱代理名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking', 'booking_no', '平台海运订舱编号');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking', 'cancellation_reason', '取消订舱原因');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking', 'change_reason', '船期变更原因');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking', 'created_at', '订舱记录创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking', 'created_by', '创建操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking', 'created_by_name', '创建操作人姓名快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking', 'customer_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking', 'customer_name', '客户名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking', 'departure_port_code', '起运港编码快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking', 'departure_port_name', '起运港名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking', 'destination_port_code', '目的港编码快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking', 'destination_port_name', '目的港名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking', 'estimated_arrival_at', '当前确认预计到港时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking', 'estimated_sailing_at', '当前确认预计开船时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking', 'failure_reason', '订舱失败原因');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking', 'freight_term', '海运费支付条款快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking', 'id', '海运订舱ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking', 'pending_estimated_arrival_at', '待确认变更预计到港时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking', 'pending_estimated_sailing_at', '待确认变更预计开船时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking', 'pending_vessel_name', '待确认变更船名');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking', 'pending_voyage_no', '待确认变更航次号');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking', 'place_of_delivery', '最终交货地快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking', 'remark', '平台内部备注');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking', 'service_term', '运输服务条款快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking', 'shipping_company_id', '船公司逻辑ID快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking', 'shipping_company_name', '船公司名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking', 'status', '订舱状态:DRAFT草稿、SUBMITTED已提交、CONFIRMED已确认、RELEASED已放舱、FAILED失败、CHANGE_PENDING船期待变更、CANCELLED已取消');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking', 'updated_at', '订舱记录最后更新时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking', 'updated_by', '最后修改操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking', 'updated_by_name', '最后修改操作人姓名快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking', 'vessel_name', '当前确认船名快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking', 'voyage_no', '当前确认航次号快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking_container_allocation', 'allocated_at', '柜次分配时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking_container_allocation', 'allocated_by', '分配操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking_container_allocation', 'allocated_by_name', '分配操作人姓名快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking_container_allocation', 'container_booking_id', '明确引用现有柜次ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking_container_allocation', 'id', '订舱柜次分配关系ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking_container_allocation', 'ocean_booking_id', '所属海运订舱ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking_container_allocation', 'requirement_id', '对应订舱箱型需求ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking_container_requirement', 'container_kind', '所需集装箱箱种编码');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking_container_requirement', 'container_type', '所需集装箱尺寸类型编码');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking_container_requirement', 'id', '订舱箱型需求ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking_container_requirement', 'ocean_booking_id', '所属海运订舱ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking_container_requirement', 'quantity', '该箱型箱种所需柜数');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking_container_requirement', 'sort_no', '订舱需求展示顺序');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking_operation', 'completed_at', '写操作成功完成时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking_operation', 'created_at', '幂等记录创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking_operation', 'id', '订舱写操作幂等记录ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking_operation', 'idempotency_key', '客户端写操作幂等键');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking_operation', 'ocean_booking_id', '关联海运订舱ID;创建请求完成前允许为空');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking_operation', 'operation', '幂等写操作类型');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking_operation', 'operator_id', '发起写操作的平台操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking_operation', 'request_hash', '规范化请求内容SHA-256摘要;用于识别同键不同请求');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking_operation', 'result_json', '成功结果JSON快照;供幂等重放返回');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking_operation', 'status', '处理状态:PROCESSING处理中、SUCCESS成功');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking_party', 'address', '关系方地址快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking_party', 'contact_name', '关系方联系人快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking_party', 'contact_phone', '关系方联系电话快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking_party', 'id', '订舱提单关系方ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking_party', 'ocean_booking_id', '所属海运订舱ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking_party', 'party_name', '关系方名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking_party', 'party_type', '关系方类型:SHIPPER发货人、CONSIGNEE收货人、NOTIFY_PARTY通知人');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking_status_history', 'action', '触发状态变更的业务动作');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking_status_history', 'after_snapshot', '状态变更后订舱数据快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking_status_history', 'before_snapshot', '状态变更前订舱数据快照;创建时为空');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking_status_history', 'created_at', '状态历史创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking_status_history', 'from_status', '变更前订舱状态;创建时为空');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking_status_history', 'id', '订舱状态历史ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking_status_history', 'ocean_booking_id', '所属海运订舱ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking_status_history', 'operator_id', '操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking_status_history', 'operator_name', '操作人姓名快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking_status_history', 'reason', '状态变更原因');
CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking_status_history', 'to_status', '变更后订舱状态');
CALL queue_column_comment('baiyue_firstleg', 'fl_outbound_dispatch', 'confirmed_at', '国内出仓确认时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_outbound_dispatch', 'confirmed_by', '确认国内出仓的操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_outbound_dispatch', 'confirmed_by_name', '确认国内出仓的操作人姓名快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_outbound_dispatch', 'container_booking_id', '关联柜次逻辑ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_outbound_dispatch', 'created_at', '出仓调度记录创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_outbound_dispatch', 'id', '国内出仓调度记录ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_outbound_dispatch', 'inbound_id', '关联入库单逻辑ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_outbound_dispatch', 'order_id', 'Order服务订单逻辑ID,不复制订单主数据');
CALL queue_column_comment('baiyue_firstleg', 'fl_outbound_dispatch', 'remark', '国内出仓调度备注');
CALL queue_column_comment('baiyue_firstleg', 'fl_outbound_dispatch', 'reversed_at', '国内出仓撤销时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_outbound_dispatch', 'reversed_by', '撤销国内出仓的操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_outbound_dispatch', 'reversed_by_name', '撤销国内出仓的操作人姓名快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_outbound_dispatch', 'status', '出仓状态:READY_FOR_DISPATCH待确认出仓、WAITING_FOR_LOADING等待装柜');
CALL queue_column_comment('baiyue_firstleg', 'fl_outbound_dispatch', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_firstleg', 'fl_outbound_dispatch', 'updated_at', '出仓调度记录最后更新时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_outbound_dispatch', 'version', '乐观锁版本号,每次更新递增');
CALL queue_column_comment('baiyue_firstleg', 'fl_outbound_dispatch_history', 'action', '出仓状态变更动作');
CALL queue_column_comment('baiyue_firstleg', 'fl_outbound_dispatch_history', 'container_booking_id', '关联柜次逻辑ID快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_outbound_dispatch_history', 'created_at', '状态历史创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_outbound_dispatch_history', 'dispatch_id', '关联国内出仓调度记录ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_outbound_dispatch_history', 'from_status', '变更前出仓状态;首次创建时为空');
CALL queue_column_comment('baiyue_firstleg', 'fl_outbound_dispatch_history', 'id', '出仓状态历史记录ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_outbound_dispatch_history', 'inbound_id', '关联入库单逻辑ID快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_outbound_dispatch_history', 'operator_id', '执行状态变更的平台用户ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_outbound_dispatch_history', 'operator_name', '执行状态变更的平台用户姓名快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_outbound_dispatch_history', 'remark', '状态变更原因或备注');
CALL queue_column_comment('baiyue_firstleg', 'fl_outbound_dispatch_history', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_firstleg', 'fl_outbound_dispatch_history', 'to_status', '变更后出仓状态');
CALL queue_column_comment('baiyue_firstleg', 'fl_outbound_dispatch_operation', 'completed_at', '幂等操作完成时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_outbound_dispatch_operation', 'created_at', '幂等声明创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_outbound_dispatch_operation', 'dispatch_id', '首次声明幂等键时允许为空');
CALL queue_column_comment('baiyue_firstleg', 'fl_outbound_dispatch_operation', 'id', '出仓幂等操作记录ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_outbound_dispatch_operation', 'idempotency_key', '客户端幂等键,同一操作重试复用');
CALL queue_column_comment('baiyue_firstleg', 'fl_outbound_dispatch_operation', 'operation', '国内出仓写操作类型');
CALL queue_column_comment('baiyue_firstleg', 'fl_outbound_dispatch_operation', 'operator_id', '执行写操作的平台用户ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_outbound_dispatch_operation', 'request_hash', '规范化请求内容SHA-256摘要');
CALL queue_column_comment('baiyue_firstleg', 'fl_outbound_dispatch_operation', 'status', '幂等处理状态:PROCESSING处理中、SUCCESS成功');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule', 'booking_cutoff_at', '截单时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule', 'container_booking_id', '关联柜次ID;订舱整票船期时为空');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule', 'created_at', '船期记录创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule', 'created_by', '创建操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule', 'created_by_name', '创建操作人姓名快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule', 'customer_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule', 'customs_cutoff_at', '截关时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule', 'departure_port_code', '起运港编码快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule', 'departure_port_name', '起运港名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule', 'destination_port_code', '目的港编码快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule', 'destination_port_name', '目的港名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule', 'eta_at', '预计到港时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule', 'etd_at', '预计离港时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule', 'expires_at', '超过该时间即由API计算为过期');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule', 'id', '港务船期记录ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule', 'observed_at', '来源数据观测时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule', 'ocean_booking_id', '关联海运订舱ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule', 'port_cutoff_at', '截港时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule', 'remark', '平台内部备注');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule', 'schedule_no', '港务船期记录编号');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule', 'source_name', '船期来源名称');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule', 'source_ref', '来源侧记录唯一引用或查询凭据摘要');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule', 'source_type', '船期来源类型:MANUAL人工录入、CARRIER船公司、TERMINAL码头、API外部接口');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule', 'updated_at', '船期记录最后更新时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule', 'updated_by', '最后修改操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule', 'updated_by_name', '最后修改操作人姓名快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule', 'vessel_name', '船名快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule', 'voyage_no', '航次号快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule_history', 'action', '变更动作:CREATE创建、UPDATE更新');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule_history', 'after_snapshot', '变更后船期数据快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule_history', 'before_snapshot', '变更前船期数据快照;创建时为空');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule_history', 'created_at', '历史记录创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule_history', 'from_version', '变更前版本;创建时为空');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule_history', 'id', '港务船期变更历史ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule_history', 'operator_id', '操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule_history', 'operator_name', '操作人姓名快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule_history', 'port_schedule_id', '所属港务船期记录ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule_history', 'reason', '变更原因或说明');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule_history', 'to_version', '变更后版本');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule_operation', 'completed_at', '写操作成功完成时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule_operation', 'created_at', '幂等记录创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule_operation', 'id', '港务船期写操作幂等记录ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule_operation', 'idempotency_key', '客户端写操作幂等键');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule_operation', 'operation', '幂等写操作类型');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule_operation', 'operator_id', '发起写操作的平台操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule_operation', 'port_schedule_id', '关联港务船期ID;创建请求完成前允许为空');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule_operation', 'request_hash', '规范化请求内容SHA-256摘要;用于识别同键不同请求');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule_operation', 'result_json', '成功结果JSON快照;供幂等重放返回');
CALL queue_column_comment('baiyue_firstleg', 'fl_port_schedule_operation', 'status', '处理状态:PROCESSING处理中、SUCCESS成功');
CALL queue_column_comment('baiyue_firstleg', 'fl_preload_loading_plan_link', 'created_at', '关联记录创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_preload_loading_plan_link', 'created_by', '建立预装与装柜计划关联的操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_preload_loading_plan_link', 'created_by_name', '建立关联的操作人姓名快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_preload_loading_plan_link', 'id', '关联ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_preload_loading_plan_link', 'idempotency_key', '建立关联幂等键');
CALL queue_column_comment('baiyue_firstleg', 'fl_preload_loading_plan_link', 'inbound_id', '入库单ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_preload_loading_plan_link', 'loading_plan_id', '柜次级装柜计划ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_preload_loading_plan_link', 'preload_plan_id', '入库单级预装计划ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_preload_loading_plan_link', 'request_hash', '建立关联请求SHA-256');
CALL queue_column_comment('baiyue_firstleg', 'fl_preload_loading_plan_link', 'tenant_id', '入库单客户隔离ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_preload_plan', 'created_at', '预装清单创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_preload_plan', 'created_by', '创建预装清单的操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_preload_plan', 'created_by_name', '创建操作人姓名快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_preload_plan', 'id', '预装清单ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_preload_plan', 'inbound_id', '关联入库单逻辑ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_preload_plan', 'items_json', '可编辑分柜明细JSON快照;人工确认后作为预装依据');
CALL queue_column_comment('baiyue_firstleg', 'fl_preload_plan', 'remark', '预装备注');
CALL queue_column_comment('baiyue_firstleg', 'fl_preload_plan', 'status', '预装状态:PENDING待确认、CONFIRMING确认中、CONFIRMED已确认');
CALL queue_column_comment('baiyue_firstleg', 'fl_preload_plan', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_firstleg', 'fl_preload_plan', 'updated_at', '预装清单最后更新时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_preload_plan', 'updated_by', '最后修改操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_preload_plan', 'updated_by_name', '最后修改操作人姓名快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_preload_plan', 'version', '乐观锁版本号,每次更新递增');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_book_channel', 'channel_code', '固定渠道编码');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_book_channel', 'channel_name', '渠道名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_book_channel', 'created_at', '渠道快照创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_book_channel', 'created_by', '创建人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_book_channel', 'deleted', '逻辑删除标记');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_book_channel', 'estimated_departure_date', '标准预计开船日期');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_book_channel', 'estimated_departure_text', '原始预计开船日期文本');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_book_channel', 'id', '版本渠道快照ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_book_channel', 'latest_warehouse_entry', '最晚进仓时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_book_channel', 'price_book_version_id', '整表价格版本ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_book_channel', 'price_model', '价格模型快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_book_channel', 'reference_transit', '参考时效');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_book_channel', 'remark', '渠道备注');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_book_channel', 'source_sheet_name', '原始Excel Sheet');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_book_channel', 'surcharge_description', '附加费说明,仅展示');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_book_channel', 'updated_at', '渠道快照最后更新时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_book_channel', 'updated_by', '最后修改人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_book_channel', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_book_channel', 'vessel_name', '船名');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_book_channel', 'voyage_no', '航次');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_book_version', 'blocking_error_count', '阻断错误数');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_book_version', 'channel_count', '解析渠道数');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_book_version', 'created_at', '上传记录创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_book_version', 'created_by', '上传价格表的操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_book_version', 'deleted', '逻辑删除标记:0未删除、1已删除');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_book_version', 'id', '整表价格版本ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_book_version', 'original_file_name', '原始Excel文件名');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_book_version', 'parse_issues_json', '上传解析问题及待确认重新校验结果');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_book_version', 'published_at', '确认并生效时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_book_version', 'published_by', '确认并发布价格版本的操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_book_version', 'rate_count', '解析价格数');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_book_version', 'source_file_id', 'File服务原始文件ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_book_version', 'source_sha256', '原始文件SHA-256');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_book_version', 'status', '价格版本状态:PENDING_CONFIRMATION待确认、ACTIVE已生效、SUPERSEDED已被替代、FAILED解析失败');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_book_version', 'superseded_at', '被下一版本替换时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_book_version', 'tenant_id', '平台公共价格作用域固定为0');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_book_version', 'updated_at', '最后更新时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_book_version', 'updated_by', '最后修改人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_book_version', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_book_version', 'version_no', '整表版本号');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_book_version', 'warning_count', '警告数');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_channel_catalog', 'aliases_json', '上传名称别名数组');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_channel_catalog', 'channel_code', '稳定渠道编码');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_channel_catalog', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_channel_catalog', 'display_name', '平台渠道名称');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_channel_catalog', 'id', '渠道目录ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_channel_catalog', 'price_model', '默认价格模型');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_channel_catalog', 'source_sheet_name', '来源Sheet名称;海派为同一Sheet');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_channel_catalog', 'status', '目录状态:ACTIVE启用、DISABLED停用');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_channel_catalog', 'updated_at', '最后更新时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_origin_alias', 'alias_name', 'Excel起运仓名称别名');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_origin_alias', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_origin_alias', 'id', '起运仓别名ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_origin_alias', 'status', '状态:ACTIVE启用、DISABLED停用');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_origin_alias', 'updated_at', '最后更新时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_origin_alias', 'warehouse_code', '平台仓库稳定编码,不存环境专属ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_warehouse_mapping', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_warehouse_mapping', 'id', '价格仓映射ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_warehouse_mapping', 'source_type', '来源维度:ORIGIN_CODE或OVERSEAS_NAME');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_warehouse_mapping', 'source_value', '解析后的来源仓编码或海外仓名称');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_warehouse_mapping', 'status', '状态:ACTIVE启用、DISABLED停用');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_warehouse_mapping', 'updated_at', '最后更新时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_warehouse_mapping', 'warehouse_code', '平台仓库稳定编码快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_warehouse_mapping', 'warehouse_id', '固定平台公共仓库ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_price_warehouse_mapping', 'warehouse_type', 'DOMESTIC或OVERSEAS');
CALL queue_column_comment('baiyue_firstleg', 'fl_split_operation', 'created_at', '幂等操作声明创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_split_operation', 'id', '幂等记录ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_split_operation', 'idempotency_key', '客户端幂等键,同一操作重试复用');
CALL queue_column_comment('baiyue_firstleg', 'fl_split_operation', 'inbound_id', '操作目标入库单逻辑ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_split_operation', 'operation', '分柜或预装写操作类型');
CALL queue_column_comment('baiyue_firstleg', 'fl_split_operation', 'operator_id', '执行分柜或预装写操作的平台用户ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_split_operation', 'result_json', '操作结果JSON快照,用于幂等重放');
CALL queue_column_comment('baiyue_firstleg', 'fl_split_operation', 'status', '幂等处理状态:PROCESSING处理中、SUCCESS成功');
CALL queue_column_comment('baiyue_firstleg', 'fl_split_operation', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_firstleg', 'fl_third_party_call_log', 'app_key', '第三方应用标识');
CALL queue_column_comment('baiyue_firstleg', 'fl_third_party_call_log', 'id', '第三方调用日志ID(雪花ID)');
CALL queue_column_comment('baiyue_firstleg', 'fl_third_party_call_log', 'idempotency_anchor', '签名通过后首个请求为1,重复调用为空');
CALL queue_column_comment('baiyue_firstleg', 'fl_third_party_call_log', 'idempotency_key', 'HTTP幂等键,查询接口为空');
CALL queue_column_comment('baiyue_firstleg', 'fl_third_party_call_log', 'nonce', '请求Nonce');
CALL queue_column_comment('baiyue_firstleg', 'fl_third_party_call_log', 'processed_at', '处理完成时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_third_party_call_log', 'processing_status', 'PROCESSING、ACCEPTED或REJECTED');
CALL queue_column_comment('baiyue_firstleg', 'fl_third_party_call_log', 'raw_payload', '未经重新序列化的UTF-8原始请求体');
CALL queue_column_comment('baiyue_firstleg', 'fl_third_party_call_log', 'request_hash', '原始请求体SHA-256小写十六进制');
CALL queue_column_comment('baiyue_firstleg', 'fl_third_party_call_log', 'request_method', 'HTTP方法');
CALL queue_column_comment('baiyue_firstleg', 'fl_third_party_call_log', 'request_path', '签名使用的规范路径');
CALL queue_column_comment('baiyue_firstleg', 'fl_third_party_call_log', 'request_timestamp', '请求头Unix毫秒时间戳原值');
CALL queue_column_comment('baiyue_firstleg', 'fl_third_party_call_log', 'requested_at', '服务端接收时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_third_party_call_log', 'result_code', '统一响应业务码');
CALL queue_column_comment('baiyue_firstleg', 'fl_third_party_call_log', 'result_json', '首次成功结果快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_third_party_call_log', 'result_message', '处理结果或失败原因');
CALL queue_column_comment('baiyue_firstleg', 'fl_third_party_call_log', 'signature_subject', '签名主体快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_third_party_call_log', 'signature_value', '第三方签名原值');
CALL queue_column_comment('baiyue_firstleg', 'fl_third_party_call_log', 'tenant_id', '第三方未知/未解析主体哨兵值;不是平台客户记录或平台作用域,不得用于业务数据授权');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment', 'actual_arrived_warehouse_at', '车辆实际到达收货仓时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment', 'actual_departed_at', '车辆实际离仓时间;DEPARTED后供LOADED门禁使用');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment', 'actual_loading_at', '实际开始装柜时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment', 'appointment_no', '平台拖车预约编号');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment', 'carrier_id', '拖车服务商逻辑ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment', 'carrier_name', '拖车服务商名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment', 'container_booking_id', '关联柜次逻辑ID;拖车完成供装柜门禁校验');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment', 'created_at', '拖车预约创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment', 'currency', '金额币种;首期默认RMB');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment', 'customer_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment', 'customer_name', '客户名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment', 'driver_id_card', '司机身份证号;平台内部敏感信息');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment', 'driver_name', '司机姓名');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment', 'driver_phone', '司机联系电话');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment', 'fleet_contact', '车队联系人');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment', 'fleet_phone', '车队联系电话');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment', 'gross_weight_kg', '预约货物毛重,单位千克');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment', 'id', '拖车预约ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment', 'loading_plan_id', '关联装柜计划逻辑ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment', 'ocean_booking_id', '关联海运订舱逻辑ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment', 'outbound_order_id', '关联头程订单逻辑ID;不跨库建外键');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment', 'package_count', '预约货物包装件数快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment', 'pallet_count', '预约托盘数量');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment', 'payable_amount', '应付拖车服务商金额;币种由currency指定');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment', 'planned_loading_at', '预约到仓装柜时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment', 'po_no', '关联采购单号或客户PO号');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment', 'receivable_amount', '向客户收取的拖车金额;币种由currency指定');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment', 'remark', '平台内部备注或异常说明');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment', 'revision', '拖车预约业务修订号;每次状态或改约递增');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment', 'status', '拖车预约状态:DRAFT草稿、PENDING_DISPATCH待派车、DISPATCHED已派车、PICKUP_IN_PROGRESS提货中、ARRIVED_WAREHOUSE已到仓、LOADING装柜中、DEPARTED已离仓、COMPLETED已完成、RESCHEDULED已改约、EXCEPTION异常、CANCELLED已取消');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment', 'store_address', '到仓装柜地点地址快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment', 'store_city', '到仓地点城市快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment', 'store_contact', '到仓地点联系人快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment', 'store_name', '到仓装柜地点名称快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment', 'store_phone', '到仓地点联系电话快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment', 'store_zipcode', '到仓地点邮编快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment', 'transit_minutes', '从到仓到离仓的运输作业耗时,单位分钟');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment', 'updated_at', '拖车预约最后更新时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment', 'vehicle_no', '车辆车牌号');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment', 'volume_cbm', '预约货物体积,单位立方米');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment', 'warehouse_id', '预约到仓的国内收货仓逻辑ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment_attachment', 'appointment_id', '所属拖车预约ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment_attachment', 'attachment_type', '附件类型编码;由业务动作约定');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment_attachment', 'file_hash', '文件内容SHA-256摘要');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment_attachment', 'file_id', 'File服务文件逻辑ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment_attachment', 'file_name', '上传时文件名快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment_attachment', 'id', '拖车附件关系ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment_attachment', 'uploaded_at', '附件上传时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment_attachment', 'uploaded_by', '上传操作人ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment_attachment', 'visibility', '可见范围:INTERNAL仅平台内部、CLIENT客户可见');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment_history', 'action', '触发状态或改约的业务动作');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment_history', 'appointment_id', '所属拖车预约ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment_history', 'created_at', '历史记录创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment_history', 'from_status', '变更前拖车状态;创建时为空');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment_history', 'id', '拖车预约历史ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment_history', 'operator_id', '执行操作的平台用户ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment_history', 'operator_name', '操作人姓名快照');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment_history', 'reason', '状态或改约原因');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment_history', 'request_id', '请求链路中的请求ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment_history', 'revision', '该预约业务修订号');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment_history', 'to_status', '变更后拖车状态;改约时可保持原状态');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment_operation', 'action', '执行的拖车业务动作');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment_operation', 'appointment_id', '所属拖车预约ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment_operation', 'created_at', '幂等记录创建时间');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment_operation', 'id', '拖车写操作幂等记录ID');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment_operation', 'idempotency_key', '客户端写操作幂等键');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment_operation', 'request_hash', '规范化请求内容SHA-256摘要;用于识别同键不同请求');
CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment_operation', 'response_snapshot', '成功响应JSON快照;供相同幂等请求重放');
CALL queue_column_comment('baiyue_firstleg', 'sys_audit_outbox', 'attempt_count', '正常发送累计尝试次数');
CALL queue_column_comment('baiyue_firstleg', 'sys_audit_outbox', 'compensation_count', '人工或自动补偿累计次数');
CALL queue_column_comment('baiyue_firstleg', 'sys_audit_outbox', 'created_at', '事件创建时间');
CALL queue_column_comment('baiyue_firstleg', 'sys_audit_outbox', 'dead_at', '转入死信状态的时间');
CALL queue_column_comment('baiyue_firstleg', 'sys_audit_outbox', 'event_id', '审计事件全局唯一ID');
CALL queue_column_comment('baiyue_firstleg', 'sys_audit_outbox', 'event_type', '审计事件类型');
CALL queue_column_comment('baiyue_firstleg', 'sys_audit_outbox', 'last_error', '最近一次发送失败原因');
CALL queue_column_comment('baiyue_firstleg', 'sys_audit_outbox', 'lease_owner', '当前发送任务租约持有者标识');
CALL queue_column_comment('baiyue_firstleg', 'sys_audit_outbox', 'lease_until', '当前发送任务租约到期时间');
CALL queue_column_comment('baiyue_firstleg', 'sys_audit_outbox', 'next_retry_at', '允许下次重试的最早时间');
CALL queue_column_comment('baiyue_firstleg', 'sys_audit_outbox', 'payload_json', '待发送的审计事件完整负载快照');
CALL queue_column_comment('baiyue_firstleg', 'sys_audit_outbox', 'resource_id', '被审计业务资源ID;无法确定时为空');
CALL queue_column_comment('baiyue_firstleg', 'sys_audit_outbox', 'resource_type', '被审计业务资源类型');
CALL queue_column_comment('baiyue_firstleg', 'sys_audit_outbox', 'sent_at', '首次成功发送时间');
CALL queue_column_comment('baiyue_firstleg', 'sys_audit_outbox', 'service_name', '产生审计事件的服务名称');
CALL queue_column_comment('baiyue_firstleg', 'sys_audit_outbox', 'status', '发送状态:PENDING待发送、SENDING发送中、SENT已发送、DEAD已转死信');
CALL queue_column_comment('baiyue_firstleg', 'sys_audit_outbox', 'updated_at', '记录最后更新时间');
CALL queue_column_comment('baiyue_iam', 'iam_data_scope', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_iam', 'iam_data_scope', 'id', '数据范围规则ID');
CALL queue_column_comment('baiyue_iam', 'iam_data_scope', 'role_id', '角色ID');
CALL queue_column_comment('baiyue_iam', 'iam_data_scope', 'scope_expression', '自定义数据范围表达式');
CALL queue_column_comment('baiyue_iam', 'iam_data_scope', 'scope_type', '数据范围类型:SELF本人、DEPARTMENT部门、TENANT当前客户、CUSTOM自定义');
CALL queue_column_comment('baiyue_iam', 'iam_data_scope', 'tenant_id', '规则作用域:0为平台,正数为客户ID和数据隔离键');
CALL queue_column_comment('baiyue_iam', 'iam_membership', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_iam', 'iam_membership', 'created_by', '客户成员关系创建人ID;系统创建时为0');
CALL queue_column_comment('baiyue_iam', 'iam_membership', 'deleted', '逻辑删除标记');
CALL queue_column_comment('baiyue_iam', 'iam_membership', 'id', '客户成员关系ID');
CALL queue_column_comment('baiyue_iam', 'iam_membership', 'joined_at', '加入客户企业时间');
CALL queue_column_comment('baiyue_iam', 'iam_membership', 'left_at', '离开客户企业时间');
CALL queue_column_comment('baiyue_iam', 'iam_membership', 'member_no', '客户企业内部成员编号');
CALL queue_column_comment('baiyue_iam', 'iam_membership', 'member_type', '成员类型:EMPLOYEE企业员工等');
CALL queue_column_comment('baiyue_iam', 'iam_membership', 'status', '成员状态:ACTIVE在职、DISABLED停用、LEFT已离开');
CALL queue_column_comment('baiyue_iam', 'iam_membership', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_iam', 'iam_membership', 'updated_at', '更新时间');
CALL queue_column_comment('baiyue_iam', 'iam_membership', 'updated_by', '客户成员关系最后修改人ID;系统修改时为0');
CALL queue_column_comment('baiyue_iam', 'iam_membership', 'user_id', 'IAM用户资料ID');
CALL queue_column_comment('baiyue_iam', 'iam_membership', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_iam', 'iam_membership_role', 'created_at', '授权时间');
CALL queue_column_comment('baiyue_iam', 'iam_membership_role', 'membership_id', '客户成员关系ID');
CALL queue_column_comment('baiyue_iam', 'iam_membership_role', 'role_id', '角色ID');
CALL queue_column_comment('baiyue_iam', 'iam_membership_role', 'tenant_id', '冗余客户ID和数据隔离键,用于授权归属校验和查询');
CALL queue_column_comment('baiyue_iam', 'iam_permission', 'app_code', '所属应用端:MERCHANT企业客户端、PLATFORM平台后台');
CALL queue_column_comment('baiyue_iam', 'iam_permission', 'built_in', '是否为系统预置权限;预置资源不可通过管理接口修改');
CALL queue_column_comment('baiyue_iam', 'iam_permission', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_iam', 'iam_permission', 'deleted', '逻辑删除标记');
CALL queue_column_comment('baiyue_iam', 'iam_permission', 'http_method', 'API权限对应的HTTP方法');
CALL queue_column_comment('baiyue_iam', 'iam_permission', 'id', '权限资源ID');
CALL queue_column_comment('baiyue_iam', 'iam_permission', 'parent_id', '父权限ID,用于构造菜单和页面树');
CALL queue_column_comment('baiyue_iam', 'iam_permission', 'permission_code', '鉴权使用的唯一权限码');
CALL queue_column_comment('baiyue_iam', 'iam_permission', 'permission_name', '权限名称');
CALL queue_column_comment('baiyue_iam', 'iam_permission', 'permission_type', '权限类型:MENU、PAGE、BUTTON或API');
CALL queue_column_comment('baiyue_iam', 'iam_permission', 'route_path', '前端路由或API路径');
CALL queue_column_comment('baiyue_iam', 'iam_permission', 'sort_no', '同级显示顺序');
CALL queue_column_comment('baiyue_iam', 'iam_permission', 'status', '权限状态:ACTIVE启用、DISABLED停用');
CALL queue_column_comment('baiyue_iam', 'iam_permission', 'tenant_id', '权限作用域:0为平台预置权限,正数为客户ID和数据隔离键');
CALL queue_column_comment('baiyue_iam', 'iam_permission', 'updated_at', '更新时间');
CALL queue_column_comment('baiyue_iam', 'iam_role', 'app_code', '应用端:MERCHANT企业客户端、PLATFORM平台后台');
CALL queue_column_comment('baiyue_iam', 'iam_role', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_iam', 'iam_role', 'created_by', '创建人ID');
CALL queue_column_comment('baiyue_iam', 'iam_role', 'deleted', '逻辑删除标记');
CALL queue_column_comment('baiyue_iam', 'iam_role', 'description', '角色用途说明');
CALL queue_column_comment('baiyue_iam', 'iam_role', 'id', '角色ID');
CALL queue_column_comment('baiyue_iam', 'iam_role', 'role_code', '作用域内唯一角色编码');
CALL queue_column_comment('baiyue_iam', 'iam_role', 'role_name', '角色名称');
CALL queue_column_comment('baiyue_iam', 'iam_role', 'role_type', '角色类型:SYSTEM或CUSTOM');
CALL queue_column_comment('baiyue_iam', 'iam_role', 'status', '角色状态:ACTIVE启用、DISABLED停用');
CALL queue_column_comment('baiyue_iam', 'iam_role', 'tenant_id', '角色作用域:0为平台预置模板,正数为客户ID和数据隔离键');
CALL queue_column_comment('baiyue_iam', 'iam_role', 'updated_at', '更新时间');
CALL queue_column_comment('baiyue_iam', 'iam_role', 'updated_by', '更新人ID');
CALL queue_column_comment('baiyue_iam', 'iam_role', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_iam', 'iam_role_permission', 'created_at', '授权时间');
CALL queue_column_comment('baiyue_iam', 'iam_role_permission', 'permission_id', '权限资源ID');
CALL queue_column_comment('baiyue_iam', 'iam_role_permission', 'role_id', '角色ID');
CALL queue_column_comment('baiyue_iam', 'iam_role_permission', 'tenant_id', '冗余客户ID或平台作用域ID,用于授权归属校验和查询');
CALL queue_column_comment('baiyue_iam', 'iam_tenant', 'approved_at', '客户资料审核通过时间');
CALL queue_column_comment('baiyue_iam', 'iam_tenant', 'approved_by', '客户资料审核人ID');
CALL queue_column_comment('baiyue_iam', 'iam_tenant', 'contact_email', '联系邮箱');
CALL queue_column_comment('baiyue_iam', 'iam_tenant', 'contact_name', '企业联系人');
CALL queue_column_comment('baiyue_iam', 'iam_tenant', 'contact_phone', '联系电话');
CALL queue_column_comment('baiyue_iam', 'iam_tenant', 'contact_wechat', '联系微信');
CALL queue_column_comment('baiyue_iam', 'iam_tenant', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_iam', 'iam_tenant', 'created_by', '创建人ID');
CALL queue_column_comment('baiyue_iam', 'iam_tenant', 'deleted', '逻辑删除标记');
CALL queue_column_comment('baiyue_iam', 'iam_tenant', 'enterprise_type', '企业类型');
CALL queue_column_comment('baiyue_iam', 'iam_tenant', 'established_date', '成立日期');
CALL queue_column_comment('baiyue_iam', 'iam_tenant', 'id', '客户主档ID(雪花ID);0保留为平台技术作用域');
CALL queue_column_comment('baiyue_iam', 'iam_tenant', 'internal_remark', '平台内部备注');
CALL queue_column_comment('baiyue_iam', 'iam_tenant', 'legal_name', '企业法定名称');
CALL queue_column_comment('baiyue_iam', 'iam_tenant', 'legal_representative', '法定代表人');
CALL queue_column_comment('baiyue_iam', 'iam_tenant', 'payment_term_days', '账期天数');
CALL queue_column_comment('baiyue_iam', 'iam_tenant', 'registered_address', '注册地址文本');
CALL queue_column_comment('baiyue_iam', 'iam_tenant', 'registered_capital', '注册资本');
CALL queue_column_comment('baiyue_iam', 'iam_tenant', 'registered_capital_currency', '注册资本币种 ISO 4217');
CALL queue_column_comment('baiyue_iam', 'iam_tenant', 'reject_reason', '客户资料审核驳回原因');
CALL queue_column_comment('baiyue_iam', 'iam_tenant', 'sales_owner_name', '销售负责人姓名');
CALL queue_column_comment('baiyue_iam', 'iam_tenant', 'service_package', '客户服务套餐');
CALL queue_column_comment('baiyue_iam', 'iam_tenant', 'status', '客户状态:PROFILE_REQUIRED待完善资料、PENDING待审核、ACTIVE正常、REJECTED已驳回、DISABLED已停用');
CALL queue_column_comment('baiyue_iam', 'iam_tenant', 'tenant_code', '全局唯一客户编码');
CALL queue_column_comment('baiyue_iam', 'iam_tenant', 'tenant_name', '客户企业名称');
CALL queue_column_comment('baiyue_iam', 'iam_tenant', 'tenant_type', '主体类型:MERCHANT企业客户');
CALL queue_column_comment('baiyue_iam', 'iam_tenant', 'unified_social_credit_code', '统一社会信用代码');
CALL queue_column_comment('baiyue_iam', 'iam_tenant', 'updated_at', '更新时间');
CALL queue_column_comment('baiyue_iam', 'iam_tenant', 'updated_by', '最后修改人ID');
CALL queue_column_comment('baiyue_iam', 'iam_tenant', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_iam', 'iam_tenant_address', 'address_type', '地址类型:注册地址、办公地址等');
CALL queue_column_comment('baiyue_iam', 'iam_tenant_address', 'city', '城市');
CALL queue_column_comment('baiyue_iam', 'iam_tenant_address', 'country_code', '国家二字码');
CALL queue_column_comment('baiyue_iam', 'iam_tenant_address', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_iam', 'iam_tenant_address', 'deleted', '逻辑删除标记');
CALL queue_column_comment('baiyue_iam', 'iam_tenant_address', 'detail', '详细地址');
CALL queue_column_comment('baiyue_iam', 'iam_tenant_address', 'district', '区县');
CALL queue_column_comment('baiyue_iam', 'iam_tenant_address', 'id', '客户地址ID');
CALL queue_column_comment('baiyue_iam', 'iam_tenant_address', 'is_default', '是否默认地址:0否、1是');
CALL queue_column_comment('baiyue_iam', 'iam_tenant_address', 'postal_code', '邮政编码');
CALL queue_column_comment('baiyue_iam', 'iam_tenant_address', 'province', '省/州');
CALL queue_column_comment('baiyue_iam', 'iam_tenant_address', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_iam', 'iam_tenant_address', 'updated_at', '更新时间');
CALL queue_column_comment('baiyue_iam', 'iam_tenant_qualification', 'certificate_no', '证书编号');
CALL queue_column_comment('baiyue_iam', 'iam_tenant_qualification', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_iam', 'iam_tenant_qualification', 'deleted', '逻辑删除标记');
CALL queue_column_comment('baiyue_iam', 'iam_tenant_qualification', 'document_url', '资质文件存储地址');
CALL queue_column_comment('baiyue_iam', 'iam_tenant_qualification', 'id', '资质记录ID');
CALL queue_column_comment('baiyue_iam', 'iam_tenant_qualification', 'qualification_type', '客户资质类型');
CALL queue_column_comment('baiyue_iam', 'iam_tenant_qualification', 'review_comment', '客户资质审核意见');
CALL queue_column_comment('baiyue_iam', 'iam_tenant_qualification', 'status', '资质审核状态:PENDING待审核、APPROVED已通过、REJECTED已驳回');
CALL queue_column_comment('baiyue_iam', 'iam_tenant_qualification', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_iam', 'iam_tenant_qualification', 'updated_at', '更新时间');
CALL queue_column_comment('baiyue_iam', 'iam_tenant_qualification', 'valid_from', '有效期开始日期');
CALL queue_column_comment('baiyue_iam', 'iam_tenant_qualification', 'valid_to', '有效期结束日期');
CALL queue_column_comment('baiyue_iam', 'iam_tenant_review', 'created_at', '进入审核队列时间');
CALL queue_column_comment('baiyue_iam', 'iam_tenant_review', 'id', '审核记录ID');
CALL queue_column_comment('baiyue_iam', 'iam_tenant_review', 'review_comment', '审核意见');
CALL queue_column_comment('baiyue_iam', 'iam_tenant_review', 'review_type', '审核类型:REGISTER注册审核等');
CALL queue_column_comment('baiyue_iam', 'iam_tenant_review', 'reviewed_at', '审核完成时间');
CALL queue_column_comment('baiyue_iam', 'iam_tenant_review', 'reviewer_id', '审核人ID');
CALL queue_column_comment('baiyue_iam', 'iam_tenant_review', 'status', '审核状态:PENDING待审核、APPROVED已通过、REJECTED已驳回');
CALL queue_column_comment('baiyue_iam', 'iam_tenant_review', 'tenant_id', '被审核客户ID和数据隔离键');
CALL queue_column_comment('baiyue_iam', 'iam_user', 'auth_user_id', '认证服务账号逻辑ID');
CALL queue_column_comment('baiyue_iam', 'iam_user', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_iam', 'iam_user', 'deleted', '逻辑删除标记');
CALL queue_column_comment('baiyue_iam', 'iam_user', 'display_name', '用户展示名称');
CALL queue_column_comment('baiyue_iam', 'iam_user', 'email', '邮箱');
CALL queue_column_comment('baiyue_iam', 'iam_user', 'id', 'IAM用户资料ID');
CALL queue_column_comment('baiyue_iam', 'iam_user', 'phone', '手机号');
CALL queue_column_comment('baiyue_iam', 'iam_user', 'status', '用户资料状态:ACTIVE正常、DISABLED停用');
CALL queue_column_comment('baiyue_iam', 'iam_user', 'updated_at', '更新时间');
CALL queue_column_comment('baiyue_iam', 'iam_user', 'user_type', '用户类型:ENTERPRISE企业用户、PLATFORM平台用户');
CALL queue_column_comment('baiyue_iam', 'iam_user', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_iam', 'sys_audit_outbox', 'attempt_count', '当前重试周期发送次数');
CALL queue_column_comment('baiyue_iam', 'sys_audit_outbox', 'compensation_count', '自动补偿发送次数');
CALL queue_column_comment('baiyue_iam', 'sys_audit_outbox', 'created_at', '记录创建时间');
CALL queue_column_comment('baiyue_iam', 'sys_audit_outbox', 'dead_at', '进入死信时间');
CALL queue_column_comment('baiyue_iam', 'sys_audit_outbox', 'event_id', '审计事件全局唯一ID');
CALL queue_column_comment('baiyue_iam', 'sys_audit_outbox', 'event_type', '审计事件类型');
CALL queue_column_comment('baiyue_iam', 'sys_audit_outbox', 'last_error', '最近一次发送错误信息');
CALL queue_column_comment('baiyue_iam', 'sys_audit_outbox', 'lease_owner', '当前租约持有者');
CALL queue_column_comment('baiyue_iam', 'sys_audit_outbox', 'lease_until', '发送租约截止时间');
CALL queue_column_comment('baiyue_iam', 'sys_audit_outbox', 'next_retry_at', '下一次重试时间');
CALL queue_column_comment('baiyue_iam', 'sys_audit_outbox', 'payload_json', '脱敏后的审计事件内容');
CALL queue_column_comment('baiyue_iam', 'sys_audit_outbox', 'resource_id', '被审计资源ID');
CALL queue_column_comment('baiyue_iam', 'sys_audit_outbox', 'resource_type', '被审计资源类型');
CALL queue_column_comment('baiyue_iam', 'sys_audit_outbox', 'sent_at', '成功发送时间');
CALL queue_column_comment('baiyue_iam', 'sys_audit_outbox', 'service_name', '来源服务名称');
CALL queue_column_comment('baiyue_iam', 'sys_audit_outbox', 'status', '发送状态:PENDING待发送、SENDING发送中、SENT已发送、DEAD进入死信');
CALL queue_column_comment('baiyue_iam', 'sys_audit_outbox', 'updated_at', '记录更新时间');
CALL queue_column_comment('baiyue_order', 'ord_idempotency_record', 'completed_at', '处理完成时间');
CALL queue_column_comment('baiyue_order', 'ord_idempotency_record', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_order', 'ord_idempotency_record', 'id', '幂等记录 ID');
CALL queue_column_comment('baiyue_order', 'ord_idempotency_record', 'idempotency_key', '客户端幂等键');
CALL queue_column_comment('baiyue_order', 'ord_idempotency_record', 'operation', '幂等操作类型');
CALL queue_column_comment('baiyue_order', 'ord_idempotency_record', 'operator_id', '发起写操作的用户ID');
CALL queue_column_comment('baiyue_order', 'ord_idempotency_record', 'request_hash', '规范化请求内容SHA-256摘要,用于识别同键不同请求');
CALL queue_column_comment('baiyue_order', 'ord_idempotency_record', 'resource_id', '操作目标资源ID;创建前未知时为0');
CALL queue_column_comment('baiyue_order', 'ord_idempotency_record', 'result_order_id', '成功操作生成或更新的订单ID,用于幂等重放');
CALL queue_column_comment('baiyue_order', 'ord_idempotency_record', 'status', '处理状态:PROCESSING处理中、COMPLETED已完成');
CALL queue_column_comment('baiyue_order', 'ord_idempotency_record', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_order', 'ord_idempotency_record', 'updated_at', '更新时间');
CALL queue_column_comment('baiyue_order', 'ord_order', 'amazon_warehouse_code', 'Amazon目的仓编码');
CALL queue_column_comment('baiyue_order', 'ord_order', 'carrier_id', '船司逻辑ID快照');
CALL queue_column_comment('baiyue_order', 'ord_order', 'carrier_name', '船司名称快照');
CALL queue_column_comment('baiyue_order', 'ord_order', 'channel_code', '运输渠道编码');
CALL queue_column_comment('baiyue_order', 'ord_order', 'channel_name', '下单时运输渠道名称快照;渠道主数据变化不回写');
CALL queue_column_comment('baiyue_order', 'ord_order', 'completed_at', '完成时间');
CALL queue_column_comment('baiyue_order', 'ord_order', 'container_mode', '装运方式:FULL_CONTAINER整柜、LCL散货拼箱');
CALL queue_column_comment('baiyue_order', 'ord_order', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_order', 'ord_order', 'created_by', '创建人ID');
CALL queue_column_comment('baiyue_order', 'ord_order', 'customer_id', '客户逻辑ID');
CALL queue_column_comment('baiyue_order', 'ord_order', 'customer_name', '下单时客户名称快照');
CALL queue_column_comment('baiyue_order', 'ord_order', 'customer_remark', '客户可见备注');
CALL queue_column_comment('baiyue_order', 'ord_order', 'customs_inspection', '是否需要商检:0否、1是');
CALL queue_column_comment('baiyue_order', 'ord_order', 'deleted', '逻辑删除标记');
CALL queue_column_comment('baiyue_order', 'ord_order', 'destination_country_code', '目的国家二字码');
CALL queue_column_comment('baiyue_order', 'ord_order', 'destination_type', '目的仓类型:FBA亚马逊仓、OVERSEAS海外仓、PRIVATE私人地址');
CALL queue_column_comment('baiyue_order', 'ord_order', 'destination_warehouse_id', '目的仓逻辑ID');
CALL queue_column_comment('baiyue_order', 'ord_order', 'destination_warehouse_name', '目的仓名称快照');
CALL queue_column_comment('baiyue_order', 'ord_order', 'expected_ship_date', '预计发运日期');
CALL queue_column_comment('baiyue_order', 'ord_order', 'express_company', '履约快递公司名称快照');
CALL queue_column_comment('baiyue_order', 'ord_order', 'express_tracking_no', '履约快递单号');
CALL queue_column_comment('baiyue_order', 'ord_order', 'id', '订单ID(雪花ID)');
CALL queue_column_comment('baiyue_order', 'ord_order', 'inbound_code', '客户提交后生成的入仓码,草稿为空');
CALL queue_column_comment('baiyue_order', 'ord_order', 'order_no', '客户范围内唯一订单号');
CALL queue_column_comment('baiyue_order', 'ord_order', 'order_role', '订单层级:MASTER主订单、CHILD子订单');
CALL queue_column_comment('baiyue_order', 'ord_order', 'parent_order_id', '直属父订单ID;主订单为空;禁止子订单嵌套');
CALL queue_column_comment('baiyue_order', 'ord_order', 'pickup_method', '取货方式');
CALL queue_column_comment('baiyue_order', 'ord_order', 'pricing_tax_mode', '报价包税维度:TAX_INCLUDED或TAX_EXCLUDED;独立于tax_mode');
CALL queue_column_comment('baiyue_order', 'ord_order', 'receiving_time', '国内收货仓收货时间快照');
CALL queue_column_comment('baiyue_order', 'ord_order', 'receiving_warehouse_address', '国内收货仓完整地址快照');
CALL queue_column_comment('baiyue_order', 'ord_order', 'receiving_warehouse_id', '国内收货仓逻辑ID');
CALL queue_column_comment('baiyue_order', 'ord_order', 'receiving_warehouse_name', '下单时国内收货仓名称快照;主数据变化不回写');
CALL queue_column_comment('baiyue_order', 'ord_order', 'reference_id', '订单级Amazon Reference ID');
CALL queue_column_comment('baiyue_order', 'ord_order', 'root_order_id', '根主订单ID');
CALL queue_column_comment('baiyue_order', 'ord_order', 'shipment_id', '订单级Amazon Shipment ID');
CALL queue_column_comment('baiyue_order', 'ord_order', 'split_sequence', '同一父订单下的同级子订单顺序');
CALL queue_column_comment('baiyue_order', 'ord_order', 'status', '订单业务状态:DRAFT草稿、SUBMITTED已提交、APPROVED审核通过、ACCEPTED已接单、REJECTED审核驳回、PROCESSING履约中、COMPLETED已完成、CANCELLED已取消');
CALL queue_column_comment('baiyue_order', 'ord_order', 'submitted_at', '提交时间');
CALL queue_column_comment('baiyue_order', 'ord_order', 'tax_mode', '报关税务方式:BUY_DECLARATION买单、TAX_REFUND退税');
CALL queue_column_comment('baiyue_order', 'ord_order', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_order', 'ord_order', 'trade_term', '贸易条款');
CALL queue_column_comment('baiyue_order', 'ord_order', 'transport_mode', '运输方式');
CALL queue_column_comment('baiyue_order', 'ord_order', 'updated_at', '更新时间');
CALL queue_column_comment('baiyue_order', 'ord_order', 'updated_by', '最后修改人ID');
CALL queue_column_comment('baiyue_order', 'ord_order', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_order', 'ord_order_cargo', 'brand', '产品品牌');
CALL queue_column_comment('baiyue_order', 'ord_order_cargo', 'cargo_name', '货物名称');
CALL queue_column_comment('baiyue_order', 'ord_order_cargo', 'cargo_name_en', '货物英文品名');
CALL queue_column_comment('baiyue_order', 'ord_order_cargo', 'cargo_remark', '货物备注');
CALL queue_column_comment('baiyue_order', 'ord_order_cargo', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_order', 'ord_order_cargo', 'created_by', '创建人ID');
CALL queue_column_comment('baiyue_order', 'ord_order_cargo', 'currency', '币种三字码');
CALL queue_column_comment('baiyue_order', 'ord_order_cargo', 'customs_hs_code', '目的国清关HS编码');
CALL queue_column_comment('baiyue_order', 'ord_order_cargo', 'dangerous_goods', '是否危险品');
CALL queue_column_comment('baiyue_order', 'ord_order_cargo', 'declared_value', '申报价值');
CALL queue_column_comment('baiyue_order', 'ord_order_cargo', 'deleted', '逻辑删除标记');
CALL queue_column_comment('baiyue_order', 'ord_order_cargo', 'gross_weight_kg', '毛重(千克)');
CALL queue_column_comment('baiyue_order', 'ord_order_cargo', 'height_cm', '单件高度(厘米)');
CALL queue_column_comment('baiyue_order', 'ord_order_cargo', 'hs_code', '海关商品编码');
CALL queue_column_comment('baiyue_order', 'ord_order_cargo', 'id', '货物明细ID');
CALL queue_column_comment('baiyue_order', 'ord_order_cargo', 'item_no', '订单内明细序号');
CALL queue_column_comment('baiyue_order', 'ord_order_cargo', 'length_cm', '单件长度(厘米)');
CALL queue_column_comment('baiyue_order', 'ord_order_cargo', 'material', '产品材质');
CALL queue_column_comment('baiyue_order', 'ord_order_cargo', 'model', '产品型号');
CALL queue_column_comment('baiyue_order', 'ord_order_cargo', 'net_weight_kg', '货物净重,单位千克');
CALL queue_column_comment('baiyue_order', 'ord_order_cargo', 'order_id', '所属订单ID');
CALL queue_column_comment('baiyue_order', 'ord_order_cargo', 'package_count', '包装件数');
CALL queue_column_comment('baiyue_order', 'ord_order_cargo', 'package_type', '包装类型');
CALL queue_column_comment('baiyue_order', 'ord_order_cargo', 'product_height_cm', '单件产品高度,单位厘米');
CALL queue_column_comment('baiyue_order', 'ord_order_cargo', 'product_image_file_id', 'File服务中的产品图片逻辑文件ID');
CALL queue_column_comment('baiyue_order', 'ord_order_cargo', 'product_length_cm', '单件产品长度,单位厘米');
CALL queue_column_comment('baiyue_order', 'ord_order_cargo', 'product_usage', '产品用途');
CALL queue_column_comment('baiyue_order', 'ord_order_cargo', 'product_width_cm', '单件产品宽度,单位厘米');
CALL queue_column_comment('baiyue_order', 'ord_order_cargo', 'reference_id', '本货物明细对应的Amazon Reference ID');
CALL queue_column_comment('baiyue_order', 'ord_order_cargo', 'shipment_id', '本货物明细对应的Amazon Shipment ID');
CALL queue_column_comment('baiyue_order', 'ord_order_cargo', 'sku_no', '货物SKU编码');
CALL queue_column_comment('baiyue_order', 'ord_order_cargo', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_order', 'ord_order_cargo', 'total_quantity', '产品总数量');
CALL queue_column_comment('baiyue_order', 'ord_order_cargo', 'unit_declared_value', '单件申报价值;币种取订单申报币种');
CALL queue_column_comment('baiyue_order', 'ord_order_cargo', 'units_per_package', '每箱产品数量');
CALL queue_column_comment('baiyue_order', 'ord_order_cargo', 'upc_no', '货物UPC条码');
CALL queue_column_comment('baiyue_order', 'ord_order_cargo', 'updated_at', '更新时间');
CALL queue_column_comment('baiyue_order', 'ord_order_cargo', 'updated_by', '最后修改人ID');
CALL queue_column_comment('baiyue_order', 'ord_order_cargo', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_order', 'ord_order_cargo', 'volume_cbm', '体积(立方米)');
CALL queue_column_comment('baiyue_order', 'ord_order_cargo', 'width_cm', '单件宽度(厘米)');
CALL queue_column_comment('baiyue_order', 'ord_order_fulfillment_split', 'child_order_id', '本次分片生成或承接的子订单ID');
CALL queue_column_comment('baiyue_order', 'ord_order_fulfillment_split', 'container_booking_id', 'Firstleg柜次逻辑ID');
CALL queue_column_comment('baiyue_order', 'ord_order_fulfillment_split', 'created_at', '台账创建时间');
CALL queue_column_comment('baiyue_order', 'ord_order_fulfillment_split', 'created_by', '创建该分片台账的用户ID');
CALL queue_column_comment('baiyue_order', 'ord_order_fulfillment_split', 'created_by_name', '创建人名称快照');
CALL queue_column_comment('baiyue_order', 'ord_order_fulfillment_split', 'expected_parent_version', '执行前预期父订单乐观锁版本号');
CALL queue_column_comment('baiyue_order', 'ord_order_fulfillment_split', 'id', '订单履约分片台账ID');
CALL queue_column_comment('baiyue_order', 'ord_order_fulfillment_split', 'idempotency_key', '客户端幂等键');
CALL queue_column_comment('baiyue_order', 'ord_order_fulfillment_split', 'loaded_package_count', '本次已装柜分片的包装件数');
CALL queue_column_comment('baiyue_order', 'ord_order_fulfillment_split', 'loading_plan_id', 'Firstleg装柜计划逻辑ID');
CALL queue_column_comment('baiyue_order', 'ord_order_fulfillment_split', 'operation_id', '履约分片业务操作ID');
CALL queue_column_comment('baiyue_order', 'ord_order_fulfillment_split', 'parent_order_id', '直属父订单ID;主订单自身作为拆分根');
CALL queue_column_comment('baiyue_order', 'ord_order_fulfillment_split', 'remaining_package_count', '本次分片后剩余包装件数');
CALL queue_column_comment('baiyue_order', 'ord_order_fulfillment_split', 'request_hash', '请求内容SHA-256;存量记录允许为空');
CALL queue_column_comment('baiyue_order', 'ord_order_fulfillment_split', 'source_detail_json', '来源货物明细及数量快照;来源订单变化不回写');
CALL queue_column_comment('baiyue_order', 'ord_order_fulfillment_split', 'source_order_id', '实际被本次分片消费的来源订单ID');
CALL queue_column_comment('baiyue_order', 'ord_order_fulfillment_split', 'source_package_count', '来源订单本次分片前的总包装件数');
CALL queue_column_comment('baiyue_order', 'ord_order_fulfillment_split', 'split_role', '分片角色:LOADED已装柜部分、REMAINDER剩余部分');
CALL queue_column_comment('baiyue_order', 'ord_order_fulfillment_split', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_order', 'ord_order_fulfillment_split', 'version', '台账乐观锁版本号');
CALL queue_column_comment('baiyue_order', 'ord_order_internal_note', 'content', '仅内部人员可见的备注内容');
CALL queue_column_comment('baiyue_order', 'ord_order_internal_note', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_order', 'ord_order_internal_note', 'created_by', '创建人ID');
CALL queue_column_comment('baiyue_order', 'ord_order_internal_note', 'deleted', '逻辑删除标记');
CALL queue_column_comment('baiyue_order', 'ord_order_internal_note', 'id', '内部备注ID');
CALL queue_column_comment('baiyue_order', 'ord_order_internal_note', 'order_id', '所属订单ID');
CALL queue_column_comment('baiyue_order', 'ord_order_internal_note', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_order', 'ord_order_internal_note', 'updated_at', '更新时间');
CALL queue_column_comment('baiyue_order', 'ord_order_internal_note', 'updated_by', '最后修改人ID');
CALL queue_column_comment('baiyue_order', 'ord_order_internal_note', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_order', 'ord_order_party', 'address_line', '详细地址');
CALL queue_column_comment('baiyue_order', 'ord_order_party', 'city', '城市');
CALL queue_column_comment('baiyue_order', 'ord_order_party', 'company_name', '公司名称快照');
CALL queue_column_comment('baiyue_order', 'ord_order_party', 'contact_name', '联系人快照');
CALL queue_column_comment('baiyue_order', 'ord_order_party', 'country_code', '国家二字码');
CALL queue_column_comment('baiyue_order', 'ord_order_party', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_order', 'ord_order_party', 'created_by', '创建人ID');
CALL queue_column_comment('baiyue_order', 'ord_order_party', 'deleted', '逻辑删除标记');
CALL queue_column_comment('baiyue_order', 'ord_order_party', 'district', '区县');
CALL queue_column_comment('baiyue_order', 'ord_order_party', 'email', '联系邮箱快照');
CALL queue_column_comment('baiyue_order', 'ord_order_party', 'id', '参与方快照ID');
CALL queue_column_comment('baiyue_order', 'ord_order_party', 'order_id', '所属订单ID');
CALL queue_column_comment('baiyue_order', 'ord_order_party', 'party_type', '参与方类型:SHIPPER或CONSIGNEE');
CALL queue_column_comment('baiyue_order', 'ord_order_party', 'phone', '联系电话快照');
CALL queue_column_comment('baiyue_order', 'ord_order_party', 'postal_code', '邮政编码');
CALL queue_column_comment('baiyue_order', 'ord_order_party', 'province', '省/州');
CALL queue_column_comment('baiyue_order', 'ord_order_party', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_order', 'ord_order_party', 'updated_at', '更新时间');
CALL queue_column_comment('baiyue_order', 'ord_order_party', 'updated_by', '最后修改人ID');
CALL queue_column_comment('baiyue_order', 'ord_order_party', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_order', 'ord_order_status_history', 'action', '触发状态变化的业务动作');
CALL queue_column_comment('baiyue_order', 'ord_order_status_history', 'from_status', '变更前状态,首次创建可为空');
CALL queue_column_comment('baiyue_order', 'ord_order_status_history', 'id', '状态历史ID');
CALL queue_column_comment('baiyue_order', 'ord_order_status_history', 'occurred_at', '状态变化时间');
CALL queue_column_comment('baiyue_order', 'ord_order_status_history', 'operator_id', '操作人ID');
CALL queue_column_comment('baiyue_order', 'ord_order_status_history', 'operator_name', '操作人名称快照');
CALL queue_column_comment('baiyue_order', 'ord_order_status_history', 'order_id', '所属订单ID');
CALL queue_column_comment('baiyue_order', 'ord_order_status_history', 'reason', '变更原因');
CALL queue_column_comment('baiyue_order', 'ord_order_status_history', 'tenant_id', '客户ID和数据隔离键');
CALL queue_column_comment('baiyue_order', 'ord_order_status_history', 'to_status', '变更后状态');
CALL queue_column_comment('baiyue_order', 'ord_outbox_event', 'aggregate_id', '发生变更的聚合根ID,例如订单ID');
CALL queue_column_comment('baiyue_order', 'ord_outbox_event', 'aggregate_type', '聚合类型,例如 ORDER');
CALL queue_column_comment('baiyue_order', 'ord_outbox_event', 'created_at', '事件在本地事务中的创建时间');
CALL queue_column_comment('baiyue_order', 'ord_outbox_event', 'event_type', '领域事件类型,例如 ORDER_SUBMITTED');
CALL queue_column_comment('baiyue_order', 'ord_outbox_event', 'id', '领域事件全局ID,同时作为消费幂等键');
CALL queue_column_comment('baiyue_order', 'ord_outbox_event', 'next_retry_at', '允许再次投递的最早时间');
CALL queue_column_comment('baiyue_order', 'ord_outbox_event', 'payload_json', '供下游消费的事件负载快照');
CALL queue_column_comment('baiyue_order', 'ord_outbox_event', 'publish_status', '发布状态:PENDING待发布、PUBLISHED已发布、FAILED发布失败');
CALL queue_column_comment('baiyue_order', 'ord_outbox_event', 'published_at', '成功发布到消息中间件的时间');
CALL queue_column_comment('baiyue_order', 'ord_outbox_event', 'retry_count', '投递失败后的累计重试次数');
CALL queue_column_comment('baiyue_order', 'ord_outbox_event', 'tenant_id', '事件所属客户ID和数据隔离键');
CALL queue_column_comment('baiyue_order', 'sys_audit_outbox', 'attempt_count', '当前重试周期发送次数');
CALL queue_column_comment('baiyue_order', 'sys_audit_outbox', 'compensation_count', '自动补偿发送次数');
CALL queue_column_comment('baiyue_order', 'sys_audit_outbox', 'created_at', '记录创建时间');
CALL queue_column_comment('baiyue_order', 'sys_audit_outbox', 'dead_at', '进入死信时间');
CALL queue_column_comment('baiyue_order', 'sys_audit_outbox', 'event_id', '审计事件全局唯一ID');
CALL queue_column_comment('baiyue_order', 'sys_audit_outbox', 'event_type', '审计事件类型');
CALL queue_column_comment('baiyue_order', 'sys_audit_outbox', 'last_error', '最近一次发送错误信息');
CALL queue_column_comment('baiyue_order', 'sys_audit_outbox', 'lease_owner', '当前租约持有者');
CALL queue_column_comment('baiyue_order', 'sys_audit_outbox', 'lease_until', '发送租约截止时间');
CALL queue_column_comment('baiyue_order', 'sys_audit_outbox', 'next_retry_at', '下一次重试时间');
CALL queue_column_comment('baiyue_order', 'sys_audit_outbox', 'payload_json', '脱敏后的审计事件内容');
CALL queue_column_comment('baiyue_order', 'sys_audit_outbox', 'resource_id', '被审计资源ID');
CALL queue_column_comment('baiyue_order', 'sys_audit_outbox', 'resource_type', '被审计资源类型');
CALL queue_column_comment('baiyue_order', 'sys_audit_outbox', 'sent_at', '成功发送时间');
CALL queue_column_comment('baiyue_order', 'sys_audit_outbox', 'service_name', '来源服务名称');
CALL queue_column_comment('baiyue_order', 'sys_audit_outbox', 'status', '发送状态:PENDING待发送、SENDING发送中、SENT已发送、DEAD进入死信');
CALL queue_column_comment('baiyue_order', 'sys_audit_outbox', 'updated_at', '记录更新时间');
CALL queue_column_comment('baiyue_warehouse', 'sys_audit_outbox', 'attempt_count', '发送尝试次数');
CALL queue_column_comment('baiyue_warehouse', 'sys_audit_outbox', 'compensation_count', '补偿处理次数');
CALL queue_column_comment('baiyue_warehouse', 'sys_audit_outbox', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_warehouse', 'sys_audit_outbox', 'dead_at', '进入死信时间');
CALL queue_column_comment('baiyue_warehouse', 'sys_audit_outbox', 'event_id', '审计事件全局唯一ID');
CALL queue_column_comment('baiyue_warehouse', 'sys_audit_outbox', 'event_type', '审计事件类型');
CALL queue_column_comment('baiyue_warehouse', 'sys_audit_outbox', 'last_error', '最近一次发送错误');
CALL queue_column_comment('baiyue_warehouse', 'sys_audit_outbox', 'lease_owner', '当前租约持有实例');
CALL queue_column_comment('baiyue_warehouse', 'sys_audit_outbox', 'lease_until', '当前租约截止时间');
CALL queue_column_comment('baiyue_warehouse', 'sys_audit_outbox', 'next_retry_at', '下次允许重试时间');
CALL queue_column_comment('baiyue_warehouse', 'sys_audit_outbox', 'payload_json', '待投递审计事件载荷');
CALL queue_column_comment('baiyue_warehouse', 'sys_audit_outbox', 'resource_id', '被审计资源ID');
CALL queue_column_comment('baiyue_warehouse', 'sys_audit_outbox', 'resource_type', '被审计资源类型');
CALL queue_column_comment('baiyue_warehouse', 'sys_audit_outbox', 'sent_at', '发送成功时间');
CALL queue_column_comment('baiyue_warehouse', 'sys_audit_outbox', 'service_name', '产生事件的服务名');
CALL queue_column_comment('baiyue_warehouse', 'sys_audit_outbox', 'status', '投递状态:PENDING待发送、PROCESSING处理中、SENT已发送、FAILED失败');
CALL queue_column_comment('baiyue_warehouse', 'sys_audit_outbox', 'updated_at', '更新时间');
CALL queue_column_comment('baiyue_warehouse', 'wh_express_company', 'company_code', '平台内唯一快递公司编码');
CALL queue_column_comment('baiyue_warehouse', 'wh_express_company', 'company_name', '快递公司名称');
CALL queue_column_comment('baiyue_warehouse', 'wh_express_company', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_warehouse', 'wh_express_company', 'created_by', '创建人ID');
CALL queue_column_comment('baiyue_warehouse', 'wh_express_company', 'deleted', '逻辑删除标记');
CALL queue_column_comment('baiyue_warehouse', 'wh_express_company', 'id', '快递公司记录ID');
CALL queue_column_comment('baiyue_warehouse', 'wh_express_company', 'remark', '备注');
CALL queue_column_comment('baiyue_warehouse', 'wh_express_company', 'service_phone', '客服电话');
CALL queue_column_comment('baiyue_warehouse', 'wh_express_company', 'status', '快递公司状态');
CALL queue_column_comment('baiyue_warehouse', 'wh_express_company', 'tenant_id', '平台公共快递公司固定使用 tenant_id=0');
CALL queue_column_comment('baiyue_warehouse', 'wh_express_company', 'tracking_url_template', '轨迹查询地址模板,可包含{trackingNo}');
CALL queue_column_comment('baiyue_warehouse', 'wh_express_company', 'updated_at', '更新时间');
CALL queue_column_comment('baiyue_warehouse', 'wh_express_company', 'updated_by', '最后修改人ID');
CALL queue_column_comment('baiyue_warehouse', 'wh_express_company', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_warehouse', 'wh_express_company', 'website', '官方网站');
CALL queue_column_comment('baiyue_warehouse', 'wh_location', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_warehouse', 'wh_location', 'created_by', '创建人ID');
CALL queue_column_comment('baiyue_warehouse', 'wh_location', 'deleted', '逻辑删除标记');
CALL queue_column_comment('baiyue_warehouse', 'wh_location', 'id', '库位ID(雪花ID)');
CALL queue_column_comment('baiyue_warehouse', 'wh_location', 'location_code', '平台仓库内唯一库位编码');
CALL queue_column_comment('baiyue_warehouse', 'wh_location', 'location_name', '库位名称');
CALL queue_column_comment('baiyue_warehouse', 'wh_location', 'location_type', '库位类型,二期为PALLET');
CALL queue_column_comment('baiyue_warehouse', 'wh_location', 'max_weight_kg', '最大承重展示值,二期不作容量拦截');
CALL queue_column_comment('baiyue_warehouse', 'wh_location', 'remark', '备注');
CALL queue_column_comment('baiyue_warehouse', 'wh_location', 'shared_overflow', '是否为系统共享溢出位A0');
CALL queue_column_comment('baiyue_warehouse', 'wh_location', 'status', 'AVAILABLE、RESERVED、OCCUPIED或DISABLED');
CALL queue_column_comment('baiyue_warehouse', 'wh_location', 'tenant_id', '平台公共库位固定使用 tenant_id=0');
CALL queue_column_comment('baiyue_warehouse', 'wh_location', 'updated_at', '更新时间');
CALL queue_column_comment('baiyue_warehouse', 'wh_location', 'updated_by', '最后修改人ID');
CALL queue_column_comment('baiyue_warehouse', 'wh_location', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_warehouse', 'wh_location', 'warehouse_id', '所属仓库ID');
CALL queue_column_comment('baiyue_warehouse', 'wh_location', 'zone_code', '库区编码');
CALL queue_column_comment('baiyue_warehouse', 'wh_location', 'zone_name', '库区名称');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_occupancy', 'active_location_occupancy', '普通库位活动占用唯一标记');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_occupancy', 'active_pallet_occupancy', '托盘活动占用唯一标记(包含A0)');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_occupancy', 'allocated_package_count', '本托盘分配箱数');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_occupancy', 'allocated_volume_cbm', '本托盘分配体积CBM');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_occupancy', 'business_id', '来源服务入库单ID');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_occupancy', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_occupancy', 'created_by', '创建人或系统主体ID');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_occupancy', 'id', '库位占用ID');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_occupancy', 'inbound_code', '入仓码快照');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_occupancy', 'location_code', '库位编码快照');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_occupancy', 'location_id', '库位ID');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_occupancy', 'occupancy_no', '平台内唯一占用业务号');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_occupancy', 'occupancy_status', 'OCCUPIED或RELEASED');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_occupancy', 'occupied_at', '占用开始时间');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_occupancy', 'pallet_code', '托盘编码快照');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_occupancy', 'pallet_id', '托盘ID');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_occupancy', 'release_reason', '释放原因');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_occupancy', 'released_at', '释放时间');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_occupancy', 'reservation_id', '来源预占ID');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_occupancy', 'shared_overflow', '是否使用共享溢出位A0');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_occupancy', 'source_service', '来源服务');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_occupancy', 'tenant_id', '平台公共库位占用固定使用 tenant_id=0');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_occupancy', 'updated_at', '更新时间');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_occupancy', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_occupancy', 'warehouse_id', '仓库ID');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_reservation', 'active_pallet_reservation', '托盘活动预占唯一标记(包含A0)');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_reservation', 'active_reservation', '普通库位活动预占唯一标记,A0 返回 NULL');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_reservation', 'business_id', '来源服务入库单ID');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_reservation', 'confirmed_at', '确认占用时间');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_reservation', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_reservation', 'created_by', '创建人或系统主体ID');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_reservation', 'expires_at', '预占过期时间');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_reservation', 'id', '库位预占ID');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_reservation', 'idempotency_key', '调用方幂等键');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_reservation', 'inbound_code', '入仓码快照');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_reservation', 'location_code', '库位编码快照');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_reservation', 'location_id', '库位ID');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_reservation', 'pallet_code', '托盘编码快照');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_reservation', 'pallet_id', '托盘ID');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_reservation', 'release_reason', '释放或过期原因');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_reservation', 'released_at', '释放时间');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_reservation', 'reservation_no', '平台内唯一预占业务号');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_reservation', 'reservation_status', 'RESERVED、CONFIRMED、RELEASED或EXPIRED');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_reservation', 'shared_overflow', '是否使用共享溢出位A0');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_reservation', 'source_service', '来源服务');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_reservation', 'tenant_id', '平台公共库位预占固定使用 tenant_id=0');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_reservation', 'updated_at', '更新时间');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_reservation', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_warehouse', 'wh_location_reservation', 'warehouse_id', '仓库ID');
CALL queue_column_comment('baiyue_warehouse', 'wh_overseas_storage_fee_rule', 'active_customer_id', '未删除规则的客户唯一索引值');
CALL queue_column_comment('baiyue_warehouse', 'wh_overseas_storage_fee_rule', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_warehouse', 'wh_overseas_storage_fee_rule', 'created_by', '创建人ID');
CALL queue_column_comment('baiyue_warehouse', 'wh_overseas_storage_fee_rule', 'currency', '费用币种三字码;首版为USD');
CALL queue_column_comment('baiyue_warehouse', 'wh_overseas_storage_fee_rule', 'customer_name', '客户名称快照');
CALL queue_column_comment('baiyue_warehouse', 'wh_overseas_storage_fee_rule', 'deleted', '逻辑删除标记:0未删除、1已删除');
CALL queue_column_comment('baiyue_warehouse', 'wh_overseas_storage_fee_rule', 'id', '海外仓费用规则ID(雪花ID)');
CALL queue_column_comment('baiyue_warehouse', 'wh_overseas_storage_fee_rule', 'initialization_key', '初始化或导入幂等键');
CALL queue_column_comment('baiyue_warehouse', 'wh_overseas_storage_fee_rule', 'tenant_id', '客户ID和数据隔离键;客户费用规则不得使用0');
CALL queue_column_comment('baiyue_warehouse', 'wh_overseas_storage_fee_rule', 'updated_at', '更新时间');
CALL queue_column_comment('baiyue_warehouse', 'wh_overseas_storage_fee_rule', 'updated_by', '最后修改人ID');
CALL queue_column_comment('baiyue_warehouse', 'wh_overseas_storage_fee_rule', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_warehouse', 'wh_overseas_storage_fee_tier', 'active_start_day', '未删除阶梯的起始天唯一索引值');
CALL queue_column_comment('baiyue_warehouse', 'wh_overseas_storage_fee_tier', 'billing_unit', '计费单位;首版为CBM立方米');
CALL queue_column_comment('baiyue_warehouse', 'wh_overseas_storage_fee_tier', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_warehouse', 'wh_overseas_storage_fee_tier', 'created_by', '创建人ID');
CALL queue_column_comment('baiyue_warehouse', 'wh_overseas_storage_fee_tier', 'deleted', '逻辑删除标记:0未删除、1已删除');
CALL queue_column_comment('baiyue_warehouse', 'wh_overseas_storage_fee_tier', 'end_day', '计费阶梯结束存储天数,含边界;NULL表示以上');
CALL queue_column_comment('baiyue_warehouse', 'wh_overseas_storage_fee_tier', 'fee_type', '费用类型;首版为STORAGE仓储费');
CALL queue_column_comment('baiyue_warehouse', 'wh_overseas_storage_fee_tier', 'id', '海外仓费用阶梯ID(雪花ID)');
CALL queue_column_comment('baiyue_warehouse', 'wh_overseas_storage_fee_tier', 'rule_id', '所属海外仓费用规则ID');
CALL queue_column_comment('baiyue_warehouse', 'wh_overseas_storage_fee_tier', 'sort_no', '阶梯展示和计算顺序');
CALL queue_column_comment('baiyue_warehouse', 'wh_overseas_storage_fee_tier', 'start_day', '计费阶梯起始存储天数,含边界');
CALL queue_column_comment('baiyue_warehouse', 'wh_overseas_storage_fee_tier', 'tenant_id', '客户ID和数据隔离键;与规则归属一致');
CALL queue_column_comment('baiyue_warehouse', 'wh_overseas_storage_fee_tier', 'unit_price', '每计费单位单价,非负');
CALL queue_column_comment('baiyue_warehouse', 'wh_overseas_storage_fee_tier', 'updated_at', '更新时间');
CALL queue_column_comment('baiyue_warehouse', 'wh_overseas_storage_fee_tier', 'updated_by', '最后修改人ID');
CALL queue_column_comment('baiyue_warehouse', 'wh_overseas_storage_fee_tier', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet', 'capacity_cbm', '托盘可用容积CBM');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet', 'color', '托盘颜色');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet', 'created_by', '创建人ID');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet', 'deleted', '逻辑删除标记');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet', 'id', '托盘ID(雪花ID)');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet', 'pallet_code', '平台仓库内唯一托盘编码');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet', 'pallet_name', '托盘名称');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet', 'pallet_type', '托盘类型');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet', 'remark', '备注');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet', 'status', '内部状态:AVAILABLE、RESERVED或OCCUPIED');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet', 'tenant_id', '平台公共托盘固定使用 tenant_id=0');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet', 'updated_at', '更新时间');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet', 'updated_by', '最后修改人ID');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet', 'warehouse_id', '所属仓库ID');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet', 'weight_kg', '托盘重量kg');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet_location_release_operation', 'business_id', '入库单ID');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet_location_release_operation', 'completed_at', '完成时间');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet_location_release_operation', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet_location_release_operation', 'id', '精确释放幂等操作ID');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet_location_release_operation', 'idempotency_key', '调用方幂等键');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet_location_release_operation', 'operation_status', 'PROCESSING处理中,COMPLETED已完成');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet_location_release_operation', 'request_hash', '规范化请求SHA-256摘要');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet_location_release_operation', 'response_snapshot', '完成后的精确释放结果快照,用于重复请求重放');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet_location_release_operation', 'source_service', '来源服务');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet_location_release_operation', 'tenant_id', '仓储公共资源作用域,固定为0');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet_location_settlement_operation', 'completed_at', '完成时间');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet_location_settlement_operation', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet_location_settlement_operation', 'id', 'PDA资源结算幂等操作ID');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet_location_settlement_operation', 'idempotency_key', 'PDA结算幂等键');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet_location_settlement_operation', 'operation_status', 'PROCESSING处理中,COMPLETED已完成');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet_location_settlement_operation', 'request_hash', '规范化请求SHA-256摘要');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet_location_settlement_operation', 'response_snapshot', '完成后的释放和转移组合结果快照,用于重复请求重放');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet_location_settlement_operation', 'source_business_id', '主入库单ID');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet_location_settlement_operation', 'source_service', '来源服务');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet_location_settlement_operation', 'target_business_id', '二级子入库单ID;全拣完成时为0,仅作审计快照');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet_location_settlement_operation', 'tenant_id', '仓储公共资源作用域,固定为0');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet_location_transfer_operation', 'completed_at', '完成时间');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet_location_transfer_operation', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet_location_transfer_operation', 'id', '调拨幂等操作ID');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet_location_transfer_operation', 'idempotency_key', '调用方幂等键');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet_location_transfer_operation', 'operation_status', 'PROCESSING处理中、COMPLETED已完成');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet_location_transfer_operation', 'request_hash', '规范化请求SHA-256摘要');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet_location_transfer_operation', 'response_snapshot', '完成后的调拨结果快照,用于重复请求重放');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet_location_transfer_operation', 'source_business_id', '来源入仓单ID');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet_location_transfer_operation', 'source_service', '来源服务');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet_location_transfer_operation', 'target_business_id', '目标子入仓单ID');
CALL queue_column_comment('baiyue_warehouse', 'wh_pallet_location_transfer_operation', 'tenant_id', '仓储公共资源域,固定为0');
CALL queue_column_comment('baiyue_warehouse', 'wh_partner', 'address_line', '详细地址');
CALL queue_column_comment('baiyue_warehouse', 'wh_partner', 'city', '城市');
CALL queue_column_comment('baiyue_warehouse', 'wh_partner', 'contact_email', '联系邮箱');
CALL queue_column_comment('baiyue_warehouse', 'wh_partner', 'contact_name', '联系人姓名');
CALL queue_column_comment('baiyue_warehouse', 'wh_partner', 'contact_phone', '联系电话');
CALL queue_column_comment('baiyue_warehouse', 'wh_partner', 'country_code', '国家二字码');
CALL queue_column_comment('baiyue_warehouse', 'wh_partner', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_warehouse', 'wh_partner', 'created_by', '创建人ID');
CALL queue_column_comment('baiyue_warehouse', 'wh_partner', 'data_scope_id', '所属业务数据范围');
CALL queue_column_comment('baiyue_warehouse', 'wh_partner', 'deleted', '逻辑删除标记:0未删除、1已删除');
CALL queue_column_comment('baiyue_warehouse', 'wh_partner', 'district', '区县');
CALL queue_column_comment('baiyue_warehouse', 'wh_partner', 'id', '往来单位ID(雪花ID)');
CALL queue_column_comment('baiyue_warehouse', 'wh_partner', 'partner_code', '平台内唯一往来单位编码');
CALL queue_column_comment('baiyue_warehouse', 'wh_partner', 'partner_name', '往来单位名称');
CALL queue_column_comment('baiyue_warehouse', 'wh_partner', 'partner_type', 'CUSTOMER、SUPPLIER或BOTH');
CALL queue_column_comment('baiyue_warehouse', 'wh_partner', 'postal_code', '邮政编码');
CALL queue_column_comment('baiyue_warehouse', 'wh_partner', 'province', '省或州');
CALL queue_column_comment('baiyue_warehouse', 'wh_partner', 'remark', '备注');
CALL queue_column_comment('baiyue_warehouse', 'wh_partner', 'status', '状态:ACTIVE启用、DISABLED停用');
CALL queue_column_comment('baiyue_warehouse', 'wh_partner', 'tenant_id', '平台公共往来单位固定使用 tenant_id=0');
CALL queue_column_comment('baiyue_warehouse', 'wh_partner', 'updated_at', '更新时间');
CALL queue_column_comment('baiyue_warehouse', 'wh_partner', 'updated_by', '最后修改人ID');
CALL queue_column_comment('baiyue_warehouse', 'wh_partner', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_warehouse', 'wh_partner_type', 'data_scope_id', '所属业务数据范围,平台公共数据为0');
CALL queue_column_comment('baiyue_warehouse', 'wh_partner_type', 'partner_id', '往来单位ID');
CALL queue_column_comment('baiyue_warehouse', 'wh_partner_type', 'partner_type', '稳定业务类型:CUSTOMER客户、SHIPPER发货方等');
CALL queue_column_comment('baiyue_warehouse', 'wh_partner_type', 'tenant_id', '平台公共往来单位固定使用 tenant_id=0');
CALL queue_column_comment('baiyue_warehouse', 'wh_service_capability', 'capability_code', '能力编码:RECEIVING、STORAGE、PICK_PACK、RETURN等');
CALL queue_column_comment('baiyue_warehouse', 'wh_service_capability', 'capability_name', '能力名称');
CALL queue_column_comment('baiyue_warehouse', 'wh_service_capability', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_warehouse', 'wh_service_capability', 'created_by', '创建人ID');
CALL queue_column_comment('baiyue_warehouse', 'wh_service_capability', 'cutoff_time', '当日作业截单时间');
CALL queue_column_comment('baiyue_warehouse', 'wh_service_capability', 'deleted', '逻辑删除标记');
CALL queue_column_comment('baiyue_warehouse', 'wh_service_capability', 'description', '能力说明');
CALL queue_column_comment('baiyue_warehouse', 'wh_service_capability', 'enabled', '是否启用');
CALL queue_column_comment('baiyue_warehouse', 'wh_service_capability', 'id', '服务能力ID');
CALL queue_column_comment('baiyue_warehouse', 'wh_service_capability', 'tenant_id', '平台公共仓库服务能力固定使用 tenant_id=0');
CALL queue_column_comment('baiyue_warehouse', 'wh_service_capability', 'updated_at', '更新时间');
CALL queue_column_comment('baiyue_warehouse', 'wh_service_capability', 'updated_by', '最后修改人ID');
CALL queue_column_comment('baiyue_warehouse', 'wh_service_capability', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_warehouse', 'wh_service_capability', 'warehouse_id', '所属仓库ID');
CALL queue_column_comment('baiyue_warehouse', 'wh_warehouse', 'contact_email', '联系邮箱');
CALL queue_column_comment('baiyue_warehouse', 'wh_warehouse', 'contact_name', '仓库联系人');
CALL queue_column_comment('baiyue_warehouse', 'wh_warehouse', 'contact_phone', '联系电话');
CALL queue_column_comment('baiyue_warehouse', 'wh_warehouse', 'country_code', '所在国家二字码');
CALL queue_column_comment('baiyue_warehouse', 'wh_warehouse', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_warehouse', 'wh_warehouse', 'created_by', '创建人ID');
CALL queue_column_comment('baiyue_warehouse', 'wh_warehouse', 'deleted', '逻辑删除标记');
CALL queue_column_comment('baiyue_warehouse', 'wh_warehouse', 'id', '仓库ID(雪花ID)');
CALL queue_column_comment('baiyue_warehouse', 'wh_warehouse', 'remark', '仓库备注');
CALL queue_column_comment('baiyue_warehouse', 'wh_warehouse', 'status', '仓库状态');
CALL queue_column_comment('baiyue_warehouse', 'wh_warehouse', 'tenant_id', '平台公共仓库固定使用 tenant_id=0');
CALL queue_column_comment('baiyue_warehouse', 'wh_warehouse', 'timezone', '仓库所在地UTC偏移量');
CALL queue_column_comment('baiyue_warehouse', 'wh_warehouse', 'updated_at', '更新时间');
CALL queue_column_comment('baiyue_warehouse', 'wh_warehouse', 'updated_by', '最后修改人ID');
CALL queue_column_comment('baiyue_warehouse', 'wh_warehouse', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_warehouse', 'wh_warehouse', 'warehouse_code', '平台内唯一仓库编码');
CALL queue_column_comment('baiyue_warehouse', 'wh_warehouse', 'warehouse_name', '仓库名称');
CALL queue_column_comment('baiyue_warehouse', 'wh_warehouse', 'warehouse_type', '公共仓库类型:DOMESTIC、FBA、OVERSEAS');
CALL queue_column_comment('baiyue_warehouse', 'wh_warehouse_address', 'address_line1', '主地址行');
CALL queue_column_comment('baiyue_warehouse', 'wh_warehouse_address', 'address_line2', '补充地址行');
CALL queue_column_comment('baiyue_warehouse', 'wh_warehouse_address', 'address_type', '地址类型,例如OPERATING或RETURN');
CALL queue_column_comment('baiyue_warehouse', 'wh_warehouse_address', 'city', '城市');
CALL queue_column_comment('baiyue_warehouse', 'wh_warehouse_address', 'country_code', '国家二字码');
CALL queue_column_comment('baiyue_warehouse', 'wh_warehouse_address', 'created_at', '创建时间');
CALL queue_column_comment('baiyue_warehouse', 'wh_warehouse_address', 'created_by', '创建人ID');
CALL queue_column_comment('baiyue_warehouse', 'wh_warehouse_address', 'deleted', '逻辑删除标记');
CALL queue_column_comment('baiyue_warehouse', 'wh_warehouse_address', 'district', '区县');
CALL queue_column_comment('baiyue_warehouse', 'wh_warehouse_address', 'id', '仓库地址ID');
CALL queue_column_comment('baiyue_warehouse', 'wh_warehouse_address', 'latitude', '纬度');
CALL queue_column_comment('baiyue_warehouse', 'wh_warehouse_address', 'longitude', '经度');
CALL queue_column_comment('baiyue_warehouse', 'wh_warehouse_address', 'postal_code', '邮政编码');
CALL queue_column_comment('baiyue_warehouse', 'wh_warehouse_address', 'state_province', '省或州');
CALL queue_column_comment('baiyue_warehouse', 'wh_warehouse_address', 'tenant_id', '平台公共仓库地址固定使用 tenant_id=0');
CALL queue_column_comment('baiyue_warehouse', 'wh_warehouse_address', 'updated_at', '更新时间');
CALL queue_column_comment('baiyue_warehouse', 'wh_warehouse_address', 'updated_by', '最后修改人ID');
CALL queue_column_comment('baiyue_warehouse', 'wh_warehouse_address', 'version', '乐观锁版本号');
CALL queue_column_comment('baiyue_warehouse', 'wh_warehouse_address', 'warehouse_id', '所属仓库ID');
-- END GENERATED NINE-SCHEMA COMMENT UPGRADE CALLS
CALL apply_queued_comments();
DROP PROCEDURE IF EXISTS apply_queued_comments;
DROP PROCEDURE IF EXISTS apply_one_table_comments;
DROP PROCEDURE IF EXISTS queue_column_comment;
DROP PROCEDURE IF EXISTS queue_table_comment;
DROP TEMPORARY TABLE IF EXISTS tmp_column_comment_target;
DROP TEMPORARY TABLE IF EXISTS tmp_table_comment_target;
-- 三期统一最终总断言:稳定箱号、单箱测量、墨家收件箱、客户尺寸和装柜权限。
USE baiyue_firstleg;
DROP PROCEDURE IF EXISTS assert_unified_final_schema;
DELIMITER $$
CREATE PROCEDURE assert_unified_final_schema()
BEGIN
IF (SELECT COUNT(*) FROM information_schema.schemata
WHERE schema_name IN ('baiyue_auth','baiyue_iam','baiyue_order','baiyue_warehouse',
'baiyue_firstleg','baiyue_file','baiyue_audit','baiyue_event_bridge','baiyue_finance'))<>9 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '统一迁移失败:九个业务数据库未完整创建';
END IF;
IF EXISTS (
SELECT 1 FROM information_schema.tables
WHERE table_schema IN ('baiyue_auth','baiyue_iam','baiyue_order','baiyue_warehouse',
'baiyue_firstleg','baiyue_file','baiyue_audit','baiyue_event_bridge','baiyue_finance')
AND table_type='BASE TABLE'
AND (TRIM(COALESCE(table_comment,''))='' OR table_comment NOT REGEXP '[一-龥]')
) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '统一迁移失败:存在缺失中文注释的业务表';
END IF;
IF EXISTS (
SELECT 1 FROM information_schema.columns column_info
JOIN information_schema.tables table_info
ON table_info.table_schema=column_info.table_schema
AND table_info.table_name=column_info.table_name AND table_info.table_type='BASE TABLE'
WHERE column_info.table_schema IN ('baiyue_auth','baiyue_iam','baiyue_order','baiyue_warehouse',
'baiyue_firstleg','baiyue_file','baiyue_audit','baiyue_event_bridge','baiyue_finance')
AND column_info.table_name NOT LIKE 'iam_notification_%'
AND NOT (column_info.table_schema='baiyue_finance' AND column_info.table_name IN (
'fin_settlement','fin_settlement_line','fin_settlement_payment'
))
AND (TRIM(COALESCE(column_info.column_comment,''))=''
OR column_info.column_comment NOT REGEXP '[一-龥]')
) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '统一迁移失败:存在缺失中文注释的业务字段';
END IF;
IF (SELECT COUNT(*) FROM information_schema.tables
WHERE table_schema = 'baiyue_finance'
AND table_name IN ('fin_fee_source', 'fin_inbox_event', 'fin_subject',
'fin_customer_settlement_profile', 'fin_bill', 'fin_bill_line',
'fin_receipt', 'fin_receipt_bill', 'fin_cost', 'fin_cost_allocation',
'fin_bill_revision', 'fin_idempotency_record', 'fin_audit_log')) <> 13 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:Finance 首期人工财务表未完整创建';
END IF;
IF (SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = 'baiyue_finance'
AND ((table_name = 'fin_customer_settlement_profile' AND column_name IN ('tenant_id', 'credit_days'))
OR (table_name = 'fin_bill' AND column_name IN ('tenant_id', 'currency_code', 'total_amount', 'received_amount', 'outstanding_amount'))
OR (table_name = 'fin_receipt' AND column_name IN ('tenant_id', 'currency_code', 'voucher_file_ids'))
OR (table_name = 'fin_cost' AND column_name IN ('tenant_id', 'cost_type', 'currency_code', 'voucher_file_ids')))) <> 14 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:Finance 客户隔离、CNY 金额或人工凭证字段不完整';
END IF;
IF (SELECT COUNT(DISTINCT CONCAT(table_name, '.', index_name))
FROM information_schema.statistics
WHERE table_schema = 'baiyue_finance'
AND non_unique = 0
AND ((table_name = 'fin_fee_source' AND index_name = 'uk_fin_fee_source')
OR (table_name = 'fin_inbox_event' AND index_name = 'uk_fin_inbox_event')
OR (table_name = 'fin_bill' AND index_name = 'uk_fin_bill_no')
OR (table_name = 'fin_receipt' AND index_name = 'uk_fin_receipt_no')
OR (table_name = 'fin_cost' AND index_name = 'uk_fin_cost_no')
OR (table_name = 'fin_bill_revision' AND index_name = 'uk_fin_bill_revision')
OR (table_name = 'fin_idempotency_record' AND index_name = 'uk_fin_idempotency'))) <> 7 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:Finance 来源事件或人工写操作幂等约束不完整';
END IF;
IF (SELECT COUNT(*) FROM information_schema.tables
WHERE table_schema = 'baiyue_firstleg'
AND table_name = 'fl_inbound_carton_label') <> 1 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:稳定箱号映射表未创建';
END IF;
IF (SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = 'baiyue_firstleg'
AND table_name = 'fl_inbound_measurement_item'
AND column_name IN ('box_code', 'machine_no', 'device_measured_at', 'updated_at')) <> 4 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:单箱当前测量字段未完整创建';
END IF;
IF (SELECT COUNT(DISTINCT CONCAT(table_name, '.', index_name))
FROM information_schema.statistics
WHERE table_schema = 'baiyue_firstleg'
AND ((table_name = 'fl_inbound_carton_label'
AND index_name IN ('uk_carton_label_box_code', 'uk_carton_label_index'))
OR (table_name = 'fl_inbound_measurement_item'
AND index_name = 'uk_measure_box_code'))
AND non_unique = 0) <> 3 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:稳定箱号或单箱测量唯一约束未完整创建';
END IF;
IF (SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = 'baiyue_firstleg'
AND table_name = 'fl_mojia_measurement_receipt'
AND column_name IN ('id', 'tenant_id', 'subject_id', 'box_code', 'length_cm', 'width_cm',
'scanned_shipment_id', 'height_cm', 'weight_kg', 'machine_no', 'measured_at', 'image_file_id',
'status', 'match_failure_code', 'match_failure_reason', 'request_id',
'trace_id', 'created_at', 'updated_at')) <> 19 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:墨家未匹配测量收件箱字段未完整创建';
END IF;
IF (SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = 'baiyue_firstleg'
AND table_name = 'fl_inbound_measurement_item'
AND column_name = 'scanned_shipment_id'
AND character_maximum_length = 64) <> 1 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:箱级测量实扫Shipment ID字段未创建';
END IF;
IF (SELECT character_maximum_length FROM information_schema.columns
WHERE table_schema = 'baiyue_firstleg'
AND table_name = 'fl_inbound_measurement_item'
AND column_name = 'calculation_rule_version') <> 64 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:测量计算规则版本字段长度不是64';
END IF;
IF (SELECT COUNT(*) FROM information_schema.tables
WHERE table_schema = 'baiyue_firstleg'
AND table_name = 'fl_customer_dimension_config') <> 1 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_customer_dimension_config'
AND index_name = 'uk_customer_dimension_config_tenant_box'
AND non_unique = 0
AND ((seq_in_index = 1 AND column_name = 'tenant_id')
OR (seq_in_index = 2 AND column_name = 'box_type'))) <> 2 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:客户四箱规尺寸配置唯一约束未创建';
END IF;
IF (SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_customer_dimension_config'
AND column_name IN ('id', 'tenant_id', 'box_type', 'inner_length_cm', 'inner_width_cm',
'inner_height_cm', 'edge_addition_cm', 'remark', 'initialization_key',
'created_by', 'created_at', 'updated_by', 'updated_at', 'deleted', 'version')) <> 15 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:客户四箱规尺寸配置字段不完整';
END IF;
IF EXISTS (
SELECT tenant.id
FROM baiyue_iam.iam_tenant tenant
LEFT JOIN baiyue_firstleg.fl_customer_dimension_config config
ON config.tenant_id = tenant.id AND config.deleted = 0
WHERE tenant.tenant_type = 'MERCHANT' AND tenant.deleted = 0 AND tenant.id > 0
GROUP BY tenant.id
HAVING COUNT(config.id) <> 4
OR COUNT(DISTINCT CASE
WHEN config.box_type IN ('SMALL', 'MEDIUM', 'LARGE', 'EXTRA_LARGE')
THEN config.box_type END) <> 4
) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:现有企业客户未完整持有固定四箱规尺寸配置';
END IF;
IF (SELECT COUNT(*) FROM baiyue_iam.iam_permission
WHERE id = 5100010 AND tenant_id = 0 AND app_code = 'PLATFORM'
AND permission_code = 'platform:customer:list' AND permission_name = '企业客户'
AND status = 'ACTIVE' AND deleted = 0) <> 1 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:客户尺寸计算配置权限父节点企业客户不存在';
END IF;
IF (SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = 'baiyue_auth' AND table_name = 'auth_account'
AND column_name = 'password_change_required') <> 1 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 permission_type = 'BUTTON'
AND parent_id = 5100010 AND status = 'ACTIVE' AND deleted = 0
AND ((id = 5100016 AND permission_code = 'platform:customer:reset-password')
OR (id = 5100017 AND permission_code = 'platform:customer:delete')
OR (id = 5100018 AND permission_code = 'platform:customer:impersonate'))) <> 3 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:平台客户操作权限未完整创建';
END IF;
IF (SELECT COUNT(DISTINCT 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.app_code = 'PLATFORM' AND r.role_code = 'PLATFORM_ADMIN'
AND p.permission_code IN ('platform:customer:reset-password',
'platform:customer:delete',
'platform:customer:impersonate')) <> 3 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 permission_type = 'BUTTON'
AND parent_id = 5100010 AND status = 'ACTIVE' AND deleted = 0
AND ((id = 5102111 AND permission_code = 'firstleg:dimension-config:view')
OR (id = 5102112 AND permission_code = 'firstleg:dimension-config:edit'))) <> 2 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:客户尺寸计算配置权限码、固定ID或父节点不正确';
END IF;
IF (SELECT COUNT(DISTINCT 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.app_code = 'PLATFORM' AND r.role_code = 'PLATFORM_ADMIN'
AND p.permission_code IN ('firstleg:dimension-config:view', 'firstleg:dimension-config:edit')) <> 2 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:平台管理员未获得客户尺寸计算配置读写权限';
END IF;
IF (SELECT COUNT(DISTINCT 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.app_code = 'PLATFORM' AND r.role_code = 'ORDER_OPERATOR'
AND p.permission_code = 'firstleg:dimension-config:view') <> 1
OR (SELECT COUNT(*)
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.app_code = 'PLATFORM' AND r.role_code = 'ORDER_OPERATOR'
AND p.permission_code = 'firstleg:dimension-config:edit') <> 0 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:订单运营角色的客户尺寸计算配置权限不是只读';
END IF;
IF (SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_loading_pick_task'
AND column_name IN ('loading_plan_id', 'container_no', 'task_status')) <> 3
OR (SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_loading_pick_task'
AND column_name IN ('tenant_id', 'inbound_id', 'status', 'expected_count', 'picked_count')) <> 0 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:智能装柜拣货任务表型不正确';
END IF;
IF (SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_inbound_loading_pick_task'
AND column_name IN ('tenant_id', 'inbound_id', 'status', 'expected_count', 'picked_count')) <> 5
OR (SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_inbound_loading_pick_task'
AND column_name IN ('loading_plan_id', 'container_no', 'task_status')) <> 0 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 = 5103040 AND permission_code = 'platform:download:list'
AND permission_name = '下载中心' AND route_path = '/downloads')
OR (id = 5103041 AND permission_code = 'platform:download:all' AND parent_id = 5103040))) <> 2
OR (SELECT COUNT(*) FROM baiyue_iam.iam_permission
WHERE tenant_id = 0 AND app_code = 'PLATFORM'
AND ((id = 5103060 AND permission_code = 'platform:location:list' AND parent_id = 5103000)
OR (id = 5103061 AND permission_code = 'platform:location:create' AND parent_id = 5103060)
OR (id = 5103062 AND permission_code = 'platform:location:edit' AND parent_id = 5103060)
OR (id = 5103063 AND permission_code = 'platform:location:status' AND parent_id = 5103060)
OR (id = 5103064 AND permission_code = 'platform:location:delete' AND parent_id = 5103060))) <> 5 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 5102121 AND 5102129
AND permission_code IN ('firstleg:inbound:split', 'firstleg:inbound:preload',
'firstleg:inbound:preload-view', 'platform:firstleg:inbound:loading:edit',
'platform:firstleg:inbound:loading:view', 'platform:firstleg:inbound:loading:complete',
'platform:firstleg:inbound:delivery:complete', 'platform:firstleg:inbound:picking:edit',
'platform:firstleg:inbound:picking:view')) <> 9 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:三期入库装柜权限 ID 未完成迁移';
END IF;
IF (SELECT COUNT(DISTINCT CONCAT(role.role_code, '|', permission.permission_code))
FROM baiyue_iam.iam_role_permission role_permission
JOIN baiyue_iam.iam_role role
ON role.id = role_permission.role_id AND role.tenant_id = role_permission.tenant_id
JOIN baiyue_iam.iam_permission permission
ON permission.id = role_permission.permission_id
AND permission.tenant_id = role_permission.tenant_id
WHERE role.tenant_id = 0
AND role.role_code IN ('PLATFORM_ADMIN', 'ORDER_OPERATOR')
AND permission.permission_code IN (
'firstleg:inbound:split', 'firstleg:inbound:preload',
'firstleg:inbound:preload-view', 'platform:firstleg:inbound:loading:edit',
'platform:firstleg:inbound:loading:view', 'platform:firstleg:inbound:loading:complete',
'platform:firstleg:inbound:delivery:complete', 'platform:firstleg:inbound:picking:edit',
'platform:firstleg:inbound:picking:view')) <> 18 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 = 5102091 AND permission_code = 'platform:firstleg:container:view' AND parent_id = 5102090)
OR (id = 5102092 AND permission_code = 'platform:firstleg:container:create' AND parent_id = 5102090)
OR (id = 5102093 AND permission_code = 'platform:firstleg:container:edit' AND parent_id = 5102090)
OR (id = 5102094 AND permission_code = 'platform:firstleg:smart-loading:list' AND parent_id = 5102000)
OR (id = 5102096 AND permission_code = 'platform:firstleg:smart-loading:create' AND parent_id = 5102094)
OR (id = 5102097 AND permission_code = 'platform:firstleg:smart-loading:assign' AND parent_id = 5102094)
OR (id = 5102098 AND permission_code = 'platform:firstleg:smart-loading:delete' AND parent_id = 5102094)
OR (id = 5102099 AND permission_code = 'platform:firstleg:smart-loading:push' AND parent_id = 5102094))) <> 8 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:智能装柜权限 ID 或父子关系不正确';
END IF;
IF (SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_fee_item'
AND column_name IN ('id', 'tenant_id', 'trade_scope', 'fee_code', 'fee_type_name')) <> 5
OR (SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_fee_item'
AND column_name = 'fee_code' AND is_nullable = 'NO') <> 1 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_fee_item'
AND index_name = 'uk_fee_item_code' AND non_unique = 0) <> 1
OR (SELECT COUNT(*) FROM information_schema.statistics
WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_fee_item'
AND index_name = 'uk_fee_item_code' AND non_unique = 0
AND seq_in_index = 1 AND column_name = 'fee_code') <> 1 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:费用项目编码唯一索引未创建';
END IF;
IF (SELECT COUNT(*) FROM baiyue_firstleg.fl_fee_item WHERE tenant_id <> 0) <> 0
OR (SELECT COUNT(*) FROM baiyue_firstleg.fl_fee_item
WHERE tenant_id = 0 AND status = 'ACTIVE' AND deleted = 0
AND id BETWEEN 193000000001000001 AND 193000000001000110
AND fee_code = CONCAT('FEE_TYPE_', LPAD(id - 193000000001000000, 3, '0'))) <> 110
OR EXISTS (
SELECT 1
FROM baiyue_firstleg.fl_inbound_fee_detail detail
JOIN baiyue_firstleg.fl_fee_item item ON item.id = detail.fee_item_id
WHERE detail.tenant_id = 0 OR item.tenant_id <> 0
) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:费用项目公共作用域、110条种子或客户明细引用不正确';
END IF;
IF (SELECT COUNT(*) FROM baiyue_iam.iam_permission
WHERE tenant_id = 0 AND app_code = 'PLATFORM' AND status = 'ACTIVE' AND deleted = 0
AND ((id = 5102070 AND permission_type = 'MENU' AND parent_id = 5103000
AND permission_code = 'firstleg:fee-item:view')
OR (id = 5102071 AND permission_type = 'BUTTON' AND parent_id = 5102070
AND permission_code = 'firstleg:fee-item:create')
OR (id = 5102072 AND permission_type = 'BUTTON' AND parent_id = 5102070
AND permission_code = 'firstleg:fee-item:edit')
OR (id = 5102073 AND permission_type = 'BUTTON' AND parent_id = 5102070
AND permission_code = 'firstleg:fee-item:delete'))) <> 4 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:费用项目权限父子关系未完整创建';
END IF;
IF (SELECT COUNT(DISTINCT 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.app_code = 'PLATFORM' AND r.role_code = 'PLATFORM_ADMIN'
AND p.permission_code IN ('firstleg:fee-item:view', 'firstleg:fee-item:create',
'firstleg:fee-item:edit', 'firstleg:fee-item:delete')) <> 4 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '统一迁移失败:平台管理员未获得费用项目完整授权';
END IF;
END$$
DELIMITER ;
-- Finance F1 人工财务权限:平台财务可写,客户财务仅本客户只读。
INSERT INTO baiyue_iam.iam_permission
(id, tenant_id, app_code, permission_type, parent_id, permission_code, permission_name, route_path, http_method, sort_no, status, deleted)
VALUES
(5105000, 0, 'PLATFORM', 'MENU', 0, 'platform:finance', '财务管理', '/finance', NULL, 60, 'ACTIVE', 0),
(5105010, 0, 'PLATFORM', 'BUTTON', 5105000, 'platform:finance:bill:view', '查看账单', NULL, 'GET', 10, 'ACTIVE', 0),
(5105011, 0, 'PLATFORM', 'BUTTON', 5105000, 'platform:finance:bill:create', '创建账单', NULL, 'POST', 20, 'ACTIVE', 0),
(5105012, 0, 'PLATFORM', 'BUTTON', 5105000, 'platform:finance:bill:update', '修改草稿账单', NULL, 'PUT', 30, 'ACTIVE', 0),
(5105013, 0, 'PLATFORM', 'BUTTON', 5105000, 'platform:finance:bill:issue', '出账', NULL, 'POST', 40, 'ACTIVE', 0),
(5105014, 0, 'PLATFORM', 'BUTTON', 5105000, 'platform:finance:bill:adjust', '调整账单', NULL, 'POST', 50, 'ACTIVE', 0),
(5105015, 0, 'PLATFORM', 'BUTTON', 5105000, 'platform:finance:bill:void', '作废账单', NULL, 'POST', 60, 'ACTIVE', 0),
(5105016, 0, 'PLATFORM', 'BUTTON', 5105000, 'platform:finance:receipt:view', '查看收款', NULL, 'GET', 70, 'ACTIVE', 0),
(5105017, 0, 'PLATFORM', 'BUTTON', 5105000, 'platform:finance:receipt:create', '登记收款', NULL, 'POST', 80, 'ACTIVE', 0),
(5105018, 0, 'PLATFORM', 'BUTTON', 5105000, 'platform:finance:cost:view', '查看成本', NULL, 'GET', 90, 'ACTIVE', 0),
(5105019, 0, 'PLATFORM', 'BUTTON', 5105000, 'platform:finance:cost:create', '登记成本', NULL, 'POST', 100, 'ACTIVE', 0),
(5105020, 0, 'PLATFORM', 'BUTTON', 5105000, 'platform:finance:customer-terms:view', '查看客户账期', NULL, 'GET', 110, 'ACTIVE', 0),
(5105021, 0, 'PLATFORM', 'BUTTON', 5105000, 'platform:finance:customer-terms:update', '修改客户账期', NULL, 'PUT', 120, 'ACTIVE', 0),
(5205000, 10001, 'MERCHANT', 'MENU', 5200000, 'merchant:finance:statement', '财务账单', '/finance/statements', NULL, 40, 'ACTIVE', 0),
(5205010, 10001, 'MERCHANT', 'BUTTON', 5205000, 'merchant:finance:statement:list', '查看账单', NULL, 'GET', 10, 'ACTIVE', 0),
(5205011, 10001, 'MERCHANT', 'BUTTON', 5205000, 'merchant:finance:statement:view', '查看账单详情及收款', NULL, 'GET', 20, 'ACTIVE', 0)
ON DUPLICATE KEY UPDATE permission_name=VALUES(permission_name), parent_id=VALUES(parent_id), route_path=VALUES(route_path), http_method=VALUES(http_method), status='ACTIVE', deleted=0;
-- Finance G1-G4 新权限只进入可分配权限目录,不默认授予平台管理员。
DROP PROCEDURE IF EXISTS migrate_finance_g1_g4_permissions;
DELIMITER $$
CREATE PROCEDURE migrate_finance_g1_g4_permissions()
BEGIN
IF EXISTS (
SELECT 1
FROM baiyue_iam.iam_permission
WHERE (id = 5105022 AND permission_code <> 'platform:finance:account:view')
OR (id = 5105023 AND permission_code <> 'platform:finance:fee-source:view')
OR (id = 5105024 AND permission_code <> 'platform:finance:fee-source:review')
OR (id = 5105025 AND permission_code <> 'platform:finance:fee-source:settlement')
OR (id = 5105026 AND permission_code <> 'platform:finance:reconciliation:create')
OR (id = 5105027 AND permission_code <> 'platform:finance:reconciliation:review')
OR (id = 5105028 AND permission_code <> 'platform:finance:reconciliation:issue')
OR (id = 5105029 AND permission_code <> 'platform:finance:reconciliation:adjust')
OR (id = 5105030 AND permission_code <> 'platform:finance:reconciliation:void')
OR (id = 5105031 AND permission_code <> 'platform:finance:settlement-ar:create')
OR (id = 5105032 AND permission_code <> 'platform:finance:settlement-ap:create')
OR (id = 5105033 AND permission_code <> 'platform:finance:settlement-ar:view')
OR (id = 5105034 AND permission_code <> 'platform:finance:settlement-ap:view')
OR (id = 5105035 AND permission_code <> 'platform:finance:payment:create')
OR (id = 5105036 AND permission_code <> 'platform:finance:settlement:void')
OR (permission_code = 'platform:finance:account:view' AND id <> 5105022)
OR (permission_code = 'platform:finance:fee-source:view' AND id <> 5105023)
OR (permission_code = 'platform:finance:fee-source:review' AND id <> 5105024)
OR (permission_code = 'platform:finance:fee-source:settlement' AND id <> 5105025)
OR (permission_code = 'platform:finance:reconciliation:create' AND id <> 5105026)
OR (permission_code = 'platform:finance:reconciliation:review' AND id <> 5105027)
OR (permission_code = 'platform:finance:reconciliation:issue' AND id <> 5105028)
OR (permission_code = 'platform:finance:reconciliation:adjust' AND id <> 5105029)
OR (permission_code = 'platform:finance:reconciliation:void' AND id <> 5105030)
OR (permission_code = 'platform:finance:settlement-ar:create' AND id <> 5105031)
OR (permission_code = 'platform:finance:settlement-ap:create' AND id <> 5105032)
OR (permission_code = 'platform:finance:settlement-ar:view' AND id <> 5105033)
OR (permission_code = 'platform:finance:settlement-ap:view' AND id <> 5105034)
OR (permission_code = 'platform:finance:payment:create' AND id <> 5105035)
OR (permission_code = 'platform:finance:settlement:void' AND id <> 5105036)
OR (id BETWEEN 5105022 AND 5105036
AND (tenant_id <> 0 OR app_code <> 'PLATFORM'))
) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'finance G1-G4 permission id/code conflict';
END IF;
INSERT INTO baiyue_iam.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
(5105022, 0, 'PLATFORM', 'BUTTON', 5105000, 'platform:finance:account:view',
'查看客户财务账户及流水', NULL, 'GET', 130, 'ACTIVE', 1, 0),
(5105023, 0, 'PLATFORM', 'BUTTON', 5105000, 'platform:finance:fee-source:view',
'查看财务费用来源', NULL, 'GET', 140, 'ACTIVE', 1, 0),
(5105024, 0, 'PLATFORM', 'BUTTON', 5105000, 'platform:finance:fee-source:review',
'审核财务费用来源', NULL, 'POST', 150, 'ACTIVE', 1, 0),
(5105025, 0, 'PLATFORM', 'BUTTON', 5105000, 'platform:finance:fee-source:settlement',
'设置费用来源参与结算', NULL, 'PUT', 160, 'ACTIVE', 1, 0),
(5105026, 0, 'PLATFORM', 'BUTTON', 5105000, 'platform:finance:reconciliation:create', '生成和提交客户对账单', NULL, 'POST', 170, 'ACTIVE', 1, 0),
(5105027, 0, 'PLATFORM', 'BUTTON', 5105000, 'platform:finance:reconciliation:review', '审核客户对账单', NULL, 'POST', 180, 'ACTIVE', 1, 0),
(5105028, 0, 'PLATFORM', 'BUTTON', 5105000, 'platform:finance:reconciliation:issue', '出账客户对账单', NULL, 'POST', 190, 'ACTIVE', 1, 0),
(5105029, 0, 'PLATFORM', 'BUTTON', 5105000, 'platform:finance:reconciliation:adjust', '调整客户对账单', NULL, 'POST', 200, 'ACTIVE', 1, 0),
(5105030, 0, 'PLATFORM', 'BUTTON', 5105000, 'platform:finance:reconciliation:void', '作废或重开客户对账单', NULL, 'POST', 210, 'ACTIVE', 1, 0),
(5105031, 0, 'PLATFORM', 'BUTTON', 5105000, 'platform:finance:settlement-ar:create', '生成应收结算单', NULL, 'POST', 220, 'ACTIVE', 1, 0),
(5105032, 0, 'PLATFORM', 'BUTTON', 5105000, 'platform:finance:settlement-ap:create', '生成应付结算单', NULL, 'POST', 230, 'ACTIVE', 1, 0),
(5105033, 0, 'PLATFORM', 'BUTTON', 5105000, 'platform:finance:settlement-ar:view', '查看应收结算单', NULL, 'GET', 240, 'ACTIVE', 1, 0),
(5105034, 0, 'PLATFORM', 'BUTTON', 5105000, 'platform:finance:settlement-ap:view', '查看应付结算单', NULL, 'GET', 250, 'ACTIVE', 1, 0),
(5105035, 0, 'PLATFORM', 'BUTTON', 5105000, 'platform:finance:payment:create', '登记应付付款', NULL, 'POST', 260, 'ACTIVE', 1, 0),
(5105036, 0, 'PLATFORM', 'BUTTON', 5105000, 'platform:finance:settlement:void', '作废或重开结算单', NULL, 'POST', 270, 'ACTIVE', 1, 0)
ON DUPLICATE KEY UPDATE
permission_type = VALUES(permission_type), parent_id = VALUES(parent_id),
permission_name = VALUES(permission_name), route_path = VALUES(route_path),
http_method = VALUES(http_method), sort_no = VALUES(sort_no),
status = 'ACTIVE', built_in = 1, deleted = 0;
IF (SELECT COUNT(*)
FROM baiyue_iam.iam_permission
WHERE tenant_id = 0
AND app_code = 'PLATFORM'
AND permission_type = 'BUTTON'
AND parent_id = 5105000
AND status = 'ACTIVE'
AND built_in = 1
AND deleted = 0
AND permission_code IN (
'platform:finance:account:view',
'platform:finance:fee-source:view',
'platform:finance:fee-source:review',
'platform:finance:fee-source:settlement',
'platform:finance:reconciliation:create',
'platform:finance:reconciliation:review',
'platform:finance:reconciliation:issue',
'platform:finance:reconciliation:adjust',
'platform:finance:reconciliation:void',
'platform:finance:settlement-ar:create',
'platform:finance:settlement-ap:create',
'platform:finance:settlement-ar:view',
'platform:finance:settlement-ap:view',
'platform:finance:payment:create',
'platform:finance:settlement:void')) <> 15 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'finance G1-G4 permission catalog is incomplete';
END IF;
END$$
DELIMITER ;
CALL migrate_finance_g1_g4_permissions();
DROP PROCEDURE IF EXISTS migrate_finance_g1_g4_permissions;
INSERT INTO baiyue_iam.iam_role_permission (role_id, permission_id, tenant_id)
SELECT 4000001, id, 0 FROM baiyue_iam.iam_permission
WHERE tenant_id=0 AND app_code='PLATFORM' AND permission_code IN (
'platform:finance',
'platform:finance:bill:view', 'platform:finance:bill:create',
'platform:finance:bill:update', 'platform:finance:bill:issue',
'platform:finance:bill:adjust', 'platform:finance:bill:void',
'platform:finance:receipt:view', 'platform:finance:receipt:create',
'platform:finance:cost:view', 'platform:finance:cost:create',
'platform:finance:customer-terms:view', 'platform:finance:customer-terms:update')
ON DUPLICATE KEY UPDATE tenant_id=VALUES(tenant_id);
INSERT INTO baiyue_iam.iam_role_permission (role_id, permission_id, tenant_id)
SELECT 4000001, id, 0 FROM baiyue_iam.iam_permission
WHERE tenant_id=0 AND app_code='PLATFORM' AND permission_code IN (
'platform:finance:account:view', 'platform:finance:fee-source:view',
'platform:finance:fee-source:review', 'platform:finance:fee-source:settlement',
'platform:finance:reconciliation:create', 'platform:finance:reconciliation:review',
'platform:finance:reconciliation:issue', 'platform:finance:reconciliation:adjust',
'platform:finance:reconciliation:void', 'platform:finance:settlement-ar:create',
'platform:finance:settlement-ap:create', 'platform:finance:settlement-ar:view',
'platform:finance:settlement-ap:view', 'platform:finance:payment:create',
'platform:finance:settlement:void')
ON DUPLICATE KEY UPDATE tenant_id=VALUES(tenant_id);
INSERT INTO baiyue_iam.iam_role_permission (role_id, permission_id, tenant_id)
SELECT 4000101, id, 10001 FROM baiyue_iam.iam_permission
WHERE tenant_id=10001 AND app_code='MERCHANT' AND (permission_code='merchant:finance:statement' OR permission_code LIKE 'merchant:finance:%')
ON DUPLICATE KEY UPDATE tenant_id=VALUES(tenant_id);
-- Finance 权限和角色授权断言。
DELIMITER $$
CREATE PROCEDURE assert_finance_permissions()
BEGIN
IF (SELECT COUNT(*) FROM baiyue_iam.iam_permission WHERE tenant_id=0 AND app_code='PLATFORM' AND permission_code LIKE 'platform:finance:%' AND status='ACTIVE' AND deleted=0) <> 27 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='统一迁移失败:平台 Finance 权限不完整';
END IF;
IF (SELECT COUNT(*) FROM baiyue_iam.iam_permission WHERE tenant_id=10001 AND app_code='MERCHANT' AND permission_code IN ('merchant:finance:statement:list','merchant:finance:statement:view') AND status='ACTIVE' AND deleted=0) <> 2 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='统一迁移失败:客户 Finance 只读权限不完整';
END IF;
IF (SELECT COUNT(*) FROM baiyue_iam.iam_role_permission rp JOIN baiyue_iam.iam_permission p ON p.id=rp.permission_id AND p.tenant_id=rp.tenant_id WHERE rp.role_id=4000001 AND rp.tenant_id=0 AND (p.permission_code='platform:finance' OR p.permission_code LIKE 'platform:finance:%')) <> 27 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='统一迁移失败:平台管理员 Finance 授权不完整';
END IF;
IF (SELECT COUNT(*) FROM baiyue_iam.iam_role_permission rp JOIN baiyue_iam.iam_permission p ON p.id=rp.permission_id AND p.tenant_id=rp.tenant_id WHERE rp.role_id=4000101 AND rp.tenant_id=10001 AND (p.permission_code='merchant:finance:statement' OR p.permission_code LIKE 'merchant:finance:%')) <> 3 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='统一迁移失败:客户管理员 Finance 只读授权不完整';
END IF;
END$$
DELIMITER ;
CALL assert_finance_permissions();
DROP PROCEDURE IF EXISTS assert_finance_permissions;
CALL assert_unified_final_schema();
DROP PROCEDURE IF EXISTS assert_unified_final_schema;
CALL assert_loading_pick_task_shapes(1);
DROP PROCEDURE IF EXISTS assert_loading_pick_task_shapes;
-- 修复旧版本“推送预装即拣货中”的状态污染;从未实际拣货的计划回收到预装柜中。
UPDATE fl_loading_plan plan
SET plan_status = 'EDITING',
updated_at = CURRENT_TIMESTAMP(3),
version = version + 1
WHERE plan.plan_status = 'LOADING'
AND NOT EXISTS (
SELECT 1
FROM fl_loading_pick_item pick_item
JOIN fl_loading_pick_task pick_task ON pick_task.id = pick_item.pick_task_id
WHERE pick_task.loading_plan_id = plan.id
AND pick_item.picked_package_count > 0
);
/* Phase 3 channel-owned containers and stable PDA identity. Source: firstleg phase3-channel-container-migration.sql */
DROP PROCEDURE IF EXISTS migrate_phase3_channel_containers;
DELIMITER $$
CREATE PROCEDURE migrate_phase3_channel_containers()
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
GET DIAGNOSTICS CONDITION 1
@phase3_channel_sqlstate = RETURNED_SQLSTATE,
@phase3_channel_errno = MYSQL_ERRNO,
@phase3_channel_message = MESSAGE_TEXT;
END;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='baiyue_firstleg' AND table_name='fl_ocean_booking' AND column_name='channel_code') THEN
ALTER TABLE baiyue_firstleg.fl_ocean_booking ADD COLUMN channel_code VARCHAR(64) NULL COMMENT '订舱渠道编码快照' AFTER customer_name;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='baiyue_firstleg' AND table_name='fl_ocean_booking' AND column_name='channel_name') THEN
ALTER TABLE baiyue_firstleg.fl_ocean_booking ADD COLUMN channel_name VARCHAR(128) NULL COMMENT '订舱渠道名称快照' AFTER channel_code;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='baiyue_firstleg' AND table_name='fl_ocean_booking' AND column_name='warehouse_id') THEN
ALTER TABLE baiyue_firstleg.fl_ocean_booking ADD COLUMN warehouse_id BIGINT UNSIGNED NULL COMMENT '国内中转仓ID快照' AFTER channel_name;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='baiyue_firstleg' AND table_name='fl_ocean_booking' AND column_name='warehouse_name') THEN
ALTER TABLE baiyue_firstleg.fl_ocean_booking ADD COLUMN warehouse_name VARCHAR(128) NULL COMMENT '国内中转仓名称快照' AFTER warehouse_id;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='baiyue_firstleg' AND table_name='fl_ocean_booking_container_requirement' AND column_name='capacity_cbm') THEN
ALTER TABLE baiyue_firstleg.fl_ocean_booking_container_requirement ADD COLUMN capacity_cbm DECIMAL(18,4) NULL COMMENT '柜型容量快照m3' AFTER container_kind;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='baiyue_firstleg' AND table_name='fl_ocean_booking_container_requirement' AND column_name='max_weight_kg') THEN
ALTER TABLE baiyue_firstleg.fl_ocean_booking_container_requirement ADD COLUMN max_weight_kg DECIMAL(18,3) NULL COMMENT '柜型最大承重快照kg' AFTER capacity_cbm;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='baiyue_firstleg' AND table_name='fl_container_booking' AND column_name='channel_code') THEN
ALTER TABLE baiyue_firstleg.fl_container_booking ADD COLUMN channel_code VARCHAR(64) NULL COMMENT '订舱渠道编码快照' AFTER cabinet_no;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='baiyue_firstleg' AND table_name='fl_container_booking' AND column_name='channel_name') THEN
ALTER TABLE baiyue_firstleg.fl_container_booking ADD COLUMN channel_name VARCHAR(128) NULL COMMENT '订舱渠道名称快照' AFTER channel_code;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='baiyue_firstleg' AND table_name='fl_container_booking' AND column_name='ocean_booking_id') THEN
ALTER TABLE baiyue_firstleg.fl_container_booking ADD COLUMN ocean_booking_id BIGINT UNSIGNED NULL COMMENT '来源订舱ID' AFTER channel_name;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='baiyue_firstleg' AND table_name='fl_container_booking' AND column_name='ocean_booking_requirement_id') THEN
ALTER TABLE baiyue_firstleg.fl_container_booking ADD COLUMN ocean_booking_requirement_id BIGINT UNSIGNED NULL COMMENT '来源订舱箱型需求ID' AFTER ocean_booking_id;
END IF;
UPDATE baiyue_firstleg.fl_container_booking c JOIN baiyue_firstleg.fl_ocean_booking_container_allocation a ON a.container_booking_id=c.id
JOIN baiyue_firstleg.fl_ocean_booking b ON b.id=a.ocean_booking_id
SET c.ocean_booking_id=b.id,c.ocean_booking_requirement_id=a.requirement_id,c.channel_code=COALESCE(c.channel_code,b.channel_code),c.channel_name=COALESCE(c.channel_name,b.channel_name)
WHERE c.ocean_booking_id IS NULL OR c.ocean_booking_requirement_id IS NULL
OR c.channel_code IS NULL OR c.channel_name IS NULL;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='baiyue_firstleg' AND table_name='fl_loading_pick_task' AND column_name='container_booking_id') THEN
ALTER TABLE baiyue_firstleg.fl_loading_pick_task ADD COLUMN container_booking_id BIGINT UNSIGNED NULL COMMENT '稳定柜次ID' AFTER loading_plan_id;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='baiyue_firstleg' AND table_name='fl_loading_pick_task' AND column_name='cabinet_no') THEN
ALTER TABLE baiyue_firstleg.fl_loading_pick_task ADD COLUMN cabinet_no VARCHAR(64) NULL COMMENT '稳定内部柜次号快照' AFTER container_booking_id;
END IF;
ALTER TABLE baiyue_firstleg.fl_loading_pick_task MODIFY COLUMN container_no VARCHAR(64) NULL COMMENT '废弃兼容字段;不得作为任务身份';
UPDATE baiyue_firstleg.fl_loading_pick_task t JOIN baiyue_firstleg.fl_loading_plan p ON p.id=t.loading_plan_id JOIN baiyue_firstleg.fl_container_booking c ON c.id=p.container_booking_id
SET t.container_booking_id=c.id,t.cabinet_no=c.cabinet_no WHERE t.container_booking_id IS NULL OR t.cabinet_no IS NULL;
IF EXISTS (SELECT 1 FROM baiyue_firstleg.fl_loading_pick_task WHERE container_booking_id IS NULL OR cabinet_no IS NULL) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='渠道柜迁移失败:PDA任务无法回填稳定柜次归属';
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema='baiyue_firstleg' AND table_name='fl_loading_pick_task' AND index_name='uk_loading_pick_task_booking') THEN
ALTER TABLE baiyue_firstleg.fl_loading_pick_task ADD UNIQUE KEY uk_loading_pick_task_booking (container_booking_id);
END IF;
END$$
DELIMITER ;
SET @phase3_channel_sqlstate = NULL;
SET @phase3_channel_errno = NULL;
SET @phase3_channel_message = NULL;
DROP TRIGGER IF EXISTS baiyue_firstleg.trg_container_booking_before_update;
DROP TRIGGER IF EXISTS baiyue_firstleg.trg_loading_pick_task_before_update;
CALL migrate_phase3_channel_containers();
DROP PROCEDURE IF EXISTS migrate_phase3_channel_containers;
DROP TRIGGER IF EXISTS baiyue_firstleg.trg_container_booking_before_update;
DELIMITER $$
CREATE TRIGGER baiyue_firstleg.trg_container_booking_before_update
BEFORE UPDATE ON baiyue_firstleg.fl_container_booking
FOR EACH ROW
BEGIN
DECLARE current_plan_status VARCHAR(24);
SELECT MAX(plan_status) INTO current_plan_status
FROM baiyue_firstleg.fl_loading_plan WHERE container_booking_id = OLD.id;
IF current_plan_status = 'COMPLETED' THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '装柜完成后货柜全部只读,不允许修改';
END IF;
IF current_plan_status = 'LOADING' 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.bill_of_lading_no <=> NEW.bill_of_lading_no) OR NOT (OLD.vessel_name <=> NEW.vessel_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 <=> NEW.terminal_trucking_company) OR NOT (OLD.loading_at <=> NEW.loading_at)
OR NOT (OLD.estimated_sailing_at <=> NEW.estimated_sailing_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 baiyue_firstleg.trg_loading_pick_task_before_update;
DELIMITER $$
CREATE TRIGGER baiyue_firstleg.trg_loading_pick_task_before_update
BEFORE UPDATE ON baiyue_firstleg.fl_loading_pick_task
FOR EACH ROW
BEGIN
IF OLD.task_status = 'COMPLETED' THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '已完成PDA拣货任务不可修改';
END IF;
IF NEW.loading_plan_id <> OLD.loading_plan_id
OR NOT (NEW.container_booking_id <=> OLD.container_booking_id)
OR NOT (NEW.cabinet_no <=> OLD.cabinet_no) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'PDA拣货任务稳定归属不可修改';
END IF;
IF NOT (OLD.task_status = 'LOADING' AND NEW.task_status IN ('LOADING', 'COMPLETED')) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'PDA拣货任务状态只允许完成';
END IF;
END$$
DELIMITER ;
DROP PROCEDURE IF EXISTS assert_phase3_channel_container_migration;
DELIMITER $$
CREATE PROCEDURE assert_phase3_channel_container_migration()
BEGIN
DECLARE diagnostic_message VARCHAR(128);
IF @phase3_channel_sqlstate IS NOT NULL THEN
SET diagnostic_message = LEFT(CONCAT('渠道柜迁移失败 [', @phase3_channel_sqlstate, '/',
@phase3_channel_errno, ']: ', @phase3_channel_message), 128);
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = diagnostic_message;
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.triggers WHERE trigger_schema='baiyue_firstleg'
AND trigger_name='trg_container_booking_before_update') THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='渠道柜迁移失败:货柜只读触发器未恢复';
END IF;
IF NOT EXISTS (SELECT 1 FROM information_schema.triggers WHERE trigger_schema='baiyue_firstleg'
AND trigger_name='trg_loading_pick_task_before_update') THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='渠道柜迁移失败:PDA任务只读触发器未恢复';
END IF;
END$$
DELIMITER ;
CALL assert_phase3_channel_container_migration();
DROP PROCEDURE IF EXISTS assert_phase3_channel_container_migration;
CREATE TABLE IF NOT EXISTS baiyue_firstleg.fl_container_transport_history (
id BIGINT UNSIGNED NOT NULL COMMENT '运输字段变更历史ID',container_booking_id BIGINT UNSIGNED NOT NULL COMMENT '稳定柜次ID',
before_container_no VARCHAR(64) NULL COMMENT '变更前真实集装箱号',after_container_no VARCHAR(64) NULL COMMENT '变更后真实集装箱号',
before_voyage_no VARCHAR(64) NULL COMMENT '变更前航次号',after_voyage_no VARCHAR(64) NULL COMMENT '变更后航次号',
reason VARCHAR(500) 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_container_transport_history(container_booking_id,created_at,id)
) ENGINE=InnoDB COMMENT='柜次真实运输字段不可变变更审计';
/* IAM 消息中心基线。来源:iam/src/main/resources/db/notification-center.sql */
DROP PROCEDURE IF EXISTS baiyue_iam.assert_notification_permission_ids;
DELIMITER $$
CREATE PROCEDURE baiyue_iam.assert_notification_permission_ids()
BEGIN
IF EXISTS (
SELECT 1
FROM baiyue_iam.iam_permission
WHERE id BETWEEN 5106000 AND 5106010
AND permission_code NOT IN (
'platform:notification', 'platform:notification:view', 'platform:notification:create',
'platform:notification:template:list', 'platform:notification:template:create',
'platform:notification:template:edit', 'platform:notification:template:status',
'platform:notification:task:list', 'platform:notification:task:cancel',
'platform:notification:task:retry', 'merchant:notification:view'
)
) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='消息中心迁移失败:权限ID 5106000-5106010 已被其他语义占用';
END IF;
END$$
DELIMITER ;
CALL baiyue_iam.assert_notification_permission_ids();
DROP PROCEDURE IF EXISTS baiyue_iam.assert_notification_permission_ids;
CREATE TABLE IF NOT EXISTS baiyue_iam.iam_notification_template (
id BIGINT NOT NULL, tenant_id BIGINT NOT NULL DEFAULT 0, template_code VARCHAR(64) NOT NULL,
template_name VARCHAR(128) NOT NULL, category VARCHAR(64) NOT NULL, channel VARCHAR(16) NOT NULL DEFAULT 'IN_APP',
title_template VARCHAR(256) NOT NULL, body_template VARCHAR(10000) NOT NULL,
variable_whitelist_json JSON NOT NULL, status VARCHAR(16) NOT NULL DEFAULT 'DRAFT', version INT NOT NULL DEFAULT 1,
created_by BIGINT NOT NULL, updated_by BIGINT NOT 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_notification_template_code (tenant_id, template_code),
KEY idx_notification_template_list (tenant_id, status, updated_at)
) ENGINE=InnoDB COMMENT='消息模板;首期仅IN_APP';
CREATE TABLE IF NOT EXISTS baiyue_iam.iam_notification_task (
id BIGINT NOT NULL, task_no VARCHAR(64) NOT NULL, tenant_id BIGINT NOT NULL, template_id BIGINT NOT NULL,
template_code VARCHAR(64) NOT NULL, category VARCHAR(64) NOT NULL, title VARCHAR(256) NOT NULL,
body VARCHAR(10000) NOT NULL, recipient_type VARCHAR(32) NOT NULL, variables_json JSON NOT NULL,
idempotency_key VARCHAR(128) NOT NULL, source_event_id VARCHAR(128) NULL, source_event_type VARCHAR(128) NULL,
status VARCHAR(16) NOT NULL, recipient_count INT NOT NULL DEFAULT 0, attempt_count INT NOT NULL DEFAULT 0,
scheduled_at DATETIME(3) NULL, sent_at DATETIME(3) NULL, last_error VARCHAR(1000) NULL, created_by BIGINT NOT 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_notification_task_no (task_no), UNIQUE KEY uk_notification_task_idempotency (idempotency_key),
UNIQUE KEY uk_notification_source_event (source_event_id, source_event_type), KEY idx_notification_task_dispatch (status, scheduled_at, created_at),
KEY idx_notification_task_tenant (tenant_id, created_at)
) ENGINE=InnoDB COMMENT='消息任务与幂等状态机';
CREATE TABLE IF NOT EXISTS baiyue_iam.iam_notification_recipient (
id BIGINT NOT NULL, task_id BIGINT NOT NULL, tenant_id BIGINT NOT NULL, membership_id BIGINT NOT NULL,
user_id BIGINT NOT NULL, display_name VARCHAR(128) NOT NULL, app_code VARCHAR(16) NOT NULL, channel VARCHAR(16) NOT NULL DEFAULT 'IN_APP',
status VARCHAR(24) NOT NULL DEFAULT 'RESOLVED', failure_reason VARCHAR(1000) NULL, delivered_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),
PRIMARY KEY (id), UNIQUE KEY uk_notification_recipient (task_id, membership_id, channel),
KEY idx_notification_recipient_member (tenant_id, membership_id, created_at)
) ENGINE=InnoDB COMMENT='消息接收人不可变快照';
CREATE TABLE IF NOT EXISTS baiyue_iam.iam_notification_inbox (
id BIGINT NOT NULL, task_id BIGINT NOT NULL, recipient_id BIGINT NOT NULL, tenant_id BIGINT NOT NULL,
membership_id BIGINT NOT NULL, app_code VARCHAR(16) NOT NULL, category VARCHAR(64) NOT NULL,
title VARCHAR(256) NOT NULL, body VARCHAR(10000) NOT NULL, source_type VARCHAR(128) NULL, source_id VARCHAR(128) NULL,
status VARCHAR(16) NOT NULL DEFAULT 'UNREAD', sent_at DATETIME(3) NOT NULL, read_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),
PRIMARY KEY (id), UNIQUE KEY uk_notification_inbox_recipient (recipient_id),
KEY idx_notification_inbox_list (tenant_id, membership_id, app_code, status, sent_at),
KEY idx_notification_inbox_category (tenant_id, membership_id, app_code, category, sent_at)
) ENGINE=InnoDB COMMENT='按客户成员隔离的站内信';
CREATE TABLE IF NOT EXISTS baiyue_iam.iam_notification_event_inbox (
id BIGINT NOT NULL, source_event_id VARCHAR(128) NOT NULL, event_type VARCHAR(128) NOT NULL,
payload_hash CHAR(64) NOT NULL, task_id BIGINT NULL, status VARCHAR(16) NOT NULL DEFAULT 'PROCESSING',
last_error VARCHAR(1000) NULL, processed_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),
PRIMARY KEY (id), UNIQUE KEY uk_notification_event (source_event_id, event_type),
KEY idx_notification_event_status (status, updated_at)
) ENGINE=InnoDB COMMENT='业务事件消费幂等记录';
INSERT INTO baiyue_iam.iam_permission (id, tenant_id, app_code, permission_type, parent_id, permission_code, permission_name, route_path, http_method, sort_no, status, deleted)
VALUES
(5106000,0,'PLATFORM','MENU',0,'platform:notification','消息中心','/notifications',NULL,600,'ACTIVE',0),
(5106001,0,'PLATFORM','API',5106000,'platform:notification:view','查看消息','/api/platform/v1/notifications','GET',601,'ACTIVE',0),
(5106002,0,'PLATFORM','BUTTON',5106000,'platform:notification:create','发送消息','/api/platform/v1/notifications','POST',602,'ACTIVE',0),
(5106003,0,'PLATFORM','API',5106000,'platform:notification:template:list','查看消息模板','/api/platform/v1/notification-templates','GET',603,'ACTIVE',0),
(5106004,0,'PLATFORM','BUTTON',5106000,'platform:notification:template:create','新增消息模板','/api/platform/v1/notification-templates','POST',604,'ACTIVE',0),
(5106005,0,'PLATFORM','BUTTON',5106000,'platform:notification:template:edit','编辑消息模板','/api/platform/v1/notification-templates/{id}','PUT',605,'ACTIVE',0),
(5106006,0,'PLATFORM','BUTTON',5106000,'platform:notification:template:status','变更模板状态','/api/platform/v1/notification-templates/{id}/status','PUT',606,'ACTIVE',0),
(5106007,0,'PLATFORM','API',5106000,'platform:notification:task:list','查看消息任务','/api/platform/v1/notification-tasks','GET',607,'ACTIVE',0),
(5106008,0,'PLATFORM','BUTTON',5106000,'platform:notification:task:cancel','取消消息任务','/api/platform/v1/notification-tasks/{id}/cancel','PUT',608,'ACTIVE',0),
(5106009,0,'PLATFORM','BUTTON',5106000,'platform:notification:task:retry','重试消息任务','/api/platform/v1/notification-tasks/{id}/retry','PUT',609,'ACTIVE',0),
(5106010,0,'MERCHANT','API',0,'merchant:notification:view','查看我的消息','/api/merchant/v1/notifications','GET',900,'ACTIVE',0)
ON DUPLICATE KEY UPDATE permission_name=VALUES(permission_name), route_path=VALUES(route_path), http_method=VALUES(http_method), status='ACTIVE', deleted=0;
INSERT IGNORE INTO baiyue_iam.iam_role_permission (tenant_id, role_id, permission_id, created_at)
SELECT 0, r.id, p.id, CURRENT_TIMESTAMP(3)
FROM baiyue_iam.iam_permission p
JOIN baiyue_iam.iam_role r ON r.tenant_id=0 AND r.app_code='MERCHANT' AND r.status='ACTIVE'
WHERE p.tenant_id=0 AND p.permission_code='merchant:notification:view' AND r.role_code='MERCHANT_OWNER';
INSERT IGNORE INTO baiyue_iam.iam_role_permission (tenant_id, role_id, permission_id, created_at)
SELECT 0, r.id, p.id, CURRENT_TIMESTAMP(3)
FROM baiyue_iam.iam_permission p
JOIN baiyue_iam.iam_role r ON r.tenant_id=0 AND r.app_code='PLATFORM' AND r.status='ACTIVE'
WHERE p.tenant_id=0 AND p.permission_code LIKE 'platform:notification%' AND r.role_code='PLATFORM_ADMIN';
DROP PROCEDURE IF EXISTS baiyue_iam.assert_notification_center_schema;
DELIMITER $$
CREATE PROCEDURE baiyue_iam.assert_notification_center_schema()
BEGIN
IF (SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='baiyue_iam' AND table_name IN (
'iam_notification_template','iam_notification_task','iam_notification_recipient','iam_notification_inbox','iam_notification_event_inbox'
)) <> 5 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='统一迁移失败:IAM 消息中心表不完整';
END IF;
IF (SELECT COUNT(DISTINCT index_name) FROM information_schema.statistics WHERE table_schema='baiyue_iam' AND index_name IN (
'uk_notification_template_code','uk_notification_task_no','uk_notification_task_idempotency','uk_notification_source_event',
'uk_notification_recipient','uk_notification_inbox_recipient','uk_notification_event'
)) <> 7 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='统一迁移失败:IAM 消息中心唯一索引不完整';
END IF;
IF (SELECT COUNT(*) FROM baiyue_iam.iam_permission WHERE tenant_id=0 AND status='ACTIVE' AND deleted=0
AND (permission_code LIKE 'platform:notification%' OR permission_code='merchant:notification:view')) <> 11 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='统一迁移失败:IAM 消息中心权限不完整';
END IF;
IF (SELECT COUNT(*) 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.role_code='PLATFORM_ADMIN' AND p.permission_code LIKE 'platform:notification%') <> 10 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='统一迁移失败:平台管理员消息中心授权不完整';
END IF;
IF (SELECT COUNT(*) 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.role_code='MERCHANT_OWNER' AND p.permission_code='merchant:notification:view') <> 1 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='统一迁移失败:企业管理员消息查看授权不完整';
END IF;
END$$
DELIMITER ;
CALL baiyue_iam.assert_notification_center_schema();
DROP PROCEDURE IF EXISTS baiyue_iam.assert_notification_center_schema;
-- 货柜清关与装柜清单报关采用同一份轻量文件事实,不再要求独立报关 case 状态机。
USE baiyue_firstleg;
DROP PROCEDURE IF EXISTS phase3_simple_customs_add_column;
DELIMITER $$
CREATE PROCEDURE phase3_simple_customs_add_column(IN p_name VARCHAR(64), IN p_definition TEXT)
BEGIN
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
WHERE table_schema = 'baiyue_firstleg' 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 phase3_simple_customs_add_column('file_hash', "VARCHAR(64) NULL COMMENT 'File服务SHA-256快照' AFTER file_name");
CALL phase3_simple_customs_add_column('completed_by', "BIGINT UNSIGNED NULL COMMENT '一键完成操作人ID' AFTER uploaded_by_name");
CALL phase3_simple_customs_add_column('completed_by_name', "VARCHAR(64) NULL COMMENT '一键完成操作人名称快照' AFTER completed_by");
CALL phase3_simple_customs_add_column('completed_at', "DATETIME(3) NULL COMMENT '一键完成时间' AFTER completed_by_name");
DROP PROCEDURE phase3_simple_customs_add_column;
SET @has_simple_customs_check = (SELECT COUNT(*) FROM information_schema.table_constraints
WHERE table_schema='baiyue_firstleg' AND table_name='fl_container_document'
AND constraint_name='chk_container_document_type' AND constraint_type='CHECK');
SET @ddl = IF(@has_simple_customs_check=0, 'SELECT 1',
'ALTER TABLE fl_container_document DROP CHECK chk_container_document_type');
PREPARE statement FROM @ddl; EXECUTE statement; DEALLOCATE PREPARE statement;
ALTER TABLE fl_container_document ADD CONSTRAINT chk_container_document_type
CHECK (document_type IN ('DESTINATION_CLEARANCE','CUSTOMS_DECLARATION','DELIVERY_ORDER','PICKUP_ORDER'));
DROP TRIGGER IF EXISTS trg_container_document_before_insert;
DELIMITER $$
CREATE TRIGGER trg_container_document_before_insert BEFORE INSERT ON fl_container_document FOR EACH ROW
BEGIN
DECLARE current_plan_status VARCHAR(24);
SELECT MAX(plan_status) INTO current_plan_status FROM fl_loading_plan WHERE container_booking_id=NEW.container_booking_id;
IF current_plan_status='COMPLETED' AND NEW.document_type NOT IN ('DESTINATION_CLEARANCE','CUSTOMS_DECLARATION') THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='装柜完成后货柜物流文件不可新增';
END IF;
END$$
DELIMITER ;
DROP TRIGGER IF EXISTS trg_container_document_before_update;
DELIMITER $$
CREATE TRIGGER trg_container_document_before_update BEFORE UPDATE ON fl_container_document FOR EACH ROW
BEGIN
DECLARE current_plan_status VARCHAR(24);
SELECT MAX(plan_status) INTO current_plan_status FROM fl_loading_plan WHERE container_booking_id=OLD.container_booking_id;
IF current_plan_status='COMPLETED' AND OLD.document_type NOT IN ('DESTINATION_CLEARANCE','CUSTOMS_DECLARATION') THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='装柜完成后货柜物流文件不可修改';
END IF;
END$$
DELIMITER ;
-- 二、三期对外统一使用 INBOUNDED;历史 WAIT_SPLIT 仅由查询适配为 INBOUNDED,统一迁移不改写主表或历史状态。
USE baiyue_firstleg;
CREATE TABLE IF NOT EXISTS fl_container_exception_history (
id BIGINT UNSIGNED NOT NULL COMMENT '异常历史ID',
container_booking_id BIGINT UNSIGNED NOT NULL COMMENT '稳定货柜预订ID',
action VARCHAR(16) NOT NULL COMMENT 'MARK标记异常/CANCEL取消异常',
reason VARCHAR(500) NOT NULL COMMENT '标记或取消原因',
operator_id BIGINT UNSIGNED NOT NULL COMMENT '操作人ID',
operator_name VARCHAR(64) NULL COMMENT '操作人名称快照',
operation_id VARCHAR(128) NOT NULL COMMENT '同一操作人幂等键',
request_hash CHAR(64) NOT NULL COMMENT '规范化请求摘要',
occurred_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '发生时间',
PRIMARY KEY (id), UNIQUE KEY uk_container_exception_operation (container_booking_id, operator_id, operation_id),
KEY idx_container_exception_history (container_booking_id, occurred_at, id),
CONSTRAINT fk_container_exception_booking FOREIGN KEY (container_booking_id) REFERENCES fl_container_booking(id),
CONSTRAINT chk_container_exception_action CHECK (action IN ('MARK','CANCEL'))
) ENGINE=InnoDB COMMENT='货柜异常标记及取消原因历史';
DROP PROCEDURE IF EXISTS phase3_container_exception_add_column;
DELIMITER $$
CREATE PROCEDURE phase3_container_exception_add_column(IN p_name VARCHAR(64),IN p_definition TEXT)
BEGIN
IF NOT EXISTS(SELECT 1 FROM information_schema.columns WHERE table_schema='baiyue_firstleg' AND table_name='fl_container_exception_history' AND column_name=p_name) THEN
SET @ddl=CONCAT('ALTER TABLE fl_container_exception_history ADD COLUMN ',p_name,' ',p_definition);
PREPARE statement FROM @ddl; EXECUTE statement; DEALLOCATE PREPARE statement;
END IF;
END$$
DELIMITER ;
CALL phase3_container_exception_add_column('operation_id',"VARCHAR(128) NULL COMMENT '同一操作人幂等键' AFTER operator_name");
CALL phase3_container_exception_add_column('request_hash',"CHAR(64) NULL COMMENT '规范化请求摘要' AFTER operation_id");
DROP PROCEDURE phase3_container_exception_add_column;
UPDATE fl_container_exception_history SET operation_id=CONCAT('LEGACY-',id) WHERE operation_id IS NULL OR operation_id='';
UPDATE fl_container_exception_history SET request_hash=SHA2(CONCAT(action,'|',container_booking_id,'|',id,'|',reason),256) WHERE request_hash IS NULL OR request_hash='';
ALTER TABLE fl_container_exception_history MODIFY operation_id VARCHAR(128) NOT NULL, MODIFY request_hash CHAR(64) NOT NULL;
SET @has_exception_operation_index=(SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema='baiyue_firstleg' AND table_name='fl_container_exception_history' AND index_name='uk_container_exception_operation');
SET @ddl=IF(@has_exception_operation_index=0,'ALTER TABLE fl_container_exception_history ADD UNIQUE KEY uk_container_exception_operation(container_booking_id,operator_id,operation_id)','SELECT 1');
PREPARE statement FROM @ddl; EXECUTE statement; DEALLOCATE PREPARE statement;
DROP TRIGGER IF EXISTS trg_container_exception_history_before_update;
DROP TRIGGER IF EXISTS trg_container_exception_history_before_delete;
DELIMITER $$
CREATE TRIGGER trg_container_exception_history_before_update BEFORE UPDATE ON fl_container_exception_history FOR EACH ROW
BEGIN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='货柜异常历史不可修改'; END$$
CREATE TRIGGER trg_container_exception_history_before_delete BEFORE DELETE ON fl_container_exception_history FOR EACH ROW
BEGIN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='货柜异常历史不可删除'; END$$
DELIMITER ;
-- Admin 原型菜单与财务五页权限补充(来源:iam/src/main/resources/db/admin-prototype-menu-permissions.sql)。
USE baiyue_iam;
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
(5105040,0,'PLATFORM','MENU',5105000,'platform:finance:customer-account:list','客户账户','/finance/customer-accounts','GET',10,'ACTIVE',1,0),
(5105041,0,'PLATFORM','MENU',5105000,'platform:finance:customer-arap:list','客户应收应付','/finance/customer-arap','GET',20,'ACTIVE',1,0),
(5105042,0,'PLATFORM','MENU',5105000,'platform:finance:customer-statement:list','客户对账单','/finance/customer-statements','GET',30,'ACTIVE',1,0),
(5105043,0,'PLATFORM','MENU',5105000,'platform:finance:settlement-ar:list','结算单应收','/finance/settlement-receivables','GET',40,'ACTIVE',1,0),
(5105044,0,'PLATFORM','MENU',5105000,'platform:finance:settlement-ap:list','结算单应付','/finance/settlement-payables','GET',50,'ACTIVE',1,0)
ON DUPLICATE KEY UPDATE permission_type=VALUES(permission_type), parent_id=VALUES(parent_id), permission_name=VALUES(permission_name), route_path=VALUES(route_path), http_method=VALUES(http_method), sort_no=VALUES(sort_no), status='ACTIVE', built_in=1, deleted=0;
UPDATE iam_permission SET status='INACTIVE', deleted=1 WHERE tenant_id=0 AND app_code='PLATFORM' AND permission_code IN ('platform:overseas-storage-fee:create','platform:overseas-storage-fee:delete');
INSERT IGNORE INTO iam_role_permission (tenant_id, role_id, permission_id, created_at)
SELECT 0, r.id, p.id, CURRENT_TIMESTAMP(3) FROM iam_role r JOIN iam_permission p ON p.tenant_id=0 AND p.app_code='PLATFORM'
WHERE r.tenant_id=0 AND r.app_code='PLATFORM' AND r.role_code='PLATFORM_ADMIN' AND p.permission_code IN ('platform:finance:customer-account:list','platform:finance:customer-arap:list','platform:finance:customer-statement:list','platform:finance:settlement-ar:list','platform:finance:settlement-ap:list');
UPDATE iam_permission p JOIN (SELECT 'platform:finance:account:view' code,5105040 parent_id UNION ALL SELECT 'platform:finance:fee-source:view',5105041 UNION ALL SELECT 'platform:finance:fee-source:review',5105041 UNION ALL SELECT 'platform:finance:fee-source:settlement',5105041 UNION ALL SELECT 'platform:finance:reconciliation:create',5105042 UNION ALL SELECT 'platform:finance:reconciliation:review',5105042 UNION ALL SELECT 'platform:finance:reconciliation:issue',5105042 UNION ALL SELECT 'platform:finance:reconciliation:adjust',5105042 UNION ALL SELECT 'platform:finance:reconciliation:void',5105042 UNION ALL SELECT 'platform:finance:settlement-ar:create',5105043 UNION ALL SELECT 'platform:finance:settlement-ar:view',5105043 UNION ALL SELECT 'platform:finance:settlement-ap:create',5105044 UNION ALL SELECT 'platform:finance:settlement-ap:view',5105044 UNION ALL SELECT 'platform:finance:payment:create',5105044) mapping ON mapping.code=p.permission_code SET p.parent_id=mapping.parent_id WHERE p.tenant_id=0 AND p.app_code='PLATFORM' AND p.permission_type='BUTTON';
INSERT INTO baiyue_iam.iam_permission
(id, tenant_id, app_code, permission_type, parent_id, permission_code, permission_name, route_path, http_method, sort_no, status, deleted)
VALUES
(5201024, 10001, 'MERCHANT', 'BUTTON', 5201020, 'merchant:order:quick-create', '快速下单', NULL, NULL, 40, 'ACTIVE', 0)
ON DUPLICATE KEY UPDATE
permission_type = VALUES(permission_type), parent_id = VALUES(parent_id), permission_name = VALUES(permission_name),
route_path = VALUES(route_path), sort_no = VALUES(sort_no), status = 'ACTIVE', deleted = 0;
INSERT INTO baiyue_iam.iam_role_permission (role_id, permission_id, tenant_id)
SELECT r.id, p.id, r.tenant_id
FROM baiyue_iam.iam_role r
JOIN baiyue_iam.iam_permission p ON p.tenant_id = r.tenant_id
WHERE r.role_code = 'MERCHANT_OWNER' AND r.status = 'ACTIVE' AND r.deleted = 0
AND p.permission_code IN ('merchant:order:quick-create', 'merchant:order:create')
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
ALTER TABLE baiyue_order.ord_order_cargo
MODIFY COLUMN cargo_name VARCHAR(128) NULL COMMENT '货物名称;草稿可空,提交前必填',
MODIFY COLUMN package_type VARCHAR(32) NULL COMMENT '包装类型;草稿可空,提交前必填',
MODIFY COLUMN gross_weight_kg DECIMAL(18,3) NULL COMMENT '毛重(千克);草稿可空,提交前必须大于0',
MODIFY COLUMN volume_cbm DECIMAL(18,4) NULL COMMENT '体积(立方米);草稿可空,提交前必须大于0';
-- 平台公共供应商业务档案及权限(来源:warehouse-supplier.sql / platform-supplier-permissions.sql)。
USE baiyue_warehouse;
CREATE TABLE IF NOT EXISTS wh_supplier (
id BIGINT UNSIGNED NOT NULL COMMENT '供应商ID', tenant_id BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '平台公共作用域,固定为0',
name VARCHAR(160) NOT NULL COMMENT '供应商名称', category VARCHAR(20) NOT NULL COMMENT 'BOOKING/CUSTOMS/TRUCKING/CERTIFICATE',
scope VARCHAR(20) NULL COMMENT '订舱分类范围:DOMESTIC/INTERNATIONAL', region VARCHAR(80) NOT NULL COMMENT '地区',
legal_representative VARCHAR(80) NOT NULL COMMENT '法人代表', contact_name VARCHAR(80) NOT NULL COMMENT '联系人',
phone VARCHAR(40) NOT NULL COMMENT '联系电话', email VARCHAR(160) NULL COMMENT '电子邮箱', address VARCHAR(255) NULL COMMENT '公司地址',
business_license_no VARCHAR(64) NULL COMMENT '营业执照号', license_file_id VARCHAR(128) NULL COMMENT 'File服务营业执照文件ID',
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE' COMMENT 'ACTIVE/INACTIVE', settlement_period VARCHAR(32) NOT NULL COMMENT '结算周期', remark VARCHAR(500) NULL COMMENT '备注',
created_by BIGINT UNSIGNED NULL COMMENT '创建人ID', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_by BIGINT UNSIGNED NULL COMMENT '最后修改人ID', updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '最后修改时间',
deleted TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除标记', version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本', PRIMARY KEY(id),
UNIQUE KEY uk_wh_supplier_name(tenant_id,name), KEY idx_wh_supplier_filter(tenant_id,category,status,region),
CONSTRAINT chk_wh_supplier_category CHECK(category IN ('BOOKING','CUSTOMS','TRUCKING','CERTIFICATE')),
CONSTRAINT chk_wh_supplier_scope CHECK((category='BOOKING' AND scope IN ('DOMESTIC','INTERNATIONAL')) OR (category<>'BOOKING' AND scope IS NULL)),
CONSTRAINT chk_wh_supplier_status CHECK(status IN ('ACTIVE','INACTIVE')),
CONSTRAINT chk_wh_supplier_settlement CHECK(settlement_period IN ('MONTHLY_30','MONTHLY_45','MONTHLY_60','MONTHLY_90','PER_TRANSACTION','IMMEDIATE'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='平台公共供应商业务档案';
DROP PROCEDURE IF EXISTS supplier_add_column_if_missing;
DELIMITER $$
CREATE PROCEDURE supplier_add_column_if_missing(IN p_name VARCHAR(64),IN p_definition TEXT)
BEGIN
IF NOT EXISTS(SELECT 1 FROM information_schema.columns WHERE table_schema='baiyue_warehouse' AND table_name='wh_supplier' AND column_name=p_name) THEN
SET @supplier_ddl=CONCAT('ALTER TABLE wh_supplier ADD COLUMN ',p_name,' ',p_definition); PREPARE supplier_statement FROM @supplier_ddl; EXECUTE supplier_statement; DEALLOCATE PREPARE supplier_statement;
END IF;
END$$
DELIMITER ;
CALL supplier_add_column_if_missing('scope',"VARCHAR(20) NULL COMMENT '订舱分类范围:DOMESTIC/INTERNATIONAL' AFTER category");
CALL supplier_add_column_if_missing('license_file_id',"VARCHAR(128) NULL COMMENT 'File服务营业执照文件ID' AFTER business_license_no");
CALL supplier_add_column_if_missing('created_by',"BIGINT UNSIGNED NULL COMMENT '创建人ID'");
CALL supplier_add_column_if_missing('created_at',"DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间'");
CALL supplier_add_column_if_missing('updated_by',"BIGINT UNSIGNED NULL COMMENT '最后修改人ID'");
CALL supplier_add_column_if_missing('updated_at',"DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '最后修改时间'");
CALL supplier_add_column_if_missing('deleted',"TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除标记'");
CALL supplier_add_column_if_missing('version',"INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本'");
DROP PROCEDURE supplier_add_column_if_missing;
SET @supplier_ddl=IF(EXISTS(SELECT 1 FROM information_schema.statistics WHERE table_schema='baiyue_warehouse' AND table_name='wh_supplier' AND index_name='uk_wh_supplier_name'),'SELECT 1','ALTER TABLE wh_supplier ADD UNIQUE KEY uk_wh_supplier_name(tenant_id,name)'); PREPARE supplier_statement FROM @supplier_ddl; EXECUTE supplier_statement; DEALLOCATE PREPARE supplier_statement;
SET @supplier_ddl=IF(EXISTS(SELECT 1 FROM information_schema.statistics WHERE table_schema='baiyue_warehouse' AND table_name='wh_supplier' AND index_name='idx_wh_supplier_filter'),'SELECT 1','ALTER TABLE wh_supplier ADD KEY idx_wh_supplier_filter(tenant_id,category,status,region)'); PREPARE supplier_statement FROM @supplier_ddl; EXECUTE supplier_statement; DEALLOCATE PREPARE supplier_statement;
DROP PROCEDURE IF EXISTS supplier_add_check_if_missing;
DELIMITER $$
CREATE PROCEDURE supplier_add_check_if_missing(IN p_name VARCHAR(64),IN p_expression TEXT)
BEGIN
IF NOT EXISTS(SELECT 1 FROM information_schema.table_constraints WHERE table_schema='baiyue_warehouse' AND table_name='wh_supplier' AND constraint_name=p_name AND constraint_type='CHECK') THEN
SET @supplier_ddl=CONCAT('ALTER TABLE wh_supplier ADD CONSTRAINT ',p_name,' CHECK(',p_expression,')'); PREPARE supplier_statement FROM @supplier_ddl; EXECUTE supplier_statement; DEALLOCATE PREPARE supplier_statement;
END IF;
END$$
DELIMITER ;
CALL supplier_add_check_if_missing('chk_wh_supplier_category',"category IN ('BOOKING','CUSTOMS','TRUCKING','CERTIFICATE')");
CALL supplier_add_check_if_missing('chk_wh_supplier_scope',"(category='BOOKING' AND scope IN ('DOMESTIC','INTERNATIONAL')) OR (category<>'BOOKING' AND scope IS NULL)");
CALL supplier_add_check_if_missing('chk_wh_supplier_status',"status IN ('ACTIVE','INACTIVE')");
CALL supplier_add_check_if_missing('chk_wh_supplier_settlement',"settlement_period IN ('MONTHLY_30','MONTHLY_45','MONTHLY_60','MONTHLY_90','PER_TRANSACTION','IMMEDIATE')");
DROP PROCEDURE supplier_add_check_if_missing;
USE baiyue_iam;
DROP PROCEDURE IF EXISTS guard_platform_supplier_permission_ids;
DELIMITER $$
CREATE PROCEDURE guard_platform_supplier_permission_ids()
BEGIN
IF EXISTS(SELECT 1 FROM iam_permission WHERE id BETWEEN 5103080 AND 5103083 AND permission_code<>CASE id WHEN 5103080 THEN 'platform:supplier:list' WHEN 5103081 THEN 'platform:supplier:create' WHEN 5103082 THEN 'platform:supplier:edit' WHEN 5103083 THEN 'platform:supplier:status' END)
OR EXISTS(SELECT 1 FROM iam_permission WHERE permission_code IN('platform:supplier:list','platform:supplier:create','platform:supplier:edit','platform:supplier:status') AND id NOT BETWEEN 5103080 AND 5103083) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='供应商权限固定ID或权限码已被其他语义占用';
END IF;
END$$
DELIMITER ;
CALL guard_platform_supplier_permission_ids(); DROP PROCEDURE guard_platform_supplier_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
(5103080,0,'PLATFORM','MENU',5103000,'platform:supplier:list','供应商列表','/suppliers','GET',80,'ACTIVE',1,0),
(5103081,0,'PLATFORM','BUTTON',5103080,'platform:supplier:create','新增供应商',NULL,'POST',10,'ACTIVE',1,0),
(5103082,0,'PLATFORM','BUTTON',5103080,'platform:supplier:edit','编辑供应商',NULL,'PUT',20,'ACTIVE',1,0),
(5103083,0,'PLATFORM','BUTTON',5103080,'platform:supplier:status','启停供应商',NULL,'PATCH',30,'ACTIVE',1,0)
ON DUPLICATE KEY UPDATE permission_type=VALUES(permission_type),parent_id=VALUES(parent_id),permission_name=VALUES(permission_name),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(tenant_id,role_id,permission_id,created_at)
SELECT 0,r.id,p.id,CURRENT_TIMESTAMP(3) FROM iam_role r JOIN iam_permission p ON p.tenant_id=0 AND p.app_code='PLATFORM'
WHERE r.tenant_id=0 AND r.app_code='PLATFORM' AND r.role_code='PLATFORM_ADMIN' AND r.status='ACTIVE' AND r.deleted=0
AND p.permission_code IN('platform:supplier:list','platform:supplier:create','platform:supplier:edit','platform:supplier:status');
DROP PROCEDURE IF EXISTS assert_platform_supplier_permissions;
DELIMITER $$
CREATE PROCEDURE assert_platform_supplier_permissions()
BEGIN
IF (SELECT COUNT(*) FROM baiyue_warehouse.wh_supplier WHERE tenant_id<>0)<>0 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='供应商存在非平台作用域数据'; END IF;
IF (SELECT COUNT(DISTINCT index_name) FROM information_schema.statistics WHERE table_schema='baiyue_warehouse' AND table_name='wh_supplier' AND index_name IN('PRIMARY','uk_wh_supplier_name','idx_wh_supplier_filter'))<>3 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='供应商表索引不完整'; END IF;
IF (SELECT COUNT(*) FROM iam_permission WHERE tenant_id=0 AND app_code='PLATFORM' AND status='ACTIVE' AND deleted=0 AND permission_code IN('platform:supplier:list','platform:supplier:create','platform:supplier:edit','platform:supplier:status'))<>4 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='供应商权限资源初始化不完整'; END IF;
IF (SELECT COUNT(*) FROM iam_role_permission rp JOIN iam_role r ON r.id=rp.role_id AND r.tenant_id=rp.tenant_id JOIN iam_permission p ON p.id=rp.permission_id AND p.tenant_id=rp.tenant_id WHERE r.role_code='PLATFORM_ADMIN' AND p.permission_code IN('platform:supplier:list','platform:supplier:create','platform:supplier:edit','platform:supplier:status'))<>4 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='PLATFORM_ADMIN供应商权限授权不完整'; END IF;
END$$
DELIMITER ;
CALL assert_platform_supplier_permissions(); DROP PROCEDURE assert_platform_supplier_permissions;
-- 国内仓整柜入库、出库、库存和不可变交易流水。
USE baiyue_warehouse;
CREATE TABLE IF NOT EXISTS wh_domestic_container_order (
id BIGINT UNSIGNED NOT NULL, tenant_id BIGINT UNSIGNED NOT NULL DEFAULT 0, business_type VARCHAR(16) NOT NULL,
order_no VARCHAR(40) NOT NULL, warehouse_id BIGINT UNSIGNED NOT NULL, warehouse_name VARCHAR(120) NOT NULL,
container_no VARCHAR(32) NOT NULL, cargo_name VARCHAR(128) NOT NULL, box_count INT UNSIGNED NOT NULL,
volume_cbm DECIMAL(16,4) NOT NULL DEFAULT 0, operation_time DATETIME(3) NOT NULL, status VARCHAR(16) NOT NULL,
remark VARCHAR(500) NULL, created_by BIGINT UNSIGNED NULL, created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_by BIGINT UNSIGNED NULL, updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
deleted TINYINT(1) NOT NULL DEFAULT 0, version INT NOT NULL DEFAULT 0,
PRIMARY KEY(id), UNIQUE KEY uk_dc_order_no(tenant_id,order_no,deleted),
KEY idx_dc_order_list(tenant_id,business_type,warehouse_id,status,operation_time),
KEY idx_dc_order_container(tenant_id,warehouse_id,container_no,business_type),
CONSTRAINT chk_dc_order_scope CHECK(tenant_id=0), CONSTRAINT chk_dc_order_type CHECK(business_type IN('INBOUND','OUTBOUND')),
CONSTRAINT chk_dc_order_status CHECK(status IN('PENDING','COMPLETED')), CONSTRAINT chk_dc_order_quantity CHECK(box_count>0 AND volume_cbm>=0)
) ENGINE=InnoDB COMMENT='国内仓独立整柜入出库单';
CREATE TABLE IF NOT EXISTS wh_domestic_container_inventory (
id BIGINT UNSIGNED NOT NULL, tenant_id BIGINT UNSIGNED NOT NULL DEFAULT 0, warehouse_id BIGINT UNSIGNED NOT NULL,
warehouse_name VARCHAR(120) NOT NULL, container_no VARCHAR(32) NOT NULL, cargo_name VARCHAR(128) NOT NULL,
inbound_boxes INT UNSIGNED NOT NULL, outbound_boxes INT UNSIGNED NOT NULL DEFAULT 0, available_boxes INT UNSIGNED NOT NULL,
inbound_volume_cbm DECIMAL(16,4) NOT NULL, inventory_status VARCHAR(24) NOT NULL,
created_by BIGINT UNSIGNED NULL, created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_by BIGINT UNSIGNED NULL, updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
deleted TINYINT(1) NOT NULL DEFAULT 0, version INT NOT NULL DEFAULT 0,
PRIMARY KEY(id), UNIQUE KEY uk_dc_inventory_container(tenant_id,warehouse_id,container_no,deleted),
KEY idx_dc_inventory_list(tenant_id,warehouse_id,inventory_status,updated_at),
CONSTRAINT chk_dc_inventory_scope CHECK(tenant_id=0),
CONSTRAINT chk_dc_inventory_balance CHECK(inbound_boxes=outbound_boxes+available_boxes),
CONSTRAINT chk_dc_inventory_status CHECK(inventory_status IN('IN_STOCK','PARTIALLY_OUTBOUND','OUTBOUND'))
) ENGINE=InnoDB COMMENT='平台国内仓整柜库存台账';
CREATE TABLE IF NOT EXISTS wh_domestic_container_transaction (
id BIGINT UNSIGNED NOT NULL, tenant_id BIGINT UNSIGNED NOT NULL DEFAULT 0, inventory_id BIGINT UNSIGNED NOT NULL,
warehouse_id BIGINT UNSIGNED NOT NULL, container_no VARCHAR(32) NOT NULL, business_type VARCHAR(16) NOT NULL,
order_id BIGINT UNSIGNED NOT NULL, order_no VARCHAR(40) NOT NULL, quantity INT UNSIGNED NOT NULL,
operation_time DATETIME(3) NOT NULL, remark VARCHAR(500) NULL, created_by BIGINT UNSIGNED NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), updated_by BIGINT UNSIGNED NULL,
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
deleted TINYINT(1) NOT NULL DEFAULT 0, version INT NOT NULL DEFAULT 0,
PRIMARY KEY(id), UNIQUE KEY uk_dc_transaction_order(tenant_id,business_type,order_id),
KEY idx_dc_transaction_detail(tenant_id,inventory_id,operation_time),
CONSTRAINT chk_dc_transaction_scope CHECK(tenant_id=0), CONSTRAINT chk_dc_transaction_type CHECK(business_type IN('INBOUND','OUTBOUND')),
CONSTRAINT chk_dc_transaction_quantity CHECK(quantity>0)
) ENGINE=InnoDB COMMENT='国内仓整柜不可变进销存交易流水';
CREATE TABLE IF NOT EXISTS wh_domestic_container_idempotency (
id BIGINT UNSIGNED NOT NULL, tenant_id BIGINT UNSIGNED NOT NULL DEFAULT 0, operation_type VARCHAR(16) NOT NULL,
idempotency_key VARCHAR(128) NOT NULL, request_hash CHAR(64) NOT NULL, resource_id BIGINT UNSIGNED NULL,
resource_no VARCHAR(40) NULL, operation_status VARCHAR(16) NOT NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), completed_at DATETIME(3) NULL,
PRIMARY KEY(id), UNIQUE KEY uk_dc_idempotency(tenant_id,operation_type,idempotency_key),
CONSTRAINT chk_dc_idempotency_scope CHECK(tenant_id=0), CONSTRAINT chk_dc_idempotency_type CHECK(operation_type IN('INBOUND','OUTBOUND')),
CONSTRAINT chk_dc_idempotency_status CHECK(operation_status IN('PROCESSING','COMPLETED'))
) ENGINE=InnoDB COMMENT='国内仓整柜写操作幂等记录';
USE baiyue_iam;
DROP PROCEDURE IF EXISTS migrate_domestic_container_permissions;
DELIMITER $$
CREATE PROCEDURE migrate_domestic_container_permissions()
BEGIN
IF EXISTS(SELECT 1 FROM iam_permission WHERE (id=5103070 AND permission_code<>'platform:domestic-container:list') OR (id=5103071 AND permission_code<>'platform:domestic-container:inbound:create') OR (id=5103072 AND permission_code<>'platform:domestic-container:outbound:create') OR (permission_code='platform:domestic-container:list' AND id<>5103070) OR (permission_code='platform:domestic-container:inbound:create' AND id<>5103071) OR (permission_code='platform:domestic-container:outbound:create' AND id<>5103072)) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='domestic container permission id/code conflict';
END IF;
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
(5103070,0,'PLATFORM','MENU',5103000,'platform:domestic-container:list','整柜业务','/domestic-containers','GET',70,'ACTIVE',1,0),
(5103071,0,'PLATFORM','BUTTON',5103070,'platform:domestic-container:inbound:create','新建整柜入库单','/api/platform/v1/domestic-containers/inbounds','POST',10,'ACTIVE',1,0),
(5103072,0,'PLATFORM','BUTTON',5103070,'platform:domestic-container:outbound:create','新建整柜出库单','/api/platform/v1/domestic-containers/outbounds','POST',20,'ACTIVE',1,0)
ON DUPLICATE KEY UPDATE permission_type=VALUES(permission_type),parent_id=VALUES(parent_id),permission_name=VALUES(permission_name),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(tenant_id,role_id,permission_id,created_at)
SELECT 0,r.id,p.id,CURRENT_TIMESTAMP(3) FROM iam_role r JOIN iam_permission p ON p.tenant_id=0 AND p.app_code='PLATFORM'
WHERE r.tenant_id=0 AND r.app_code='PLATFORM' AND r.role_code='PLATFORM_ADMIN' AND r.status='ACTIVE' AND r.deleted=0
AND p.permission_code IN('platform:domestic-container:list','platform:domestic-container:inbound:create','platform:domestic-container:outbound:create');
IF (SELECT COUNT(*) FROM iam_permission WHERE tenant_id=0 AND app_code='PLATFORM' AND status='ACTIVE' AND deleted=0 AND permission_code IN('platform:domestic-container:list','platform:domestic-container:inbound:create','platform:domestic-container:outbound:create'))<>3 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='domestic container permission catalog is incomplete'; END IF;
IF (SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='baiyue_warehouse' AND table_name IN('wh_domestic_container_order','wh_domestic_container_inventory','wh_domestic_container_transaction','wh_domestic_container_idempotency'))<>4 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='domestic container tables are incomplete'; END IF;
END$$
DELIMITER ;
CALL migrate_domestic_container_permissions(); DROP PROCEDURE migrate_domestic_container_permissions;
-- 消息页面菜单与已读操作权限分离。
USE baiyue_iam;
UPDATE iam_permission SET permission_type='MENU',parent_id=5106000,permission_name='模板管理',route_path='/notifications/templates',http_method='GET',status='ACTIVE',deleted=0 WHERE id=5106003 AND permission_code='platform:notification:template:list';
UPDATE iam_permission SET permission_type='MENU',parent_id=5106000,permission_name='消息管理',route_path='/notifications/messages',http_method='GET',status='ACTIVE',deleted=0 WHERE id=5106007 AND permission_code='platform:notification:task:list';
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
(5106011,0,'PLATFORM','BUTTON',5106001,'platform:notification:read','标记消息已读','/api/platform/v1/notifications/{id}/read','PUT',610,'ACTIVE',1,0),
(5106012,0,'PLATFORM','BUTTON',5106001,'platform:notification:read-all','全部标记已读','/api/platform/v1/notifications/read-all','PUT',611,'ACTIVE',1,0),
(5106013,0,'MERCHANT','BUTTON',5106010,'merchant:notification:read','标记消息已读','/api/merchant/v1/notifications/{id}/read','PUT',901,'ACTIVE',1,0),
(5106014,0,'MERCHANT','BUTTON',5106010,'merchant:notification:read-all','全部标记已读','/api/merchant/v1/notifications/read-all','PUT',902,'ACTIVE',1,0)
ON DUPLICATE KEY UPDATE permission_type=VALUES(permission_type),parent_id=VALUES(parent_id),permission_name=VALUES(permission_name),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(tenant_id,role_id,permission_id,created_at)
SELECT 0,r.id,p.id,CURRENT_TIMESTAMP(3) FROM iam_role r JOIN iam_permission p ON p.tenant_id=0
WHERE r.tenant_id=0 AND r.status='ACTIVE' AND r.deleted=0
AND ((r.app_code='PLATFORM' AND r.role_code='PLATFORM_ADMIN' AND p.permission_code IN('platform:notification:read','platform:notification:read-all'))
OR (r.app_code='MERCHANT' AND r.role_code='MERCHANT_OWNER' AND p.permission_code IN('merchant:notification:read','merchant:notification:read-all')));
-- 新完成入库记录使用 WAIT_SPLIT;存量 INBOUNDED 继续作为可分柜兼容状态,统一迁移不改写主表或历史状态。
SELECT 'PASS' AS migration_status,
'统一增量全部执行完成;父子订单、履约分片、装柜门禁、订舱拖车、报关船期、船司查询、仓库角色、费用、Finance人工财务、公共资源及可靠审计结构校验通过' AS message;