-- 柏悦头程一期、二期数据库统一增量脚本 -- 固定发布入口:后续数据库变更只在本文件末尾按日期追加,不再新增部署入口脚本。 -- 适用: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 '目标区域', customer_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 '目标区域', customer_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='跨区域事件已接收箱'; -- 旧开发库曾使用 tenant_id;当前 Event Bridge 实体统一为 customer_id。幂等改名并保留原数据。 SET @bridge_inbox_customer_column = ( SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = 'baiyue_event_bridge' AND table_name = 'bridge_inbox' AND column_name = 'customer_id' ); SET @bridge_inbox_tenant_column = ( SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = 'baiyue_event_bridge' AND table_name = 'bridge_inbox' AND column_name = 'tenant_id' ); SET @bridge_inbox_column_sql = IF( @bridge_inbox_customer_column = 0 AND @bridge_inbox_tenant_column = 1, 'ALTER TABLE bridge_inbox RENAME COLUMN tenant_id TO customer_id', 'SELECT 1' ); PREPARE bridge_inbox_column_statement FROM @bridge_inbox_column_sql; EXECUTE bridge_inbox_column_statement; DEALLOCATE PREPARE bridge_inbox_column_statement; SET @bridge_outbox_customer_column = ( SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = 'baiyue_event_bridge' AND table_name = 'bridge_outbox' AND column_name = 'customer_id' ); SET @bridge_outbox_tenant_column = ( SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = 'baiyue_event_bridge' AND table_name = 'bridge_outbox' AND column_name = 'tenant_id' ); SET @bridge_outbox_column_sql = IF( @bridge_outbox_customer_column = 0 AND @bridge_outbox_tenant_column = 1, 'ALTER TABLE bridge_outbox RENAME COLUMN tenant_id TO customer_id', 'SELECT 1' ); PREPARE bridge_outbox_column_statement FROM @bridge_outbox_column_sql; EXECUTE bridge_outbox_column_statement; DEALLOCATE PREPARE bridge_outbox_column_statement; 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; -- 开发/测试环境最小登录基线只保留平台系统管理员;客户必须走正式建档/注册流程创建。 -- 缺失账号首次创建时写入开发基线 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) ON DUPLICATE KEY UPDATE 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 '客户企业内部成员编号', overseas_warehouse_id BIGINT NULL COMMENT '海外仓PDA操作员绑定的唯一实际海外仓ID', 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='客户成员关系表:表示用户在客户企业中的成员身份,是分配企业客户端角色的主体'; SET @ddl = (SELECT IF(EXISTS(SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='iam_membership' AND column_name='overseas_warehouse_id'), 'SELECT 1', 'ALTER TABLE iam_membership ADD COLUMN overseas_warehouse_id BIGINT NULL COMMENT ''海外仓PDA操作员绑定的唯一实际海外仓ID'' AFTER member_no')); PREPARE iam_membership_overseas_warehouse_stmt FROM @ddl; EXECUTE iam_membership_overseas_warehouse_stmt; DEALLOCATE PREPARE iam_membership_overseas_warehouse_stmt; 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 '预计发运日期', expected_arrival_at DATETIME(3) NULL COMMENT '客户申报预计到货时间', estimated_volume_cbm DECIMAL(18,4) NULL COMMENT '平台运营预估体积(立方米),独立于客户下单及实测体积', customer_remark VARCHAR(1000) NULL COMMENT '客户可见备注', pickup_method VARCHAR(24) NULL COMMENT '取货方式', requested_pickup_at DATETIME(3) 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 '下单时运输渠道名称快照;渠道主数据变化不回写', channel_changed TINYINT(1) NOT NULL DEFAULT 0 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私人地址', fulfillment_type VARCHAR(32) NOT NULL DEFAULT 'STANDARD' COMMENT '履约类型:STANDARD普通头程、ONE_CLICK_DROPSHIP一键代发', lx_code VARCHAR(128) NULL COMMENT '一件代发领星入库单代码', 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_inbound_code (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='头程订单主表:保存商户委托、目的仓、运输方式和订单当前状态'; SET @order_fulfillment_type_sql = IF( EXISTS( SELECT 1 FROM information_schema.columns WHERE table_schema = 'baiyue_order' AND table_name = 'ord_order' AND column_name = 'fulfillment_type' ), 'SELECT 1', 'ALTER TABLE baiyue_order.ord_order ADD COLUMN fulfillment_type VARCHAR(32) NULL COMMENT ''履约类型:STANDARD普通头程、ONE_CLICK_DROPSHIP一键代发'' AFTER destination_type' ); PREPARE order_fulfillment_type_stmt FROM @order_fulfillment_type_sql; EXECUTE order_fulfillment_type_stmt; DEALLOCATE PREPARE order_fulfillment_type_stmt; UPDATE baiyue_order.ord_order SET fulfillment_type = 'STANDARD' WHERE fulfillment_type IS NULL OR fulfillment_type = ''; ALTER TABLE baiyue_order.ord_order MODIFY COLUMN fulfillment_type VARCHAR(32) NOT NULL DEFAULT 'STANDARD' COMMENT '履约类型:STANDARD普通头程、ONE_CLICK_DROPSHIP一键代发'; -- 既有开发库可能仍保留草稿入仓码 NOT NULL 约束;必须先放宽再清理草稿入仓码。 SET @order_inbound_code_nullable_sql = IF( EXISTS( SELECT 1 FROM information_schema.columns WHERE table_schema = 'baiyue_order' AND table_name = 'ord_order' AND column_name = 'inbound_code' AND is_nullable = 'NO' ), 'ALTER TABLE baiyue_order.ord_order MODIFY COLUMN inbound_code VARCHAR(40) NULL COMMENT ''全平台唯一入仓码;来源由全局模式决定,提交后固定''', 'SELECT 1' ); PREPARE order_inbound_code_nullable_stmt FROM @order_inbound_code_nullable_sql; EXECUTE order_inbound_code_nullable_stmt; DEALLOCATE PREPARE order_inbound_code_nullable_stmt; -- CREATE TABLE IF NOT EXISTS 不会给既有表补列,故显式幂等补齐预约提货时间。 SET @order_requested_pickup_at_sql = IF( EXISTS( SELECT 1 FROM information_schema.columns WHERE table_schema = 'baiyue_order' AND table_name = 'ord_order' AND column_name = 'requested_pickup_at' ), 'SELECT 1', 'ALTER TABLE baiyue_order.ord_order ADD COLUMN requested_pickup_at DATETIME(3) NULL COMMENT ''客户要求平台上门提货的预约时间'' AFTER pickup_method' ); PREPARE order_requested_pickup_at_stmt FROM @order_requested_pickup_at_sql; EXECUTE order_requested_pickup_at_stmt; DEALLOCATE PREPARE order_requested_pickup_at_stmt; CREATE TABLE IF NOT EXISTS ord_inbound_code_daily_sequence ( business_date DATE NOT NULL COMMENT '上海时区业务日期', current_value BIGINT UNSIGNED NOT 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 (business_date) ) ENGINE=InnoDB COMMENT='入仓码每日票序;不同客户和物流渠道共用,按上海日期重新从1开始'; 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_customer_address ( id BIGINT UNSIGNED NOT NULL COMMENT '客户地址ID', tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键', address_type VARCHAR(32) NOT NULL COMMENT 'PICKUP取货地址或PRIVATE_DESTINATION私人目的仓', address_name VARCHAR(64) NOT NULL COMMENT '客户自定义地址名称', 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 '邮政编码', default_address TINYINT(1) NOT NULL DEFAULT 0 COMMENT '同类型默认地址', active TINYINT(1) NOT NULL DEFAULT 1 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_customer_address_scope (tenant_id, address_type, active, updated_at) ) 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', wooden_product TINYINT(1) NOT NULL DEFAULT 0 COMMENT '木制品权威判定快照;1时订单必须商检', 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 '平台内唯一仓库编码', internal_code VARCHAR(16) NULL COMMENT 'FBA内部编号,仅FBA仓可配置', warehouse_name VARCHAR(120) NOT NULL COMMENT '仓库名称', warehouse_type VARCHAR(32) NOT NULL DEFAULT 'DOMESTIC' COMMENT '公共仓库类型:DOMESTIC国内仓、FBA亚马逊仓、OVERSEAS普通海外仓、OVERSEAS_PARTNER海外合作仓', 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'; SET @warehouse_internal_code_ddl = IF( EXISTS( SELECT 1 FROM information_schema.columns WHERE table_schema = 'baiyue_warehouse' AND table_name = 'wh_warehouse' AND column_name = 'internal_code' ), 'SELECT 1', 'ALTER TABLE wh_warehouse ADD COLUMN internal_code VARCHAR(16) NULL COMMENT ''FBA内部编号,仅FBA仓可配置'' AFTER warehouse_code' ); PREPARE warehouse_internal_code_statement FROM @warehouse_internal_code_ddl; EXECUTE warehouse_internal_code_statement; DEALLOCATE PREPARE warehouse_internal_code_statement; 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 '往来单位类型:CARRIER、SHIPPER或CONSIGNEE', carrier_code VARCHAR(16) NULL COMMENT '船司标准编码:MATSON、SML、ZIM、COSCO、EMC、HEDE或OTHER;仅CARRIER必填', 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), CONSTRAINT chk_wh_partner_type CHECK (partner_type IN ('CARRIER','SHIPPER','CONSIGNEE')), CONSTRAINT chk_wh_partner_carrier_code CHECK (carrier_code IS NULL OR carrier_code IN ('MATSON','SML','ZIM','COSCO','EMC','HEDE','OTHER')) ) 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 '稳定业务类型:CARRIER船公司、SHIPPER发货方、CONSIGNEE收货方', PRIMARY KEY(partner_id,partner_type), KEY idx_wh_partner_type_scope(tenant_id,data_scope_id,partner_type,partner_id), CONSTRAINT chk_wh_partner_type_value CHECK (partner_type IN ('CARRIER','SHIPPER','CONSIGNEE')) ) 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,'CAR-001','示例船公司','CARRIER','张三','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,'CARRIER'), (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) 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) 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) 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) 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), (5102026, 0, 'PLATFORM', 'BUTTON', 5102010, 'platform:order:export', '导出订单列表', NULL, NULL, 120, '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, 5102026) 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, 5102026) ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id); INSERT INTO iam_membership_role (membership_id, role_id, tenant_id) VALUES (3000001, 4000001, 0) 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); -- 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%' OR permission_code = 'platform:download: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) 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; -- Global merchant permission template used by every formally onboarded customer. 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 (5300000, 0, 'MERCHANT', 'MENU', 0, 'merchant:enterprise', '企业管理', '/enterprise', NULL, 10, 'ACTIVE', 0), (5301000, 0, 'MERCHANT', 'MENU', 0, 'merchant:order', '头程订单', '/first-leg-orders', NULL, 20, 'ACTIVE', 0), (5302000, 0, 'MERCHANT', 'MENU', 0, 'merchant:master-data', '基础资料', '/master-data', NULL, 30, 'ACTIVE', 0), (5300010, 0, 'MERCHANT', 'MENU', 5300000, 'merchant:profile:view', '企业资料', '/enterprise/profile', NULL, 10, 'ACTIVE', 0), (5300020, 0, 'MERCHANT', 'MENU', 5300000, 'merchant:member:list', '企业成员', '/enterprise/members', NULL, 20, 'ACTIVE', 0), (5300030, 0, 'MERCHANT', 'MENU', 5300000, 'merchant:role:list', '角色管理', '/enterprise/roles', NULL, 30, 'ACTIVE', 0), (5301010, 0, 'MERCHANT', 'MENU', 5301000, 'merchant:order:list', '我的订单', '/first-leg-orders/list', NULL, 10, 'ACTIVE', 0), (5301020, 0, 'MERCHANT', 'MENU', 5301000, 'merchant:order:create-page', '新建订单', '/first-leg-orders/create', NULL, 20, 'ACTIVE', 0), (5302010, 0, 'MERCHANT', 'MENU', 5302000, 'merchant:warehouse:list', '收货仓选择', '/master-data/warehouses', NULL, 10, 'ACTIVE', 0), (5302020, 0, 'MERCHANT', 'MENU', 5302000, 'merchant:counterparty:list', '往来单位', '/master-data/counterparties', NULL, 20, 'ACTIVE', 0), (5300011, 0, 'MERCHANT', 'BUTTON', 5300010, 'merchant:profile:edit', '编辑企业资料', NULL, NULL, 10, 'ACTIVE', 0), (5300012, 0, 'MERCHANT', 'BUTTON', 5300010, 'merchant:qualification:upload', '上传企业资质', NULL, NULL, 20, 'ACTIVE', 0), (5300021, 0, 'MERCHANT', 'BUTTON', 5300020, 'merchant:member:create', '新增成员', NULL, NULL, 10, 'ACTIVE', 0), (5300022, 0, 'MERCHANT', 'BUTTON', 5300020, 'merchant:member:grant-role', '分配成员角色', NULL, NULL, 20, 'ACTIVE', 0), (5300031, 0, 'MERCHANT', 'BUTTON', 5300030, 'merchant:role:create', '新增角色', NULL, NULL, 10, 'ACTIVE', 0), (5300032, 0, 'MERCHANT', 'BUTTON', 5300030, 'merchant:role:grant', '角色授权', NULL, NULL, 20, 'ACTIVE', 0), (5301011, 0, 'MERCHANT', 'BUTTON', 5301010, 'merchant:order:view', '查看订单', NULL, NULL, 10, 'ACTIVE', 0), (5301012, 0, 'MERCHANT', 'BUTTON', 5301010, 'merchant:order:cancel', '取消订单', NULL, NULL, 20, 'ACTIVE', 0), (5301021, 0, 'MERCHANT', 'BUTTON', 5301020, 'merchant:order:create', '创建草稿', NULL, NULL, 10, 'ACTIVE', 0), (5301022, 0, 'MERCHANT', 'BUTTON', 5301020, 'merchant:order:edit', '编辑草稿', NULL, NULL, 20, 'ACTIVE', 0), (5301023, 0, 'MERCHANT', 'BUTTON', 5301020, 'merchant:order:submit', '提交订单', NULL, NULL, 30, 'ACTIVE', 0), (5301024, 0, 'MERCHANT', 'BUTTON', 5301020, 'merchant:order:quick-create', '快速下单', NULL, NULL, 40, 'ACTIVE', 0) 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 (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 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), (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 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 = VALUES(status), built_in = 1, deleted = VALUES(deleted); 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'"); CALL phase1_add_column('ord_order_cargo', 'wooden_product', "TINYINT(1) NOT NULL DEFAULT 0 COMMENT '木制品权威判定快照;1时订单必须商检'"); 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', 'carrier_code', 'VARCHAR(16) NULL COMMENT ''船司标准编码:MATSON、SML、ZIM、COSCO、EMC、HEDE或OTHER;仅CARRIER必填'' AFTER partner_type'); CALL add_column_if_missing('wh_partner', 'contact_name', 'VARCHAR(64) NOT NULL DEFAULT '''' AFTER carrier_code'); 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'); UPDATE wh_partner SET carrier_code = 'OTHER' WHERE id = 191000000000000001 AND partner_type = 'CARRIER' AND carrier_code IS NULL; -- 该表在旧库中不存在时先由 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 '托盘颜色', 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_warehouse (tenant_id, warehouse_id, deleted, pallet_code), KEY idx_pallet_type (tenant_id, warehouse_id, pallet_type, capacity_cbm, 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; -- 托盘是可复用主数据,不再用状态或活动唯一约束模拟库存资源。 SET @sql = IF((SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = 'wh_pallet' AND index_name = 'idx_pallet_available') > 0, 'ALTER TABLE wh_pallet DROP INDEX idx_pallet_available', 'SELECT 1'); PREPARE statement FROM @sql; EXECUTE statement; DEALLOCATE PREPARE statement; SET @sql = IF((SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = 'wh_pallet' AND index_name = 'idx_pallet_type') > 0, 'ALTER TABLE wh_pallet DROP INDEX idx_pallet_type', 'SELECT 1'); PREPARE statement FROM @sql; EXECUTE statement; DEALLOCATE PREPARE statement; SET @sql = IF((SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'wh_pallet' AND column_name = 'status') > 0, 'ALTER TABLE wh_pallet DROP COLUMN status', 'SELECT 1'); PREPARE statement FROM @sql; EXECUTE statement; DEALLOCATE PREPARE statement; SET @sql = IF((SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = 'wh_pallet' AND index_name = 'idx_pallet_warehouse') = 0, 'ALTER TABLE wh_pallet ADD KEY idx_pallet_warehouse (tenant_id, warehouse_id, deleted, pallet_code)', 'SELECT 1'); PREPARE statement FROM @sql; EXECUTE statement; DEALLOCATE PREPARE statement; SET @sql = IF((SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = 'wh_pallet' AND index_name = 'idx_pallet_type') = 0, 'ALTER TABLE wh_pallet ADD KEY idx_pallet_type (tenant_id, warehouse_id, pallet_type, capacity_cbm, deleted)', 'SELECT 1'); PREPARE statement FROM @sql; EXECUTE statement; DEALLOCATE PREPARE statement; 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 '普通库位活动预占唯一标记', 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), 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 '普通库位活动占用唯一标记', 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), 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, '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), 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; 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_occupancy', 'active_location_occupancy', 'TINYINT GENERATED ALWAYS AS (CASE WHEN occupancy_status = ''OCCUPIED'' AND shared_overflow = 0 THEN 1 ELSE NULL END) STORED COMMENT ''普通库位活动占用唯一标记''' ); 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_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_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((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; SET @sql = IF((SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = @schema_name AND table_name = 'wh_location_reservation' AND column_name = 'active_pallet_reservation') > 0, 'ALTER TABLE wh_location_reservation DROP COLUMN active_pallet_reservation', 'SELECT 1'); PREPARE statement FROM @sql; EXECUTE statement; DEALLOCATE PREPARE statement; SET @sql = IF((SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = @schema_name AND table_name = 'wh_location_occupancy' AND column_name = 'active_pallet_occupancy') > 0, 'ALTER TABLE wh_location_occupancy DROP COLUMN 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; SET @expected_arrival_at_exists := ( SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'ord_order' AND column_name = 'expected_arrival_at' ); SET @expected_arrival_at_sql := IF(@expected_arrival_at_exists = 0, 'ALTER TABLE ord_order ADD COLUMN expected_arrival_at DATETIME(3) NULL COMMENT ''客户申报预计到货时间'' AFTER expected_ship_date', 'SELECT 1'); PREPARE expected_arrival_at_statement FROM @expected_arrival_at_sql; EXECUTE expected_arrival_at_statement; DEALLOCATE PREPARE expected_arrival_at_statement; SET @channel_changed_exists := ( SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'ord_order' AND column_name = 'channel_changed' ); SET @channel_changed_sql := IF(@channel_changed_exists = 0, 'ALTER TABLE ord_order ADD COLUMN channel_changed TINYINT(1) NOT NULL DEFAULT 0 COMMENT ''订单历史上是否由平台变更过渠道'' AFTER channel_name', 'SELECT 1'); PREPARE channel_changed_statement FROM @channel_changed_sql; EXECUTE channel_changed_statement; DEALLOCATE PREPARE channel_changed_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_inbound_code' ); SET @inbound_code_index_sql := IF(@inbound_code_index_exists = 0, 'ALTER TABLE ord_order ADD UNIQUE KEY uk_order_inbound_code (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; SET @legacy_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 @legacy_inbound_code_index_sql := IF(@legacy_inbound_code_index_exists > 0, 'ALTER TABLE ord_order DROP INDEX uk_order_tenant_inbound_code', 'SELECT 1'); PREPARE legacy_inbound_code_index_statement FROM @legacy_inbound_code_index_sql; EXECUTE legacy_inbound_code_index_statement; DEALLOCATE PREPARE legacy_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待测量、PREPARING草稿预入库、MEASURING测量中、MEASURE_PENDING待审核、MEASURE_REJECTED测量驳回、WAIT_PRICING待计费、FEE_CONFIRMING费用确认中、INBOUNDING入库收口中、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 '业务类型', fulfillment_type VARCHAR(32) NOT NULL DEFAULT 'STANDARD' COMMENT '履约类型:STANDARD普通头程、ONE_CLICK_DROPSHIP一键代发', lx_code 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 '买单或退税模式', 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(1000) NULL COMMENT '当前扣货理由快照', last_overseas_hold_version BIGINT UNSIGNED 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_code (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), KEY idx_inbound_smart_loading (warehouse_id, process_status, channel_code, expected_arrival_at, id), CONSTRAINT fk_inbound_task FOREIGN KEY (task_id) REFERENCES fl_fulfillment_task (id) ) ENGINE=InnoDB COMMENT='二期头程入库单聚合根和列表查询快照'; SET @inbound_fulfillment_type_sql = IF( EXISTS( SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'fl_inbound_order' AND column_name = 'fulfillment_type' ), 'SELECT 1', 'ALTER TABLE fl_inbound_order ADD COLUMN fulfillment_type VARCHAR(32) NULL COMMENT ''履约类型:STANDARD普通头程、ONE_CLICK_DROPSHIP一键代发'' AFTER business_type' ); PREPARE inbound_fulfillment_type_stmt FROM @inbound_fulfillment_type_sql; EXECUTE inbound_fulfillment_type_stmt; DEALLOCATE PREPARE inbound_fulfillment_type_stmt; UPDATE fl_inbound_order SET fulfillment_type = 'STANDARD' WHERE fulfillment_type IS NULL OR fulfillment_type = ''; ALTER TABLE fl_inbound_order MODIFY COLUMN fulfillment_type VARCHAR(32) NOT NULL DEFAULT 'STANDARD' COMMENT '履约类型:STANDARD普通头程、ONE_CLICK_DROPSHIP一键代发'; SET @inbound_global_code_index_exists := ( SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = 'fl_inbound_order' AND index_name = 'uk_inbound_code' ); SET @inbound_global_code_index_sql := IF(@inbound_global_code_index_exists = 0, 'ALTER TABLE fl_inbound_order ADD UNIQUE KEY uk_inbound_code (inbound_code)', 'SELECT 1'); PREPARE inbound_global_code_index_statement FROM @inbound_global_code_index_sql; EXECUTE inbound_global_code_index_statement; DEALLOCATE PREPARE inbound_global_code_index_statement; SET @inbound_legacy_code_index_exists := ( SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = 'fl_inbound_order' AND index_name = 'uk_inbound_tenant_code' ); SET @inbound_legacy_code_index_sql := IF(@inbound_legacy_code_index_exists > 0, 'ALTER TABLE fl_inbound_order DROP INDEX uk_inbound_tenant_code', 'SELECT 1'); PREPARE inbound_legacy_code_index_statement FROM @inbound_legacy_code_index_sql; EXECUTE inbound_legacy_code_index_statement; DEALLOCATE PREPARE inbound_legacy_code_index_statement; UPDATE baiyue_order.ord_order o 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 = i.inbound_code 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 AND i.inbound_code IS NOT NULL AND TRIM(i.inbound_code) <> ''; -- 非草稿订单若既无订单入仓码也无 Firstleg 权威入仓码,禁止用内部 ID 伪造业务键。 DROP PROCEDURE IF EXISTS assert_submitted_order_inbound_code; DELIMITER $$ CREATE PROCEDURE assert_submitted_order_inbound_code() BEGIN IF EXISTS ( SELECT 1 FROM baiyue_order.ord_order o 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 OR TRIM(o.inbound_code) = '') ) THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '存在已提交订单缺少权威入仓码,请先修复订单/Firstleg业务事实后重跑'; END IF; END$$ DELIMITER ; CALL assert_submitted_order_inbound_code(); DROP PROCEDURE IF EXISTS assert_submitted_order_inbound_code; -- 从未提交的草稿或由草稿直接取消的订单不应拥有入仓码。 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), KEY idx_measure_inbound_status_id (tenant_id, inbound_id, status, id), 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', charge_rule_id BIGINT UNSIGNED NULL COMMENT '该测量明细绑定的客户计费规则ID', coefficient_mode VARCHAR(16) NULL COMMENT '箱规计算策略快照:ACTUAL、ADD或MULTIPLY', 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', manual_outer_length_cm DECIMAL(12,3) NULL COMMENT '仓库人工外箱规长度计算基数cm', manual_outer_width_cm DECIMAL(12,3) NULL COMMENT '仓库人工外箱规宽度计算基数cm', manual_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), KEY idx_measure_item_candidate (tenant_id, measurement_id, inbound_detail_id), 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 '货物名称快照', sku_code VARCHAR(128) NULL COMMENT '箱唛绑定的SKU快照;二维码内容不变', 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开始的稳定箱下标', label_version INT UNSIGNED NOT NULL DEFAULT 1 COMMENT '内部兼容批次字段,固定为1且不进入箱码', batch_status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE' COMMENT '批次状态:ACTIVE有效、VOID已作废', cargo_fingerprint CHAR(64) NULL COMMENT '影响标签字段的SHA-256指纹', voided_at DATETIME(3) NULL COMMENT '批次作废时间', 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, label_version, carton_index), KEY idx_carton_label_active (tenant_id, inbound_id, batch_status, label_version), 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 不会补列。 DROP PROCEDURE IF EXISTS unified_carton_label_add_column; DELIMITER $$ CREATE PROCEDURE unified_carton_label_add_column(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='fl_inbound_carton_label' AND column_name=p_column) THEN SET @ddl = CONCAT('ALTER TABLE fl_inbound_carton_label ADD COLUMN `', p_column, '` ', p_definition); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt; END IF; END$$ DELIMITER ; CALL unified_carton_label_add_column('label_version', 'INT UNSIGNED NOT NULL DEFAULT 1 COMMENT ''内部兼容批次字段,固定为1且不进入箱码'' AFTER carton_index'); CALL unified_carton_label_add_column('sku_code', 'VARCHAR(128) NULL COMMENT ''箱唛绑定的SKU快照;二维码内容不变'' AFTER cargo_name'); CALL unified_carton_label_add_column('batch_status', 'VARCHAR(16) NOT NULL DEFAULT ''ACTIVE'' COMMENT ''批次状态:ACTIVE有效、VOID已作废'' AFTER label_version'); CALL unified_carton_label_add_column('cargo_fingerprint', 'CHAR(64) NULL COMMENT ''影响标签字段的SHA-256指纹'' AFTER batch_status'); CALL unified_carton_label_add_column('voided_at', 'DATETIME(3) NULL COMMENT ''批次作废时间'' AFTER cargo_fingerprint'); DROP PROCEDURE IF EXISTS unified_carton_label_add_column; UPDATE fl_inbound_carton_label SET label_version=1, batch_status='ACTIVE' WHERE label_version IS NULL OR batch_status IS NULL; DELETE FROM fl_inbound_carton_label WHERE batch_status='VOID'; UPDATE fl_inbound_carton_label SET label_version=1, box_code=CONCAT(inbound_code, '#', total_carton_count, '-', carton_index) WHERE batch_status='ACTIVE'; SET @ddl = IF( EXISTS(SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='fl_inbound_carton_label' AND index_name='uk_carton_label_index'), 'ALTER TABLE fl_inbound_carton_label DROP INDEX uk_carton_label_index', '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_inbound_carton_label' AND index_name='uk_carton_label_index'), 'ALTER TABLE fl_inbound_carton_label ADD UNIQUE KEY uk_carton_label_index (tenant_id,inbound_id,label_version,carton_index)', '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_inbound_carton_label' AND index_name='idx_carton_label_active'), 'ALTER TABLE fl_inbound_carton_label ADD KEY idx_carton_label_active (tenant_id,inbound_id,batch_status,label_version)', 'SELECT 1'); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt; 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', pallet_id BIGINT UNSIGNED NOT NULL COMMENT 'Warehouse托盘ID', pallet_code VARCHAR(50) NOT NULL COMMENT '托盘编码快照', pallet_type VARCHAR(32) NOT NULL COMMENT '托盘类型快照', pallet_capacity_cbm DECIMAL(18,4) NOT NULL COMMENT '托盘容量CBM快照', pallet_tare_weight_kg DECIMAL(18,3) NOT NULL COMMENT '托盘皮重KG快照', 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 '人工修正后的单位', gross_weight_kg DECIMAL(18,3) NULL COMMENT '确认时统一换算后的含托盘毛重kg', net_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), KEY idx_weight_inbound_pallet (tenant_id, inbound_id, pallet_id), 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 '变更后单位', gross_weight_kg DECIMAL(18,3) NULL COMMENT '确认时统一换算后的含托盘毛重kg', net_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', measurement_id BIGINT UNSIGNED NULL COMMENT '费用绑定的测量批次ID快照', measurement_source VARCHAR(32) NULL COMMENT '计费尺寸来源快照:OUTER为外箱规数据', 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 NULL COMMENT '可选业务客户快照;平台公共渠道计费不依赖该字段', 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 '换算后的重量或体积', baseline_chargeable_quantity DECIMAL(18,6) NOT NULL COMMENT '系统按权威测量与生效报价计算的原始计费量', tolerance_percent DECIMAL(5,2) NOT NULL DEFAULT 10.00 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已计算、CONFIRMED已确认、SENT已发送、SUPERSEDED已被新版本替代、VOIDED已作废', 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 '发送客户时间', customer_confirmed_by BIGINT UNSIGNED NULL COMMENT '客户确认人用户ID', customer_confirmed_by_name VARCHAR(64) NULL COMMENT '客户确认人名称快照', customer_confirmed_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='入库头程费用计算和确认快照'; ALTER TABLE fl_inbound_fee MODIFY COLUMN customer_id BIGINT UNSIGNED NULL COMMENT '可选业务客户快照;平台公共渠道计费不依赖该字段'; SET @fee_baseline_quantity_sql = IF( EXISTS(SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='fl_inbound_fee' AND column_name='baseline_chargeable_quantity'), 'SELECT 1', 'ALTER TABLE fl_inbound_fee ADD COLUMN baseline_chargeable_quantity DECIMAL(18,6) NULL COMMENT ''系统按权威测量与生效报价计算的原始计费量'' AFTER converted_quantity' ); PREPARE fee_baseline_quantity_stmt FROM @fee_baseline_quantity_sql; EXECUTE fee_baseline_quantity_stmt; DEALLOCATE PREPARE fee_baseline_quantity_stmt; UPDATE fl_inbound_fee SET baseline_chargeable_quantity = chargeable_quantity WHERE baseline_chargeable_quantity IS NULL; ALTER TABLE fl_inbound_fee MODIFY COLUMN baseline_chargeable_quantity DECIMAL(18,6) NOT NULL COMMENT '系统按权威测量与生效报价计算的原始计费量'; SET @fee_tolerance_percent_sql = IF( EXISTS(SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='fl_inbound_fee' AND column_name='tolerance_percent'), 'SELECT 1', 'ALTER TABLE fl_inbound_fee ADD COLUMN tolerance_percent DECIMAL(5,2) NOT NULL DEFAULT 10.00 COMMENT ''人工调整允许的最大偏差百分比,边界包含'' AFTER baseline_chargeable_quantity' ); PREPARE fee_tolerance_percent_stmt FROM @fee_tolerance_percent_sql; EXECUTE fee_tolerance_percent_stmt; DEALLOCATE PREPARE fee_tolerance_percent_stmt; 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 '入库单内分配序号', block_id BIGINT UNSIGNED NOT NULL COMMENT 'Warehouse整票连续库位块ID', slot_no INT UNSIGNED NOT NULL COMMENT '连续块内稳定槽位序号', weight_result_id BIGINT UNSIGNED NULL COMMENT '已绑定称重结果ID;预留空槽为空', pallet_sequence INT UNSIGNED 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 NULL COMMENT '仓库服务托盘ID;预留空槽为空', pallet_code VARCHAR(50) NULL COMMENT '托盘编码快照;预留空槽为空', pallet_capacity_cbm DECIMAL(18,4) 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 '库位编码快照', shared_overflow TINYINT(1) NOT NULL DEFAULT 0 COMMENT '普通连续位不足时是否使用共享A0', reservation_id BIGINT UNSIGNED NOT NULL COMMENT '仓库预占资源唯一ID,A0共享场景用于区分槽位', slot_status VARCHAR(24) NOT NULL DEFAULT 'RESERVED_EMPTY' COMMENT '槽位状态:RESERVED_EMPTY、BOUND、TRANSFERRED、RELEASED', 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_slot (tenant_id, inbound_id, block_id, slot_no), UNIQUE KEY uk_pallet_allocation_weight (tenant_id, inbound_id, weight_result_id), KEY idx_allocation_location (tenant_id, warehouse_id, location_id, allocation_status), KEY idx_allocation_reservation (reservation_id), CONSTRAINT fk_allocation_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id) ) ENGINE=InnoDB COMMENT='头程侧托盘、库位和仓库预占结果引用'; SET @sql = IF((SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = 'fl_inbound_pallet_allocation' AND index_name = 'uk_pallet_allocation_pallet') > 0, 'ALTER TABLE fl_inbound_pallet_allocation DROP INDEX uk_pallet_allocation_pallet', 'SELECT 1'); PREPARE statement FROM @sql; EXECUTE statement; DEALLOCATE PREPARE statement; 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', 'charge_rule_id', 'BIGINT UNSIGNED NULL COMMENT ''该测量明细绑定的客户计费规则ID'' AFTER volume_weight_kg'); CALL phase2_measure_add_column('fl_inbound_measurement_item', 'coefficient_mode', 'VARCHAR(16) NULL COMMENT ''系数模式快照:ADD或MULTIPLY'' AFTER charge_rule_id'); 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', 'manual_outer_length_cm', 'DECIMAL(12,3) NULL COMMENT ''仓库人工外箱规长度计算基数cm'' AFTER outer_height_cm'); CALL phase2_measure_add_column('fl_inbound_measurement_item', 'manual_outer_width_cm', 'DECIMAL(12,3) NULL COMMENT ''仓库人工外箱规宽度计算基数cm'' AFTER manual_outer_length_cm'); CALL phase2_measure_add_column('fl_inbound_measurement_item', 'manual_outer_height_cm', 'DECIMAL(12,3) NULL COMMENT ''仓库人工外箱规高度计算基数cm'' AFTER manual_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'); CALL phase2_measure_add_column('fl_inbound_weight_result', 'pallet_id', 'BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT ''Warehouse托盘ID'' AFTER file_id'); CALL phase2_measure_add_column('fl_inbound_weight_result', 'pallet_code', 'VARCHAR(50) NOT NULL DEFAULT '''' COMMENT ''托盘编码快照'' AFTER pallet_id'); CALL phase2_measure_add_column('fl_inbound_weight_result', 'pallet_type', 'VARCHAR(32) NOT NULL DEFAULT '''' COMMENT ''托盘类型快照'' AFTER pallet_code'); CALL phase2_measure_add_column('fl_inbound_weight_result', 'pallet_capacity_cbm', 'DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT ''托盘容量CBM快照'' AFTER pallet_type'); CALL phase2_measure_add_column('fl_inbound_weight_result', 'pallet_tare_weight_kg', 'DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT ''托盘皮重KG快照'' AFTER pallet_capacity_cbm'); CALL phase2_measure_add_column('fl_inbound_weight_result', 'gross_weight_kg', 'DECIMAL(18,3) NULL COMMENT ''确认时统一换算后的含托盘毛重kg'' AFTER adjusted_weight_unit'); CALL phase2_measure_add_column('fl_inbound_weight_result', 'net_weight_kg', 'DECIMAL(18,3) NULL COMMENT ''确认毛重扣除托盘皮重后的货物净重kg'' AFTER gross_weight_kg'); CALL phase2_measure_add_column('fl_inbound_weight_revision', 'gross_weight_kg', 'DECIMAL(18,3) NULL COMMENT ''确认时统一换算后的含托盘毛重kg'' AFTER after_weight_unit'); CALL phase2_measure_add_column('fl_inbound_weight_revision', 'net_weight_kg', 'DECIMAL(18,3) NULL COMMENT ''确认毛重扣除托盘皮重后的货物净重kg'' AFTER gross_weight_kg'); 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; SET @phase2_measure_sql = IF((SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = 'fl_inbound_weight_result' AND index_name = 'idx_weight_inbound_pallet') = 0, 'ALTER TABLE fl_inbound_weight_result ADD KEY idx_weight_inbound_pallet (tenant_id, inbound_id, pallet_id)', 'SELECT 1'); PREPARE phase2_measure_statement FROM @phase2_measure_sql; EXECUTE phase2_measure_statement; DEALLOCATE PREPARE phase2_measure_statement; SET @phase2_measure_sql = IF((SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'fl_inbound_weight_result' AND column_name = 'final_weight_kg') > 0, 'ALTER TABLE fl_inbound_weight_result DROP COLUMN final_weight_kg', 'SELECT 1'); PREPARE phase2_measure_statement FROM @phase2_measure_sql; EXECUTE phase2_measure_statement; DEALLOCATE PREPARE phase2_measure_statement; SET @phase2_measure_sql = IF((SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'fl_inbound_weight_revision' AND column_name = 'final_weight_kg') > 0, 'ALTER TABLE fl_inbound_weight_revision DROP COLUMN final_weight_kg', 'SELECT 1'); PREPARE phase2_measure_statement FROM @phase2_measure_sql; EXECUTE phase2_measure_statement; DEALLOCATE PREPARE phase2_measure_statement; 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 '变更后单位', gross_weight_kg DECIMAL(18,3) NULL COMMENT '确认时统一换算后的含托盘毛重kg', net_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_customer_charge_rule', 'length_factor', "DECIMAL(18,6) NOT NULL DEFAULT 1 COMMENT '长度计算系数' AFTER conversion_factor"); CALL phase2_fba_add_column('fl_customer_charge_rule', 'width_factor', "DECIMAL(18,6) NOT NULL DEFAULT 1 COMMENT '宽度计算系数' AFTER length_factor"); CALL phase2_fba_add_column('fl_customer_charge_rule', 'height_factor', "DECIMAL(18,6) NOT NULL DEFAULT 1 COMMENT '高度计算系数' AFTER width_factor"); CALL phase2_fba_add_column('fl_customer_charge_rule', 'default_rule', "TINYINT(1) NOT NULL DEFAULT 0 COMMENT '客户默认生效规则' AFTER status"); 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', 'measurement_id', "BIGINT UNSIGNED NULL COMMENT '费用绑定的测量批次ID快照' AFTER inbound_id"); CALL phase2_fba_add_column('fl_inbound_fee', 'measurement_source', "VARCHAR(32) NULL COMMENT '计费尺寸来源快照:OUTER为外箱规数据' AFTER measurement_id"); 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_fee', 'customer_confirmed_by', "BIGINT UNSIGNED NULL COMMENT '客户确认人用户ID' AFTER sent_at"); CALL phase2_fba_add_column('fl_inbound_fee', 'customer_confirmed_by_name', "VARCHAR(64) NULL COMMENT '客户确认人名称快照' AFTER customer_confirmed_by"); CALL phase2_fba_add_column('fl_inbound_fee', 'customer_confirmed_at', "DATETIME(3) NULL COMMENT '客户确认费用时间;非空后费用版本锁定' AFTER customer_confirmed_by_name"); 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 '上传名称别名数组', inbound_code_prefix VARCHAR(6) NOT NULL COMMENT '普通目的仓进仓编码前缀;包税与不包税共用', overseas_inbound_code_prefix VARCHAR(6) 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='平台公共船期价格固定渠道目录'; DROP PROCEDURE IF EXISTS price_channel_add_column; DELIMITER $$ CREATE PROCEDURE price_channel_add_column(IN p_column 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_price_channel_catalog' AND COLUMN_NAME = p_column ) THEN SET @ddl = CONCAT('ALTER TABLE baiyue_firstleg.fl_price_channel_catalog ADD COLUMN ', p_definition); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt; END IF; END$$ DELIMITER ; CALL price_channel_add_column('inbound_code_prefix', "inbound_code_prefix VARCHAR(6) NULL COMMENT '普通目的仓进仓编码前缀;包税与不包税共用' AFTER aliases_json"); CALL price_channel_add_column('overseas_inbound_code_prefix', "overseas_inbound_code_prefix VARCHAR(6) NULL COMMENT '海外仓中转进仓编码前缀;包税与不包税共用,为空时使用普通前缀' AFTER inbound_code_prefix"); DROP PROCEDURE IF EXISTS price_channel_add_column; 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, inbound_code_prefix, overseas_inbound_code_prefix, status) VALUES (1, 'MATSON_CLX_TRUCK', '美森正班海卡', 'FBA_WAREHOUSE_RATE', '美森正班海卡', JSON_ARRAY('美森正班海卡'), 'ZBYHK', 'ZBYHWK', 'ACTIVE'), (2, 'MATSON_MAX_TRUCK', '美森加班海卡', 'FBA_WAREHOUSE_RATE', '美森加班海卡', JSON_ARRAY('美森加班海卡'), 'BYHK', 'BYHWK', 'ACTIVE'), (3, 'EXX_TRUCK', 'EXX海卡', 'FBA_WAREHOUSE_RATE', 'EXX海卡', JSON_ARRAY('EXX海卡'), 'EXXHK', 'EXXHWK', 'ACTIVE'), (4, 'HEDE_TRUCK', '合德海卡', 'FBA_WAREHOUSE_RATE', '合德海卡', JSON_ARRAY('合德海卡'), 'HDHK', 'HDHWK', 'ACTIVE'), (5, 'EXX_RAIL_NY', '纽约EXX海铁', 'FBA_WAREHOUSE_RATE', '纽约EXX海铁', JSON_ARRAY('纽约EXX海铁'), 'NYEXX', NULL, 'ACTIVE'), (6, 'OA_LA_TRUCK', '洛杉矶普船海卡', 'FBA_WAREHOUSE_RATE', '洛杉矶普船海卡', JSON_ARRAY('洛杉矶普船海卡'), 'PCHK', 'PCHWK', 'ACTIVE'), (7, 'OA_LA_FULL_TRUCK', '洛杉矶普船全卡', 'FBA_WAREHOUSE_RATE', '洛杉矶普船全卡', JSON_ARRAY('洛杉矶普船全卡'), 'PCKK', NULL, 'ACTIVE'), (8, 'OA_CHICAGO_RAIL', '芝加哥普船海铁', 'FBA_WAREHOUSE_RATE', '芝加哥普船海铁', JSON_ARRAY('芝加哥普船海铁'), 'CPHT', NULL, 'ACTIVE'), (9, 'OA_HOUSTON_TRUCK', '休斯顿普船海卡', 'FBA_WAREHOUSE_RATE', '休斯顿普船海卡', JSON_ARRAY('休斯顿普船海卡'), 'HPHK', NULL, 'ACTIVE'), (10, 'OA_NEW_YORK_TRUCK', '纽约普船海卡', 'FBA_WAREHOUSE_RATE', '纽约普船海卡', JSON_ARRAY('纽约普船海卡'), 'NPHK', 'NPHWK', 'ACTIVE'), (11, 'OA_SAVANNAH_TRUCK', '萨凡纳普船海卡', 'FBA_WAREHOUSE_RATE', '萨凡纳普船海卡', JSON_ARRAY('萨凡纳普船海卡'), 'SPHK', NULL, 'ACTIVE'), (12, 'MATSON_CLX_PARCEL', '美森正班海派', 'REGION_WEIGHT_TIER', '海派', JSON_ARRAY('美森正班海派'), 'ZBYHP', NULL, 'ACTIVE'), (13, 'MATSON_MAX_PARCEL', '美森加班海派', 'REGION_WEIGHT_TIER', '海派', JSON_ARRAY('美森加班海派'), 'BYHP', NULL, 'ACTIVE'), (14, 'HEDE_PARCEL', '合德海派', 'REGION_WEIGHT_TIER', '海派', JSON_ARRAY('合德海派'), 'HDHP', NULL, 'ACTIVE'), (15, 'OA_PARCEL', '普船海派', 'REGION_WEIGHT_TIER', '海派', JSON_ARRAY('普船海派'), 'PCHP', NULL, '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), inbound_code_prefix = VALUES(inbound_code_prefix), overseas_inbound_code_prefix = VALUES(overseas_inbound_code_prefix), 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), (5102195, 0, 'PLATFORM', 'BUTTON', 5102030, 'platform:firstleg:inbound:warehouse-suggestion:edit', '维护仓库建议测量数据', NULL, NULL, 27, '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, 5102195) 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, 5102195) 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:warehouse-suggestion:edit' WHERE role.tenant_id = 0 AND UPPER(role.role_code) = 'PLATFORM_CUSTOM_ADMIN' 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), (5102069, 0, 'PLATFORM', 'BUTTON', 5102030, 'firstleg:inbound:confirm-send-fee', '确认并推送客户费用', NULL, NULL, 90, '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 5102069 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, 5102069) 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 = 'firstleg:inbound:confirm-send-fee' WHERE role.tenant_id = 0 AND UPPER(role.role_code) = 'PLATFORM_CUSTOM_ADMIN' 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_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_order', 'ord_customer_address'); 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', 'expected_arrival_at'); CALL unified_assert_column('baiyue_order', 'ord_order', 'channel_changed'); CALL unified_assert_column('baiyue_order', 'ord_order', 'inbound_code'); CALL unified_assert_column('baiyue_order', 'ord_order', 'fulfillment_type'); CALL unified_assert_column('baiyue_order', 'ord_order_cargo', 'wooden_product'); 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_order', 'fulfillment_type'); CALL unified_assert_column('baiyue_firstleg', 'fl_inbound_measurement_item', 'external_detail_no'); CALL unified_assert_column('baiyue_firstleg', 'fl_inbound_measurement_item', 'coefficient_mode'); 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_firstleg', 'fl_inbound_fee', 'baseline_chargeable_quantity'); CALL unified_assert_column('baiyue_firstleg', 'fl_inbound_fee', 'tolerance_percent'); 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:warehouse-suggestion:edit', '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:inbound:confirm-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 <> 32 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '统一迁移失败:二期 IAM 权限资源未达到 32 项'; 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')) <> 4 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 '费用类型名称', supplier_category VARCHAR(16) NULL COMMENT 'PAYABLE供应商类别约束;为空表示任意启用供应商', 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应付', supplier_id BIGINT UNSIGNED NULL COMMENT '应付Warehouse供应商ID快照;应收为空', supplier_name VARCHAR(200) NULL COMMENT '应付Warehouse供应商名称快照;应收为空', supplier_category VARCHAR(16) NULL COMMENT '费用项目供应商类别约束快照;为空表示任意启用供应商', 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) COMMENT ''记录创建时间'',', 'updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT ''最后更新时间'',', '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_document_sequence ( sequence_key VARCHAR(32) NOT NULL COMMENT '单据类型与北京时间日期组合键,例如CAB:20260818', current_value INT UNSIGNED NOT NULL COMMENT '该类型当日已分配的最大序号', PRIMARY KEY (sequence_key), CONSTRAINT chk_fl_document_sequence_value CHECK (current_value > 0) ) ENGINE=InnoDB COMMENT='头程履约业务单号日序列;生成YYMMDD加四位当日序号'; 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 '国内唯一权威真实集装箱号;ISO 6346标准格式;订舱关联可选写入、提柜后可原子补录、预装纠错命令可修改', cabinet_no VARCHAR(64) NOT NULL COMMENT '平台唯一内部柜号', source_type VARCHAR(24) NOT NULL DEFAULT 'SIMULATION' COMMENT '记录来源:SIMULATION内部模拟柜、OCEAN_BOOKING订舱真实柜', record_status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE' COMMENT '记录状态:ACTIVE可操作、MERGED已合并历史', merged_into_container_booking_id BIGINT UNSIGNED NULL COMMENT '真实柜被模拟柜吸收后的目标稳定柜次ID', 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', customer_id BIGINT UNSIGNED NULL COMMENT '已关联真实订舱指定的客户范围', customer_name VARCHAR(128) NULL COMMENT '已关联真实订舱客户名称快照', 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_id BIGINT UNSIGNED NULL COMMENT '码头拖车Supplier逻辑ID快照;历史数据无法推导时为空', 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 '实际到仓时间', pickup_code VARCHAR(64) NULL COMMENT 'PU数字码;按字符串保存以保留前导零', pickup_code_updated_by BIGINT UNSIGNED NULL COMMENT 'PU码最后填写人ID', pickup_code_updated_by_name VARCHAR(64) NULL COMMENT 'PU码最后填写人名称快照', pickup_code_updated_at DATETIME(3) NULL COMMENT 'PU码最后填写时间', 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), UNIQUE KEY uk_container_booking_container_no (container_no), KEY idx_container_booking_merge (record_status,merged_into_container_booking_id), KEY idx_container_booking_warehouse (warehouse_id, updated_at, id), KEY idx_container_booking_voyage (voyage_no, updated_at), CONSTRAINT chk_container_booking_source CHECK (source_type IN ('SIMULATION','OCEAN_BOOKING')), CONSTRAINT chk_container_booking_record_status CHECK (record_status IN ('ACTIVE','MERGED')), 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), CONSTRAINT chk_container_booking_pickup_code CHECK (pickup_code IS NULL OR pickup_code REGEXP '^[0-9]{1,64}$'), CONSTRAINT chk_container_booking_iso6346 CHECK (container_no IS NULL OR container_no REGEXP '^[A-Z]{3}[UJZ][0-9]{7}$') ) ENGINE=InnoDB COMMENT='平台货柜预订资料;预装状态唯一来源为装柜计划'; DROP PROCEDURE IF EXISTS phase3_container_real_unit_columns; DELIMITER $$ CREATE PROCEDURE phase3_container_real_unit_columns() BEGIN IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='fl_container_booking' AND column_name='source_type') THEN ALTER TABLE fl_container_booking ADD COLUMN source_type VARCHAR(24) NOT NULL DEFAULT 'SIMULATION' COMMENT '记录来源:SIMULATION内部模拟柜、OCEAN_BOOKING订舱真实柜' AFTER cabinet_no; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='fl_container_booking' AND column_name='record_status') THEN ALTER TABLE fl_container_booking ADD COLUMN record_status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE' COMMENT '记录状态:ACTIVE可操作、MERGED已合并历史' AFTER source_type; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='fl_container_booking' AND column_name='merged_into_container_booking_id') THEN ALTER TABLE fl_container_booking ADD COLUMN merged_into_container_booking_id BIGINT UNSIGNED NULL COMMENT '真实柜被模拟柜吸收后的目标稳定柜次ID' AFTER record_status; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='fl_container_booking' AND column_name='customer_id') THEN ALTER TABLE fl_container_booking ADD COLUMN customer_id BIGINT UNSIGNED NULL COMMENT '已关联真实订舱指定的客户范围' AFTER ocean_booking_requirement_id; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='fl_container_booking' AND column_name='customer_name') THEN ALTER TABLE fl_container_booking ADD COLUMN customer_name VARCHAR(128) NULL COMMENT '已关联真实订舱客户名称快照' AFTER customer_id; END IF; IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='fl_ocean_booking') THEN UPDATE fl_container_booking container_booking JOIN fl_ocean_booking ocean_booking ON ocean_booking.id=container_booking.ocean_booking_id SET container_booking.customer_id=COALESCE(container_booking.customer_id,ocean_booking.customer_id), container_booking.customer_name=COALESCE(container_booking.customer_name,ocean_booking.customer_name) WHERE container_booking.customer_id IS NULL OR container_booking.customer_name IS NULL; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='fl_container_booking' AND index_name='uk_container_booking_container_no') THEN ALTER TABLE fl_container_booking ADD UNIQUE KEY uk_container_booking_container_no (container_no); END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='fl_container_booking' AND index_name='idx_container_booking_merge') THEN ALTER TABLE fl_container_booking ADD KEY idx_container_booking_merge (record_status,merged_into_container_booking_id); END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.table_constraints WHERE constraint_schema=DATABASE() AND table_name='fl_container_booking' AND constraint_name='chk_container_booking_source') THEN ALTER TABLE fl_container_booking ADD CONSTRAINT chk_container_booking_source CHECK (source_type IN ('SIMULATION','OCEAN_BOOKING')); END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.table_constraints WHERE constraint_schema=DATABASE() AND table_name='fl_container_booking' AND constraint_name='chk_container_booking_record_status') THEN ALTER TABLE fl_container_booking ADD CONSTRAINT chk_container_booking_record_status CHECK (record_status IN ('ACTIVE','MERGED')); END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.table_constraints WHERE constraint_schema=DATABASE() AND table_name='fl_container_booking' AND constraint_name='chk_container_booking_iso6346') THEN ALTER TABLE fl_container_booking ADD CONSTRAINT chk_container_booking_iso6346 CHECK (container_no IS NULL OR container_no REGEXP '^[A-Z]{3}[UJZ][0-9]{7}$'); END IF; END$$ DELIMITER ; CALL phase3_container_real_unit_columns(); DROP PROCEDURE phase3_container_real_unit_columns; -- 触发器创建前先收敛旧库货柜供应商ID列;历史名称无法反推ID,保持NULL。 DROP PROCEDURE IF EXISTS phase3_container_supplier_snapshot_add_column; DELIMITER $$ CREATE PROCEDURE phase3_container_supplier_snapshot_add_column() BEGIN IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='fl_container_booking' AND column_name='terminal_trucking_company_id') THEN ALTER TABLE fl_container_booking ADD COLUMN terminal_trucking_company_id BIGINT UNSIGNED NULL COMMENT '码头拖车Supplier逻辑ID快照;历史数据无法推导时为空' AFTER max_weight_kg; END IF; END$$ DELIMITER ; CALL phase3_container_supplier_snapshot_add_column(); DROP PROCEDURE phase3_container_supplier_snapshot_add_column; 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装柜完成;建议须经人工确认', 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), 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_candidate (account_id, inbound_id, inbound_detail_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_loading_pallet_label ( id BIGINT UNSIGNED NOT NULL COMMENT '散货托盘标签ID(雪花ID)', loading_plan_id BIGINT UNSIGNED NOT NULL COMMENT '装柜计划ID', loading_plan_item_id BIGINT UNSIGNED NOT NULL COMMENT '标签归属装柜明细ID', account_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID快照', inbound_id BIGINT UNSIGNED NOT NULL COMMENT '入仓单ID快照', inbound_code VARCHAR(40) NOT NULL COMMENT '入仓码快照', container_no VARCHAR(64) NOT NULL COMMENT '真实集装箱号快照', destination_warehouse_code VARCHAR(64) NOT NULL COMMENT 'FBA仓编码快照', destination_warehouse_name VARCHAR(128) NULL COMMENT 'FBA仓名称快照', pallet_index INT UNSIGNED NOT NULL COMMENT '当前柜当前FBA仓内从1开始的序号', pallet_code VARCHAR(128) NOT NULL COMMENT '托盘码:集装箱号-FBA仓编码-三位序号', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '生成时间', PRIMARY KEY (id), UNIQUE KEY uk_loading_pallet_code (pallet_code), UNIQUE KEY uk_loading_pallet_index (loading_plan_id,destination_warehouse_code,pallet_index), KEY idx_loading_pallet_item (loading_plan_item_id,pallet_index), KEY idx_loading_pallet_inbound (account_id,inbound_id), CONSTRAINT fk_loading_pallet_plan FOREIGN KEY (loading_plan_id) REFERENCES fl_loading_plan(id), CONSTRAINT fk_loading_pallet_item FOREIGN KEY (loading_plan_item_id) REFERENCES fl_loading_plan_item(id), CONSTRAINT chk_loading_pallet_index CHECK (pallet_index BETWEEN 1 AND 999) ) ENGINE=InnoDB COMMENT='散货按柜号和FBA仓冻结的可打印托盘码'; 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目的港清关、DELIVERY_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', 'DELIVERY_ORDER')) ) ENGINE=InnoDB COMMENT='平台货柜清关与DO文件关联'; 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(24)' 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(24) NOT NULL DEFAULT 'LOADING' COMMENT 'PDA任务状态:LOADING、COMPLETING、COMPLETION_FAILED、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','COMPLETING','COMPLETION_FAILED','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拣货幂等和操作审计历史'; CREATE TABLE IF NOT EXISTS fl_loading_work_pallet ( id BIGINT UNSIGNED NOT NULL COMMENT '一件代发现场作业托盘ID', pick_task_id BIGINT UNSIGNED NOT NULL COMMENT 'PDA装柜拣货任务ID', loading_plan_id BIGINT UNSIGNED NOT NULL COMMENT '装柜计划ID快照', warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '作业仓库ID', warehouse_pallet_id BIGINT UNSIGNED NULL COMMENT '仓库实物托盘ID;未绑定仓库资源时为空', pallet_code VARCHAR(64) NOT NULL COMMENT '现场托盘号', open_operation_id VARCHAR(128) NOT NULL COMMENT '开托幂等操作号', seal_operation_id VARCHAR(128) NULL COMMENT '封托幂等操作号', pallet_status VARCHAR(16) NOT NULL DEFAULT 'OPEN' COMMENT '托盘状态:OPEN打托中、SEALED已封托、CANCELLED已取消', created_by BIGINT UNSIGNED NOT NULL COMMENT '创建操作人ID', created_by_name VARCHAR(64) NULL COMMENT '创建操作人名称快照', sealed_by BIGINT UNSIGNED NULL COMMENT '封托操作人ID', sealed_by_name VARCHAR(64) NULL COMMENT '封托操作人名称快照', sealed_at DATETIME(3) NULL COMMENT '封托时间', 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 (id), UNIQUE KEY uk_loading_work_pallet_code (pick_task_id, pallet_code), UNIQUE KEY uk_loading_work_pallet_open_operation (open_operation_id), UNIQUE KEY uk_loading_work_pallet_seal_operation (seal_operation_id), KEY idx_loading_work_pallet_plan (loading_plan_id, pallet_status, id), CONSTRAINT fk_loading_work_pallet_task FOREIGN KEY (pick_task_id) REFERENCES fl_loading_pick_task (id), CONSTRAINT fk_loading_work_pallet_plan FOREIGN KEY (loading_plan_id) REFERENCES fl_loading_plan (id), CONSTRAINT chk_loading_work_pallet_status CHECK (pallet_status IN ('OPEN','SEALED','CANCELLED')) ) ENGINE=InnoDB COMMENT='一件代发PDA现场实际托盘;允许同托盘承载多个客户订单'; CREATE TABLE IF NOT EXISTS fl_loading_work_pallet_carton ( id BIGINT UNSIGNED NOT NULL COMMENT '一件代发现场托盘箱唛绑定ID', pick_task_id BIGINT UNSIGNED NOT NULL COMMENT 'PDA装柜拣货任务ID', work_pallet_id BIGINT UNSIGNED NOT NULL COMMENT '现场作业托盘ID', loading_plan_item_id BIGINT UNSIGNED NOT NULL COMMENT '装柜计划货物明细ID', pick_item_id BIGINT UNSIGNED NOT NULL COMMENT 'PDA拣货明细ID', tenant_id BIGINT UNSIGNED NOT NULL COMMENT '箱唛所属客户ID', inbound_id BIGINT UNSIGNED NOT NULL COMMENT '来源入仓单ID', inbound_detail_id BIGINT UNSIGNED NOT NULL COMMENT '来源入仓货物明细ID', carton_label_id BIGINT UNSIGNED NOT NULL COMMENT '稳定箱唛记录ID', box_code VARCHAR(80) NOT NULL COMMENT '稳定箱唛箱码快照', sku_code VARCHAR(128) NOT NULL COMMENT 'SKU编码快照', units_per_carton INT UNSIGNED NULL COMMENT '每箱产品数量快照', bound_by BIGINT UNSIGNED NOT NULL COMMENT '最后绑定操作人ID', bound_by_name VARCHAR(64) NULL COMMENT '最后绑定操作人名称快照', operation_id VARCHAR(128) NOT NULL COMMENT '最近一次绑定或移托幂等操作号', 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 (id), UNIQUE KEY uk_loading_work_carton_task_label (pick_task_id, carton_label_id), UNIQUE KEY uk_loading_work_carton_operation (operation_id), KEY idx_loading_work_carton_pallet (work_pallet_id, sku_code, id), KEY idx_loading_work_carton_plan_item (loading_plan_item_id, id), CONSTRAINT fk_loading_work_carton_task FOREIGN KEY (pick_task_id) REFERENCES fl_loading_pick_task (id), CONSTRAINT fk_loading_work_carton_pallet FOREIGN KEY (work_pallet_id) REFERENCES fl_loading_work_pallet (id), CONSTRAINT fk_loading_work_carton_plan_item FOREIGN KEY (loading_plan_item_id) REFERENCES fl_loading_plan_item (id), CONSTRAINT fk_loading_work_carton_pick_item FOREIGN KEY (pick_item_id) REFERENCES fl_loading_pick_item (id), CONSTRAINT fk_loading_work_carton_label FOREIGN KEY (carton_label_id) REFERENCES fl_inbound_carton_label (id), CONSTRAINT chk_loading_work_carton_units CHECK (units_per_carton IS NULL OR units_per_carton > 0) ) 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 IN ('LOADING', 'COMPLETED') AND ( NOT (OLD.id <=> NEW.id) OR NOT (OLD.booking_no <=> NEW.booking_no) OR NOT (OLD.cabinet_no <=> NEW.cabinet_no) OR NOT (OLD.channel_code <=> NEW.channel_code) OR NOT (OLD.channel_name <=> NEW.channel_name) OR NOT (OLD.ocean_booking_id <=> NEW.ocean_booking_id) OR NOT (OLD.ocean_booking_requirement_id <=> NEW.ocean_booking_requirement_id) OR NOT (OLD.warehouse_id <=> NEW.warehouse_id) OR NOT (OLD.warehouse_name <=> NEW.warehouse_name) OR NOT (OLD.container_type <=> NEW.container_type) OR NOT (OLD.shipping_company_id <=> NEW.shipping_company_id) OR NOT (OLD.shipping_company_name <=> NEW.shipping_company_name) OR NOT (OLD.departure_port_code <=> NEW.departure_port_code) OR NOT (OLD.departure_port_name <=> NEW.departure_port_name) OR NOT (OLD.destination_port_code <=> NEW.destination_port_code) OR NOT (OLD.destination_port_name <=> NEW.destination_port_name) OR NOT (OLD.destination_warehouse_code <=> NEW.destination_warehouse_code) OR NOT (OLD.destination_warehouse_name <=> NEW.destination_warehouse_name) OR NOT (OLD.capacity_cbm <=> NEW.capacity_cbm) OR NOT (OLD.max_weight_kg <=> NEW.max_weight_kg) OR NOT (OLD.terminal_trucking_company_id <=> NEW.terminal_trucking_company_id) OR NOT (OLD.terminal_trucking_company <=> NEW.terminal_trucking_company) OR NOT (OLD.loading_at <=> NEW.loading_at) OR NOT (OLD.actual_sailing_at <=> NEW.actual_sailing_at) OR NOT (OLD.estimated_port_arrival_at <=> NEW.estimated_port_arrival_at) OR NOT (OLD.actual_port_arrival_at <=> NEW.actual_port_arrival_at) OR NOT (OLD.estimated_warehouse_arrival_at <=> NEW.estimated_warehouse_arrival_at) OR NOT (OLD.actual_warehouse_arrival_at <=> NEW.actual_warehouse_arrival_at) OR NOT (OLD.created_by <=> NEW.created_by) OR NOT (OLD.created_by_name <=> NEW.created_by_name) OR NOT (OLD.created_at <=> NEW.created_at) ) THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '装柜作业开始后仅允许修改集装箱号与放舱运输数据'; END IF; END$$ DELIMITER ; DROP TRIGGER IF EXISTS trg_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.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 = 'LOADING' AND NEW.plan_status = 'COMPLETED' THEN IF EXISTS (SELECT 1 FROM fl_loading_plan_item item WHERE item.loading_plan_id = OLD.id AND item.deleted = 0 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_item.loading_plan_item_id = item.id AND pick_task.loading_plan_id = OLD.id AND ( (pick_item.item_status = 'PICKED' AND pick_item.picked_package_count = item.package_count) OR (pick_item.item_status = 'PARTIAL' AND pick_item.picked_package_count > 0 AND pick_item.picked_package_count = item.package_count) OR (pick_item.item_status = 'SKIPPED' AND pick_item.picked_package_count = 0) ) )) 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 (NEW.warehouse_remark <=> OLD.warehouse_remark) THEN IF NEW.package_count <> OLD.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.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; ELSEIF 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.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', 'DELIVERY_ORDER') THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '装柜完成后货柜文件不可新增'; END IF; END$$ DELIMITER ; DROP TRIGGER IF EXISTS trg_container_document_before_update; DELIMITER $$ CREATE TRIGGER trg_container_document_before_update BEFORE UPDATE ON fl_container_document FOR EACH ROW BEGIN DECLARE current_plan_status VARCHAR(24); SELECT MAX(plan_status) INTO current_plan_status FROM fl_loading_plan WHERE container_booking_id = OLD.container_booking_id; IF current_plan_status = 'COMPLETED' AND OLD.document_type NOT IN ('DESTINATION_CLEARANCE', 'DELIVERY_ORDER') 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; DELETE role_permission FROM iam_role_permission role_permission JOIN iam_permission permission ON permission.id = role_permission.permission_id WHERE permission.tenant_id = 0 AND permission.permission_code IN ( 'platform:firstleg:smart-loading:create', 'platform:firstleg:smart-loading:push'); DELETE FROM iam_permission WHERE tenant_id = 0 AND permission_code IN ( 'platform:firstleg:smart-loading:create', 'platform:firstleg:smart-loading:push'); 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), (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), (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:assign', 'platform:firstleg:smart-loading:delete', '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_id', 'terminal_trucking_company')) <> 11 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:assign', 'platform:firstleg:smart-loading:delete', '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')) <> 14 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:assign', 'platform:firstleg:smart-loading:delete', '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')) <> 28 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 地址库。 -- 来源:用户提供的 FBA地址表.xls,只读整理后生成。 -- 完整保留 314 条,拒绝 0 条;拒绝清单见 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), (193000000000400003,0,193000000000200003,'RECEIVING','海外收货',1,'16:00:00', '洛杉矶仓海外接柜入仓收货能力',NULL,CURRENT_TIMESTAMP(3),NULL,CURRENT_TIMESTAMP(3),0,0), (193000000000400004,0,193000000000200004,'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, remark, created_by, updated_by ) VALUES (193000000000200101,0,193000000000200001,'TP-001','木托盘',1.200000,8.000,'黄色',NULL,0,0), (193000000000200102,0,193000000000200002,'TP-002','塑料托盘',2.400000,12.000,'黑色',NULL,0,0), (193000000000200103,0,193000000000200001,'YW-TP-002','塑料托盘',2.400000,12.000,'黑色',NULL,0,0), (193000000000200104,0,193000000000200002,'DG-TP-002','木托盘',1.200000,8.000,'黄色',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); DROP PROCEDURE IF EXISTS assert_domestic_pallet_seed; DELIMITER // CREATE PROCEDURE assert_domestic_pallet_seed() BEGIN IF EXISTS ( SELECT expected.warehouse_id FROM ( SELECT 193000000000200001 AS warehouse_id UNION ALL SELECT 193000000000200002 ) expected LEFT JOIN wh_pallet pallet ON pallet.tenant_id = 0 AND pallet.warehouse_id = expected.warehouse_id AND pallet.pallet_type IN ('木托盘','塑料托盘') AND pallet.deleted = 0 GROUP BY expected.warehouse_id HAVING COUNT(DISTINCT pallet.pallet_type) <> 2 ) THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '国内作业仓木托盘、塑料托盘初始化不完整'; END IF; END// DELIMITER ; CALL assert_domestic_pallet_seed(); DROP PROCEDURE IF EXISTS assert_domestic_pallet_seed; -- 义乌仓初始化 A-F 六个库区,每区 A1-A15 共 90 个普通库位。 -- A0 仍是系统共享溢出位;东莞仓继续保留最小 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 ) WITH RECURSIVE zones AS ( SELECT 0 AS zone_index UNION ALL SELECT zone_index + 1 FROM zones WHERE zone_index < 5 ), slots AS ( SELECT 1 AS slot_number UNION ALL SELECT slot_number + 1 FROM slots WHERE slot_number < 15 ) SELECT CAST(193000000000200000 + CASE WHEN zones.zone_index = 0 AND slots.slot_number = 1 THEN 201 ELSE 203 + zones.zone_index * 15 + slots.slot_number END AS UNSIGNED), 0, 193000000000200001, CHAR(65 + zones.zone_index), CONCAT(CHAR(65 + zones.zone_index), '区'), CONCAT(CHAR(65 + zones.zone_index), slots.slot_number), CONCAT(CHAR(65 + zones.zone_index), slots.slot_number, ' 普通库位'), 'PALLET',1000.000,0,'AVAILABLE',NULL,0,0 FROM zones CROSS JOIN slots WHERE 1 = 1 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), remark = VALUES(remark); 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 (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), remark = VALUES(remark); DROP PROCEDURE IF EXISTS assert_yiwu_location_seed; DELIMITER // CREATE PROCEDURE assert_yiwu_location_seed() BEGIN IF (SELECT COUNT(*) FROM wh_location WHERE tenant_id = 0 AND warehouse_id = 193000000000200001 AND deleted = 0 AND shared_overflow = 0 AND zone_code IN ('A','B','C','D','E','F') AND location_code REGEXP '^[A-F]([1-9]|1[0-5])$' AND LEFT(location_code, 1) = zone_code) <> 90 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '义乌仓 A-F 每区 15 个普通库位初始化不完整'; END IF; IF (SELECT COUNT(*) FROM wh_location WHERE tenant_id = 0 AND warehouse_id = 193000000000200001 AND location_code = 'A0' AND shared_overflow = 1 AND deleted = 0) <> 1 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '义乌仓系统共享溢出位 A0 初始化不完整'; END IF; END// DELIMITER ; CALL assert_yiwu_location_seed(); DROP PROCEDURE IF EXISTS assert_yiwu_location_seed; 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, internal_code VARCHAR(16) 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,internal_code,state_province,city,postal_code,timezone,address_line1,source_row) VALUES (193000000000000001,193000000000100001,'ABE2','ABE2',NULL,'PA','Breinigsville','18031','UTC-5','705 Boulder Drive',246), (193000000000000002,193000000000100002,'ABE3','ABE3',NULL,'PA','Breinigsville','18031','UTC-5','650 Boulder Drive',245), (193000000000000003,193000000000100003,'ABE4','ABE4','126','PA','Easton','18045','UTC-5','1610 Van Buren Road',142), (193000000000000004,193000000000100004,'ABE5','ABE5',NULL,'PA','Harrisburg','17112','UTC-5','6455 Allentown Boulevard',239), (193000000000000005,193000000000100005,'ABE8','ABE8','39','NJ','Florence','08518','UTC-5','401 Independence Road 08518-2200',133), (193000000000000253,193000000000100253,'ABE8-309','ABE8-309',NULL,'NJ','Florence','08518-2802','UTC-5','309 Cedar Lane',259), (193000000000000254,193000000000100254,'ABE8-401','ABE8-401',NULL,'NJ','Florence','08518','UTC-5','401 Independence Road',260), (193000000000000006,193000000000100006,'ABQ2','ABQ2','76','NM','LOS LUNAS','87031','UTC-7','6251 Pioneer Trail NW',107), (193000000000000255,193000000000100255,'ABQ2-UPS','ABQ2-UPS',NULL,'AZ','GOODYEAR','85395','UTC-7','16601 W. SELLS DRIVE',104), (193000000000000007,193000000000100007,'ACY2','ACY2','101','NJ','BURLINGTON','08016','UTC-5','1101 E PEARL ST',92), (193000000000000008,193000000000100008,'AFW1','AFW1',NULL,'TX','FORT WORTH','76131','UTC-6','1851 NE LOOP 820',149), (193000000000000009,193000000000100009,'AFW5','AFW5','181','TX','BALCH SPRINGS','75180-1636','UTC-6','12374 LAKE JUNE RD',35), (193000000000000010,193000000000100010,'AKR1','AKR1','153','OH','Canton','44705','UTC-5','4747 Rebar Ave NE',156), (193000000000000011,193000000000100011,'ALB1','ALB1','102','NY','Castleton','12033-9634','UTC-5','1835 US Route 9',182), (193000000000000012,193000000000100012,'AMA1','AMA1','132','TX','Amarillo','79108','UTC-6','8590 NE 24th Avenue',137), (193000000000000256,193000000000100256,'ATL1','ATL1','200','GA','Atlanta','30349','UTC-5','ATL1, 6055 S Fulton Pkwy, Atlanta, GA, 30349',2), (193000000000000257,193000000000100257,'ATL1-WALMART','ATL1-WALMART',NULL,'GA','Atlanta','30349','UTC-5','6055 S Fulton Pkwy,',146), (193000000000000013,193000000000100013,'ATL2N','ATL2N','157','GA','Carrollton','30117','UTC-5','3101 N. Highway 27',39), (193000000000000014,193000000000100014,'ATL3','ATL3',NULL,'GA','Pendergrass','30567','UTC-5','445 Valentine Industrial Parkway',155), (193000000000000015,193000000000100015,'ATL6','ATL6',NULL,'GA','East Point','30344','UTC-5','4200 North Commerce',210), (193000000000000016,193000000000100016,'AVP1','AVP1','70','PA','Hazle Township','18202-9361','UTC-5','550 Oak Ridge Road',241), (193000000000000017,193000000000100017,'AVP3','AVP3',NULL,'PA','GOULDSBORO','18424-9492','UTC-5','298 1ST AVE',240), (193000000000000258,193000000000100258,'AZA4','AZA4','16','','','','','',72), (193000000000000018,193000000000100018,'BDL1','BDL1',NULL,'CT','Windsor','06095','UTC-5','801 Day Hill Rd.',297), (193000000000000019,193000000000100019,'BFI1','BFI1',NULL,'WA','Sumner','98390','UTC-8','1800 140th Avenue E.',216), (193000000000000020,193000000000100020,'BFI3','BFI3','34','WA','Dupont','98327-9607','UTC-8','2700 Center Drive',215), (193000000000000021,193000000000100021,'BFI7','BFI7',NULL,'WA','Sumner','98390','UTC-8','1901 140th Ave E',214), (193000000000000022,193000000000100022,'BJC1','BJC1','186','CO','AURORA','80019','UTC-7','6300 N Powhaton Road',19), (193000000000000023,193000000000100023,'BNA1','BNA1',NULL,'TN','Lebanon','37090','UTC-6','14840 Central Pike Suite 190',229), (193000000000000024,193000000000100024,'BNA2','BNA2','37','TN','Lebanon','37090','UTC-6','500 Duke DR',139), (193000000000000025,193000000000100025,'BNA3','BNA3',NULL,'TN','Murfreesboro','37127','UTC-6','2020 Joe B Jackson Pkwy',148), (193000000000000026,193000000000100026,'BNA4','BNA4',NULL,'TN','Nashville','37217','UTC-6','50 airways Blvd',228), (193000000000000027,193000000000100027,'BNA6','BNA6','99','TN','CLARKSVILLE','37040-5502','UTC-6','3875 GUTHRIE HWY',118), (193000000000000028,193000000000100028,'BOS1','BOS1',NULL,'NH','Nashua','03063','UTC-5','10 State St.',251), (193000000000000029,193000000000100029,'BOS2','BOS2',NULL,'MA','Drive Stoughton','02072','UTC-5','1000 Tech Centre',201), (193000000000000030,193000000000100030,'BOS7','BOS7','103','MA','Fall River','02720','UTC-5','1180 Innovation Way',200), (193000000000000031,193000000000100031,'BWI1','BWI1',NULL,'VA','Sterling','20166-2058','UTC-5','45121 Global Plaza',250), (193000000000000032,193000000000100032,'BWI2','BWI2',NULL,'MD','Baltimore','21224','UTC-5','2010 Broening Hwy',249), (193000000000000033,193000000000100033,'BWI4','BWI4','150','VA','CLEARBROOK','22624','UTC-5','165 Business Blvd',110), (193000000000000034,193000000000100034,'CAE1','CAE1',NULL,'SC','West Columbia','29172','UTC-5','4400 12TH STREET EXT',233), (193000000000000259,193000000000100259,'CEG2','CEG2','79','','','','','',64), (193000000000000035,193000000000100035,'CHA1','CHA1',NULL,'TN','Chattanooga','37416','UTC-5','7200 Discovery Drive',231), (193000000000000036,193000000000100036,'CHA2','CHA2','66','TN','Charleston','37310','UTC-5','225 Infinity Dr NW',230), (193000000000000037,193000000000100037,'CHO1','CHO1','120','VA','FISHERSVILLE','22939','UTC-5','32 TRADER RD',83), (193000000000000038,193000000000100038,'CLT2','CLT2','59','NC','Charlotte','28214-8082','UTC-5','10240 Old Dowd Rd',199), (193000000000000039,193000000000100039,'CLT3','CLT3','104','NC','CONCORD','28027','UTC-5','6500 Davidson Hwy',122), (193000000000000260,193000000000100260,'CMH1-11999','CMH1-11999',NULL,'OH','Etna','43062-7793','UTC-5','11999 National Road SW',247), (193000000000000040,193000000000100040,'CMH2','CMH2','123','OH','Groveport','43125-9016','UTC-5','6050 Gateway Court',248), (193000000000000041,193000000000100041,'CMH3','CMH3','32','OH','Monroe','45050-1848','UTC-5','700 GATEWAY BLVD',189), (193000000000000042,193000000000100042,'CVG1','CVG1',NULL,'KY','Hebron','41048','UTC-5','1155 Worldwide Blvd',270), (193000000000000043,193000000000100043,'CVG2','CVG2',NULL,'KY','Hebron','41048','UTC-5','1600 Worldwide Blvd',269), (193000000000000044,193000000000100044,'CVG3','CVG3',NULL,'KY','Hebron','41048','UTC-5','3680 Langley Dr.',268), (193000000000000045,193000000000100045,'CVG5','CVG5',NULL,'KY','Hebron','41048','UTC-5','2285 Litton Lane',267), (193000000000000046,193000000000100046,'DCA6','DCA6','135','MD','BALTIMORE','21219','UTC-5','6001 Bethlehem Blvd',143), (193000000000000047,193000000000100047,'DEN2','DEN2','107','CO','Aurora','80019','UTC-7','22205 East 19th Ave',131), (193000000000000048,193000000000100048,'DEN8','DEN8','73','CO','Aurora','80018','UTC-7','21000 E 13th Ave',128), (193000000000000049,193000000000100049,'DET1','DET1','118','MI','Livonia','48150-1543','UTC-5','39000 Amrhein Road',197), (193000000000000050,193000000000100050,'DET2','DET2','136','MI','Shelby Township','48317-1318','UTC-5','50500 Mound Rd',190), (193000000000000051,193000000000100051,'DFW5S','DFW5S',NULL,'TX','Lancaster','75146','UTC-6','2500 E Belt Line Rd,',151), (193000000000000052,193000000000100052,'DFW6','DFW6','105','TX','Coppell','75019','UTC-6','940 W Bethel Road',227), (193000000000000053,193000000000100053,'DFW8','DFW8',NULL,'TX','DFW Airport','75261','UTC-6','2700 Regent Blvd',225), (193000000000000054,193000000000100054,'EWR4','EWR4',NULL,'NJ','Robbinsville','08691','UTC-5','50 New Canton Way',264), (193000000000000055,193000000000100055,'EWR5','EWR5',NULL,'NJ','Avenel','07001','UTC-5','301 Blair Rd.',263), (193000000000000056,193000000000100056,'EWR7','EWR7',NULL,'NJ','Avenel','07001','UTC-5','301 Blair Rd.',262), (193000000000000057,193000000000100057,'EWR9','EWR9',NULL,'NJ','Carteret','07008','UTC-5','8003 Industrial Parkway',261), (193000000000000058,193000000000100058,'FAT2','FAT2','43','CA','VISALIA','93291-1882','UTC-8','3315 N KELSEYST',125), (193000000000000261,193000000000100261,'FEDEX','FEDEX','12','','','92337','','10767',55), (193000000000000059,193000000000100059,'FOE1','FOE1','117','KS','KANSAS CITY','66109-3665','UTC-6','9400 LEAVENWORTH RD',184), (193000000000000060,193000000000100060,'FTW1','FTW1','24','TX','Dallas','75241','UTC-6','33333 Lyndon B Johnson FREEWAY',220), (193000000000000061,193000000000100061,'FTW2','FTW2',NULL,'TX','Coppell','75019','UTC-6','2701 West Bethel Road',219), (193000000000000062,193000000000100062,'FTW3','FTW3',NULL,'TX','Fort Worth','76177-2517','UTC-6','15201 Heritage Pkwy',97), (193000000000000063,193000000000100063,'FTW5','FTW5','48','TX','Forney','75126','UTC-6','1475 AKRON WAY',129), (193000000000000064,193000000000100064,'FWA4','FWA4','58','IN','FORT WAYNE','46809-9771','UTC-5','9798 Smith Road',179), (193000000000000065,193000000000100065,'GEG2','GEG2','108','WA','SPOKANE','99216','UTC-8','18007 E Garland Ave',159), (193000000000000066,193000000000100066,'GEU2','GEU2','97','AZ','Glendale','85307','UTC-7','15301 West Northern Avenue',75), (193000000000000067,193000000000100067,'GEU3','GEU3','81','AZ','BUCKEYE','85396','UTC-7','18900 W McDowell Road',86), (193000000000000068,193000000000100068,'GEU5','GEU5','100','AZ','GOODYEAR','85395','UTC-7','4660 North Cotton Lane',76), (193000000000000069,193000000000100069,'GSO1','GSO1','131','NC','KERNERSVILLE','27284','UTC-5','1656 OLD GREENSBORO RD',172), (193000000000000070,193000000000100070,'GSP1','GSP1','90','SC','Spartanburg','29303','UTC-5','402 John Dodd Rd',232), (193000000000000071,193000000000100071,'GYR2','GYR2','7','AZ','GOODYEAR','85395-7672','UTC-7','17341 W MINNEZONA AVE',196), (193000000000000072,193000000000100072,'GYR3','GYR3','8','AZ','PHOENIX','85043-2356','UTC-7','8181 W ROOSEVELT ST',195), (193000000000000262,193000000000100262,'HEA2','HEA2','77','','','18031','','650 BOULDER DR Suite A 18031-1536 - BREINIGSVILLE, PA - United States',65), (193000000000000073,193000000000100073,'HGR6','HGR6','130','MD','HAGERSTOWN','21740-7301','UTC-5','55 West Oak Ridge Drive',88), (193000000000000074,193000000000100074,'HIA1','HIA1','148','PA','MIDDLETOWN','17057','UTC-5','3327 E Harrisburg Pik',95), (193000000000000075,193000000000100075,'HLI2','HLI2','137','CA','HOLLISTER','95023','UTC-8','600 Ernie Drive',52), (193000000000000076,193000000000100076,'HOU1','HOU1',NULL,'TX','Humble','77338','UTC-6','8120 Humble Westfield Rd',224), (193000000000000077,193000000000100077,'HOU2','HOU2',NULL,'TX','Houston','77038-2324','UTC-6','10550 Ella Blvd.',223), (193000000000000078,193000000000100078,'HOU3','HOU3',NULL,'TX','Brookshire','77423','UTC-6','31555 Highway 90 E',222), (193000000000000263,193000000000100263,'HOU7','HOU7','63','','','','','',67), (193000000000000079,193000000000100079,'HOU8','HOU8','49','TX','FRESNO','77545','UTC-6','2303 Hurricane Lane',162), (193000000000000080,193000000000100080,'HSV1','HSV1','80','AL','Madison','35756','UTC-6','7817 Greenbrier Road',150), (193000000000000081,193000000000100081,'HSV2','HSV2',NULL,'AL','MADISON','35756','UTC-6','28869 Fanning Dr NW',134), (193000000000000082,193000000000100082,'IAH3','IAH3','57','TX','Houston','77032','UTC-6','15525 Milner Road',163), (193000000000000264,193000000000100264,'IAK3','IAK3','44','','','','','',70), (193000000000000083,193000000000100083,'IAZ1','IAZ1','179','AZ','GOODYEAR','85395-4515','UTC-7','4660 N COTTON LN 85395-4515 - GOODYEAR, AZ - United States',11), (193000000000000084,193000000000100084,'ICT2','ICT2','109','KS','PARK CITY','67219','UTC-6','7130 N BROADWAY AVE',144), (193000000000000085,193000000000100085,'IGA3','IGA3','197','GA','MACON','31216-6427','UTC-5','7001 Skipper Rd 31216-6427 MACON, GA - United States',10), (193000000000000086,193000000000100086,'IGQ2','IGQ2','110','IL','University park','60484','UTC-6','23257 S. Central Avenue',160), (193000000000000087,193000000000100087,'ILG1','ILG1','35','DE','NEW CASTLE','19720','UTC-5','780 S. DuPont Highway',127), (193000000000000265,193000000000100265,'IMI1','IMI1','199','MI','LANSING','48917-8539','UTC-5','6500 W Mt Hope Hwy 48917-8539 - LANSING, MI - United States',9), (193000000000000088,193000000000100088,'IMO1','IMO1','178','MO','LIBERTY','64068-8933','UTC-6','2361 S Withers RD',18), (193000000000000089,193000000000100089,'IMS1','IMS1','189','MS','OLIVE BRANCH','38654','UTC-6','IMS1-11505 Progress Way,38654-2510,OLIVE BRANCH,MS,US',14), (193000000000000090,193000000000100090,'INC1','INC1','190','NC','FAYETTEVILLE','28303-2215','UTC-5','6309 Bragg Blvd 28303-2215 - FAYETTEVILLE, NC - United States',13), (193000000000000266,193000000000100266,'IND1-AMAZON','IND1-AMAZON',NULL,'IN','Whitestown','46075','UTC-5','4255 Anson Blvd',287), (193000000000000267,193000000000100267,'IND1-WALMART','IND1-WALMART',NULL,'IN','Avon','46123','UTC-5','9590 AllPoints Pkwy',176), (193000000000000091,193000000000100091,'IND2','IND2','127','IN','Plainfield','46168-7442','UTC-5','715 Airtech Pkwy',96), (193000000000000268,193000000000100268,'IND2-AMAZON','IND2-AMAZON',NULL,'IN','Plainfield','46168','UTC-5','715 Airtech Pkwy',286), (193000000000000269,193000000000100269,'IND2-WALMART','IND2-WALMART',NULL,'IN','Avon','46123','UTC-5','9360 AllPoints Pkwy',177), (193000000000000270,193000000000100270,'IND3','IND3','198','IN','Mccordsville','46055','UTC-5','5756 Enterprise Dr',6), (193000000000000271,193000000000100271,'IND3-亚马逊','IND3-亚马逊',NULL,'IN','Pkwy Plainfield','46168','','717 Airtech Pkwy Plainfield IN 46168,USA',147), (193000000000000272,193000000000100272,'IND3-沃尔玛','IND3-沃尔玛',NULL,'IN','Mccordsville','46055','UTC-5','5756 Enterprise Dr, Mccordsville, IN 46055',285), (193000000000000092,193000000000100092,'IND4','IND4',NULL,'IN','Indianapolis','46231','UTC-5','710 S. Girls School Rd',284), (193000000000000093,193000000000100093,'IND5','IND5','140','IN','Plainfield','46168','UTC-5','800 Perry Road',283), (193000000000000094,193000000000100094,'IND6','IND6',NULL,'IN','Jeffersonville','47130','UTC-5','1453 E 10th St',282), (193000000000000095,193000000000100095,'IND9','IND9','17','IN','GREENWOOD','46143-7830','UTC-5','1151 S GRAHAM RD',181), (193000000000000273,193000000000100273,'IPA1','IPA1','194','PA','OLYPHANT','18447-2140','UTC-5','1300 Corporate Way',3), (193000000000000096,193000000000100096,'ITX3','ITX3','187','TX','AMARILLO','79108-7749','UTC-6','ITX3-8590 NE 24th Ave,79108-7749,AMARILLO,TX,US',17), (193000000000000097,193000000000100097,'IUSF','IUSF','169','TX','DALLAS','75241','UTC-6','3351 Balmorhea Dr',30), (193000000000000098,193000000000100098,'IUSJ','IUSJ','98','CA','BEAUMONT','92223','UTC-8','36900 West 4th Street',77), (193000000000000099,193000000000100099,'IUSP','IUSP','83','CA','HESPERIA,','92344','UTC-8','8140 Caliente Rd',82), (193000000000000274,193000000000100274,'IUSP-AWD','IUSP-AWD','83','CA','HESPERIA','92344','UTC-8','8140 Caliente Rd',91), (193000000000000100,193000000000100100,'IUSQ','IUSQ','95','CA','REDLANDS','92374','UTC-8','2125 W San Bernardino Ave',78), (193000000000000101,193000000000100101,'IUSR','IUSR','158','SC','COWPENS','29330','UTC-5','1120 Mt Olive Rd',48), (193000000000000102,193000000000100102,'IUST','IUST','149','PA','GREENCASTLE','17225','UTC-5','3PL AWD (Greencastle, PA, US) 15651 Greenmount RdGREENCASTLE, PA 17225 United States',47), (193000000000000103,193000000000100103,'IUSW','IUSW','156','CA','Hesperia','92344','UTC-8','8130 Caliente Rd 92344 - Hesperia, CA - United States',41), (193000000000000104,193000000000100104,'IUTE','IUTE','170','AZ','GOODYEAR','85338','UTC-7','16155 W Elwood St',29), (193000000000000105,193000000000100105,'IUTJ','IUTJ','177','CA','Fontana','92335','UTC-8','9253 Dreamland Drive 92335 - Fontana, CA - United',16), (193000000000000106,193000000000100106,'IUTL','IUTL','188','CA','Moreno Valley','92551','UTC-8','24405 Krameria Ave 92551 - Moreno Valley, CA - United States',15), (193000000000000107,193000000000100107,'IWA6','IWA6','191','WA','PASCO','99301-6161','UTC-8','1202 S Road 40 E 99301-6161 - PASCO, WA - United States',12), (193000000000000275,193000000000100275,'JAX3','JAX3','111','','','32210','','JAX3-32210',58), (193000000000000276,193000000000100276,'JAX3-32210','JAX3-32210','111','FL','Jacksonville','32210-8686','UTC-5','13333 103rd Street',289), (193000000000000277,193000000000100277,'JAX3-32221','JAX3-32221',NULL,'FL','Jacksonville','32221','UTC-5','13333 103rd Street',175), (193000000000000108,193000000000100108,'JOT1','JOT1','93','CA','SAN BERNARDINO','92408','UTC-8','1494 S WATERMAN AVE',80), (193000000000000109,193000000000100109,'JVL1','JVL1','125','WI','BELOIT','53511','UTC-6','1255 Gateway Blvd',158), (193000000000000110,193000000000100110,'KRB4','KRB4',NULL,'CA','PERRIS','92571-9722','UTC-8','4501 PATTERSON AVE',186), (193000000000000111,193000000000100111,'KY1','KY1','60','KY','Shepherdsville','40165','UTC-5','120 Velocity Way.,',169), (193000000000000112,193000000000100112,'LAN2','LAN2','96','MI','LANSING','48917','UTC-5','6500 W Mt Hope Hwy',108), (193000000000000278,193000000000100278,'LAN2-48917','LAN2-48917',NULL,'MI','LANSING','48917','UTC-5','6500 W Mt Hope Hwy',109), (193000000000000113,193000000000100113,'LAS1','LAS1','5','NV','HENDERSON','89044','UTC-8','12300 Bermuda Road',255), (193000000000000114,193000000000100114,'LAS2','LAS2',NULL,'NV','Las Vegas','89030','UTC-8','3837 Bay Lake Trail, Suite 111 North',254), (193000000000000115,193000000000100115,'LAS6','LAS6','50','NV','Las Vegas','89115','UTC-8','4550 Nexus Way',140), (193000000000000279,193000000000100279,'LAX1','LAX1','192','CA','Chino','91708','UTC-8','6750 Kimball Avenue, Chino,CA 91708,USA',8), (193000000000000280,193000000000100280,'LAX1-WALMART','LAX1-WALMART',NULL,'CA','Chino','91708','UTC-8','6750 Kimball Avenue',185), (193000000000000116,193000000000100116,'LAX2T','LAX2T','19','CA','CHINO','91708','UTC-8','6730 Kimball Ave',170), (193000000000000281,193000000000100281,'LAX2T-WALMART','LAX2T-WALMART',NULL,'CA','Chino','91708','UTC-8','6720 Kimball Ave',188), (193000000000000117,193000000000100117,'LAX9','LAX9','3','CA','FONTANA','92337','UTC-8','11263 Oleander Ave',191), (193000000000000118,193000000000100118,'LBE1','LBE1','112','PA','Greensburg','15605','UTC-5','165 Glen Fox Road',94), (193000000000000282,193000000000100282,'LBE1-UPS','LBE1-UPS',NULL,'PA','New Stanton','15672','UTC-5','165 GLENN FOX RD',93), (193000000000000119,193000000000100119,'LEX1','LEX1',NULL,'KY','Lexington','40511','UTC-5','1850 Mercer RD',276), (193000000000000120,193000000000100120,'LEX2','LEX2',NULL,'KY','Lexington','40511','UTC-5','172 Trade St.',275), (193000000000000121,193000000000100121,'LFT1','LFT1','31','LA','CARENCRO','70520-5976','UTC-6','3550 NE EVANGELINE TRWY',115), (193000000000000122,193000000000100122,'LGB3','LGB3',NULL,'CA','Eastvale','91752','UTC-8','4950 Goodman Way',299), (193000000000000123,193000000000100123,'LGB4','LGB4','45','CA','Redlands','92374','UTC-8','27517 Pioneer Avenue',300), (193000000000000124,193000000000100124,'LGB6','LGB6','26','CA','Riverside','92518-1513','UTC-8','20901 Krameria Ave',301), (193000000000000125,193000000000100125,'LGB8','LGB8','2','CA','Rialto','92376','UTC-8','1568 N Linden Ave',302), (193000000000000126,193000000000100126,'LIT2','LIT2','159','AR','NORTH LITTLE ROCK','72117','UTC-6','13001 HIGHWAY 70',114), (193000000000000127,193000000000100127,'MCC1','MCC1','161','CA','RANCHO CORDOVA','95742','UTC-8','3300 Rancho Cordova Pkwy 95742 - RANCHO CORDOVA, CA - United States',40), (193000000000000128,193000000000100128,'MCE1','MCE1','65','CA','TURLOCK','95380','UTC-8','FULKERTH RD',165), (193000000000000129,193000000000100129,'MCI1','MCI1',NULL,'KS','Lenexa','66219','UTC-6','113th St & Renner Blvd.',279), (193000000000000130,193000000000100130,'MCI4','MCI4','176','MO','KANSAS CITY','64163','UTC-6','MCI4 - 10501 NW 136th Street',20), (193000000000000283,193000000000100283,'MCO1-WALMART','MCO1-WALMART',NULL,'FL','Davenport','33897','UTC-5','5100 NORTH RIDGE TRAIL',116), (193000000000000131,193000000000100131,'MCO2','MCO2','106','FL','Deltona','32725-2610','UTC-5','2600 N Normandy Blvd',123), (193000000000000132,193000000000100132,'MDT1','MDT1','121','PA','Carlisle','17015','UTC-5','2Ames Drive',238), (193000000000000133,193000000000100133,'MDT4','MDT4','144','PA','York','17408','UTC-5','5125 Commerce Drive',136), (193000000000000134,193000000000100134,'MDW2','MDW2','46','IL','Joliet','60433-3280','UTC-6','250 Emerald Drive',208), (193000000000000284,193000000000100284,'MDW2-60433-3280','MDW2-60433-3280',NULL,'IL','Joliet','60433-3280','UTC-6','250 Emerald Drive',119), (193000000000000135,193000000000100135,'MDW6','MDW6','143','IL','ROMEOVILLE','60446-6529','UTC-6','1125 W REMINGTON BLVD ROMEOVILLE',207), (193000000000000136,193000000000100136,'MDW7','MDW7',NULL,'IL','Monee','60449','UTC-6','6605 Monee Manhattan Road',206), (193000000000000137,193000000000100137,'MDW8','MDW8',NULL,'IL','Waukegan','60085','UTC-6','1750 Bridge Drive',205), (193000000000000138,193000000000100138,'MDW9','MDW9','36','IL','Aurora','60502','UTC-6','2865 Duke Parkway',204), (193000000000000139,193000000000100139,'MEM1','MEM1','85','TN','Memphis','38118','UTC-6','3292 E Holmes Rd',171), (193000000000000285,193000000000100285,'MEM1S-WALMART','MEM1S-WALMART',NULL,'MS','OLIVE BRANCH','38654','UTC-6','9200 Alexander Road',183), (193000000000000140,193000000000100140,'MEM6','MEM6','51','MS','OLIVE BRANCH','38654','UTC-6','11505 Progress Way',126), (193000000000000141,193000000000100141,'MGE3','MGE3','133','GA','Jefferson','30549-2010','UTC-5','808 Hog Mountain Road',209), (193000000000000286,193000000000100286,'MIA1-33054','MIA1-33054',NULL,'FL','Opa-Locka','33054','UTC-5','14000 NW 37th Avenue',174), (193000000000000287,193000000000100287,'MIA1-33182','MIA1-33182',NULL,'FL','Miami','33182','UTC-5','1900 NW 132nd Place Doral',290), (193000000000000142,193000000000100142,'MIT2','MIT2','82','CA','SHAFTER','93263','UTC-8','5408 Express Avenue',85), (193000000000000143,193000000000100143,'MKC4','MKC4','67','KS','Edgerton','66021-9588','UTC-6','19645 Waverly Rd',278), (193000000000000144,193000000000100144,'MKC6','MKC6',NULL,'KS','Kansas City','66102','UTC-6','6925 Riverview Avenue',277), (193000000000000145,193000000000100145,'MKE1','MKE1',NULL,'WI','Kenosha','53144','UTC-6','3501 120th Ave',211), (193000000000000146,193000000000100146,'MQJ1','MQJ1','88','IN','GREENFIELD','46140','UTC-5','4412 W 300 RD N',102), (193000000000000288,193000000000100288,'MQY2','MQY2','193','TN','LEBANON','37090','UTC-6','MQY2 - 1301 Richard Petty Way 37090 - LEBANON, TN - United States',7), (193000000000000147,193000000000100147,'MSP1','MSP1',NULL,'MN','Shakopee','55379','UTC-6','2601 4th Avenue East',198), (193000000000000148,193000000000100148,'NJ3','NJ3','175','NJ','Pedricktown','08067','UTC-5','3 Sorbello Way',25), (193000000000000289,193000000000100289,'NJ3-WALMART','NJ3-WALMART',NULL,'NJ','Pedricktown','08067','UTC-5','3 Sorbello Way',87), (193000000000000290,193000000000100290,'NJ3T','NJ3T','195','NJ','Pedricktown','08067','UTC-5','7 Sorbello Rd',5), (193000000000000149,193000000000100149,'OAK3','OAK3','41','CA','Patterson','95363-8876','UTC-8','255 Park Center Drive',307), (193000000000000150,193000000000100150,'OAK4','OAK4',NULL,'CA','Tracy','95304-9370','UTC-8','1555 N. Chrisman Rd',308), (193000000000000151,193000000000100151,'OKC2','OKC2','33','OK','OKLAHOMA CITY','73159','UTC-6','8991 S PORTLAND AVENUE',141), (193000000000000152,193000000000100152,'OLM1','OLM1','172','WA','OLYMPIA','98516','UTC-8','3300 HOGUM BAY RD NE 98516-3111 - OLYMPIA, WA - United States',27), (193000000000000153,193000000000100153,'ONT2','ONT2',NULL,'CA','San Bernardino','92408','UTC-8','1910 E Central Ave',311), (193000000000000154,193000000000100154,'ONT6','ONT6',NULL,'CA','Moreno Valley','92551-9561','UTC-8','24208 San Michele Rd',310), (193000000000000155,193000000000100155,'ONT8','ONT8','1','CA','Moreno Valley','92551-9534','UTC-8','24300 Nandina Ave',309), (193000000000000156,193000000000100156,'ONT9','ONT9','25','CA','Redlands','92374','UTC-8','2125 W. San Bernandino Ave',306), (193000000000000291,193000000000100291,'ORD1S-WALMART','ORD1S-WALMART',NULL,'IL','Joliet','60436','UTC-6','3501 Brandon Road',99), (193000000000000157,193000000000100157,'ORD2','ORD2','21','IL','CHANNAHON','60410-8758','UTC-6','23714 W Amoco Rd',111), (193000000000000158,193000000000100158,'ORD6','ORD6',NULL,'IL','Wood Dale','60191','UTC-6','1250 N Mittel Blvd',203), (193000000000000159,193000000000100159,'ORF2','ORF2','38','VA','CHESAPEAKE','23321-1425','UTC-5','ORF2 - 5045 PORTSMOUTH BLVD',121), (193000000000000292,193000000000100292,'ORF2-FEDEX','ORF2-FEDEX',NULL,'VA','CHESAPEAKE','23321-1425','UTC-5','ORF2 - 5045 PORTSMOUTH BLVD',167), (193000000000000293,193000000000100293,'ORF2-UPS','ORF2-UPS',NULL,'VA','Richmond','23297','UTC-5','9603 Coach Road',124), (193000000000000160,193000000000100160,'PBI3','PBI3','113','FL','Jacksonville','32206','UTC-5','21 Jump Street',89), (193000000000000161,193000000000100161,'PDX7','PDX7','68','OR','Salem','97317','UTC-8','4775 Depot Ct SE',135), (193000000000000162,193000000000100162,'PHL1','PHL1',NULL,'DE','New Castle','19720','UTC-5','1 Centerpoint Blvd.',296), (193000000000000163,193000000000100163,'PHL3','PHL3',NULL,'DE','New Castle','19720','UTC-5','1600 Johnson Way',295), (193000000000000164,193000000000100164,'PHL4','PHL4','52','PA','Carlisle','17015','UTC-5','21 Roadway Dr',244), (193000000000000165,193000000000100165,'PHL5','PHL5',NULL,'PA','Lewisberry','17339','UTC-5','500 McCarthy Dr',243), (193000000000000166,193000000000100166,'PHL5S','PHL5S','171','PA','Greencastle','17225','UTC-5','1915 Ebberts Spg Ct, Greencastle, PA 17225',28), (193000000000000294,193000000000100294,'PHL5S-WALMART','PHL5S-WALMART',NULL,'PA','Greencastle','17225','UTC-5','1915 Ebberts Spg Ct',98), (193000000000000167,193000000000100167,'PHL6','PHL6','69','PA','Carlisle','17015','UTC-5','675 Allen Rd.',242), (193000000000000168,193000000000100168,'PHL7','PHL7',NULL,'DE','Middletown','19709','UTC-5','560 Merrimac Ave',294), (193000000000000169,193000000000100169,'PHL8','PHL8',NULL,'DE','Middletown','19709','UTC-5','727 N Broad St',293), (193000000000000170,193000000000100170,'PHX3','PHX3',NULL,'AZ','Phoenix','85043','UTC-7','6835 West Buckeye Road',315), (193000000000000171,193000000000100171,'PHX5','PHX5','72','AZ','Goodyear','85338','UTC-7','16920 W Commerce Drive',314), (193000000000000172,193000000000100172,'PHX6','PHX6',NULL,'AZ','Phoenix','85043','UTC-7','4750 West Mohave St',313), (193000000000000173,193000000000100173,'PHX7','PHX7','74','AZ','Phoenix','85043','UTC-7','800 N 75th Ave',312), (193000000000000174,193000000000100174,'PIT1','PIT1',NULL,'PA','Pittsburgh','15205','UTC-5','2250 Roswell Drive',237), (193000000000000175,193000000000100175,'PIT2','PIT2','78','PA','IMPERIAL','15126-1002','UTC-5','1200 Westport Rd',166), (193000000000000176,193000000000100176,'POC1','POC1','128','CA','FONTANA','92336-1123','UTC-8','4121 LYTLE CREEK RD',54), (193000000000000177,193000000000100177,'POC2','POC2','151','CA','ONTARIO','91761','UTC-8','4000 South Hamner Avenue',46), (193000000000000178,193000000000100178,'POC3','POC3','129','CA','JURUPA VALLEY','92509','UTC-8','6120 Clinker Drive',53), (193000000000000179,193000000000100179,'PPO4','PPO4','122','IN','MERRILLVILLE','46410','UTC-6','9850 Mississippi Street',57), (193000000000000180,193000000000100180,'PSC2','PSC2','75','WA','PASCO','99301','UTC-8','1351 S Road 40 E',101), (193000000000000295,193000000000100295,'PSC2-UPS','PSC2-UPS',NULL,'OR','Portland','97242','UTC-8','9930 NE Alderwood Rd',100), (193000000000000181,193000000000100181,'PSP3','PSP3','162','CA','DESERT HOT SPRINGS','92240','UTC-8','64165 19th AVE',36), (193000000000000182,193000000000100182,'QXY5','QXY5','92','CA','STOCKTON','95215','UTC-8','4532 NEWCASTLE RD',79), (193000000000000183,193000000000100183,'QXY8','QXY8','154','CA','FRESNO','93725-1962','UTC-8','3611 S NORTHPOINTE DR',43), (193000000000000184,193000000000100184,'QZZ7','QZZ7','184','AZ','MESA','85212-9608','UTC-7','8560 E ELLIOT RD',22), (193000000000000185,193000000000100185,'RDU2','RDU2','56','NC','Smithfield','27577','UTC-5','2150 US HWY 70 Business West',154), (193000000000000186,193000000000100186,'RDU4','RDU4','145','NC','FAYETTEVILLE','28303','UTC-5','6309 Bragg Blvd',103), (193000000000000187,193000000000100187,'RFD2','RFD2','71','IL','Huntley','60142','UTC-6','11500 Freeman Road',153), (193000000000000188,193000000000100188,'RFD4','RFD4','174','IL','HUNTLEY','60142','UTC-6','11400 Venture Court 60142-8075 - HUNTLEY, IL - United States',24), (193000000000000189,193000000000100189,'RIC1','RIC1','142','VA','Petersburg','23803','UTC-5','5000 Commerce Way',218), (193000000000000190,193000000000100190,'RIC2','RIC2',NULL,'VA','Chester','23836','UTC-5','1901 Meadowville Technology Pkwy',217), (193000000000000191,193000000000100191,'RMN3','RMN3','84','VA','Fredericksburg','22406','UTC-5','220 Centreport Parkway',152), (193000000000000192,193000000000100192,'RNO1','RNO1',NULL,'NV','Fernley','89408','UTC-8','1600 East Newlands RD',256), (193000000000000193,193000000000100193,'RNO2','RNO2',NULL,'NV','Reno','85906','UTC-8','8000 North Virginia Street',253), (193000000000000194,193000000000100194,'RNO4','RNO4',NULL,'NV','Reno','89506','UTC-8','8000 North Virginia Street',252), (193000000000000195,193000000000100195,'RYY2','RYY2','94','GA','White','30184','UTC-5','1250 Cassville White Road,',81), (193000000000000196,193000000000100196,'SAT1','SAT1','23','TX','Schertz','78154-1461','UTC-6','6000 Enterprise Avenue',226), (193000000000000197,193000000000100197,'SAT4','SAT4','61','TX','SAN ANTONIO','78245','UTC-6','10384 W US HIGHWAY 90',157), (193000000000000198,193000000000100198,'SAT6','SAT6','183','TX','SAN MARCOS','78666','UTC-6','1346 Fortuna Road 78666-0001 - SAN MARCOS, TX - United States',23), (193000000000000199,193000000000100199,'SAV3','SAV3','124','GA','Macon','31216-6427','UTC-5','7001 Skipper Rd',106), (193000000000000200,193000000000100200,'SBD1','SBD1','4','CA','BLOOMINGTON','92316-3819','UTC-8','3388 S Cactus Ave',193), (193000000000000201,193000000000100201,'SBD2','SBD2','15','CA','SAN BERNARDINO','92408-2805','UTC-8','1494 S WATERMAN AVE',187), (193000000000000202,193000000000100202,'SCK1','SCK1','20','CA','STOCKTON','95215','UTC-8','4532 NEWCASTLE RD',105), (193000000000000203,193000000000100203,'SCK3','SCK3','54','CA','MANTECA','95336','UTC-8','3565 N AIRPORT WAY MANTECA',68), (193000000000000204,193000000000100204,'SCK4','SCK4','10','CA','STOCKTON','95215-8354','UTC-8','6001 S AUSTIN RD',194), (193000000000000205,193000000000100205,'SCK8','SCK8','141','CA','OAKLEY','94561-2677','UTC-8','4700 WILBUR AVE',112), (193000000000000206,193000000000100206,'SDF1','SDF1',NULL,'KY','Campbellsville','42718','UTC-5','1050 South Columbia AVE',274), (193000000000000207,193000000000100207,'SDF2','SDF2',NULL,'KY','Watterson Park','40218','UTC-5','4360 Robards Ln',273), (193000000000000208,193000000000100208,'SDF4','SDF4',NULL,'KY','Shepherdsville','40165','UTC-5','376 Zappos.com Blvd',272), (193000000000000209,193000000000100209,'SDF6','SDF6',NULL,'KY','Shepherdsville','40165','UTC-5','271 Omega Pkwy',271), (193000000000000210,193000000000100210,'SDF7','SDF7',NULL,'KY','Shepherdsville','40165','UTC-5','300 Omicron Court',266), (193000000000000211,193000000000100211,'SDF8','SDF8',NULL,'IN','Jeffersonville','47130','UTC-5','900 Patrol Rd',281), (193000000000000212,193000000000100212,'SDF9','SDF9',NULL,'KY','Shepherdsville','40165','UTC-5','100 W. Thomas P. Echols Lane',265), (193000000000000213,193000000000100213,'SEA6','SEA6',NULL,'WA','South Seattle','98144','UTC-8','2646 Rainier Ave.',213), (193000000000000214,193000000000100214,'SEA8','SEA8',NULL,'WA','Northeast Bellevue','98005','UTC-8','1475 124th Avenue',212), (193000000000000215,193000000000100215,'SJC7','SJC7','30','CA','Tracy','95377-8906','UTC-8','188 Mountain House Parkway',303), (193000000000000296,193000000000100296,'SLC1N','SLC1N','196','UT','Salt Lake City','84116','UTC-7','990 N 6550 W',4), (193000000000000216,193000000000100216,'SLC2','SLC2','86','UT','West Jordan','84081','UTC-7','7148 W. Old Bingham Hwy',161), (193000000000000217,193000000000100217,'SLC3','SLC3','185','UT','SALT LAKE CITY','84116-4413','UTC-7','355 N JOHN GLENN RD',21), (193000000000000297,193000000000100297,'SMF1-AMAZON','SMF1-AMAZON',NULL,'CA','Sacramento','95835-9505','UTC-8','4900 W Elkhorn Blvd',298), (193000000000000298,193000000000100298,'SMF1-WALMART','SMF1-WALMART',NULL,'CA','Sacramento','95837','UTC-8','7000 Powerline Rd',173), (193000000000000218,193000000000100218,'SMF3','SMF3','9','CA','Stockton','95206-8202','UTC-8','3923 S B ST',192), (193000000000000219,193000000000100219,'SMF6','SMF6','28','CA','SACRAMENTO','95837-9109','UTC-8','4930 ALLBAUGH DR',113), (193000000000000299,193000000000100299,'SMF7','SMF7','53','','','','','',69), (193000000000000220,193000000000100220,'SNA4','SNA4','18','CA','Rialto','92376-3009','UTC-8','2496 W Walnut Ave',304), (193000000000000221,193000000000100221,'STL3','STL3','152','MO','REPUBLIC','65738-2779','UTC-6','3200 E SAWYER RD',117), (193000000000000222,193000000000100222,'STL4','STL4','115','IL','Edwardsville','62025-2815','UTC-6','3050 GATEWAY COMMERCE CENTER DR S',202), (193000000000000300,193000000000100300,'STL6','STL6','64','','','','','',66), (193000000000000223,193000000000100223,'SWF1','SWF1','134','NY','ROCK TAVERN','12575','UTC-5','635 INTERNATIONAL BLVD',130), (193000000000000224,193000000000100224,'SWF2','SWF2','62','NY','Hopewell Junction','12533','UTC-5','76 Patriot Way Hopewell Junction',84), (193000000000000301,193000000000100301,'SWF2-10710','SWF2-10710',NULL,'NY','Yonkers','10710','UTC-5','557 Tuckahoe Road',145), (193000000000000302,193000000000100302,'SWF2-12533','SWF2-12533',NULL,'NY','Hopewell Junction','12533-6159','UTC-5','76 Patriot Way',168), (193000000000000225,193000000000100225,'TCY1','TCY1','139','CA','STOCKTON','95206','UTC-8','2690 East Arch Airport Road',49), (193000000000000226,193000000000100226,'TCY2','TCY2','138','CA','STOCKTON','95215','UTC-8','6201 Newcastle Rd',51), (193000000000000227,193000000000100227,'TEB3','TEB3','47','NJ','Logan Township','08085','UTC-5','2651 Oldmans Creek Rd',258), (193000000000000228,193000000000100228,'TEB4','TEB4','55','NJ','PENNS GROVE','08069','UTC-5','747 COURSES LANDING RD',138), (193000000000000229,193000000000100229,'TEB6','TEB6','114','NJ','Cranbury','08512','UTC-5','22 Hightstown Cranbury State Road',257), (193000000000000230,193000000000100230,'TEB9','TEB9','40','NJ','Somerset','08873','UTC-5','601 Randolph Road',164), (193000000000000231,193000000000100231,'TMB8','TMB8','116','FL','HOMESTEAD','33032-8597','UTC-5','27505 SW 132nd AVE',73), (193000000000000303,193000000000100303,'TMB8-UPS','TMB8-UPS',NULL,'FL','Miami Gardens','33055','UTC-5','8675309 Jenny Lane',74), (193000000000000232,193000000000100232,'TOL3','TOL3','173','OH','PERRYSBURG','43551','UTC-5','9240 Fremont Pike 43551-4210 - PERRYSBURG, OH - United States',26), (193000000000000233,193000000000100233,'TPA1','TPA1',NULL,'FL','Ruskin','33570','UTC-5','3350 Laurel Ridge Ave.',292), (193000000000000234,193000000000100234,'TPA2','TPA2','22','FL','Lakeland','33811','UTC-5','1760 County Line Rd',291), (193000000000000235,193000000000100235,'TPA3','TPA3','146','FL','Auburndale','33823','UTC-5','676 C. Fred Jones Boulevard',132), (193000000000000236,193000000000100236,'TUL1','TUL1',NULL,'KS','Coffeyville','67337','UTC-6','2654 North Highway 169',280), (193000000000000304,193000000000100304,'UPS','UPS','11','','','92337','','10767',56), (193000000000000237,193000000000100237,'VGT2','VGT2','6','NV','LAS VEGAS','89115-2013','UTC-8','6401 Howdy Wells Ave',180), (193000000000000305,193000000000100305,'VGT2-FEDEX','VGT2-FEDEX',NULL,'NV','LAS VEGAS','89115-201','UTC-8','6401 Howdy Wells Ave',120), (193000000000000306,193000000000100306,'VGT2-UPS','VGT2-UPS',NULL,'CA','March Air Reserve Base','92518','UTC-8','20701 Krameria Ave',178), (193000000000000238,193000000000100238,'VUBA','VUBA',NULL,'PA','Scranton','18512','UTC-5','1000 Keystone Industrial Park',236), (193000000000000239,193000000000100239,'VUGA','VUGA',NULL,'PA','Pittston','18643','UTC-5','508 Delaware Avenue West',235), (193000000000000240,193000000000100240,'WBW2','WBW2','119','PA','OLYPHANT','18447','UTC-5','1300 Corporate Way',90), (193000000000000241,193000000000100241,'XAV3','XAV3','180','GA','PORT WENTWORTH','31407','UTC-5','XAV3-120Crossgate Dr 31407-Port Wentworth, GA- United States',44), (193000000000000307,193000000000100307,'XBN3','XBN3','91','','','','','',61), (193000000000000308,193000000000100308,'XCH2','XCH2','87','','','','','',63), (193000000000000242,193000000000100242,'XEW5','XEW5','163','NJ','Cinnaminson','08077-2015','UTC-5','995 Taylors Ln 08077-2015',45), (193000000000000243,193000000000100243,'XIN5','XIN5','182','IN','Avon','46123-1700','UTC-5','8838 E COUNTY ROAD 100 S',33), (193000000000000244,193000000000100244,'XLX1','XLX1','160','MD','HAGERSTOWN','21740','UTC-5','1115 Wesel Blvd HAGERSTOWN,MD 21740,USA',42), (193000000000000245,193000000000100245,'XLX6','XLX6','166','NC','PINEVILLE','28134','UTC-5','12220 Carolina Logistics Drive PINEVILLE,NC 28134,USA',31), (193000000000000246,193000000000100246,'XLX7','XLX7','27','CA','VICTORVILLE','92394','UTC-8','18580 Gateway Dr,Victorville,CA 92394,USA',71), (193000000000000309,193000000000100309,'XMI3','XMI3','89','','','','','',62), (193000000000000247,193000000000100247,'XPB2','XPB2','165','FL','APOPKA','32712','UTC-5','XPB2 - 4661 Apopka Logistics Pkwy 32712 - Apopka, FL - United States',34), (193000000000000248,193000000000100248,'XRI3','XRI3','167','VA','Richmond','23234','UTC-5','4949 Commerce Rd, Richmond, VA 23234',32), (193000000000000249,193000000000100249,'XUSB','XUSB',NULL,'TX','Fort Worth','76155','UTC-6','14900 Frye Rd.',221), (193000000000000250,193000000000100250,'XUSC','XUSC',NULL,'PA','Carlisle','17013','UTC-5','40 Logistics Drive',234), (193000000000000251,193000000000100251,'XUSD','XUSD',NULL,'CA','Stockton','95206','UTC-8','1909 Zephyr St',305), (193000000000000252,193000000000100252,'XUSE','XUSE',NULL,'IN','Whitestown','46075','UTC-5','5100 S Indianapolis Rd',288), (193000000000000310,193000000000100310,'洛杉矶仓一件代发','洛杉矶仓一件代发','CADF','CA','RANCHO CUCAMONGA','91730','UTC-8','10767 7TH STREET',60), (193000000000000311,193000000000100311,'海外仓打托','海外仓打托','13','','','91730','','91730',50), (193000000000000312,193000000000100312,'私人仓','私人仓','14','','','私人仓','','私人仓',59), (193000000000000313,193000000000100313,'纽约仓','纽约仓',NULL,'NJ','New Jersey','08817','UTC-5','13、14号门75 Carter Dr Edison,',38), (193000000000000314,193000000000100314,'纽约仓一件代发','纽约仓一件代发','NJDF','NJ','New Jersey','08817','UTC-5','Door13-14,75 Carter Dr Edison,',37); 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) <> 314 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'FBA公共主数据源数量不是314'; END IF; IF EXISTS ( SELECT 1 FROM tmp_wh_fba_public_master_data WHERE TRIM(warehouse_code) = '' OR warehouse_code <> UPPER(TRIM(warehouse_code)) OR (internal_code IS NOT NULL AND internal_code NOT REGEXP '^[A-Z0-9]{1,16}$') ) 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, internal_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.internal_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 internal_code = VALUES(internal_code), 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) <> 314 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 NOT (warehouse.internal_code <=> source.internal_code) 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,193000000000200003,193000000000200004) AND capability.capability_code = 'RECEIVING' AND capability.enabled = 1 AND capability.deleted = 0) <> 4 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"); CALL unified_publish_fee_add_column('fl_fee_item', 'supplier_category', "VARCHAR(16) NULL COMMENT 'PAYABLE供应商类别约束;为空表示任意启用供应商' AFTER fee_type_name"); 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"); 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`, `source_fee_version`)'); 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; -- 系统计费使用的固定公共费用项目;不属于费用类型.xls 的110条受控种子。 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 193000000001000111, 0, 'FBA_HEAD_FREIGHT', '头程运费', 'INTERNATIONAL', 'FBA头程运费', 0, 0, 0, 1, 0, '系统计费发布使用的固定应收费用项目', 'ACTIVE', 0, 'system', '2026-08-14 00:00:00.000', 0, 'system', '2026-08-14 00:00:00.000' WHERE NOT EXISTS (SELECT 1 FROM fl_fee_item WHERE fee_code = 'FBA_HEAD_FREIGHT'); 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, deleted, version) SELECT 193000000001000112, 0, 'PRIVATE_LAST_MILE_DELIVERY', '尾程派送费', 'INTERNATIONAL', '私人仓尾程派送费', 0, 0, 1, 1, 0, '仅用于destinationType=PRIVATE的入库单', 'ACTIVE', 0, 'SYSTEM', CURRENT_TIMESTAMP(3), 0, 'SYSTEM', CURRENT_TIMESTAMP(3), 0, 0 WHERE NOT EXISTS (SELECT 1 FROM fl_fee_item WHERE fee_code = 'PRIVATE_LAST_MILE_DELIVERY'); -- ============================================================================ -- 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 '预装备注', confirmed_by BIGINT UNSIGNED NULL COMMENT '确认预装的操作人ID', confirmed_by_name VARCHAR(64) NULL COMMENT '确认预装的操作人姓名快照', confirmed_at DATETIME(3) 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='订单分柜预装清单'; DROP PROCEDURE IF EXISTS migrate_preload_confirmation_columns; DELIMITER $$ CREATE PROCEDURE migrate_preload_confirmation_columns() BEGIN IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='fl_preload_plan' AND column_name='confirmed_by') THEN ALTER TABLE fl_preload_plan ADD COLUMN confirmed_by BIGINT UNSIGNED NULL COMMENT '确认预装的操作人ID' AFTER remark; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='fl_preload_plan' AND column_name='confirmed_by_name') THEN ALTER TABLE fl_preload_plan ADD COLUMN confirmed_by_name VARCHAR(64) NULL COMMENT '确认预装的操作人姓名快照' AFTER confirmed_by; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='fl_preload_plan' AND column_name='confirmed_at') THEN ALTER TABLE fl_preload_plan ADD COLUMN confirmed_at DATETIME(3) NULL COMMENT '预装确认时间' AFTER confirmed_by_name; END IF; END$$ DELIMITER ; CALL migrate_preload_confirmation_columns(); DROP PROCEDURE IF EXISTS migrate_preload_confirmation_columns; 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_cabinet_evidence ( id BIGINT UNSIGNED NOT NULL COMMENT '柜次证据记录ID', container_booking_id BIGINT UNSIGNED NOT NULL COMMENT '证据唯一归属柜次ID', source_task_id BIGINT UNSIGNED NOT NULL COMMENT '上传来源PDA任务ID,仅用于审计', evidence_type VARCHAR(32) NOT NULL COMMENT '四步照片或带锁码照类型', file_id BIGINT UNSIGNED NOT NULL COMMENT 'File服务文件ID', file_name VARCHAR(255) NOT NULL COMMENT '文件名称快照', seal_no VARCHAR(64) NULL COMMENT '带锁码照对应锁码', remark VARCHAR(1000) NULL COMMENT '证据备注', status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE' COMMENT '证据状态:ACTIVE有效、VOID作废', active_flag TINYINT UNSIGNED NULL DEFAULT 1 COMMENT '有效唯一标识;作废后置NULL', operation_key VARCHAR(128) NOT NULL COMMENT '任务范围内幂等操作键', request_hash CHAR(64) NOT NULL COMMENT '规范化请求SHA-256摘要', void_reason VARCHAR(500) NULL COMMENT '作废原因', voided_by BIGINT UNSIGNED NULL COMMENT '作废操作人ID', voided_by_name VARCHAR(128) 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(128) NULL COMMENT '创建操作人名称快照', updated_by BIGINT UNSIGNED NOT NULL COMMENT '最后修改操作人ID', updated_by_name VARCHAR(128) 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_cabinet_evidence_active (container_booking_id,evidence_type,active_flag), UNIQUE KEY uk_cabinet_evidence_operation (source_task_id,operation_key), KEY idx_cabinet_evidence_container (container_booking_id,status), KEY idx_cabinet_evidence_seal (seal_no,active_flag), CONSTRAINT fk_cabinet_evidence_booking FOREIGN KEY (container_booking_id) REFERENCES fl_container_booking(id), CONSTRAINT fk_cabinet_evidence_task FOREIGN KEY (source_task_id) REFERENCES fl_loading_pick_task(id), CONSTRAINT chk_cabinet_evidence_type CHECK (evidence_type IN ('PHOTO_STEP_1','PHOTO_STEP_2','PHOTO_STEP_3','PHOTO_STEP_4','SEAL')), CONSTRAINT chk_cabinet_evidence_status CHECK (status IN ('ACTIVE','VOID')) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='柜次物理装柜证据;task仅记录来源审计'; DROP PROCEDURE IF EXISTS normalize_loading_cabinet_evidence_collation; DELIMITER $$ CREATE PROCEDURE normalize_loading_cabinet_evidence_collation() BEGIN IF EXISTS ( SELECT 1 FROM information_schema.tables WHERE table_schema='baiyue_firstleg' AND table_name='fl_loading_cabinet_evidence' AND table_collation<>'utf8mb4_0900_ai_ci' ) THEN ALTER TABLE fl_loading_cabinet_evidence CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci; END IF; END$$ DELIMITER ; CALL normalize_loading_cabinet_evidence_collation(); DROP PROCEDURE IF EXISTS normalize_loading_cabinet_evidence_collation; 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), (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) 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:assign', 'platform:firstleg:smart-loading:delete' ) 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 '每个尺寸方向的单侧加宽厘米数', default_calculation_mode VARCHAR(16) NOT NULL DEFAULT 'ACTUAL' COMMENT '订单外箱规默认计算方式:ACTUAL不调整、ADD或MULTIPLY', 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='客户固定四箱规尺寸配置'; DROP PROCEDURE IF EXISTS unified_dimension_config_add_default_mode; DELIMITER $$ CREATE PROCEDURE unified_dimension_config_add_default_mode() BEGIN IF NOT EXISTS ( SELECT 1 FROM information_schema.columns WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_customer_dimension_config' AND column_name = 'default_calculation_mode' ) THEN ALTER TABLE baiyue_firstleg.fl_customer_dimension_config ADD COLUMN default_calculation_mode VARCHAR(16) NOT NULL DEFAULT 'ADD' COMMENT '订单外箱规默认计算方式:ADD或MULTIPLY' AFTER edge_addition_cm; END IF; END$$ DELIMITER ; CALL unified_dimension_config_add_default_mode(); DROP PROCEDURE IF EXISTS unified_dimension_config_add_default_mode; -- 新结构首次部署或旧结构受控重建后,为所有现有企业客户补齐缺失箱规。 -- 复跑命中 (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), KEY idx_inbound_split_candidate (tenant_id, parent_inbound_id, source_inbound_detail_id), 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草稿、RELEASED已发布且永久只读', customer_id BIGINT UNSIGNED NULL COMMENT '可选客户ID快照', customer_name VARCHAR(128) 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 '船公司名称快照', carrier_code VARCHAR(16) NULL COMMENT '船司标准编码:MATSON/SML/ZIM/COSCO/EMC/HEDE/OTHER;发布前必填', 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 '当前确认预计到港时间', 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','RELEASED')), CONSTRAINT chk_ocean_booking_carrier_code CHECK (carrier_code IS NULL OR carrier_code IN ('MATSON','SML','ZIM','COSCO','EMC','HEDE','OTHER')), CONSTRAINT chk_ocean_booking_schedule CHECK (estimated_sailing_at IS NULL OR estimated_arrival_at IS NULL OR estimated_arrival_at >= estimated_sailing_at) ) ENGINE=InnoDB COMMENT='独立海运订舱聚合;柜次仍由fl_container_booking承载'; SET @ocean_add_carrier_code = ( SELECT IF(COUNT(*) = 0, 'ALTER TABLE fl_ocean_booking ADD COLUMN carrier_code VARCHAR(16) NULL COMMENT ''船司标准编码:MATSON/SML/ZIM/COSCO/EMC/HEDE/OTHER;发布前必填'' AFTER shipping_company_name', 'SELECT 1') FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='fl_ocean_booking' AND column_name='carrier_code' ); PREPARE ocean_add_carrier_code_stmt FROM @ocean_add_carrier_code; EXECUTE ocean_add_carrier_code_stmt; DEALLOCATE PREPARE ocean_add_carrier_code_stmt; SET @ocean_drop_carrier_code_check = ( SELECT IF(COUNT(*) > 0, 'ALTER TABLE fl_ocean_booking DROP CHECK chk_ocean_booking_carrier_code', 'SELECT 1') FROM information_schema.table_constraints WHERE constraint_schema=DATABASE() AND table_name='fl_ocean_booking' AND constraint_name='chk_ocean_booking_carrier_code' AND constraint_type='CHECK' ); PREPARE ocean_drop_carrier_code_check_stmt FROM @ocean_drop_carrier_code_check; EXECUTE ocean_drop_carrier_code_check_stmt; DEALLOCATE PREPARE ocean_drop_carrier_code_check_stmt; ALTER TABLE fl_ocean_booking ADD CONSTRAINT chk_ocean_booking_carrier_code CHECK (carrier_code IS NULL OR carrier_code IN ('MATSON','SML','ZIM','COSCO','EMC','HEDE','OTHER')); -- CREATE TABLE IF NOT EXISTS does not reconcile a database initialized before customer became optional. ALTER TABLE fl_ocean_booking MODIFY COLUMN customer_id BIGINT UNSIGNED NULL COMMENT '可选客户ID快照', MODIFY COLUMN customer_name VARCHAR(128) NULL COMMENT '可选客户名称快照'; -- Recreate triggers and the current two-state constraint on repeatable initialization. DROP TRIGGER IF EXISTS trg_ocean_booking_before_update; 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; 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; DROP TRIGGER IF EXISTS trg_ocean_history_before_delete; -- Existing CHECK constraints are not reconciled by CREATE TABLE IF NOT EXISTS. SET @ocean_drop_status_check = ( SELECT IF(COUNT(*) > 0, 'ALTER TABLE fl_ocean_booking DROP CHECK chk_ocean_booking_status', 'SELECT 1') FROM information_schema.table_constraints WHERE table_schema=DATABASE() AND table_name='fl_ocean_booking' AND constraint_name='chk_ocean_booking_status' ); PREPARE ocean_drop_status_stmt FROM @ocean_drop_status_check; EXECUTE ocean_drop_status_stmt; DEALLOCATE PREPARE ocean_drop_status_stmt; ALTER TABLE fl_ocean_booking MODIFY COLUMN service_term VARCHAR(32) NULL COMMENT '运输服务条款快照;发布前必填', MODIFY COLUMN freight_term VARCHAR(32) NULL COMMENT '海运费支付条款快照;发布前必填', ADD CONSTRAINT chk_ocean_booking_status CHECK (status IN ('DRAFT','RELEASED')); SET @ocean_drop_failure_reason = ( SELECT IF(COUNT(*) > 0, 'ALTER TABLE fl_ocean_booking DROP COLUMN failure_reason', 'SELECT 1') FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='fl_ocean_booking' AND column_name='failure_reason' ); PREPARE ocean_drop_failure_stmt FROM @ocean_drop_failure_reason; EXECUTE ocean_drop_failure_stmt; DEALLOCATE PREPARE ocean_drop_failure_stmt; 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) ON DELETE CASCADE, 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) ON DELETE CASCADE, 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) ON DELETE CASCADE, 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_document ( id BIGINT UNSIGNED NOT NULL COMMENT '订舱文件关联ID', ocean_booking_id BIGINT UNSIGNED NOT NULL COMMENT '所属海运订舱ID', file_id BIGINT UNSIGNED NOT NULL COMMENT 'File服务文件逻辑ID', document_type VARCHAR(32) NOT NULL COMMENT '文件类型:BOOKING_CONFIRMATION订舱确认、BILL_OF_LADING提单', file_name VARCHAR(255) NOT NULL COMMENT 'File返回的原始文件名快照', file_hash CHAR(64) NULL COMMENT 'File返回的SHA-256快照', size_bytes BIGINT UNSIGNED NULL COMMENT 'File返回的文件大小快照', content_type VARCHAR(128) NULL COMMENT 'File返回的内容类型快照', valid TINYINT(1) NOT NULL DEFAULT 1 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 '上传或关联时间', invalidated_by BIGINT UNSIGNED NULL COMMENT '作废操作人ID', invalidated_by_name VARCHAR(64) NULL COMMENT '作废操作人姓名快照', invalidated_at DATETIME(3) NULL COMMENT '作废时间', PRIMARY KEY(id), KEY idx_ocean_document_booking(ocean_booking_id,valid,uploaded_at,id), KEY idx_ocean_document_file(file_id), CONSTRAINT fk_ocean_document_booking FOREIGN KEY(ocean_booking_id) REFERENCES fl_ocean_booking(id) ON DELETE CASCADE, CONSTRAINT chk_ocean_document_type CHECK(document_type IN ('BOOKING_CONFIRMATION','BILL_OF_LADING')), CONSTRAINT chk_ocean_document_valid CHECK(valid IN (0,1)) ) 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) ON DELETE CASCADE ) 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) ON DELETE CASCADE, 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 = 'RELEASED' THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='已放舱订舱不可修改'; END IF; IF OLD.status <> NEW.status AND NOT ( (OLD.status='DRAFT' AND NEW.status='RELEASED')) 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) <> 'RELEASED' 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 ; DROP TRIGGER IF EXISTS trg_ocean_document_before_insert; DROP TRIGGER IF EXISTS trg_ocean_document_before_update; DROP TRIGGER IF EXISTS trg_ocean_document_before_delete; DELIMITER $$ CREATE TRIGGER trg_ocean_document_before_insert BEFORE INSERT ON fl_ocean_booking_document 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_document_before_update BEFORE UPDATE ON fl_ocean_booking_document 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; IF NEW.ocean_booking_id <> OLD.ocean_booking_id OR NEW.file_id <> OLD.file_id OR NEW.document_type <> OLD.document_type OR NEW.uploaded_by <> OLD.uploaded_by OR NEW.uploaded_at <> OLD.uploaded_at OR NEW.valid > OLD.valid THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='订舱文件只允许在草稿期作废'; END IF; END$$ CREATE TRIGGER trg_ocean_document_before_delete BEFORE DELETE ON fl_ocean_booking_document FOR EACH ROW BEGIN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='订舱文件关联不可物理删除'; END$$ DELIMITER ; USE baiyue_iam; DELETE rp FROM iam_role_permission rp WHERE rp.permission_id IN (5102141,5102143,5102144,5102145); DELETE FROM iam_permission WHERE id IN (5102141,5102143,5102144,5102145); 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 = 5102140 AND permission_code <> 'platform:firstleg:ocean-booking:list') OR (id = 5102142 AND permission_code <> 'platform:firstleg:ocean-booking:create') OR (id = 5102146 AND permission_code <> 'platform:firstleg:ocean-booking:release') OR (id = 5102147 AND permission_code <> 'platform:firstleg:ocean-booking:edit') OR (id = 5102148 AND permission_code <> 'platform:firstleg:ocean-booking:delete') OR (id = 5102149 AND permission_code <> 'platform:firstleg:ocean-booking:allocate') OR (permission_code LIKE 'platform:firstleg:ocean-booking:%' AND (tenant_id <> 0 OR app_code <> 'PLATFORM' OR id NOT IN (5102140,5102142,5102146,5102147,5102148,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), (5102142,0,'PLATFORM','BUTTON',5102140,'platform:firstleg:ocean-booking:create','创建订舱',NULL,'POST',20,'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:edit','编辑草稿',NULL,'PUT',70,'ACTIVE',1,0), (5102148,0,'PLATFORM','BUTTON',5102140,'platform:firstleg:ocean-booking:delete','删除草稿',NULL,'DELETE',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 '停用保留列;订舱级拖车单不写入、不读取', loading_plan_id BIGINT UNSIGNED NULL COMMENT '停用保留列;订舱级拖车单不写入、不读取', warehouse_id BIGINT UNSIGNED NULL COMMENT '预约到仓的国内收货仓逻辑ID', carrier_id BIGINT UNSIGNED NULL COMMENT '拖车服务商逻辑ID', carrier_name VARCHAR(128) NULL COMMENT '拖车服务商名称快照', bill_of_lading_no VARCHAR(128) NULL COMMENT '派车提单号快照;关联RELEASED订舱时由订舱事实源冻结', 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 '关联采购单号', 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指定', currency VARCHAR(8) NOT NULL DEFAULT 'RMB' COMMENT '应付金额币种;仅RMB或USD', status VARCHAR(32) NOT NULL DEFAULT 'DRAFT' COMMENT '拖车预约状态:DRAFT草稿、PENDING_DISPATCH待派车、DISPATCHED已派车、PICKUP_IN_PROGRESS提货中、ARRIVED_WAREHOUSE已到仓、LOADING装柜中、DEPARTED已离仓、COMPLETED已完成、RESCHEDULED已改约、EXCEPTION异常、CANCELLED已取消', active_ocean_booking_id BIGINT UNSIGNED GENERATED ALWAYS AS (CASE WHEN status NOT IN ('COMPLETED','CANCELLED','EXCEPTION') THEN ocean_booking_id ELSE NULL END) STORED COMMENT '非终态预约的订舱唯一键', 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), UNIQUE KEY uk_truck_active_ocean_booking(active_ocean_booking_id), KEY idx_truck_appointment_status(status,updated_at), KEY idx_truck_appointment_ocean(ocean_booking_id), CONSTRAINT chk_truck_appointment_status CHECK(status IN ('DRAFT','PENDING_DISPATCH','DISPATCHED','PICKUP_IN_PROGRESS','ARRIVED_WAREHOUSE','LOADING','DEPARTED','COMPLETED','RESCHEDULED','EXCEPTION','CANCELLED')), CONSTRAINT chk_truck_appointment_currency CHECK(currency IN ('RMB','USD')) ) 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 '附件类型:DISPATCH_ORDER派车单、DRIVER_DOCUMENT司机证件、VEHICLE_DOCUMENT车辆证件、OTHER其他;订舱确认与提单归订舱聚合', file_name VARCHAR(255) NULL COMMENT '上传时文件名快照', file_hash CHAR(64) NULL COMMENT '文件内容SHA-256摘要', visibility VARCHAR(24) NOT NULL DEFAULT 'PLATFORM' COMMENT '可见范围:PLATFORM仅平台内部', 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), CONSTRAINT chk_truck_attachment_type CHECK(attachment_type IN ('DISPATCH_ORDER','DRIVER_DOCUMENT','VEHICLE_DOCUMENT','OTHER')) ) 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( 'bill_of_lading_no', 'VARCHAR(128) NULL AFTER carrier_name' ); CALL phase3_truck_add_column( 'actual_arrived_warehouse_at', 'DATETIME(3) NULL AFTER planned_loading_at' ); CALL phase3_truck_add_column( 'actual_loading_at', 'DATETIME(3) NULL AFTER actual_arrived_warehouse_at' ); CALL phase3_truck_add_column( 'actual_departed_at', 'DATETIME(3) NULL AFTER actual_loading_at' ); CALL phase3_truck_add_column( 'active_ocean_booking_id', 'BIGINT UNSIGNED GENERATED ALWAYS AS (CASE WHEN status NOT IN (''COMPLETED'',''CANCELLED'',''EXCEPTION'') THEN ocean_booking_id ELSE NULL END) STORED COMMENT ''非终态预约的订舱唯一键'' AFTER status' ); DROP PROCEDURE IF EXISTS phase3_truck_add_column; SET @phase3_truck_drop_old_active_index = IF( EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='fl_truck_appointment' AND index_name='uk_truck_active_container' AND non_unique=0), 'ALTER TABLE fl_truck_appointment DROP INDEX uk_truck_active_container', 'SELECT 1' ); PREPARE phase3_truck_active_stmt FROM @phase3_truck_drop_old_active_index; EXECUTE phase3_truck_active_stmt; DEALLOCATE PREPARE phase3_truck_active_stmt; SET @phase3_truck_active_index = IF( EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='fl_truck_appointment' AND index_name='uk_truck_active_ocean_booking' AND non_unique=0), 'SELECT 1', 'ALTER TABLE fl_truck_appointment ADD UNIQUE KEY uk_truck_active_ocean_booking(active_ocean_booking_id)' ); PREPARE phase3_truck_active_stmt FROM @phase3_truck_active_index; EXECUTE phase3_truck_active_stmt; DEALLOCATE PREPARE phase3_truck_active_stmt; 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 = 5102150 AND permission_code <> 'platform:firstleg:truck-appointment:list') OR (id = 5102151 AND permission_code NOT IN ( 'platform:firstleg:truck-appointment:list', CONCAT('platform:firstleg:truck-appointment:', 'view'))) OR (id = 5102152 AND permission_code <> 'platform:firstleg:truck-appointment:create') OR (id = 5102153 AND permission_code <> 'platform:firstleg:truck-appointment:edit') OR (id = 5102154 AND permission_code <> 'platform:firstleg:truck-appointment:operate') 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), (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'); DELETE rp FROM iam_role_permission rp JOIN iam_permission p ON p.tenant_id=rp.tenant_id AND p.id=rp.permission_id WHERE p.id=5102151; DELETE FROM iam_permission WHERE id=5102151; 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 status='ACTIVE' AND deleted=0 AND id BETWEEN 5102140 AND 5102149 AND permission_code LIKE 'platform:firstleg:ocean-booking:%') <> 6 OR (SELECT COUNT(*) FROM baiyue_iam.iam_permission WHERE tenant_id=0 AND app_code='PLATFORM' AND status='ACTIVE' AND deleted=0 AND id BETWEEN 5102150 AND 5102154 AND permission_code LIKE 'platform:firstleg:truck-appointment:%') <> 4 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='统一迁移失败:订舱或拖车预约权限未完整写入'; END IF; IF (SELECT COUNT(DISTINCT CONCAT(r.role_code, '|', p.permission_code)) FROM baiyue_iam.iam_role_permission rp JOIN baiyue_iam.iam_role r ON r.id=rp.role_id AND r.tenant_id=rp.tenant_id JOIN baiyue_iam.iam_permission p ON p.id=rp.permission_id AND p.tenant_id=rp.tenant_id WHERE r.tenant_id=0 AND r.role_code IN ('PLATFORM_ADMIN','ORDER_OPERATOR') AND p.permission_code LIKE 'platform:firstleg:ocean-booking:%') <> 12 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:%') <> 8 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; DELETE role_permission FROM iam_role_permission role_permission JOIN iam_permission permission ON permission.id=role_permission.permission_id WHERE permission.tenant_id=0 AND permission.permission_code='platform:firstleg:port-schedule:view'; DELETE FROM iam_permission WHERE tenant_id=0 AND permission_code='platform:firstleg:port-schedule:view'; DROP PROCEDURE IF EXISTS assert_port_schedule_permission_ids_available; DELIMITER $$ CREATE PROCEDURE assert_port_schedule_permission_ids_available() BEGIN IF EXISTS ( SELECT 1 FROM iam_permission WHERE (id IN (5102180,5102182,5102183) AND permission_code <> CASE id WHEN 5102180 THEN 'platform:firstleg:port-schedule:list' WHEN 5102182 THEN 'platform:firstleg:port-schedule:create' WHEN 5102183 THEN 'platform:firstleg:port-schedule:edit' END) OR id=5102181 OR (permission_code LIKE 'platform:firstleg:port-schedule:%' AND (tenant_id <> 0 OR app_code <> 'PLATFORM' OR id NOT IN (5102180,5102182,5102183))) ) THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '三期港务船期权限迁移失败:5102180-5102183 ID 或权限码被其他语义占用'; END IF; END$$ DELIMITER ; CALL assert_port_schedule_permission_ids_available(); DROP PROCEDURE IF EXISTS assert_port_schedule_permission_ids_available; INSERT INTO iam_permission (id,tenant_id,app_code,permission_type,parent_id,permission_code,permission_name, route_path,http_method,sort_no,status,built_in,deleted) VALUES (5102180,0,'PLATFORM','MENU',5102000,'platform:firstleg:port-schedule:list','港务船期','/first-leg/port-schedules','GET',120,'ACTIVE',1,0), (5102182,0,'PLATFORM','BUTTON',5102180,'platform:firstleg:port-schedule:create','新增港务船期',NULL,'POST',20,'ACTIVE',1,0), (5102183,0,'PLATFORM','BUTTON',5102180,'platform:firstleg:port-schedule:edit','更新港务船期',NULL,'PUT',30,'ACTIVE',1,0) ON DUPLICATE KEY UPDATE tenant_id=0,app_code='PLATFORM',permission_type=VALUES(permission_type), permission_code=VALUES(permission_code),permission_name=VALUES(permission_name),parent_id=VALUES(parent_id), route_path=VALUES(route_path),http_method=VALUES(http_method),sort_no=VALUES(sort_no), status='ACTIVE',built_in=1,deleted=0; INSERT IGNORE INTO iam_role_permission(role_id,permission_id,tenant_id) SELECT role.id,permission.id,0 FROM iam_role role JOIN iam_permission permission ON permission.tenant_id=0 AND permission.app_code='PLATFORM' AND permission.permission_code LIKE 'platform:firstleg:port-schedule:%' WHERE role.tenant_id=0 AND role.role_code IN ('PLATFORM_ADMIN','ORDER_OPERATOR'); USE baiyue_firstleg; -- 来源:firstleg/src/main/resources/db/phase3-container-customs-schema.sql SET NAMES utf8mb4; USE baiyue_firstleg; CREATE TABLE IF NOT EXISTS fl_customs_container ( id BIGINT UNSIGNED NOT NULL COMMENT '整柜报关聚合ID', loading_plan_id BIGINT UNSIGNED NOT NULL COMMENT '已完成装柜计划ID;报关柜级聚合唯一键', container_booking_id BIGINT UNSIGNED NOT NULL COMMENT '物理货柜ID', status VARCHAR(40) NOT NULL COMMENT '报关状态:WAITING_CUSTOMER_DOCUMENTS待资料、READY_TO_START可开始、DECLARING申报中、COMPLETED已完成', started_by BIGINT UNSIGNED NULL COMMENT '开始报关操作人ID', started_by_name VARCHAR(64) NULL COMMENT '开始报关操作人名称快照', started_at DATETIME(3) 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_customs_container_plan(loading_plan_id), UNIQUE KEY uk_customs_container_booking(container_booking_id), KEY idx_customs_container_status(status,updated_at), CONSTRAINT chk_customs_container_status CHECK(status IN ('WAITING_CUSTOMER_DOCUMENTS','READY_TO_START','DECLARING','COMPLETED')) ) ENGINE=InnoDB COMMENT='装柜完成后的整柜出口报关聚合'; CREATE TABLE IF NOT EXISTS fl_customs_customer_document ( id BIGINT UNSIGNED NOT NULL COMMENT '客户报送资料ID', customs_container_id BIGINT UNSIGNED NOT NULL COMMENT '所属整柜报关聚合ID', tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户隔离键;只允许关联同客户退税票', file_id BIGINT UNSIGNED NOT NULL COMMENT 'File服务文件ID', file_name VARCHAR(255) NOT NULL COMMENT '文件名称快照', file_hash CHAR(64) NOT NULL COMMENT '文件SHA-256摘要', content_type VARCHAR(128) NULL COMMENT '文件媒体类型', size_bytes BIGINT UNSIGNED NOT NULL COMMENT '文件大小(字节)', status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE' COMMENT '资料状态:ACTIVE有效、DELETED已删除', uploaded_by BIGINT UNSIGNED NOT NULL COMMENT '上传操作人ID', uploaded_at DATETIME(3) NOT NULL COMMENT '上传时间', frozen_version INT UNSIGNED NULL COMMENT '开始报关时冻结的资料版本', frozen_at DATETIME(3) NULL COMMENT '资料冻结时间', deleted_by BIGINT UNSIGNED NULL COMMENT '删除操作人ID', deleted_at DATETIME(3) NULL COMMENT '删除时间', version INT UNSIGNED NOT NULL DEFAULT 1 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_customs_customer_document_container(customs_container_id,status,tenant_id), CONSTRAINT fk_customs_customer_document_container FOREIGN KEY(customs_container_id) REFERENCES fl_customs_container(id), CONSTRAINT chk_customs_customer_document_status CHECK(status IN('ACTIVE','DELETED')), CONSTRAINT chk_customs_customer_document_freeze CHECK((frozen_at IS NULL AND frozen_version IS NULL) OR (frozen_at IS NOT NULL AND frozen_version IS NOT NULL)) ) ENGINE=InnoDB COMMENT='客户整份退税报送资料逻辑根'; CREATE TABLE IF NOT EXISTS fl_customs_customer_document_inbound ( document_id BIGINT UNSIGNED NOT NULL COMMENT '客户报送资料ID', inbound_id BIGINT UNSIGNED NOT NULL COMMENT '覆盖入仓单ID', tenant_id BIGINT UNSIGNED NOT NULL COMMENT '覆盖入仓单客户ID', order_id BIGINT UNSIGNED NOT NULL COMMENT '覆盖订单ID', inbound_code VARCHAR(40) NOT NULL COMMENT '覆盖入仓码快照', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '关联创建时间', PRIMARY KEY(document_id,inbound_id), KEY idx_customs_document_inbound(inbound_id,tenant_id), CONSTRAINT fk_customs_document_inbound_document FOREIGN KEY(document_id) REFERENCES fl_customs_customer_document(id) ) ENGINE=InnoDB COMMENT='客户报送资料与当前柜退税入仓码多对多关系'; CREATE TABLE IF NOT EXISTS fl_customs_customer_document_version ( id BIGINT UNSIGNED NOT NULL COMMENT '资料版本记录ID', document_id BIGINT UNSIGNED NOT NULL COMMENT '客户报送资料ID', document_version INT UNSIGNED NOT NULL COMMENT '资料版本号', file_id BIGINT UNSIGNED NOT NULL COMMENT '本版本File服务文件ID', file_name VARCHAR(255) NOT NULL COMMENT '本版本文件名称快照', file_hash CHAR(64) NOT NULL COMMENT '本版本文件SHA-256摘要', content_type VARCHAR(128) NULL COMMENT '本版本文件媒体类型', size_bytes BIGINT UNSIGNED NOT NULL COMMENT '本版本文件大小(字节)', scope_snapshot_json JSON NOT NULL COMMENT '本版本覆盖入仓码范围快照', action VARCHAR(24) NOT NULL COMMENT '版本动作:UPLOAD上传、REPLACE替换、SCOPE_CHANGE范围变化、DELETE删除', operator_id BIGINT UNSIGNED NOT NULL COMMENT '操作人ID', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '版本创建时间', PRIMARY KEY(id), UNIQUE KEY uk_customs_customer_document_version(document_id,document_version), CONSTRAINT fk_customs_customer_document_version FOREIGN KEY(document_id) REFERENCES fl_customs_customer_document(id), CONSTRAINT chk_customs_customer_document_version_action CHECK(action IN('UPLOAD','REPLACE','SCOPE_CHANGE','DELETE')) ) ENGINE=InnoDB COMMENT='客户报送资料文件与覆盖范围不可变版本'; CREATE TABLE IF NOT EXISTS fl_customs_frozen_document_inbound ( document_id BIGINT UNSIGNED NOT NULL COMMENT '冻结的客户报送资料ID', inbound_id BIGINT UNSIGNED NOT NULL COMMENT '冻结覆盖入仓单ID', tenant_id BIGINT UNSIGNED NOT NULL COMMENT '冻结覆盖客户ID', order_id BIGINT UNSIGNED NOT NULL COMMENT '冻结覆盖订单ID', inbound_code VARCHAR(40) NOT NULL COMMENT '冻结覆盖入仓码快照', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '冻结关联创建时间', PRIMARY KEY(document_id,inbound_id), CONSTRAINT fk_customs_frozen_document FOREIGN KEY(document_id) REFERENCES fl_customs_customer_document(id) ) ENGINE=InnoDB COMMENT='开始报关时冻结的客户资料覆盖范围'; CREATE TABLE IF NOT EXISTS fl_customs_completion_document ( id BIGINT UNSIGNED NOT NULL COMMENT '报关完成文件ID', source_document_id BIGINT UNSIGNED NOT NULL COMMENT '一份冻结客户资料严格对应一份完成文件逻辑根', file_id BIGINT UNSIGNED NOT NULL COMMENT 'File服务文件ID', file_name VARCHAR(255) NOT NULL COMMENT '完成文件名称快照', file_hash CHAR(64) NOT NULL COMMENT '完成文件SHA-256摘要', content_type VARCHAR(128) NULL COMMENT '完成文件媒体类型', size_bytes BIGINT UNSIGNED NOT NULL COMMENT '完成文件大小(字节)', uploaded_by BIGINT UNSIGNED NOT NULL COMMENT '上传操作人ID', uploaded_by_name VARCHAR(64) NULL COMMENT '上传操作人名称快照', uploaded_at DATETIME(3) NOT NULL COMMENT '上传时间', version INT UNSIGNED NOT NULL DEFAULT 1 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_customs_completion_source(source_document_id), CONSTRAINT fk_customs_completion_source FOREIGN KEY(source_document_id) REFERENCES fl_customs_customer_document(id) ) ENGINE=InnoDB COMMENT='报关完成文件当前版本'; CREATE TABLE IF NOT EXISTS fl_customs_completion_document_version ( id BIGINT UNSIGNED NOT NULL COMMENT '完成文件版本记录ID', completion_document_id BIGINT UNSIGNED NOT NULL COMMENT '报关完成文件ID', document_version INT UNSIGNED NOT NULL COMMENT '完成文件版本号', file_id BIGINT UNSIGNED NOT NULL COMMENT '本版本File服务文件ID', file_name VARCHAR(255) NOT NULL COMMENT '本版本文件名称快照', file_hash CHAR(64) NOT NULL COMMENT '本版本文件SHA-256摘要', content_type VARCHAR(128) NULL COMMENT '本版本文件媒体类型', size_bytes BIGINT UNSIGNED 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), UNIQUE KEY uk_customs_completion_version(completion_document_id,document_version), CONSTRAINT fk_customs_completion_version FOREIGN KEY(completion_document_id) REFERENCES fl_customs_completion_document(id) ) ENGINE=InnoDB COMMENT='报关完成文件不可变覆盖版本'; CREATE TABLE IF NOT EXISTS fl_customs_container_history ( id BIGINT UNSIGNED NOT NULL COMMENT '报关历史记录ID', customs_container_id BIGINT UNSIGNED NOT NULL COMMENT '所属整柜报关聚合ID', action VARCHAR(40) NOT NULL COMMENT '报关操作类型', from_status VARCHAR(40) NULL COMMENT '变更前报关状态', to_status VARCHAR(40) 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_customs_container_history(customs_container_id,created_at,id), CONSTRAINT fk_customs_container_history FOREIGN KEY(customs_container_id) REFERENCES fl_customs_container(id) ) ENGINE=InnoDB COMMENT='整柜报关状态与系统触发历史'; CREATE TABLE IF NOT EXISTS fl_customs_command ( id BIGINT UNSIGNED NOT NULL COMMENT '报关幂等命令ID', actor_id BIGINT UNSIGNED NOT NULL COMMENT '命令操作人ID', action VARCHAR(80) 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处理中、SUCCESS成功', 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_command(actor_id,action,idempotency_key), CONSTRAINT chk_customs_command_status CHECK(status IN('PROCESSING','SUCCESS')) ) ENGINE=InnoDB COMMENT='整柜报关写命令幂等事实'; USE baiyue_iam; DELETE rp FROM iam_role_permission rp WHERE rp.permission_id IN(5102171,5102173,5102174,5102175); DELETE FROM iam_permission WHERE id IN(5102171,5102173,5102174,5102175) AND permission_code IN('platform:firstleg:customs:view','platform:firstleg:customs:review','platform:firstleg:customs:submit','platform:firstleg:customs:receipt'); INSERT INTO iam_permission(id,tenant_id,app_code,permission_type,parent_id,permission_code,permission_name, route_path,http_method,sort_no,status,built_in,deleted) VALUES (5102170,0,'PLATFORM','MENU',5102000,'platform:firstleg:customs:list','整柜报关','/first-leg/customs','GET',100,'ACTIVE',1,0), (5102172,0,'PLATFORM','BUTTON',5102170,'platform:firstleg:customs:manage','维护整柜报关',NULL,'POST',20,'ACTIVE',1,0) ON DUPLICATE KEY UPDATE permission_code=VALUES(permission_code),permission_name=VALUES(permission_name), parent_id=VALUES(parent_id),route_path=VALUES(route_path),http_method=VALUES(http_method),status='ACTIVE',deleted=0; INSERT IGNORE INTO iam_role_permission(role_id,permission_id,tenant_id) SELECT r.id,p.id,0 FROM iam_role r JOIN iam_permission p ON p.tenant_id=0 AND p.app_code='PLATFORM' AND p.permission_code IN('platform:firstleg:customs:list','platform:firstleg:customs:manage') WHERE r.tenant_id=0 AND r.role_code IN('PLATFORM_ADMIN','ORDER_OPERATOR'); USE baiyue_firstleg; /* 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_container','fl_customs_customer_document','fl_customs_customer_document_inbound', 'fl_customs_customer_document_version','fl_customs_frozen_document_inbound', 'fl_customs_completion_document','fl_customs_completion_document_version','fl_customs_container_history','fl_customs_command', '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')) <> 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_loading_cabinet_evidence' AND column_name IN ('container_booking_id','source_task_id','active_flag','operation_key','request_hash')) <> 5 OR (SELECT table_collation FROM information_schema.tables WHERE table_schema='baiyue_firstleg' AND table_name='fl_loading_cabinet_evidence') <> 'utf8mb4_0900_ai_ci' OR (SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema='baiyue_firstleg' AND table_name='fl_loading_cabinet_evidence' AND index_name='uk_cabinet_evidence_active' AND non_unique=0) <> 3 OR (SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema='baiyue_firstleg' AND table_name='fl_loading_cabinet_evidence' AND index_name='uk_cabinet_evidence_operation' AND non_unique=0) <> 2 OR (SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema='baiyue_firstleg' AND table_name='fl_outbound_dispatch' AND index_name='uk_outbound_dispatch_owner' AND non_unique=0) <> 3 OR (SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema='baiyue_firstleg' AND table_name='fl_customs_container' AND index_name='uk_customs_container_plan' AND non_unique=0) <> 1 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_customer_document_container', 'fk_customs_document_inbound_document','fk_customs_customer_document_version','fk_customs_frozen_document', 'fk_customs_completion_source','fk_customs_completion_version','fk_customs_container_history', '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 IN (5102170,5102172) AND permission_code LIKE 'platform:firstleg:customs:%') <> 2 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:%') <> 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 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:%')) <> 28 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 整票库位块直接使用 Firstleg 托盘分配快照;旧明细级释放策略字段不再保留。 USE baiyue_firstleg; SET @phase3_pda_drop_release_locations = IF((SELECT COUNT(*) FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='fl_loading_pick_item' AND column_name='release_location_ids') > 0, 'ALTER TABLE fl_loading_pick_item DROP COLUMN release_location_ids', 'SELECT 1'); PREPARE phase3_pda_drop_release_locations_stmt FROM @phase3_pda_drop_release_locations; EXECUTE phase3_pda_drop_release_locations_stmt; DEALLOCATE PREPARE phase3_pda_drop_release_locations_stmt; SET @phase3_pda_drop_release_mode = IF((SELECT COUNT(*) FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='fl_loading_pick_item' AND column_name='release_mode') > 0, 'ALTER TABLE fl_loading_pick_item DROP COLUMN release_mode', 'SELECT 1'); PREPARE phase3_pda_drop_release_mode_stmt FROM @phase3_pda_drop_release_mode; EXECUTE phase3_pda_drop_release_mode_stmt; DEALLOCATE PREPARE phase3_pda_drop_release_mode_stmt; 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_inbound_pallet_allocation', 'block_id', "BIGINT UNSIGNED NOT NULL COMMENT 'Warehouse整票连续库位块ID' AFTER allocation_no" ); CALL phase3_pda_add_column( 'fl_inbound_pallet_allocation', 'slot_no', "INT UNSIGNED NOT NULL COMMENT '连续块内稳定槽位序号' AFTER block_id" ); CALL phase3_pda_add_column( 'fl_inbound_pallet_allocation', 'weight_result_id', "BIGINT UNSIGNED NULL COMMENT '已绑定称重结果ID;预留空槽为空' AFTER slot_no" ); CALL phase3_pda_add_column( 'fl_inbound_pallet_allocation', 'pallet_sequence', "INT UNSIGNED NULL COMMENT '已绑定称重托盘在整票内的稳定顺序;空槽为空' AFTER weight_result_id" ); CALL phase3_pda_add_column( 'fl_inbound_pallet_allocation', 'shared_overflow', "TINYINT(1) NOT NULL DEFAULT 0 COMMENT '普通连续位不足时是否使用共享A0' AFTER location_code" ); CALL phase3_pda_add_column( 'fl_inbound_pallet_allocation', 'reservation_id', "BIGINT UNSIGNED NOT NULL COMMENT '仓库预占资源唯一ID,A0共享场景用于区分槽位' AFTER shared_overflow" ); CALL phase3_pda_add_column( 'fl_inbound_pallet_allocation', 'slot_status', "VARCHAR(24) NOT NULL DEFAULT 'RESERVED_EMPTY' COMMENT '槽位状态:RESERVED_EMPTY、BOUND、TRANSFERRED、RELEASED' AFTER reservation_id" ); DROP PROCEDURE IF EXISTS phase3_pda_add_column; -- 当前开发基线直接收敛为整票块槽位;旧预占单号不再保留。 ALTER TABLE fl_inbound_pallet_allocation MODIFY COLUMN pallet_id BIGINT UNSIGNED NULL COMMENT '仓库服务托盘ID;预留空槽为空', MODIFY COLUMN pallet_code VARCHAR(50) NULL COMMENT '托盘编码快照;预留空槽为空', MODIFY COLUMN pallet_capacity_cbm DECIMAL(18,4) NULL COMMENT '托盘可用容积快照;预留空槽为空', MODIFY COLUMN reservation_id BIGINT UNSIGNED NOT NULL COMMENT '仓库预占资源唯一ID,A0共享场景用于区分槽位'; SET @sql = IF((SELECT COUNT(*) FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='fl_inbound_pallet_allocation' AND column_name='reservation_no') > 0, 'ALTER TABLE fl_inbound_pallet_allocation DROP COLUMN reservation_no', 'SELECT 1'); PREPARE statement FROM @sql; EXECUTE statement; DEALLOCATE PREPARE statement; SET @sql = IF((SELECT GROUP_CONCAT(column_name ORDER BY seq_in_index) FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='fl_inbound_pallet_allocation' AND index_name='uk_pallet_allocation_slot') <> 'tenant_id,inbound_id,block_id,slot_no', 'ALTER TABLE fl_inbound_pallet_allocation DROP INDEX uk_pallet_allocation_slot, ADD UNIQUE KEY uk_pallet_allocation_slot (tenant_id,inbound_id,block_id,slot_no)', IF((SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='fl_inbound_pallet_allocation' AND index_name='uk_pallet_allocation_slot') = 0, 'ALTER TABLE fl_inbound_pallet_allocation ADD UNIQUE KEY uk_pallet_allocation_slot (tenant_id,inbound_id,block_id,slot_no)', 'SELECT 1')); PREPARE statement FROM @sql; EXECUTE statement; DEALLOCATE PREPARE statement; SET @sql = IF((SELECT GROUP_CONCAT(column_name ORDER BY seq_in_index) FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='fl_inbound_pallet_allocation' AND index_name='uk_pallet_allocation_weight') <> 'tenant_id,inbound_id,weight_result_id', 'ALTER TABLE fl_inbound_pallet_allocation DROP INDEX uk_pallet_allocation_weight, ADD UNIQUE KEY uk_pallet_allocation_weight (tenant_id,inbound_id,weight_result_id)', IF((SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='fl_inbound_pallet_allocation' AND index_name='uk_pallet_allocation_weight') = 0, 'ALTER TABLE fl_inbound_pallet_allocation ADD UNIQUE KEY uk_pallet_allocation_weight (tenant_id,inbound_id,weight_result_id)', 'SELECT 1')); PREPARE statement FROM @sql; EXECUTE statement; DEALLOCATE PREPARE statement; SET @sql = IF((SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='fl_inbound_pallet_allocation' AND index_name='idx_allocation_reservation') = 0, 'ALTER TABLE fl_inbound_pallet_allocation ADD KEY idx_allocation_reservation (reservation_id)', 'SELECT 1'); PREPARE statement FROM @sql; EXECUTE statement; DEALLOCATE PREPARE statement; -- 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 '船公司标准编码;OTHER表示人工跟踪', 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 '船司访问方式;MANUAL表示仅人工补录节点', status VARCHAR(24) NOT NULL COMMENT '任务操作状态:READY、MANUAL_REQUIRED、STOPPED', trigger_source VARCHAR(32) NOT NULL DEFAULT 'MANUAL' COMMENT '任务触发来源:OUTBOUND_CONFIRM或MANUAL', latest_standard_node VARCHAR(32) NULL COMMENT '最近实际标准船节点;预计事实不推进', latest_node_observed_at DATETIME(3) NULL COMMENT '最近实际标准节点发生时间', stopped_at DATETIME(3) NULL COMMENT '任务停止时间', stop_reason VARCHAR(500) NULL COMMENT '任务停止原因', lease_owner VARCHAR(64) NULL COMMENT '当前调度租约持有者', lease_until DATETIME(3) NULL COMMENT '当前调度租约过期时间', consecutive_failures INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '当前正常查询槽位内的连续技术失败次数', window_started_at DATETIME(3) NOT NULL COMMENT '当前正常查询槽位时间', attempt_count INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '当前正常查询槽位已调用次数;最多3次', next_allowed_at DATETIME(3) 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), 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','OTHER')), 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','PUBLIC_WEB_PROXY','PUBLIC_HTML','HCAPTCHA','OFFICIAL_API','MANUAL')), CONSTRAINT chk_carrier_schedule_query_status CHECK (status IN ('READY','MANUAL_REQUIRED','STOPPED')), CONSTRAINT chk_carrier_schedule_query_node CHECK (latest_standard_node IS NULL OR latest_standard_node IN ('DEPARTED_WAREHOUSE','AT_ORIGIN_PORT','LOADED_ON_VESSEL','DEPARTED_ORIGIN_PORT','IN_TRANSIT', 'ARRIVED_DESTINATION_PORT','DISCHARGED_FROM_VESSEL','PICKUP_READY','PICKED_UP','EMPTY_RETURNED')), 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 '供应商调用结果', 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 ('SUCCESS','NO_CHANGE','RETRYABLE_FAILURE','PERMANENT_FAILURE','MANUAL_REQUIRED')) ) 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 '查询任务业务历史动作', 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','AUTO_CREATE','MANUAL_RESULT','MANUAL_NODE','NODE_OBSERVED', 'EXCEPTION_OBSERVED','PROVIDER_QUERY','STOP')) ) ENGINE=InnoDB COMMENT='船司查询任务不可变历史'; CREATE TABLE IF NOT EXISTS fl_carrier_schedule_node_fact ( id BIGINT UNSIGNED NOT NULL COMMENT '标准船节点事实ID', query_id BIGINT UNSIGNED NOT NULL COMMENT '所属查询任务ID', event_key VARCHAR(192) NOT NULL COMMENT '供应商编码与供应商事件ID组成的幂等键', provider_code VARCHAR(32) NOT NULL COMMENT '轨迹供应商编码', provider_event_id VARCHAR(128) NOT NULL COMMENT '供应商稳定事件ID', provider_node_code VARCHAR(64) NOT NULL COMMENT '供应商原始节点编码', standard_node VARCHAR(32) NOT NULL COMMENT '标准船节点', node_sequence INT UNSIGNED NOT NULL COMMENT '标准节点单调序号', observed_at DATETIME(3) NOT NULL COMMENT '供应商观测时间', estimated_at DATETIME(3) NULL COMMENT '供应商预计发生时间', actual_at DATETIME(3) NULL COMMENT '供应商确认实际发生时间', location_code VARCHAR(32) NULL COMMENT '节点地点编码', location_name VARCHAR(128) NULL COMMENT '节点地点名称', source_reference VARCHAR(500) NULL COMMENT '可审计来源引用', raw_payload_hash CHAR(64) NOT NULL COMMENT '原始响应SHA-256;不保存敏感原文', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '事实入库时间', PRIMARY KEY (id), UNIQUE KEY uk_carrier_schedule_node_event (query_id,event_key), KEY idx_carrier_schedule_node_timeline (query_id,node_sequence,observed_at,id), CONSTRAINT fk_carrier_schedule_node_query FOREIGN KEY (query_id) REFERENCES fl_carrier_schedule_query(id), CONSTRAINT chk_carrier_schedule_node_standard CHECK (standard_node IN ('DEPARTED_WAREHOUSE','AT_ORIGIN_PORT','LOADED_ON_VESSEL','DEPARTED_ORIGIN_PORT', 'IN_TRANSIT','ARRIVED_DESTINATION_PORT','DISCHARGED_FROM_VESSEL','PICKUP_READY','PICKED_UP','EMPTY_RETURNED')), CONSTRAINT chk_carrier_schedule_node_sequence CHECK (node_sequence IN (10,20,30,40,50,60,70,80,90,100)) ) ENGINE=InnoDB COMMENT='供应商标准船节点不可变事实'; CREATE TABLE IF NOT EXISTS fl_carrier_schedule_exception_fact ( id BIGINT UNSIGNED NOT NULL COMMENT '船节点异常事实ID', query_id BIGINT UNSIGNED NOT NULL COMMENT '所属查询任务ID', event_key VARCHAR(192) NOT NULL COMMENT '供应商编码与供应商事件ID组成的幂等键', provider_code VARCHAR(32) NOT NULL COMMENT '供应商编码', provider_event_id VARCHAR(128) NOT NULL COMMENT '供应商稳定事件ID', provider_status VARCHAR(128) NOT NULL COMMENT '供应商原始异常状态', exception_code VARCHAR(32) NOT NULL COMMENT '平台异常编码', active TINYINT(1) NOT NULL COMMENT '1异常发生;0异常解除', observed_at DATETIME(3) NOT NULL COMMENT '供应商观测时间', location_code VARCHAR(32) NULL COMMENT '地点编码', location_name VARCHAR(128) NULL COMMENT '地点名称', source_reference VARCHAR(500) NULL COMMENT '可审计来源引用', raw_payload_hash CHAR(64) NOT NULL COMMENT '原始响应SHA-256', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '事实入库时间', PRIMARY KEY (id), UNIQUE KEY uk_carrier_schedule_exception_event (query_id,event_key), KEY idx_carrier_schedule_exception_timeline (query_id,observed_at,id), CONSTRAINT fk_carrier_schedule_exception_query FOREIGN KEY (query_id) REFERENCES fl_carrier_schedule_query(id), CONSTRAINT chk_carrier_schedule_exception_code CHECK (exception_code IN ('CUSTOMS_HOLD','TERMINAL_HOLD','DOCUMENT_HOLD','UNKNOWN_HOLD')), CONSTRAINT chk_carrier_schedule_exception_active CHECK (active IN (0,1)) ) 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='船司查询写操作幂等与请求摘要'; -- CREATE TABLE IF NOT EXISTS 不会更新已有表的 CHECK。历史开发库仍可能只允许 -- CREATE/ATTEMPT/MANUAL_RESULT,必须在人工节点和自动节点写入前收敛为当前动作集。 DROP PROCEDURE IF EXISTS migrate_carrier_schedule_history_action_check; DELIMITER $$ CREATE PROCEDURE migrate_carrier_schedule_history_action_check() BEGIN IF EXISTS (SELECT 1 FROM information_schema.table_constraints WHERE constraint_schema=DATABASE() AND table_name='fl_carrier_schedule_query_history' AND constraint_name='chk_carrier_schedule_history_action' AND constraint_type='CHECK') THEN ALTER TABLE fl_carrier_schedule_query_history DROP CHECK chk_carrier_schedule_history_action; END IF; ALTER TABLE fl_carrier_schedule_query_history ADD CONSTRAINT chk_carrier_schedule_history_action CHECK (action IN ('CREATE','AUTO_CREATE','MANUAL_RESULT','MANUAL_NODE','NODE_OBSERVED', 'EXCEPTION_OBSERVED','PROVIDER_QUERY','STOP')); END$$ DELIMITER ; CALL migrate_carrier_schedule_history_action_check(); DROP PROCEDURE IF EXISTS migrate_carrier_schedule_history_action_check; 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; DROP TRIGGER IF EXISTS trg_carrier_schedule_node_before_update; DROP TRIGGER IF EXISTS trg_carrier_schedule_node_before_delete; DROP TRIGGER IF EXISTS trg_carrier_schedule_exception_before_update; DROP TRIGGER IF EXISTS trg_carrier_schedule_exception_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$$ CREATE TRIGGER trg_carrier_schedule_node_before_update BEFORE UPDATE ON fl_carrier_schedule_node_fact FOR EACH ROW BEGIN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='标准船节点事实不可修改'; END$$ CREATE TRIGGER trg_carrier_schedule_node_before_delete BEFORE DELETE ON fl_carrier_schedule_node_fact FOR EACH ROW BEGIN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='标准船节点事实不可删除'; END$$ CREATE TRIGGER trg_carrier_schedule_exception_before_update BEFORE UPDATE ON fl_carrier_schedule_exception_fact FOR EACH ROW BEGIN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='船节点异常事实不可修改'; END$$ CREATE TRIGGER trg_carrier_schedule_exception_before_delete BEFORE DELETE ON fl_carrier_schedule_exception_fact FOR EACH ROW BEGIN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='船节点异常事实不可删除'; END$$ DELIMITER ; USE baiyue_iam; DELETE role_permission FROM iam_role_permission role_permission JOIN iam_permission permission ON permission.id=role_permission.permission_id WHERE permission.tenant_id=0 AND permission.permission_code IN ('platform:firstleg:port-schedule-query:view', 'platform:firstleg:port-schedule-query:attempt'); DELETE FROM iam_permission WHERE tenant_id=0 AND permission_code IN ('platform:firstleg:port-schedule-query:view', 'platform:firstleg:port-schedule-query:attempt'); 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 IN (5102184,5102186,5102188) AND permission_code <> CASE id WHEN 5102184 THEN 'platform:firstleg:port-schedule-query:list' WHEN 5102186 THEN 'platform:firstleg:port-schedule-query:create' WHEN 5102188 THEN 'platform:firstleg:port-schedule-query:manual' END) OR id IN (5102185,5102187) OR (permission_code LIKE 'platform:firstleg:port-schedule-query:%' AND (tenant_id<>0 OR app_code<>'PLATFORM' OR id NOT IN (5102184,5102186,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), (5102186,0,'PLATFORM','BUTTON',5102180,'platform:firstleg:port-schedule-query:create','创建船司任务',NULL,'POST',60,'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', 'fl_carrier_schedule_node_fact','fl_carrier_schedule_exception_fact')) <> 6 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='船司查询任务表未完整创建'; END IF; IF (SELECT COUNT(*) FROM baiyue_iam.iam_permission WHERE tenant_id=0 AND id IN (5102184,5102186,5102188) AND permission_code LIKE 'platform:firstleg:port-schedule-query:%') <> 3 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:%') <> 6 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; DROP PROCEDURE IF EXISTS migrate_carrier_schedule_retry_columns; DELIMITER $$ CREATE PROCEDURE migrate_carrier_schedule_retry_columns() BEGIN IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='fl_carrier_schedule_query' AND column_name='trigger_source') THEN ALTER TABLE fl_carrier_schedule_query ADD COLUMN trigger_source VARCHAR(32) NOT NULL DEFAULT 'MANUAL' COMMENT '任务触发来源:OUTBOUND_CONFIRM或MANUAL'; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='fl_carrier_schedule_query' AND column_name='latest_standard_node') THEN ALTER TABLE fl_carrier_schedule_query ADD COLUMN latest_standard_node VARCHAR(32) NULL COMMENT '最近实际标准船节点;预计事实不推进'; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='fl_carrier_schedule_query' AND column_name='latest_node_observed_at') THEN ALTER TABLE fl_carrier_schedule_query ADD COLUMN latest_node_observed_at DATETIME(3) NULL COMMENT '最近实际标准节点发生时间'; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='fl_carrier_schedule_query' AND column_name='stopped_at') THEN ALTER TABLE fl_carrier_schedule_query ADD COLUMN stopped_at DATETIME(3) NULL COMMENT '任务停止时间'; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='fl_carrier_schedule_query' AND column_name='stop_reason') THEN ALTER TABLE fl_carrier_schedule_query ADD COLUMN stop_reason VARCHAR(500) NULL COMMENT '任务停止原因'; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='fl_carrier_schedule_query' AND column_name='lease_owner') THEN ALTER TABLE fl_carrier_schedule_query ADD COLUMN lease_owner VARCHAR(64) NULL COMMENT '当前调度租约持有者'; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='fl_carrier_schedule_query' AND column_name='lease_until') THEN ALTER TABLE fl_carrier_schedule_query ADD COLUMN lease_until DATETIME(3) NULL COMMENT '当前调度租约过期时间'; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='fl_carrier_schedule_query' AND column_name='consecutive_failures') THEN ALTER TABLE fl_carrier_schedule_query ADD COLUMN consecutive_failures INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '当前正常查询槽位内的连续技术失败次数'; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='fl_carrier_schedule_query' AND column_name='window_started_at') THEN ALTER TABLE fl_carrier_schedule_query ADD COLUMN window_started_at DATETIME(3) NULL COMMENT '当前正常查询槽位时间'; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='fl_carrier_schedule_query' AND column_name='attempt_count') THEN ALTER TABLE fl_carrier_schedule_query ADD COLUMN attempt_count INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '当前正常查询槽位已调用次数;最多3次'; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='fl_carrier_schedule_query' AND column_name='next_allowed_at') THEN ALTER TABLE fl_carrier_schedule_query ADD COLUMN next_allowed_at DATETIME(3) NULL COMMENT '下一正常槽位或槽内技术退避时间;停止后为空'; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='fl_carrier_schedule_query' AND index_name='idx_carrier_schedule_query_due') THEN ALTER TABLE fl_carrier_schedule_query ADD KEY idx_carrier_schedule_query_due (status,next_allowed_at,id); END IF; END$$ DELIMITER ; CALL migrate_carrier_schedule_retry_columns(); DROP PROCEDURE IF EXISTS migrate_carrier_schedule_retry_columns; -- CREATE TABLE IF NOT EXISTS 不会替换历史 CHECK 与唯一索引。统一收敛到当前 -- READY/MANUAL_REQUIRED/STOPPED、五种访问方式、十个实际节点和每业务范围唯一任务。 ALTER TABLE fl_carrier_schedule_query DROP CHECK chk_carrier_schedule_query_carrier, DROP CHECK chk_carrier_schedule_query_access, DROP CHECK chk_carrier_schedule_query_status; DROP PROCEDURE IF EXISTS drop_legacy_carrier_schedule_node_check; DELIMITER $$ CREATE PROCEDURE drop_legacy_carrier_schedule_node_check() BEGIN IF EXISTS (SELECT 1 FROM information_schema.table_constraints WHERE constraint_schema=DATABASE() AND table_name='fl_carrier_schedule_query' AND constraint_name='chk_carrier_schedule_query_node' AND constraint_type='CHECK') THEN ALTER TABLE fl_carrier_schedule_query DROP CHECK chk_carrier_schedule_query_node; END IF; END$$ DELIMITER ; CALL drop_legacy_carrier_schedule_node_check(); DROP PROCEDURE IF EXISTS drop_legacy_carrier_schedule_node_check; ALTER TABLE fl_carrier_schedule_query ADD CONSTRAINT chk_carrier_schedule_query_carrier CHECK (carrier_code IN ('MATSON','SML','ZIM','COSCO','EMC','HEDE','OTHER')), ADD CONSTRAINT chk_carrier_schedule_query_access CHECK (access_mode IN ('PUBLIC_FORM','PUBLIC_WEB_PROXY','PUBLIC_HTML','HCAPTCHA','OFFICIAL_API','MANUAL')), ADD CONSTRAINT chk_carrier_schedule_query_status CHECK (status IN ('READY','MANUAL_REQUIRED','STOPPED')), ADD CONSTRAINT chk_carrier_schedule_query_node CHECK (latest_standard_node IS NULL OR latest_standard_node IN ('DEPARTED_WAREHOUSE','AT_ORIGIN_PORT','LOADED_ON_VESSEL','DEPARTED_ORIGIN_PORT','IN_TRANSIT', 'ARRIVED_DESTINATION_PORT','DISCHARGED_FROM_VESSEL','PICKUP_READY','PICKED_UP','EMPTY_RETURNED')), DROP INDEX uk_carrier_schedule_query_scope, ADD UNIQUE KEY uk_carrier_schedule_query_scope (customer_id,ocean_booking_id,container_scope_id); SET @carrier_schedule_now = CONVERT_TZ(UTC_TIMESTAMP(3),'+00:00','+08:00'); SET @carrier_schedule_next = CASE WHEN TIME(@carrier_schedule_now) < '09:00:00' THEN DATE(@carrier_schedule_now) + INTERVAL 9 HOUR WHEN TIME(@carrier_schedule_now) < '12:30:00' THEN DATE(@carrier_schedule_now) + INTERVAL 12 HOUR + INTERVAL 30 MINUTE WHEN TIME(@carrier_schedule_now) < '16:00:00' THEN DATE(@carrier_schedule_now) + INTERVAL 16 HOUR ELSE DATE(@carrier_schedule_now + INTERVAL 1 DAY) + INTERVAL 9 HOUR END; UPDATE fl_carrier_schedule_query SET next_allowed_at=@carrier_schedule_next,window_started_at=@carrier_schedule_next, attempt_count=0,consecutive_failures=0,lease_owner=NULL,lease_until=NULL WHERE status IN ('READY','MANUAL_REQUIRED'); UPDATE fl_carrier_schedule_query SET next_allowed_at=NULL, window_started_at=COALESCE(window_started_at,@carrier_schedule_next), attempt_count=0,consecutive_failures=0,lease_owner=NULL,lease_until=NULL WHERE status='STOPPED'; UPDATE fl_carrier_schedule_query SET window_started_at=COALESCE(window_started_at,@carrier_schedule_next), attempt_count=LEAST(COALESCE(attempt_count,0),3), consecutive_failures=COALESCE(consecutive_failures,0); ALTER TABLE fl_carrier_schedule_query MODIFY COLUMN lease_owner VARCHAR(64) NULL COMMENT '当前调度租约持有者', MODIFY COLUMN lease_until DATETIME(3) NULL COMMENT '当前调度租约过期时间', MODIFY COLUMN consecutive_failures INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '当前正常查询槽位内的连续技术失败次数', MODIFY COLUMN window_started_at DATETIME(3) NOT NULL COMMENT '当前正常查询槽位时间', MODIFY COLUMN attempt_count INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '当前正常查询槽位已调用次数;最多3次', MODIFY COLUMN next_allowed_at DATETIME(3) NULL COMMENT '下一正常槽位或槽内技术退避时间;停止后为空'; DROP PROCEDURE IF EXISTS ensure_carrier_schedule_retry_check; DELIMITER $$ CREATE PROCEDURE ensure_carrier_schedule_retry_check() BEGIN IF NOT EXISTS (SELECT 1 FROM information_schema.table_constraints WHERE constraint_schema=DATABASE() AND table_name='fl_carrier_schedule_query' AND constraint_name='chk_carrier_schedule_query_attempt' AND constraint_type='CHECK') THEN ALTER TABLE fl_carrier_schedule_query ADD CONSTRAINT chk_carrier_schedule_query_attempt CHECK (attempt_count BETWEEN 0 AND 3); END IF; END$$ DELIMITER ; CALL ensure_carrier_schedule_retry_check(); DROP PROCEDURE IF EXISTS ensure_carrier_schedule_retry_check; -- 来源:iam/src/main/resources/db/phase3-warehouse-roles.sql -- 三期仓库系统角色:国内仓后台、国内头程 PDA、海外仓 PDA、国内仓理货复核。 -- 本脚本只组合已经存在的权限资源,需在三期 Firstleg/IAM 权限脚本之后执行,可重复部署。 USE baiyue_iam; SET NAMES utf8mb4; UPDATE iam_role SET role_code = 'OVERSEAS_WAREHOUSE_PDA_OPERATOR', role_name = '海外仓 PDA 作业员', description = '负责使用海外仓 PDA 执行接柜、入仓和库内作业,必须绑定一个海外仓' WHERE id = 4000005 AND tenant_id = 0 AND app_code = 'PLATFORM' AND role_code = 'WAREHOUSE_PICKER'; 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 <> 'OVERSEAS_WAREHOUSE_PDA_OPERATOR')) OR (id = 4000006 AND (tenant_id <> 0 OR app_code <> 'PLATFORM' OR role_code <> 'WAREHOUSE_TALLY')) OR (id = 4000007 AND (tenant_id <> 0 OR app_code <> 'PLATFORM' OR role_code <> 'CN_FIRSTLEG_PDA_OPERATOR')) 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 = 'OVERSEAS_WAREHOUSE_PDA_OPERATOR' AND id <> 4000005) OR (tenant_id = 0 AND app_code = 'PLATFORM' AND role_code = 'WAREHOUSE_TALLY' AND id <> 4000006) OR (tenant_id = 0 AND app_code = 'PLATFORM' AND role_code = 'CN_FIRSTLEG_PDA_OPERATOR' AND id <> 4000007) ) THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '仓库角色初始化失败:4000004-4000007 或角色编码已被占用'; 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 (id = 6000007 AND role_id <> 4000007) OR (role_id = 4000004 AND id <> 6000004) OR (role_id = 4000005 AND id <> 6000005) OR (role_id = 4000006 AND id <> 6000006) OR (role_id = 4000007 AND id <> 6000007) ) THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '仓库角色初始化失败:6000004-6000007 数据范围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', 'OVERSEAS_WAREHOUSE_PDA_OPERATOR', '海外仓 PDA 作业员', 'SYSTEM', 'ACTIVE', '负责使用海外仓 PDA 执行接柜、入仓和库内作业,必须绑定一个海外仓', 0, 0), (4000006, 0, 'PLATFORM', 'WAREHOUSE_TALLY', '国内仓理货复核员', 'SYSTEM', 'ACTIVE', '负责国内入库测量、PDA复核和装柜证据维护', 0, 0), (4000007, 0, 'PLATFORM', 'CN_FIRSTLEG_PDA_OPERATOR', '国内头程 PDA 作业员', '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 ('CN_FIRSTLEG_PDA_OPERATOR', 'platform:firstleg:pda:view'), ('CN_FIRSTLEG_PDA_OPERATOR', '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 domestic_pda_permission_count INT DEFAULT 0; DECLARE tally_permission_count INT DEFAULT 0; SELECT COALESCE(SUM(role_code = 'WAREHOUSE_OPERATOR'), 0), COALESCE(SUM(role_code = 'CN_FIRSTLEG_PDA_OPERATOR'), 0), COALESCE(SUM(role_code = 'WAREHOUSE_TALLY'), 0) INTO operator_permission_count, domestic_pda_permission_count, tally_permission_count FROM tmp_phase3_warehouse_role_permission; IF operator_permission_count <> 5 OR domestic_pda_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', 'OVERSEAS_WAREHOUSE_PDA_OPERATOR', 'WAREHOUSE_TALLY', 'CN_FIRSTLEG_PDA_OPERATOR') AND NOT (role.role_code = 'OVERSEAS_WAREHOUSE_PDA_OPERATOR' AND permission.permission_code = 'lastleg:pda:operate') 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), (6000007, 4000007, 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', 'OVERSEAS_WAREHOUSE_PDA_OPERATOR', 'WAREHOUSE_TALLY', 'CN_FIRSTLEG_PDA_OPERATOR')) <> 4 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', 'OVERSEAS_WAREHOUSE_PDA_OPERATOR', 'WAREHOUSE_TALLY', 'CN_FIRSTLEG_PDA_OPERATOR') AND scope_row.tenant_id = 0 AND scope_row.scope_type = 'TENANT') <> 4 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='平台财务主体;首期仅一个有效主体'; INSERT INTO fin_subject (subject_code, subject_name, status, version, created_by, updated_by) VALUES ('PLATFORM_DEFAULT', '平台', 'ACTIVE', 0, 0, 0) ON DUPLICATE KEY UPDATE subject_name = VALUES(subject_name), status = 'ACTIVE', updated_by = 0; 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,未出账时为空', customer_confirmation_status VARCHAR(24) NOT NULL DEFAULT 'NOT_REQUIRED' COMMENT '客户确认状态:NOT_REQUIRED无需确认、PENDING_CONFIRMATION待确认、CONFIRMED已确认', customer_confirmed_by BIGINT UNSIGNED NULL COMMENT '客户确认人用户ID', customer_confirmed_by_name VARCHAR(128) NULL COMMENT '客户确认人姓名快照', customer_confirmed_at DATETIME(3) 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', created_by_name VARCHAR(128) NULL COMMENT '创建操作人名称快照', updated_by BIGINT UNSIGNED NOT NULL COMMENT '最后修改操作人ID', updated_by_name VARCHAR(128) 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_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', created_by_name VARCHAR(128) NULL COMMENT '创建操作人名称快照', 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 '成本总金额;仅FIRSTLEG_DIRECT_PAID且CONFIRMED计入已线下支付供应商款', 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', created_by_name VARCHAR(128) NULL COMMENT '创建操作人名称快照', 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_by_name VARCHAR(128) NULL COMMENT '执行调整的操作人名称快照', 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_document_sequence ( sequence_key VARCHAR(32) NOT NULL COMMENT '单据类型与北京时间日期组合键,例如REC:20260818', current_value INT UNSIGNED NOT NULL COMMENT '该类型当日已分配的最大序号', PRIMARY KEY (sequence_key), CONSTRAINT chk_fin_document_sequence_value CHECK (current_value > 0) ) ENGINE=InnoDB COMMENT='Finance业务单号日序列;生成YYMMDD加四位当日序号'; 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不可变业务审计明细'; CREATE TABLE IF NOT EXISTS fin_settlement ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '正式应收或应付结算单ID', tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键', settlement_no VARCHAR(64) NOT NULL COMMENT '客户范围内唯一结算单号', direction VARCHAR(16) NOT NULL COMMENT '结算方向:RECEIVABLE应收、PAYABLE应付', customer_name_snapshot VARCHAR(200) NOT NULL COMMENT '结算生成时客户名称快照', supplier_id BIGINT UNSIGNED NULL COMMENT '应付结算供应商ID;应收结算为空', supplier_name_snapshot VARCHAR(200) NULL COMMENT '应付结算供应商名称快照', reconciliation_statement_id BIGINT UNSIGNED NULL COMMENT '应收结算关联的已出账客户对账单ID', business_period VARCHAR(32) NULL COMMENT '结算业务期间快照', currency_code CHAR(3) NOT NULL DEFAULT 'CNY' COMMENT 'ISO 4217三位币种代码,首期仅CNY', total_amount DECIMAL(20,6) NOT NULL COMMENT '正式结算总金额,精度6位小数', paid_amount DECIMAL(20,6) NOT NULL DEFAULT 0 COMMENT '累计已收或已付金额;PAYABLE已付计入已线下支付供应商款', outstanding_amount DECIMAL(20,6) NOT NULL COMMENT '未结清金额;RECEIVABLE计待收,PAYABLE与未制单ON_ACCOUNT来源共同计待付', generated_date DATE NOT NULL COMMENT '结算单生成日期', due_date DATE NOT NULL COMMENT '结算到期日期', payment_status VARCHAR(24) NOT NULL DEFAULT 'UNPAID' COMMENT '收付款状态:UNPAID未收付、PARTIALLY_PAID部分收付、PAID已结清', status VARCHAR(24) NOT NULL DEFAULT 'ISSUED' COMMENT '结算状态:ISSUED已生效、PARTIALLY_PAID部分收付、PAID已结清、VOIDED已作废', 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_settlement_no (tenant_id, settlement_no), UNIQUE KEY uk_fin_settlement_receivable_statement (tenant_id, reconciliation_statement_id, direction), KEY idx_fin_settlement_customer_direction_status (tenant_id, direction, status), KEY idx_fin_settlement_supplier_status (supplier_id, direction, status), KEY idx_fin_settlement_receivable_statistics (direction, currency_code, generated_date, status, tenant_id), CONSTRAINT chk_fin_settlement_tenant CHECK (tenant_id > 0), CONSTRAINT chk_fin_settlement_currency CHECK (currency_code = 'CNY'), CONSTRAINT chk_fin_settlement_direction CHECK (direction IN ('RECEIVABLE', 'PAYABLE')), CONSTRAINT chk_fin_settlement_amounts CHECK (total_amount >= 0 AND paid_amount >= 0 AND outstanding_amount >= 0 AND total_amount = paid_amount + outstanding_amount) ) ENGINE=InnoDB COMMENT='正式应收与应付结算单;客户账户金额由本表派生'; SET @has_receivable_statistics_index = (SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema='baiyue_finance' AND table_name='fin_settlement' AND index_name='idx_fin_settlement_receivable_statistics'); SET @ddl = IF(@has_receivable_statistics_index=0, 'ALTER TABLE baiyue_finance.fin_settlement ADD KEY idx_fin_settlement_receivable_statistics(direction,currency_code,generated_date,status,tenant_id)', 'SELECT 1'); PREPARE statement FROM @ddl; EXECUTE statement; DEALLOCATE PREPARE statement; CREATE TABLE IF NOT EXISTS fin_settlement_line ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '正式结算单明细ID', tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键', settlement_id BIGINT UNSIGNED NOT NULL COMMENT '所属正式结算单ID', fee_source_id BIGINT UNSIGNED NOT NULL COMMENT '结算固化的Finance费用来源ID', amount DECIMAL(20,6) NOT NULL COMMENT '结算明细金额,精度6位小数', 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_settlement_line_source (settlement_id, fee_source_id), KEY idx_fin_settlement_line_customer (tenant_id, settlement_id), KEY idx_fin_settlement_line_source (fee_source_id), CONSTRAINT chk_fin_settlement_line_tenant CHECK (tenant_id > 0), CONSTRAINT chk_fin_settlement_line_currency CHECK (currency_code = 'CNY'), CONSTRAINT chk_fin_settlement_line_amount CHECK (amount > 0) ) ENGINE=InnoDB COMMENT='正式结算单费用来源金额快照明细'; CREATE TABLE IF NOT EXISTS fin_settlement_payment ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '正式结算单收付款记录ID', tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键', settlement_id BIGINT UNSIGNED NOT NULL COMMENT '所属正式结算单ID', transaction_type VARCHAR(16) NOT NULL COMMENT '交易类型:RECEIPT收款、PAYMENT付款', transaction_no VARCHAR(64) NOT NULL COMMENT '客户范围内唯一收付款业务号', transaction_date DATE NOT NULL COMMENT '实际收款或付款日期', amount DECIMAL(20,6) NOT NULL COMMENT '本次收款或付款金额,精度6位小数', currency_code CHAR(3) NOT NULL DEFAULT 'CNY' COMMENT 'ISO 4217三位币种代码,首期仅CNY', method VARCHAR(32) NOT NULL COMMENT '人工登记的收付款方式', reference_no VARCHAR(128) NULL COMMENT '银行流水号或外部参考号', voucher_file_ids JSON NOT NULL COMMENT 'File服务财务凭证文件ID字符串数组', remark VARCHAR(500) NULL COMMENT '收付款内部备注', created_by BIGINT UNSIGNED NOT NULL COMMENT '登记操作人ID', created_by_name VARCHAR(128) NULL COMMENT '登记操作人名称快照', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '登记时间', PRIMARY KEY (id), UNIQUE KEY uk_fin_settlement_payment_no (tenant_id, transaction_no), KEY idx_fin_settlement_payment_settlement (tenant_id, settlement_id, created_at), CONSTRAINT chk_fin_settlement_payment_tenant CHECK (tenant_id > 0), CONSTRAINT chk_fin_settlement_payment_currency CHECK (currency_code = 'CNY'), CONSTRAINT chk_fin_settlement_payment_amount CHECK (amount > 0) ) ENGINE=InnoDB COMMENT='正式结算单人工收付款与凭证记录'; DROP PROCEDURE IF EXISTS finance_add_operator_snapshot_column; DELIMITER $$ CREATE PROCEDURE finance_add_operator_snapshot_column( IN target_table VARCHAR(64), IN target_column VARCHAR(64), IN after_column VARCHAR(64) ) 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 @finance_operator_snapshot_ddl = CONCAT( 'ALTER TABLE `', target_table, '` ADD COLUMN `', target_column, '` VARCHAR(128) NULL COMMENT ''操作人名称快照'' AFTER `', after_column, '`' ); PREPARE finance_operator_snapshot_stmt FROM @finance_operator_snapshot_ddl; EXECUTE finance_operator_snapshot_stmt; DEALLOCATE PREPARE finance_operator_snapshot_stmt; END IF; END$$ DELIMITER ; CALL finance_add_operator_snapshot_column('fin_bill', 'created_by_name', 'created_by'); CALL finance_add_operator_snapshot_column('fin_bill', 'updated_by_name', 'updated_by'); CALL finance_add_operator_snapshot_column('fin_receipt', 'created_by_name', 'created_by'); CALL finance_add_operator_snapshot_column('fin_cost', 'created_by_name', 'created_by'); CALL finance_add_operator_snapshot_column('fin_settlement_payment', 'created_by_name', 'created_by'); CALL finance_add_operator_snapshot_column('fin_bill_revision', 'created_by_name', 'created_by'); DROP PROCEDURE IF EXISTS finance_add_operator_snapshot_column; 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和数据隔离键', allocation_group_id BIGINT UNSIGNED NULL COMMENT '柜级公共费用分摊组ID;单入仓直接费用为空', allocation_order_id BIGINT UNSIGNED NULL COMMENT '本分摊行业务订单ID', allocation_order_no VARCHAR(64) NULL COMMENT '本分摊行业务订单号', allocation_inbound_id BIGINT UNSIGNED NULL COMMENT '本分摊行对应入仓单ID', allocation_inbound_code VARCHAR(64) NULL COMMENT '本分摊行对应入仓码', allocation_group_amount DECIMAL(18,2) NULL COMMENT '柜级原始费用总额快照', 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应付', supplier_id BIGINT UNSIGNED NULL COMMENT '应付Warehouse供应商ID快照;应收为空', supplier_name VARCHAR(200) NULL COMMENT '应付Warehouse供应商名称快照;应收为空', supplier_category VARCHAR(16) NULL COMMENT '费用项目供应商类别约束快照;为空表示任意启用供应商', settlement_mode VARCHAR(16) NULL COMMENT '仅PAYABLE:ON_ACCOUNT账期应付、DIRECT_PAID业务已付;RECEIVABLE为空', paid_at DATETIME(3) NULL COMMENT 'DIRECT_PAID业务已付时间', payment_method VARCHAR(32) NULL COMMENT 'DIRECT_PAID付款方式', payment_reference_no VARCHAR(128) NULL COMMENT 'DIRECT_PAID付款流水号', payment_voucher_file_ids JSON NULL COMMENT 'DIRECT_PAID付款凭证File ID数组', business_date DATE NOT NULL COMMENT '费用业务日期', amount DECIMAL(18,2) NOT NULL COMMENT 'CNY费用金额', currency CHAR(3) NOT NULL DEFAULT 'CNY' COMMENT '币种,首期固定CNY', 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_review (tenant_id, review_status, 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_settlement CHECK ( (direction='RECEIVABLE' AND settlement_mode IS NULL AND paid_at IS NULL AND payment_method IS NULL AND payment_reference_no IS NULL AND payment_voucher_file_ids IS NULL) OR (direction='PAYABLE' AND settlement_mode='ON_ACCOUNT' AND paid_at IS NULL AND payment_method IS NULL AND payment_reference_no IS NULL AND payment_voucher_file_ids IS NULL) OR (direction='PAYABLE' AND settlement_mode='DIRECT_PAID' AND paid_at IS NOT NULL AND payment_method IN ('ONLINE','BANK_TRANSFER','CASH','OTHER') AND (payment_reference_no IS NOT NULL OR JSON_LENGTH(payment_voucher_file_ids)>0)) ), CONSTRAINT chk_container_fee_source CHECK (source_status IN ('ACTIVE', 'VOIDED')) ) ENGINE=InnoDB COMMENT='柜次级人工应收应付费用来源;审核通过后自动同步Finance'; CREATE TABLE IF NOT EXISTS fl_container_fee_source_snapshot ( id BIGINT UNSIGNED NOT NULL COMMENT '柜次费用快照ID', source_fee_id BIGINT UNSIGNED NOT NULL COMMENT '柜次费用来源ID', source_version INT UNSIGNED NOT NULL COMMENT '费用来源版本', tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键', allocation_group_id BIGINT UNSIGNED NULL COMMENT '柜级公共费用分摊组ID', allocation_order_id BIGINT UNSIGNED NULL COMMENT '分摊业务订单ID快照', allocation_order_no VARCHAR(64) NULL COMMENT '分摊业务订单号快照', allocation_inbound_id BIGINT UNSIGNED NULL COMMENT '分摊入仓单ID快照', allocation_inbound_code VARCHAR(64) NULL COMMENT '分摊入仓码快照', allocation_group_amount DECIMAL(18,2) NULL COMMENT '柜级原始费用总额快照', loading_plan_id BIGINT UNSIGNED NOT NULL COMMENT '柜次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 VARCHAR(50) NOT NULL COMMENT '费用项目编码快照', fee_item_name VARCHAR(128) NOT NULL COMMENT '费用项目名称快照', direction VARCHAR(16) NOT NULL COMMENT '费用方向', supplier_id BIGINT UNSIGNED NULL COMMENT 'Warehouse供应商ID快照', supplier_name VARCHAR(200) NULL COMMENT 'Warehouse供应商名称快照', supplier_category VARCHAR(16) NULL COMMENT '费用项目供应商类别约束快照;为空表示任意启用供应商', settlement_mode VARCHAR(16) NULL COMMENT '仅PAYABLE:ON_ACCOUNT账期应付、DIRECT_PAID业务已付;RECEIVABLE为空', paid_at DATETIME(3) NULL COMMENT 'DIRECT_PAID业务已付时间', payment_method VARCHAR(32) NULL COMMENT 'DIRECT_PAID付款方式', payment_reference_no VARCHAR(128) NULL COMMENT 'DIRECT_PAID付款流水号', payment_voucher_file_ids JSON NULL COMMENT 'DIRECT_PAID付款凭证File ID数组', business_date DATE NOT NULL COMMENT '费用业务日期', amount DECIMAL(18,2) NOT NULL COMMENT 'CNY费用金额', currency CHAR(3) NOT NULL DEFAULT 'CNY' COMMENT '币种,首期固定CNY', review_status VARCHAR(16) NOT NULL COMMENT '审核状态快照', reviewed_at DATETIME(3) NULL COMMENT '审核时间快照', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '快照创建时间', PRIMARY KEY (id), UNIQUE KEY uk_container_fee_source_version (loading_plan_id, source_fee_id, source_version), KEY idx_container_fee_snapshot_tenant (tenant_id, loading_plan_id) ) ENGINE=InnoDB COMMENT='柜次费用不可变版本快照'; CREATE TABLE IF NOT EXISTS fl_finance_fee_source_command ( id BIGINT UNSIGNED NOT NULL COMMENT 'Finance费用来源命令ID', customer_id BIGINT UNSIGNED NOT NULL COMMENT '客户隔离键,由Firstleg费用事实再次校验', operator_id BIGINT UNSIGNED NOT NULL COMMENT 'Finance操作人ID', operator_name VARCHAR(64) NOT NULL COMMENT 'Finance操作人名称快照', action VARCHAR(24) NOT NULL COMMENT '命令动作;当前仅VOID', idempotency_key VARCHAR(128) NOT NULL COMMENT 'Finance透传的幂等键', request_hash CHAR(64) NOT NULL COMMENT '规范化请求SHA-256', source_type VARCHAR(24) NOT NULL COMMENT 'INBOUND_FEE或CONTAINER_FEE', source_fee_id BIGINT UNSIGNED NOT NULL COMMENT '稳定费用来源ID', source_version INT UNSIGNED NOT NULL COMMENT '精确费用来源版本', inbound_id BIGINT UNSIGNED NULL COMMENT '入库费用所属入库单ID', container_id BIGINT UNSIGNED NULL COMMENT '柜次费用所属柜次ID', reason VARCHAR(500) NOT NULL COMMENT '操作原因', status VARCHAR(16) NOT NULL COMMENT 'PROCESSING或SUCCEEDED', result_status VARCHAR(16) NULL COMMENT '成功业务结果;当前为VOIDED', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '命令创建时间', completed_at DATETIME(3) NULL COMMENT '命令成功完成时间', PRIMARY KEY (id), UNIQUE KEY uk_finance_fee_command_idem (customer_id, operator_id, action, idempotency_key), KEY idx_finance_fee_command_source (source_type, source_fee_id, source_version, created_at), CONSTRAINT chk_finance_fee_command_action CHECK (action IN ('VOID')), CONSTRAINT chk_finance_fee_command_status CHECK (status IN ('PROCESSING', 'SUCCEEDED')), CONSTRAINT chk_finance_fee_command_source CHECK (source_type IN ('INBOUND_FEE', 'CONTAINER_FEE')) ) ENGINE=InnoDB COMMENT='Finance发起、Firstleg执行业务费用命令的幂等审计记录'; CALL firstleg_finance_add_column('fl_container_fee_detail', 'supplier_id', "BIGINT UNSIGNED NULL COMMENT '应付Warehouse供应商ID快照;应收为空' AFTER direction"); CALL firstleg_finance_add_column('fl_container_fee_detail', 'supplier_name', "VARCHAR(200) NULL COMMENT '应付Warehouse供应商名称快照;应收为空' AFTER supplier_id"); CALL firstleg_finance_add_column('fl_container_fee_detail', 'supplier_category', "VARCHAR(16) NULL COMMENT '费用项目供应商类别约束快照;为空表示任意启用供应商' AFTER supplier_name"); CALL firstleg_finance_add_column('fl_container_fee_detail', 'settlement_mode', "VARCHAR(16) NULL COMMENT '仅PAYABLE:ON_ACCOUNT或DIRECT_PAID' AFTER supplier_category"); CALL firstleg_finance_add_column('fl_container_fee_detail', 'paid_at', "DATETIME(3) NULL COMMENT 'DIRECT_PAID业务已付时间' AFTER settlement_mode"); CALL firstleg_finance_add_column('fl_container_fee_detail', 'payment_method', "VARCHAR(32) NULL COMMENT 'DIRECT_PAID付款方式' AFTER paid_at"); CALL firstleg_finance_add_column('fl_container_fee_detail', 'payment_reference_no', "VARCHAR(128) NULL COMMENT 'DIRECT_PAID付款流水号' AFTER payment_method"); CALL firstleg_finance_add_column('fl_container_fee_detail', 'payment_voucher_file_ids', "JSON NULL COMMENT 'DIRECT_PAID付款凭证File ID数组' AFTER payment_reference_no"); CALL firstleg_finance_add_column('fl_container_fee_detail', 'allocation_group_id', "BIGINT UNSIGNED NULL COMMENT '柜级公共费用分摊组ID;单入仓直接费用为空' AFTER tenant_id"); CALL firstleg_finance_add_column('fl_container_fee_detail', 'allocation_order_id', "BIGINT UNSIGNED NULL COMMENT '本分摊行业务订单ID' AFTER allocation_group_id"); CALL firstleg_finance_add_column('fl_container_fee_detail', 'allocation_order_no', "VARCHAR(64) NULL COMMENT '本分摊行业务订单号' AFTER allocation_order_id"); CALL firstleg_finance_add_column('fl_container_fee_detail', 'allocation_inbound_id', "BIGINT UNSIGNED NULL COMMENT '本分摊行对应入仓单ID' AFTER allocation_order_no"); CALL firstleg_finance_add_column('fl_container_fee_detail', 'allocation_inbound_code', "VARCHAR(64) NULL COMMENT '本分摊行对应入仓码' AFTER allocation_inbound_id"); CALL firstleg_finance_add_column('fl_container_fee_detail', 'allocation_group_amount', "DECIMAL(18,2) NULL COMMENT '柜级原始费用总额快照' AFTER allocation_inbound_code"); CALL firstleg_finance_add_column('fl_container_fee_source_snapshot', 'settlement_mode', "VARCHAR(16) NULL COMMENT '仅PAYABLE:ON_ACCOUNT或DIRECT_PAID' AFTER supplier_category"); CALL firstleg_finance_add_column('fl_container_fee_source_snapshot', 'paid_at', "DATETIME(3) NULL COMMENT 'DIRECT_PAID业务已付时间' AFTER settlement_mode"); CALL firstleg_finance_add_column('fl_container_fee_source_snapshot', 'payment_method', "VARCHAR(32) NULL COMMENT 'DIRECT_PAID付款方式' AFTER paid_at"); CALL firstleg_finance_add_column('fl_container_fee_source_snapshot', 'payment_reference_no', "VARCHAR(128) NULL COMMENT 'DIRECT_PAID付款流水号' AFTER payment_method"); CALL firstleg_finance_add_column('fl_container_fee_source_snapshot', 'payment_voucher_file_ids', "JSON NULL COMMENT 'DIRECT_PAID付款凭证File ID数组' AFTER payment_reference_no"); CALL firstleg_finance_add_column('fl_container_fee_source_snapshot', 'allocation_group_id', "BIGINT UNSIGNED NULL COMMENT '柜级公共费用分摊组ID' AFTER tenant_id"); CALL firstleg_finance_add_column('fl_container_fee_source_snapshot', 'allocation_order_id', "BIGINT UNSIGNED NULL COMMENT '分摊业务订单ID快照' AFTER allocation_group_id"); CALL firstleg_finance_add_column('fl_container_fee_source_snapshot', 'allocation_order_no', "VARCHAR(64) NULL COMMENT '分摊业务订单号快照' AFTER allocation_order_id"); CALL firstleg_finance_add_column('fl_container_fee_source_snapshot', 'allocation_inbound_id', "BIGINT UNSIGNED NULL COMMENT '分摊入仓单ID快照' AFTER allocation_order_no"); CALL firstleg_finance_add_column('fl_container_fee_source_snapshot', 'allocation_inbound_code', "VARCHAR(64) NULL COMMENT '分摊入仓码快照' AFTER allocation_inbound_id"); CALL firstleg_finance_add_column('fl_container_fee_source_snapshot', 'allocation_group_amount', "DECIMAL(18,2) NULL COMMENT '柜级原始费用总额快照' AFTER allocation_inbound_code"); CALL firstleg_finance_add_column('fl_inbound_fee_detail', 'supplier_id', "BIGINT UNSIGNED NULL COMMENT '应付Warehouse供应商ID快照;应收为空' AFTER direction"); CALL firstleg_finance_add_column('fl_inbound_fee_detail', 'supplier_name', "VARCHAR(200) NULL COMMENT '应付Warehouse供应商名称快照;应收为空' AFTER supplier_id"); CALL firstleg_finance_add_column('fl_inbound_fee_detail', 'supplier_category', "VARCHAR(16) NULL COMMENT '费用项目供应商类别约束快照;为空表示任意启用供应商' AFTER supplier_name"); CALL firstleg_finance_add_column('fl_inbound_fee_detail', 'settlement_mode', "VARCHAR(16) NULL COMMENT '仅PAYABLE:ON_ACCOUNT或DIRECT_PAID' AFTER supplier_category"); CALL firstleg_finance_add_column('fl_inbound_fee_detail', 'paid_at', "DATETIME(3) NULL COMMENT 'DIRECT_PAID业务已付时间' AFTER settlement_mode"); CALL firstleg_finance_add_column('fl_inbound_fee_detail', 'payment_method', "VARCHAR(32) NULL COMMENT 'DIRECT_PAID付款方式' AFTER paid_at"); CALL firstleg_finance_add_column('fl_inbound_fee_detail', 'payment_reference_no', "VARCHAR(128) NULL COMMENT 'DIRECT_PAID付款流水号' AFTER payment_method"); CALL firstleg_finance_add_column('fl_inbound_fee_detail', 'payment_voucher_file_ids', "JSON NULL COMMENT 'DIRECT_PAID付款凭证File ID数组' AFTER payment_reference_no"); 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 NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'fl_finance_fee_source_command') 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 ('supplier_id', 'supplier_name', 'supplier_category', 'settlement_mode', 'paid_at', 'payment_method', 'payment_reference_no', 'payment_voucher_file_ids')) <> 8 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_source_snapshot' AND column_name IN ('settlement_mode', 'paid_at', 'payment_method', 'payment_reference_no', 'payment_voucher_file_ids')) <> 5 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_inbound_fee_detail' AND column_name IN ('supplier_id', 'supplier_name', 'supplier_category', 'settlement_mode', 'paid_at', 'payment_method', 'payment_reference_no', 'payment_voucher_file_ids')) <> 8 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', 'business_type', "VARCHAR(32) NULL COMMENT '来源业务类型,如海外入仓、采购、卡车或柜级分摊' AFTER source_no"); CALL finance_fee_source_add_column('fin_fee_source', 'business_id', "BIGINT UNSIGNED NULL COMMENT '来源业务记录ID,可为空' AFTER business_type"); CALL finance_fee_source_add_column('fin_fee_source', 'business_no', "VARCHAR(128) NULL COMMENT '来源业务编号快照,可为空' AFTER business_id"); CALL finance_fee_source_add_column('fin_fee_source', 'inbound_code', "VARCHAR(64) NULL COMMENT '来源入仓码快照;并非所有费用必填' AFTER business_no"); CALL finance_fee_source_add_column('fin_fee_source', 'warehouse_id', "BIGINT UNSIGNED NULL COMMENT '来源海外仓或业务仓ID,可为空' AFTER inbound_code"); 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', 'container_booking_id', "BIGINT UNSIGNED NULL COMMENT '来源订舱柜ID,可为空' AFTER container_id"); CALL finance_fee_source_add_column('fin_fee_source', 'container_no', "VARCHAR(64) NULL COMMENT '真实集装箱号快照,可为空' AFTER container_booking_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', 'settlement_mode', "VARCHAR(16) NULL COMMENT '仅PAYABLE:ON_ACCOUNT账期应付、DIRECT_PAID业务已付;RECEIVABLE为空' AFTER currency_code"); CALL finance_fee_source_add_column('fin_fee_source', 'paid_at', "DATETIME(3) NULL COMMENT 'DIRECT_PAID业务已付时间' AFTER settlement_mode"); CALL finance_fee_source_add_column('fin_fee_source', 'payment_method', "VARCHAR(32) NULL COMMENT 'DIRECT_PAID付款方式' AFTER paid_at"); CALL finance_fee_source_add_column('fin_fee_source', 'payment_reference_no', "VARCHAR(128) NULL COMMENT 'DIRECT_PAID付款流水号' AFTER payment_method"); CALL finance_fee_source_add_column('fin_fee_source', 'payment_voucher_file_ids', "JSON NULL COMMENT 'DIRECT_PAID付款凭证File ID数组' AFTER payment_reference_no"); 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 'DRAFT' COMMENT 'Finance费用状态:DRAFT草稿、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', 'operation_version', "INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '费用编辑、提交、审核、调整和作废操作版本号' AFTER review_reason"); CALL finance_fee_source_add_column('fin_fee_source', 'wallet_hold_id', "BIGINT UNSIGNED NULL COMMENT '已由客户资金账户结算时关联的资金冻结记录ID' AFTER operation_version"); 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_bill', 'customer_confirmation_status', "VARCHAR(24) NOT NULL DEFAULT 'NOT_REQUIRED' COMMENT '客户确认状态:NOT_REQUIRED无需确认、PENDING_CONFIRMATION待确认、CONFIRMED已确认' AFTER issued_by"); CALL finance_fee_source_add_column('fin_bill', 'customer_confirmed_by', "BIGINT UNSIGNED NULL COMMENT '客户确认人用户ID' AFTER customer_confirmation_status"); CALL finance_fee_source_add_column('fin_bill', 'customer_confirmed_by_name', "VARCHAR(128) NULL COMMENT '客户确认人姓名快照' AFTER customer_confirmed_by"); CALL finance_fee_source_add_column('fin_bill', 'customer_confirmed_at', "DATETIME(3) NULL COMMENT '客户确认时间' AFTER customer_confirmed_by_name"); 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"); CALL finance_fee_source_add_column('fin_cost', 'cost_origin', "VARCHAR(24) NOT NULL DEFAULT 'MANUAL' COMMENT '成本来源:MANUAL人工补充、FIRSTLEG_DIRECT_PAID头程直接已付' AFTER cost_type"); CALL finance_fee_source_add_column('fin_cost', 'source_system', "VARCHAR(32) NULL COMMENT '来源系统,头程直接已付固定FIRSTLEG' AFTER cost_origin"); CALL finance_fee_source_add_column('fin_cost', 'source_type', "VARCHAR(32) NULL COMMENT '来源费用类型快照' AFTER source_system"); CALL finance_fee_source_add_column('fin_cost', 'source_fee_id', "BIGINT UNSIGNED NULL COMMENT '来源费用ID' AFTER source_type"); CALL finance_fee_source_add_column('fin_cost', 'source_fee_version', "INT UNSIGNED NULL COMMENT '来源费用版本' AFTER source_fee_id"); CALL finance_fee_source_add_column('fin_cost', 'source_hash', "CHAR(64) NULL COMMENT '来源不可变快照SHA-256' AFTER source_fee_version"); CALL finance_fee_source_add_column('fin_cost', 'warehouse_id', "BIGINT UNSIGNED NULL COMMENT '业务直接已付成本关联仓库ID,可为空' AFTER container_id"); CALL finance_fee_source_add_column('fin_cost', 'paid_at', "DATETIME(3) NULL COMMENT '直接已付时间' AFTER voucher_file_ids"); CALL finance_fee_source_add_column('fin_cost', 'payment_method', "VARCHAR(32) NULL COMMENT '直接已付方式' AFTER paid_at"); CALL finance_fee_source_add_column('fin_cost', 'payment_reference_no', "VARCHAR(128) NULL COMMENT '直接已付流水号' AFTER payment_method"); DROP PROCEDURE IF EXISTS finance_fee_source_add_column; SET @finance_fee_source_index_ddl = ( SELECT IF( (SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='fin_fee_source' AND index_name='uk_fin_fee_source')=4, 'SELECT 1', IF(EXISTS(SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='fin_fee_source' AND index_name='uk_fin_fee_source'), 'ALTER TABLE fin_fee_source DROP INDEX uk_fin_fee_source, ADD UNIQUE KEY uk_fin_fee_source(source_system,source_type,source_fee_id,source_fee_version)', 'ALTER TABLE fin_fee_source ADD UNIQUE KEY uk_fin_fee_source(source_system,source_type,source_fee_id,source_fee_version)') ) ); PREPARE finance_fee_source_index_stmt FROM @finance_fee_source_index_ddl; EXECUTE finance_fee_source_index_stmt; DEALLOCATE PREPARE finance_fee_source_index_stmt; DROP PROCEDURE IF EXISTS finance_fee_source_add_index; DELIMITER $$ CREATE PROCEDURE finance_fee_source_add_index() BEGIN IF NOT EXISTS ( SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='fin_cost' AND index_name='uk_fin_cost_source_version' ) THEN ALTER TABLE fin_cost ADD UNIQUE KEY uk_fin_cost_source_version (source_system, source_type, source_fee_id, source_fee_version); END IF; END$$ DELIMITER ; CALL finance_fee_source_add_index(); DROP PROCEDURE IF EXISTS finance_fee_source_add_index; ALTER TABLE fin_cost MODIFY COLUMN cost_type VARCHAR(24) NOT NULL COMMENT '成本归属类型:SINGLE_ORDER单票、CONTAINER_SHARED整柜公共、SOURCE_DIRECT_PAID来源直接已付', MODIFY COLUMN cost_origin VARCHAR(24) NOT NULL DEFAULT 'MANUAL' COMMENT '成本来源:MANUAL人工补充、FIRSTLEG_DIRECT_PAID头程直接已付、BUSINESS_DIRECT_PAID其他业务直接已付', MODIFY COLUMN source_hash VARCHAR(71) NULL COMMENT '来源不可变快照SHA-256,兼容sha256前缀格式'; DROP PROCEDURE IF EXISTS finance_fee_source_replace_cost_checks; DELIMITER $$ CREATE PROCEDURE finance_fee_source_replace_cost_checks() BEGIN IF EXISTS (SELECT 1 FROM information_schema.table_constraints WHERE constraint_schema=DATABASE() AND table_name='fin_cost' AND constraint_name='chk_fin_cost_target') THEN ALTER TABLE fin_cost DROP CHECK chk_fin_cost_target; END IF; IF EXISTS (SELECT 1 FROM information_schema.table_constraints WHERE constraint_schema=DATABASE() AND table_name='fin_cost' AND constraint_name='chk_fin_cost_origin') THEN ALTER TABLE fin_cost DROP CHECK chk_fin_cost_origin; END IF; ALTER TABLE fin_cost ADD 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) OR (cost_type='SOURCE_DIRECT_PAID' AND (order_id IS NOT NULL OR inbound_id IS NOT NULL OR container_id IS NOT NULL OR warehouse_id IS NOT NULL)) ), ADD CONSTRAINT chk_fin_cost_origin CHECK ( (cost_origin='MANUAL' AND source_system IS NULL AND source_type IS NULL AND source_fee_id IS NULL AND source_fee_version IS NULL AND source_hash IS NULL) OR (cost_origin='FIRSTLEG_DIRECT_PAID' AND source_system='FIRSTLEG' AND source_type IS NOT NULL AND source_fee_id IS NOT NULL AND source_fee_version IS NOT NULL AND source_hash IS NOT NULL AND status IN ('CONFIRMED','VOIDED') AND paid_at IS NOT NULL AND payment_method IN ('ONLINE','BANK_TRANSFER','CASH','OTHER')) OR (cost_origin='BUSINESS_DIRECT_PAID' AND source_system NOT IN ('', 'FIRSTLEG') AND source_type IS NOT NULL AND source_fee_id IS NOT NULL AND source_fee_version IS NOT NULL AND source_hash IS NOT NULL AND status IN ('CONFIRMED','VOIDED') AND paid_at IS NOT NULL AND payment_method IN ('ONLINE','BANK_TRANSFER','CASH','OTHER')) ); END$$ DELIMITER ; CALL finance_fee_source_replace_cost_checks(); DROP PROCEDURE IF EXISTS finance_fee_source_replace_cost_checks; -- 产品确认无历史财务数据;以下兼容值仅用于空环境重复迁移时保证结构收敛。 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, 'DRAFT'), 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 VARCHAR(71) NOT NULL COMMENT '业务费用来源完整快照SHA-256哈希,兼容sha256前缀格式', MODIFY COLUMN source_snapshot JSON NOT NULL COMMENT '业务费用来源完整不可变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 VARCHAR(71) NOT NULL COMMENT '规范化事件内容SHA-256哈希,兼容sha256前缀格式', 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', 'settlement_mode','paid_at','payment_method','payment_reference_no', 'payment_voucher_file_ids','source_hash','source_snapshot','review_status','review_reason', 'operation_version','updated_at')) <> 16 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; IF (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'fin_bill' AND column_name IN ('customer_confirmation_status','customer_confirmed_by', 'customer_confirmed_by_name','customer_confirmed_at')) <> 4 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_cost' AND column_name IN ('cost_origin','source_system','source_type','source_fee_id', 'source_fee_version','source_hash','paid_at','payment_method', 'payment_reference_no','warehouse_id')) <> 10 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Finance直接已付成本字段不完整'; END IF; IF (SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='fin_cost' AND index_name='uk_fin_cost_source_version') <> 4 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Finance直接已付成本唯一索引不完整'; 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,''),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,''),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_carrier_schedule_node_fact', '供应商标准船节点不可变事实'); CALL queue_table_comment('baiyue_firstleg', 'fl_carrier_schedule_exception_fact', '船节点异常发生与解除不可变事实'); 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文件关联'); 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_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_cabinet_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_customer_address', '客户自有下单地址簿;不承载平台仓库主档'); 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_firstleg', 'fl_order_declaration_event', 'Order客户申报事件永久幂等账本'); CALL queue_table_comment('baiyue_firstleg', 'fl_order_declaration_snapshot', 'Order客户申报版本快照;与箱码、实测、托盘、PDA和计费事实隔离'); 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', 'customer_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', 'customer_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', '成本总金额;仅FIRSTLEG_DIRECT_PAID且CONFIRMED计入已线下支付供应商款'); 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、PUBLIC_WEB_PROXY、PUBLIC_HTML、HCAPTCHA、OFFICIAL_API'); 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、STOPPED'); CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query', 'trigger_source', '任务触发来源:OUTBOUND_CONFIRM或MANUAL'); CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query', 'latest_standard_node', '最近实际标准船节点;预计事实不推进'); CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query', 'latest_node_observed_at', '最近实际标准节点发生时间'); CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query', 'stopped_at', '任务停止时间'); CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query', 'stop_reason', '任务停止原因'); CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query', 'lease_owner', '当前调度租约持有者'); CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query', 'lease_until', '当前调度租约过期时间'); CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_query', 'consecutive_failures', '当前正常查询槽位内的连续技术失败次数'); 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', '供应商调用结果'); 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', '查询任务业务历史动作'); 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_carrier_schedule_node_fact', 'id', '标准船节点事实ID'); CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_node_fact', 'query_id', '所属查询任务ID'); CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_node_fact', 'event_key', '供应商编码与供应商事件ID组成的幂等键'); CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_node_fact', 'provider_code', '轨迹供应商编码'); CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_node_fact', 'provider_event_id', '供应商稳定事件ID'); CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_node_fact', 'provider_node_code', '供应商原始节点编码'); CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_node_fact', 'standard_node', '标准船节点'); CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_node_fact', 'node_sequence', '标准节点单调序号'); CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_node_fact', 'observed_at', '供应商观测时间'); CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_node_fact', 'estimated_at', '供应商预计发生时间'); CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_node_fact', 'actual_at', '供应商确认实际发生时间'); CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_node_fact', 'location_code', '节点地点编码'); CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_node_fact', 'location_name', '节点地点名称'); CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_node_fact', 'source_reference', '可审计来源引用'); CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_node_fact', 'raw_payload_hash', '原始响应SHA-256;不保存敏感原文'); CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_node_fact', 'created_at', '事实入库时间'); CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_exception_fact', 'id', '船节点异常事实ID'); CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_exception_fact', 'query_id', '所属查询任务ID'); CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_exception_fact', 'event_key', '供应商异常事件幂等键'); CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_exception_fact', 'provider_code', '供应商编码'); CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_exception_fact', 'provider_event_id', '供应商稳定事件ID'); CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_exception_fact', 'provider_status', '供应商原始异常状态'); CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_exception_fact', 'exception_code', '平台异常编码'); CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_exception_fact', 'active', '1异常发生;0异常解除'); CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_exception_fact', 'observed_at', '供应商观测时间'); CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_exception_fact', 'location_code', '地点编码'); CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_exception_fact', 'location_name', '地点名称'); CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_exception_fact', 'source_reference', '可审计来源引用'); CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_exception_fact', 'raw_payload_hash', '原始响应SHA-256'); CALL queue_column_comment('baiyue_firstleg', 'fl_carrier_schedule_exception_fact', 'created_at', '事实入库时间'); 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、40HC、40NR、45HC'); 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', 'terminal_trucking_company_id', '码头拖车Supplier逻辑ID快照;历史数据无法推导时为空'); 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目的港清关、DELIVERY_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', 'supplier_id', '应付Warehouse供应商ID快照;应收为空'); CALL queue_column_comment('baiyue_firstleg', 'fl_container_fee_detail', 'supplier_name', '应付Warehouse供应商名称快照;应收为空'); CALL queue_column_comment('baiyue_firstleg', 'fl_container_fee_detail', 'supplier_category', '费用项目供应商类别约束快照;为空表示任意启用供应商'); 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', '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', 'default_calculation_mode', '订单外箱规默认计算方式:ADD或MULTIPLY'); 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_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', 'supplier_category', 'PAYABLE供应商类别约束;为空表示任意启用供应商'); 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', 'baseline_chargeable_quantity', '系统按权威测量与生效报价计算的原始计费量'); 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已计算、CONFIRMED已确认、SENT已发送、SUPERSEDED已被新版本替代、VOIDED已作废'); 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', 'tolerance_percent', '人工调整允许的最大偏差百分比,边界包含'); 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', 'supplier_id', '应付Warehouse供应商ID快照;应收为空'); CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'supplier_name', '应付Warehouse供应商名称快照;应收为空'); CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_fee_detail', 'supplier_category', '费用项目供应商类别约束快照;为空表示任意启用供应商'); 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', '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', 'coefficient_mode', '系数模式快照:ADD或MULTIPLY'); 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', 'manual_outer_height_cm', '仓库人工外箱规高度计算基数cm'); CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'manual_outer_length_cm', '仓库人工外箱规长度计算基数cm'); CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_measurement_item', 'manual_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入库收口中、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', 'block_id', 'Warehouse整票连续库位块ID'); 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', 'pallet_sequence', '已绑定称重托盘在整票内的稳定顺序;空槽为空'); 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', 'shared_overflow', '普通连续位不足时是否使用共享A0'); CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_pallet_allocation', 'slot_no', '连续块内稳定槽位序号'); CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_pallet_allocation', 'slot_status', '槽位状态:RESERVED_EMPTY、BOUND、TRANSFERRED、RELEASED'); 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_pallet_allocation', 'weight_result_id', '已绑定称重结果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', 'gross_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', 'net_weight_kg', '确认毛重扣除托盘皮重后的货物净重kg'); 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', 'pallet_capacity_cbm', '托盘容量CBM快照'); CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_weight_result', 'pallet_code', '托盘编码快照'); CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_weight_result', 'pallet_id', 'Warehouse托盘ID'); CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_weight_result', 'pallet_tare_weight_kg', '托盘皮重KG快照'); CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_weight_result', 'pallet_type', '托盘类型快照'); 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', 'gross_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', 'net_weight_kg', '确认毛重扣除托盘皮重后的货物净重kg'); 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_cabinet_evidence', 'container_booking_id', '证据唯一归属柜次ID'); CALL queue_column_comment('baiyue_firstleg', 'fl_loading_cabinet_evidence', 'source_task_id', '上传来源PDA任务ID,仅用于审计'); CALL queue_column_comment('baiyue_firstleg', 'fl_loading_cabinet_evidence', 'evidence_type', 'PHOTO_STEP_1至PHOTO_STEP_4或SEAL'); CALL queue_column_comment('baiyue_firstleg', 'fl_loading_cabinet_evidence', 'active_flag', '有效记录为1,作废记录为NULL,保证每柜每类型仅一条有效事实'); CALL queue_column_comment('baiyue_firstleg', 'fl_loading_cabinet_evidence', 'operation_key', '任务范围内幂等操作键'); CALL queue_column_comment('baiyue_firstleg', 'fl_loading_cabinet_evidence', 'request_hash', '规范化请求SHA-256摘要'); 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', '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、COMPLETING、COMPLETION_FAILED、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', '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', 'carrier_code', '船司标准编码:MATSON/SML/ZIM/COSCO/EMC/HEDE/OTHER;发布前必填'); 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', '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', 'freight_term', '海运费支付条款快照'); CALL queue_column_comment('baiyue_firstleg', 'fl_ocean_booking', 'id', '海运订舱ID'); 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草稿、RELEASED已发布且永久只读'); 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', 'active_ocean_booking_id', '非终态预约的订舱唯一键'); CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment', 'appointment_no', '平台拖车预约编号'); CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment', 'bill_of_lading_no', '派车提单号快照;关联RELEASED订舱时由订舱事实源冻结'); 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', '停用保留列;订舱级拖车单不写入、不读取'); CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment', 'created_at', '拖车预约创建时间'); CALL queue_column_comment('baiyue_firstleg', 'fl_truck_appointment', 'currency', '应付金额币种;仅RMB或USD'); 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', '停用保留列;订舱级拖车单不写入、不读取'); 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', '关联采购单号'); 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', '附件类型:BILL_OF_LADING提单、DISPATCH_ORDER派车单、DRIVER_DOCUMENT司机证件、VEHICLE_DOCUMENT车辆证件、OTHER其他'); 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', '可见范围:PLATFORM仅平台内部'); 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', 'channel_changed', '订单历史上是否由平台变更过渠道'); 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', 'expected_arrival_at', '客户申报预计到货时间'); 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', '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_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', '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'); USE baiyue_warehouse; CREATE TABLE IF NOT EXISTS wh_location_block ( id BIGINT UNSIGNED NOT NULL COMMENT '整票库位块ID', tenant_id BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '平台公共仓储资源域,固定为0', warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '仓库ID', source_service VARCHAR(40) NOT NULL COMMENT '来源服务', business_id BIGINT UNSIGNED NOT NULL COMMENT '当前归属入库单业务ID', inbound_code VARCHAR(40) NOT NULL COMMENT '当前归属入仓码', slot_count INT UNSIGNED NOT NULL COMMENT '整票独占槽位数', idempotency_key VARCHAR(128) NOT NULL COMMENT '预占幂等键', block_status VARCHAR(20) NOT NULL COMMENT '整票块状态:RESERVED、OCCUPIED或RELEASED', expires_at DATETIME(3) NOT NULL COMMENT '预占过期时间', last_operation_key VARCHAR(128) 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_business_block TINYINT GENERATED ALWAYS AS (CASE WHEN block_status IN ('RESERVED','OCCUPIED') THEN 1 ELSE NULL END) STORED COMMENT '同一来源入库单仅允许一个活动整票块', PRIMARY KEY (id), UNIQUE KEY uk_location_block_idempotency (tenant_id, source_service, idempotency_key), UNIQUE KEY uk_location_block_active_business (tenant_id, source_service, business_id, active_business_block), KEY idx_location_block_business (tenant_id, source_service, business_id, block_status), CONSTRAINT fk_location_block_warehouse FOREIGN KEY (warehouse_id) REFERENCES wh_warehouse (id), CONSTRAINT ck_location_block_slot_count CHECK (slot_count BETWEEN 1 AND 200) ) ENGINE=InnoDB COMMENT='一票入仓码原子持有的稳定库位块'; CREATE TABLE IF NOT EXISTS wh_location_block_slot ( id BIGINT UNSIGNED NOT NULL COMMENT '槽位预占ID,对外作为reservationId', tenant_id BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '平台公共仓储资源域,固定为0', block_id BIGINT UNSIGNED NOT NULL COMMENT '整票库位块ID', slot_no INT UNSIGNED NOT NULL COMMENT '块内稳定槽位序号,从1开始', 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', resource_status VARCHAR(20) NOT NULL COMMENT '资源状态:RESERVED、OCCUPIED或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 '更新时间', version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号', active_normal_slot TINYINT GENERATED ALWAYS AS (CASE WHEN resource_status IN ('RESERVED','OCCUPIED') AND shared_overflow = 0 THEN 1 ELSE NULL END) STORED COMMENT '普通库位活动块槽唯一标记;A0返回NULL', PRIMARY KEY (id), UNIQUE KEY uk_location_block_slot_no (tenant_id, block_id, slot_no), UNIQUE KEY uk_location_block_active_normal (tenant_id, location_id, active_normal_slot), KEY idx_location_block_slot_block (tenant_id, block_id, resource_status), CONSTRAINT fk_location_block_slot_block FOREIGN KEY (block_id) REFERENCES wh_location_block (id), CONSTRAINT fk_location_block_slot_location FOREIGN KEY (location_id) REFERENCES wh_location (id) ) ENGINE=InnoDB COMMENT='整票库位块槽位;空槽也由当前入仓码独占'; USE baiyue_firstleg; CALL queue_column_comment('baiyue_warehouse', 'wh_partner', 'address_line', '详细地址'); CALL queue_column_comment('baiyue_warehouse', 'wh_partner', 'carrier_code', '船司标准编码:MATSON、SML、ZIM、COSCO、EMC、HEDE或OTHER;仅CARRIER必填'); 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', '往来单位类型:CARRIER、SHIPPER或CONSIGNEE'); 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普通海外仓、OVERSEAS_PARTNER海外合作仓'); 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(); -- 后续还会创建整柜报关和同柜组最新基线,保留本会话内的结构保真注释升级器并清空首批队列。 TRUNCATE TABLE tmp_column_comment_target; TRUNCATE TABLE 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 (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema='baiyue_iam' AND table_name='iam_tenant' AND column_name='dimension_initialization_status') <> 1 OR (SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='baiyue_iam' AND table_name='iam_customer_dimension_initialization_task') <> 1 OR (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema='baiyue_iam' AND table_name='iam_customer_dimension_initialization_task' AND column_name IN ('tenant_id','initialization_key','action','status','attempt_count', 'next_retry_at','lease_owner','lease_until','last_error_code','last_error_message', 'task_version')) <> 11 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '统一迁移失败:IAM客户默认尺寸后台初始化状态或可靠任务结构不完整'; END IF; IF (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema='baiyue_firstleg' AND table_name='fl_carrier_schedule_query' AND column_name IN ('trigger_source','latest_standard_node','latest_node_observed_at', 'stopped_at','stop_reason','lease_owner','lease_until','consecutive_failures', 'window_started_at','attempt_count','next_allowed_at')) <> 11 OR (SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema='baiyue_firstleg' AND table_name='fl_carrier_schedule_query' AND index_name='idx_carrier_schedule_query_due' AND column_name IN ('status','next_allowed_at','id')) <> 3 OR (SELECT COUNT(*) FROM information_schema.table_constraints WHERE constraint_schema='baiyue_firstleg' AND table_name='fl_carrier_schedule_query' AND constraint_name='chk_carrier_schedule_query_attempt' AND constraint_type='CHECK') <> 1 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', 'fin_settlement', 'fin_settlement_line', 'fin_settlement_payment')) <> 16 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '统一迁移失败:Finance 首期人工财务表未完整创建'; END IF; IF (SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema='baiyue_finance' AND table_name='fin_settlement' AND index_name='idx_fin_settlement_customer_direction_status' AND column_name IN ('tenant_id','direction','status')) <> 3 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.columns WHERE table_schema = 'baiyue_warehouse' AND ((table_name = 'wh_pallet' AND column_name = 'status') OR (table_name = 'wh_location_reservation' AND column_name = 'active_pallet_reservation') OR (table_name = 'wh_location_occupancy' AND column_name = 'active_pallet_occupancy'))) <> 0 OR (SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = 'baiyue_warehouse' AND index_name IN ('uk_pallet_active_reservation', 'uk_pallet_active_occupancy')) <> 0 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_warehouse' AND ((table_name = 'wh_location_reservation' AND index_name = 'uk_location_active_reservation') OR (table_name = 'wh_location_occupancy' AND index_name = 'uk_location_active_occupancy')) 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_inbound_weight_result' AND column_name IN ( 'pallet_id', 'pallet_code', 'pallet_type', 'pallet_capacity_cbm', 'pallet_tare_weight_kg', 'gross_weight_kg', 'net_weight_kg')) OR (table_name = 'fl_inbound_weight_revision' AND column_name IN ('gross_weight_kg', 'net_weight_kg')))) <> 9 OR (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = 'baiyue_firstleg' AND table_name IN ('fl_inbound_weight_result', 'fl_inbound_weight_revision') AND column_name = 'final_weight_kg') <> 0 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_inbound_pallet_allocation' AND index_name = 'uk_pallet_allocation_pallet') <> 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_pallet_allocation' AND column_name IN ('block_id','slot_no','weight_result_id','pallet_sequence', 'shared_overflow','reservation_id','slot_status')) <> 7 OR (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_inbound_pallet_allocation' AND column_name = 'reservation_no') <> 0 OR (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_inbound_pallet_allocation' AND column_name IN ('pallet_id','pallet_code','pallet_capacity_cbm') AND is_nullable = 'YES') <> 3 OR (SELECT GROUP_CONCAT(column_name ORDER BY seq_in_index) FROM information_schema.statistics WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_inbound_pallet_allocation' AND index_name = 'uk_pallet_allocation_slot') <> 'tenant_id,inbound_id,block_id,slot_no' OR (SELECT GROUP_CONCAT(column_name ORDER BY seq_in_index) FROM information_schema.statistics WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_inbound_pallet_allocation' AND index_name = 'uk_pallet_allocation_weight') <> 'tenant_id,inbound_id,weight_result_id' OR (SELECT COUNT(DISTINCT index_name) FROM information_schema.statistics WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_inbound_pallet_allocation' AND index_name IN ('uk_pallet_allocation_slot','uk_pallet_allocation_weight') AND non_unique = 0) <> 2 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '统一迁移失败:整票库位块槽位字段、空槽语义或唯一索引未正确收敛'; 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_carton_label' AND column_name IN ('label_version', 'batch_status', 'cargo_fingerprint', 'voided_at')) <> 4 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_inbound_carton_label' AND index_name = 'idx_carton_label_active' AND non_unique = 1) <> 4 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', 'default_calculation_mode', 'remark', 'initialization_key', 'created_by', 'created_at', 'updated_by', 'updated_at', 'deleted', 'version')) <> 16 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 = 5102026 AND permission_code = 'platform:order:export' AND permission_name = '导出订单列表' AND parent_id = 5102010) OR (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))) <> 3 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(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.app_code = 'PLATFORM' AND r.role_code IN ('PLATFORM_ADMIN', 'ORDER_OPERATOR') AND p.permission_code IN ('platform:order:export', 'platform:download:list') AND p.status = 'ACTIVE' AND p.deleted = 0) <> 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 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 = 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) )) <> 6 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 (SELECT COUNT(*) FROM baiyue_firstleg.fl_fee_item WHERE tenant_id = 0 AND fee_code = 'FBA_HEAD_FREIGHT' AND category = '头程运费' AND trade_scope = 'INTERNATIONAL' AND fee_type_name = 'FBA头程运费' AND pickup_payable = 0 AND pickup_receivable = 0 AND warehouse_payable = 0 AND warehouse_receivable = 1 AND allocation_fee = 0 AND status = 'ACTIVE' AND deleted = 0) <> 1 OR (SELECT COUNT(*) FROM baiyue_firstleg.fl_fee_item WHERE tenant_id = 0 AND fee_code = 'PRIVATE_LAST_MILE_DELIVERY' AND category = '尾程派送费' AND trade_scope = 'INTERNATIONAL' AND fee_type_name = '私人仓尾程派送费' AND warehouse_payable = 1 AND warehouse_receivable = 1 AND status = 'ACTIVE' AND deleted = 0) <> 1 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), (5105012, 0, 'PLATFORM', 'BUTTON', 5105000, 'platform:finance:bill:update', '修改草稿账单', NULL, 'PUT', 30, '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), (5305000, 0, 'MERCHANT', 'MENU', 5300000, 'merchant:finance:statement', '财务账单', '/finance/statements', NULL, 40, 'ACTIVE', 0), (5305010, 0, 'MERCHANT', 'BUTTON', 5305000, 'merchant:finance:statement:list', '查看账单', NULL, 'GET', 10, 'ACTIVE', 0), (5305011, 0, 'MERCHANT', 'BUTTON', 5305000, 'merchant:finance:statement:view', '查看账单详情及收款', NULL, 'GET', 20, 'ACTIVE', 0), (5305012, 0, 'MERCHANT', 'BUTTON', 5305000, 'merchant:finance:statement:confirm', '确认客户对账单', NULL, 'POST', 30, '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; DELETE rp 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 p.tenant_id=0 AND p.app_code='PLATFORM' AND p.permission_code IN ('platform:finance:fee-source:settlement','platform:finance:bill:create','platform:finance:bill:issue'); DELETE FROM baiyue_iam.iam_permission WHERE tenant_id=0 AND app_code='PLATFORM' AND permission_code IN ('platform:finance:fee-source:settlement','platform:finance:bill:create','platform:finance:bill:issue'); -- 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 = 5105024 AND permission_code <> 'platform:finance:fee-source:review') OR (id = 5105025 AND permission_code <> 'platform:finance:fee-source:edit') 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 = 5105035 AND permission_code <> 'platform:finance:payment:create') OR (id = 5105036 AND permission_code <> 'platform:finance:settlement:void') OR (permission_code = 'platform:finance:fee-source:review' AND id <> 5105024) OR (permission_code = 'platform:finance:fee-source:edit' 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: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, 'INACTIVE', 1, 1), (5105023, 0, 'PLATFORM', 'BUTTON', 5105000, 'platform:finance:fee-source:view', '查看财务费用来源(已停用)', NULL, 'GET', 140, 'INACTIVE', 1, 1), (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:edit', '编辑财务费用来源', 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), (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 = VALUES(status), built_in = 1, deleted = VALUES(deleted); 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:fee-source:review', 'platform:finance:fee-source:edit', '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:payment:create', 'platform:finance:settlement:void')) <> 11 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; DELETE rp 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 p.tenant_id=0 AND p.app_code='PLATFORM' AND p.permission_code IN ('platform:finance:bill:view','platform:finance:account:view','platform:finance:fee-source:view','platform:finance:settlement-ar:view','platform:finance:settlement-ap:view'); UPDATE baiyue_iam.iam_permission SET status='INACTIVE',deleted=1 WHERE tenant_id=0 AND app_code='PLATFORM' AND permission_code IN ('platform:finance:bill:view','platform:finance:account:view','platform:finance:fee-source:view','platform:finance:settlement-ar:view','platform:finance:settlement-ap:view'); 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:update', '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:fee-source:review', 'platform:finance:fee-source:edit', '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:payment:create', 'platform:finance:settlement:void') ON DUPLICATE KEY UPDATE tenant_id=VALUES(tenant_id); -- 开发期 list-only 收口:先移除角色关联并停用旧只读资源,再执行最终 Finance 断言。 DELETE rp 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 p.tenant_id=0 AND p.app_code='PLATFORM' AND p.permission_code IN ('platform:finance:bill:view','platform:finance:account:view', 'platform:finance:fee-source:view','platform:finance:settlement-ar:view', 'platform:finance:settlement-ap:view'); UPDATE baiyue_iam.iam_permission SET status='INACTIVE', deleted=1 WHERE tenant_id=0 AND app_code='PLATFORM' AND permission_code IN ('platform:finance:bill:view','platform:finance:account:view', 'platform:finance:fee-source:view','platform:finance:settlement-ar:view', 'platform:finance:settlement-ap:view'); CALL assert_loading_pick_task_shapes(1); DROP PROCEDURE IF EXISTS assert_loading_pick_task_shapes; /* 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; IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='baiyue_firstleg' AND table_name='fl_container_booking' AND column_name='customer_id') THEN ALTER TABLE baiyue_firstleg.fl_container_booking ADD COLUMN customer_id BIGINT UNSIGNED NULL COMMENT '已关联真实订舱指定的客户范围' AFTER ocean_booking_requirement_id; 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='customer_name') THEN ALTER TABLE baiyue_firstleg.fl_container_booking ADD COLUMN customer_name VARCHAR(128) NULL COMMENT '已关联真实订舱客户名称快照' AFTER customer_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),c.customer_id=COALESCE(c.customer_id,b.customer_id),c.customer_name=COALESCE(c.customer_name,b.customer_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 OR c.customer_id IS NULL OR c.customer_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 IN ('LOADING', 'COMPLETED') AND ( NOT (OLD.id <=> NEW.id) OR NOT (OLD.booking_no <=> NEW.booking_no) OR NOT (OLD.cabinet_no <=> NEW.cabinet_no) OR NOT (OLD.channel_code <=> NEW.channel_code) OR NOT (OLD.channel_name <=> NEW.channel_name) OR NOT (OLD.ocean_booking_id <=> NEW.ocean_booking_id) OR NOT (OLD.ocean_booking_requirement_id <=> NEW.ocean_booking_requirement_id) OR NOT (OLD.warehouse_id <=> NEW.warehouse_id) OR NOT (OLD.warehouse_name <=> NEW.warehouse_name) OR NOT (OLD.container_type <=> NEW.container_type) OR NOT (OLD.shipping_company_id <=> NEW.shipping_company_id) OR NOT (OLD.shipping_company_name <=> NEW.shipping_company_name) OR NOT (OLD.departure_port_code <=> NEW.departure_port_code) OR NOT (OLD.departure_port_name <=> NEW.departure_port_name) OR NOT (OLD.destination_port_code <=> NEW.destination_port_code) OR NOT (OLD.destination_port_name <=> NEW.destination_port_name) OR NOT (OLD.destination_warehouse_code <=> NEW.destination_warehouse_code) OR NOT (OLD.destination_warehouse_name <=> NEW.destination_warehouse_name) OR NOT (OLD.capacity_cbm <=> NEW.capacity_cbm) OR NOT (OLD.max_weight_kg <=> NEW.max_weight_kg) OR NOT (OLD.terminal_trucking_company_id <=> NEW.terminal_trucking_company_id) OR NOT (OLD.terminal_trucking_company <=> NEW.terminal_trucking_company) OR NOT (OLD.loading_at <=> NEW.loading_at) OR NOT (OLD.actual_sailing_at <=> NEW.actual_sailing_at) OR NOT (OLD.estimated_port_arrival_at <=> NEW.estimated_port_arrival_at) OR NOT (OLD.actual_port_arrival_at <=> NEW.actual_port_arrival_at) OR NOT (OLD.estimated_warehouse_arrival_at <=> NEW.estimated_warehouse_arrival_at) OR NOT (OLD.actual_warehouse_arrival_at <=> NEW.actual_warehouse_arrival_at) OR NOT (OLD.created_by <=> NEW.created_by) OR NOT (OLD.created_by_name <=> NEW.created_by_name) OR NOT (OLD.created_at <=> NEW.created_at) ) THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '装柜作业开始后仅允许修改集装箱号与放舱运输数据'; END IF; END$$ DELIMITER ; DROP TRIGGER IF EXISTS 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 '变更后航次号', operation_id VARCHAR(64) NOT NULL COMMENT '运输字段操作幂等ID',request_hash CHAR(64) NOT 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),UNIQUE KEY uk_container_transport_history_operation(container_booking_id,operator_id,operation_id), KEY idx_container_transport_history(container_booking_id,created_at,id) ) ENGINE=InnoDB COMMENT='柜次真实运输字段不可变变更审计'; DROP PROCEDURE IF EXISTS baiyue_firstleg.ensure_container_transport_history_schema; DELIMITER $$ CREATE PROCEDURE baiyue_firstleg.ensure_container_transport_history_schema() BEGIN IF (NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='baiyue_firstleg' AND table_name='fl_container_transport_history' AND column_name='operation_id') OR NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='baiyue_firstleg' AND table_name='fl_container_transport_history' AND column_name='request_hash')) AND EXISTS (SELECT 1 FROM baiyue_firstleg.fl_container_transport_history LIMIT 1) THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='运输历史升级失败:旧表已有数据且缺少幂等字段'; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='baiyue_firstleg' AND table_name='fl_container_transport_history' AND column_name='operation_id') THEN ALTER TABLE baiyue_firstleg.fl_container_transport_history ADD COLUMN operation_id VARCHAR(64) NOT NULL COMMENT '运输字段操作幂等ID' AFTER after_voyage_no; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='baiyue_firstleg' AND table_name='fl_container_transport_history' AND column_name='request_hash') THEN ALTER TABLE baiyue_firstleg.fl_container_transport_history ADD COLUMN request_hash CHAR(64) NOT NULL COMMENT '运输字段请求哈希' AFTER operation_id; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema='baiyue_firstleg' AND table_name='fl_container_transport_history' AND index_name='uk_container_transport_history_operation') THEN ALTER TABLE baiyue_firstleg.fl_container_transport_history ADD UNIQUE KEY uk_container_transport_history_operation (container_booking_id,operator_id,operation_id); END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema='baiyue_firstleg' AND table_name='fl_container_transport_history' AND index_name='idx_container_transport_history') THEN ALTER TABLE baiyue_firstleg.fl_container_transport_history ADD KEY idx_container_transport_history (container_booking_id,created_at,id); END IF; END$$ DELIMITER ; CALL baiyue_firstleg.ensure_container_transport_history_schema(); DROP PROCEDURE baiyue_firstleg.ensure_container_transport_history_schema; ALTER TABLE baiyue_firstleg.fl_container_transport_history MODIFY COLUMN id BIGINT UNSIGNED NOT NULL COMMENT '运输字段变更历史ID', MODIFY COLUMN container_booking_id BIGINT UNSIGNED NOT NULL COMMENT '稳定柜次ID', MODIFY COLUMN before_container_no VARCHAR(64) NULL COMMENT '变更前真实集装箱号', MODIFY COLUMN after_container_no VARCHAR(64) NULL COMMENT '变更后真实集装箱号', MODIFY COLUMN before_voyage_no VARCHAR(64) NULL COMMENT '变更前航次号', MODIFY COLUMN after_voyage_no VARCHAR(64) NULL COMMENT '变更后航次号', MODIFY COLUMN operation_id VARCHAR(64) NOT NULL COMMENT '运输字段操作幂等ID', MODIFY COLUMN request_hash CHAR(64) NOT NULL COMMENT '运输字段请求哈希', MODIFY COLUMN reason VARCHAR(500) NULL COMMENT '运输字段变更原因', MODIFY COLUMN operator_id BIGINT UNSIGNED NOT NULL COMMENT '变更操作人ID', MODIFY COLUMN operator_name VARCHAR(64) NULL COMMENT '变更操作人名称快照', MODIFY COLUMN created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) 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) COMMENT '记录创建时间', updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '最后更新时间', 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) COMMENT '记录创建时间', updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '最后更新时间', 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) COMMENT '记录创建时间', updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '最后更新时间', 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) COMMENT '记录创建时间', updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '最后更新时间', 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) COMMENT '记录创建时间', updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '最后更新时间', 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')) <> 13 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%') <> 12 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 IN ('merchant:notification:view','merchant:notification:read','merchant:notification:read-all')) <> 3 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='统一迁移失败:企业管理员消息查看授权不完整'; END IF; END$$ DELIMITER ; -- 货柜目的港清关与DO采用轻量文件事实;PU为货柜主表数字码;出口报关由customs-v1整柜聚合唯一承载。 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; DROP PROCEDURE IF EXISTS phase3_pickup_code_add_column; DELIMITER $$ CREATE PROCEDURE phase3_pickup_code_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_booking' AND column_name=p_name) THEN SET @ddl=CONCAT('ALTER TABLE fl_container_booking ADD COLUMN ',p_name,' ',p_definition); PREPARE statement FROM @ddl; EXECUTE statement; DEALLOCATE PREPARE statement; END IF; END$$ DELIMITER ; CALL phase3_pickup_code_add_column('pickup_code', "VARCHAR(64) NULL COMMENT 'PU数字码;按字符串保存以保留前导零' AFTER actual_warehouse_arrival_at"); CALL phase3_pickup_code_add_column('pickup_code_updated_by', "BIGINT UNSIGNED NULL COMMENT 'PU码最后填写人ID' AFTER pickup_code"); CALL phase3_pickup_code_add_column('pickup_code_updated_by_name', "VARCHAR(64) NULL COMMENT 'PU码最后填写人名称快照' AFTER pickup_code_updated_by"); CALL phase3_pickup_code_add_column('pickup_code_updated_at', "DATETIME(3) NULL COMMENT 'PU码最后填写时间' AFTER pickup_code_updated_by_name"); DROP PROCEDURE phase3_pickup_code_add_column; SET @has_pickup_code_check=(SELECT COUNT(*) FROM information_schema.table_constraints WHERE table_schema='baiyue_firstleg' AND table_name='fl_container_booking' AND constraint_name='chk_container_booking_pickup_code' AND constraint_type='CHECK'); SET @ddl=IF(@has_pickup_code_check=0, "ALTER TABLE fl_container_booking ADD CONSTRAINT chk_container_booking_pickup_code CHECK (pickup_code IS NULL OR pickup_code REGEXP '^[0-9]{1,64}$')", 'SELECT 1'); PREPARE statement FROM @ddl; EXECUTE statement; DEALLOCATE PREPARE statement; 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','DELIVERY_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','DELIVERY_ORDER') THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='装柜完成后货柜物流文件不可新增'; END IF; END$$ DELIMITER ; DROP TRIGGER IF EXISTS trg_container_document_before_update; DELIMITER $$ CREATE TRIGGER trg_container_document_before_update BEFORE UPDATE ON fl_container_document FOR EACH ROW BEGIN DECLARE current_plan_status VARCHAR(24); SELECT MAX(plan_status) INTO current_plan_status FROM fl_loading_plan WHERE container_booking_id=OLD.container_booking_id; IF current_plan_status='COMPLETED' AND OLD.document_type NOT IN ('DESTINATION_CLEARANCE','DELIVERY_ORDER') THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='装柜完成后货柜物流文件不可修改'; END IF; END$$ DELIMITER ; -- 二、三期入库完成后的唯一状态为 WAIT_SPLIT。 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 COMMENT '同一操作人幂等键', MODIFY request_hash CHAR(64) NOT NULL COMMENT '规范化请求摘要'; 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), (5105045,0,'PLATFORM','MENU',5105000,'platform:finance:receivable-statistics:list','应收款统计表','/finance/receivable-statistics','GET',25,'ACTIVE',1,0), (5105050,0,'PLATFORM','BUTTON',5105040,'platform:finance:customer-account:export','导出客户账户',NULL,'POST',90,'ACTIVE',1,0), (5105051,0,'PLATFORM','BUTTON',5105041,'platform:finance:customer-arap:export','导出客户应收应付',NULL,'POST',90,'ACTIVE',1,0), (5105052,0,'PLATFORM','BUTTON',5105042,'platform:finance:customer-statement:export','导出客户对账单',NULL,'POST',90,'ACTIVE',1,0), (5105053,0,'PLATFORM','BUTTON',5105043,'platform:finance:settlement-ar:export','导出应收结算单',NULL,'POST',90,'ACTIVE',1,0), (5105054,0,'PLATFORM','BUTTON',5105044,'platform:finance:settlement-ap:export','导出应付结算单',NULL,'POST',90,'ACTIVE',1,0), (5105055,0,'PLATFORM','BUTTON',5105045,'platform:finance:receivable-statistics:export','导出应收款统计表',NULL,'POST',90,'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'); DELETE rp FROM iam_role_permission rp JOIN iam_permission p ON p.id=rp.permission_id AND p.tenant_id=rp.tenant_id WHERE p.tenant_id=0 AND p.app_code='PLATFORM' AND p.permission_code IN ('platform:finance:bill:view','platform:finance:account:view','platform:finance:fee-source:view','platform:finance:settlement-ar:view','platform:finance:settlement-ap:view'); UPDATE iam_permission SET status='INACTIVE', deleted=1 WHERE tenant_id=0 AND app_code='PLATFORM' AND permission_code IN ('platform:finance:bill:view','platform:finance:account:view','platform:finance:fee-source:view','platform:finance:settlement-ar:view','platform:finance:settlement-ap:view'); 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:receivable-statistics:list','platform:finance:customer-statement:list','platform:finance:settlement-ar:list','platform:finance:settlement-ap:list','platform:finance:customer-account:export','platform:finance:customer-arap:export','platform:finance:receivable-statistics:export','platform:finance:customer-statement:export','platform:finance:settlement-ar:export','platform:finance:settlement-ap:export','platform:download:list'); UPDATE iam_permission p JOIN (SELECT 'platform:finance:fee-source:review' code,5105041 parent_id UNION ALL SELECT 'platform:finance:fee-source:edit',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-ap:create',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 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='MERCHANT' WHERE r.tenant_id=0 AND r.app_code='MERCHANT' AND r.role_code='MERCHANT_OWNER' AND r.status='ACTIVE' AND r.deleted=0 AND p.permission_code IN ('merchant:finance:statement','merchant:finance:statement:list', 'merchant:finance:statement:view','merchant:finance:statement:confirm') AND p.status='ACTIVE' AND p.deleted=0; 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 (5301024, 0, 'MERCHANT', 'BUTTON', 5301020, '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认证', 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_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='平台公共供应商业务档案'; CREATE TABLE IF NOT EXISTS wh_supplier_carrier ( supplier_id BIGINT UNSIGNED NOT NULL COMMENT 'BOOKING供应商ID', tenant_id BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '平台公共作用域,固定为0', carrier_code VARCHAR(16) NOT NULL COMMENT '船司标准编码', created_by BIGINT UNSIGNED NULL COMMENT '创建人ID', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', PRIMARY KEY(supplier_id,carrier_code), KEY idx_wh_supplier_carrier_code(tenant_id,carrier_code,supplier_id), CONSTRAINT chk_wh_supplier_carrier_tenant CHECK(tenant_id=0), CONSTRAINT chk_wh_supplier_carrier_code CHECK(carrier_code IN('MATSON','SML','ZIM','COSCO','EMC','HEDE','OTHER')) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='BOOKING供应商与标准船司编码多对多关联'; 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('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_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(DISTINCT index_name) FROM information_schema.statistics WHERE table_schema='baiyue_warehouse' AND table_name='wh_supplier_carrier' AND index_name IN('PRIMARY','idx_wh_supplier_carrier_code'))<>2 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='供应商船司关联表索引不完整'; END IF; IF (SELECT COUNT(*) FROM information_schema.table_constraints WHERE table_schema='baiyue_warehouse' AND table_name='wh_supplier_carrier' AND constraint_type='CHECK' AND constraint_name IN('chk_wh_supplier_carrier_tenant','chk_wh_supplier_carrier_code'))<>2 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 COMMENT '整柜订单ID', tenant_id BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '平台公共作用域,固定为0', business_type VARCHAR(16) NOT NULL COMMENT '业务类型:INBOUND入库、OUTBOUND出库', order_no VARCHAR(40) NOT NULL COMMENT '业务单号', warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '平台国内仓ID', warehouse_name VARCHAR(120) NOT NULL COMMENT '仓库名称', container_no VARCHAR(32) NOT NULL COMMENT '柜号', cargo_name VARCHAR(128) NOT NULL COMMENT '货物名称', box_count INT UNSIGNED NOT NULL COMMENT '箱数', volume_cbm DECIMAL(16,4) NOT NULL DEFAULT 0 COMMENT '体积(立方米)', operation_time DATETIME(3) NOT NULL COMMENT '操作时间', status VARCHAR(16) NOT NULL COMMENT '状态:PENDING待处理、COMPLETED已完成', 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 '逻辑删除标记', version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本', 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='国内仓独立整柜入出库单'; ALTER TABLE wh_domestic_container_order MODIFY COLUMN id BIGINT UNSIGNED NOT NULL COMMENT '整柜订单ID'; CREATE TABLE IF NOT EXISTS wh_domestic_container_inventory ( id BIGINT UNSIGNED NOT NULL COMMENT '库存ID', tenant_id BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '平台公共作用域,固定为0', warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '平台国内仓ID', warehouse_name VARCHAR(120) NOT NULL COMMENT '仓库名称', container_no VARCHAR(32) NOT NULL COMMENT '柜号', cargo_name VARCHAR(128) NOT NULL COMMENT '货物名称', inbound_boxes INT UNSIGNED NOT NULL COMMENT '入库箱数', outbound_boxes INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '出库箱数', available_boxes INT UNSIGNED NOT NULL COMMENT '可用箱数', inbound_volume_cbm DECIMAL(16,4) NOT NULL COMMENT '入库体积(立方米)', inventory_status VARCHAR(24) NOT NULL COMMENT '库存状态:IN_STOCK在库、PARTIALLY_OUTBOUND部分出库、OUTBOUND已出库', 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_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 COMMENT '流水ID', tenant_id BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '平台公共作用域,固定为0', inventory_id BIGINT UNSIGNED NOT NULL COMMENT '库存台账ID', warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '平台国内仓ID', container_no VARCHAR(32) NOT NULL COMMENT '柜号', business_type VARCHAR(16) NOT NULL COMMENT '业务类型:INBOUND入库、OUTBOUND出库', order_id BIGINT UNSIGNED NOT NULL COMMENT '业务单ID', order_no VARCHAR(40) NOT NULL COMMENT '业务单号', quantity INT UNSIGNED NOT NULL COMMENT '变动箱数', operation_time DATETIME(3) 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(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记', version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本', 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 COMMENT '幂等记录ID', tenant_id BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '平台公共作用域,固定为0', operation_type VARCHAR(16) NOT NULL COMMENT '操作类型:INBOUND入库、OUTBOUND出库', idempotency_key VARCHAR(128) NOT NULL COMMENT '幂等键', request_hash CHAR(64) NOT NULL COMMENT '请求哈希', resource_id BIGINT UNSIGNED NULL COMMENT '业务资源ID', resource_no VARCHAR(40) NULL COMMENT '业务资源编号', operation_status VARCHAR(16) NOT NULL COMMENT '状态:PROCESSING处理中、COMPLETED已完成', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', completed_at DATETIME(3) NULL COMMENT '完成时间', 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'))); CALL baiyue_iam.assert_notification_center_schema(); DROP PROCEDURE IF EXISTS baiyue_iam.assert_notification_center_schema; -- 开发期不保留独立详情权限:列表权限是唯一只读权限。 DELETE rp FROM iam_role_permission rp JOIN iam_permission p ON p.tenant_id=rp.tenant_id AND p.id=rp.permission_id WHERE p.permission_code IN('platform:firstleg:container:view','platform:firstleg:preload:view','platform:firstleg:loading-list:view','platform:supplier:view'); UPDATE iam_permission SET status='INACTIVE',deleted=1 WHERE permission_code IN('platform:firstleg:container:view','platform:firstleg:preload:view','platform:firstleg:loading-list:view','platform:supplier:view'); -- 入库完成后的唯一状态为 WAIT_SPLIT。 -- Finance 权限和角色授权断言必须位于五个财务列表菜单及其授权写入之后,保证整份脚本可重复执行。 DROP PROCEDURE IF EXISTS assert_finance_permissions; 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) <> 34 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='统一迁移失败:平台 Finance 权限不完整'; END IF; IF (SELECT COUNT(*) FROM baiyue_iam.iam_permission WHERE tenant_id=0 AND app_code='MERCHANT' AND permission_code IN ('merchant:finance:statement','merchant:finance:statement:list','merchant:finance:statement:view','merchant:finance:statement:confirm') AND status='ACTIVE' AND deleted=0) <> 4 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:%')) <> 35 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.status='ACTIVE' AND p.deleted=0 AND p.permission_code IN ('platform:download:list','platform:finance:customer-account:export','platform:finance:customer-arap:export','platform:finance:receivable-statistics:export','platform:finance:customer-statement:export','platform:finance:settlement-ar:export','platform:finance:settlement-ap:export')) <> 7 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='统一迁移失败:平台管理员 Finance 导出和下载中心授权不完整'; 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.id=4000100 AND r.tenant_id=0 AND r.app_code='MERCHANT' AND r.role_code='MERCHANT_OWNER' AND r.status='ACTIVE' AND r.deleted=0 AND p.permission_code IN ('merchant:finance:statement','merchant:finance:statement:list','merchant:finance:statement:view','merchant:finance:statement:confirm') AND p.status='ACTIVE' AND p.deleted=0) <> 4 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='统一迁移失败:客户全局管理员 Finance 模板授权不完整'; END IF; END$$ DELIMITER ; -- IAM 跨区安全事件可靠发送箱。来源:iam/src/main/resources/db/security-event-outbox.sql USE baiyue_iam; CREATE TABLE IF NOT EXISTS iam_security_subject_version ( auth_user_id BIGINT NOT NULL COMMENT '认证账号ID', permission_version BIGINT NOT NULL DEFAULT 0 COMMENT '权限版本,身份或授权变化时单调递增', updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间', PRIMARY KEY (auth_user_id) ) ENGINE=InnoDB COMMENT='安全主体权限版本'; CREATE TABLE IF NOT EXISTS iam_security_tenant_version ( tenant_id BIGINT NOT NULL COMMENT '客户ID', status_version BIGINT NOT NULL DEFAULT 0 COMMENT '客户状态单调版本', updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间', PRIMARY KEY (tenant_id) ) ENGINE=InnoDB COMMENT='客户状态事件版本'; CREATE TABLE IF NOT EXISTS iam_security_event_outbox ( event_id VARCHAR(64) NOT NULL COMMENT '全局事件ID,消费者幂等键', event_type VARCHAR(64) NOT NULL COMMENT '安全事件类型', aggregate_type VARCHAR(32) NOT NULL COMMENT 'USER、TENANT或AUTHORIZATION', aggregate_id VARCHAR(64) NOT NULL COMMENT '聚合标识', payload_json JSON NOT NULL COMMENT '标准跨区事件信封,不含密码、令牌和权限全集', status VARCHAR(16) NOT NULL DEFAULT 'PENDING' COMMENT '发送状态:PENDING、SENDING、SENT、DEAD', attempt_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 '最近一次错误', 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 (event_id), KEY idx_security_outbox_publish (status,next_retry_at,lease_until,created_at), KEY idx_security_outbox_aggregate (aggregate_type,aggregate_id,created_at) ) ENGINE=InnoDB COMMENT='IAM可靠安全事件本地发送箱'; DROP PROCEDURE IF EXISTS assert_iam_security_event_schema; DELIMITER $$ CREATE PROCEDURE assert_iam_security_event_schema() BEGIN IF (SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='baiyue_iam' AND table_name IN ('iam_security_subject_version','iam_security_tenant_version','iam_security_event_outbox')) <> 3 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 table_name='iam_security_event_outbox' AND index_name IN ('PRIMARY','idx_security_outbox_publish','idx_security_outbox_aggregate')) <> 3 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='统一迁移失败:IAM安全事件索引不完整'; END IF; END$$ DELIMITER ; CALL assert_iam_security_event_schema(); DROP PROCEDURE IF EXISTS assert_iam_security_event_schema; USE baiyue_firstleg; DROP PROCEDURE IF EXISTS add_container_empty_report_fields; DELIMITER $$ CREATE PROCEDURE add_container_empty_report_fields() BEGIN IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='fl_container_booking' AND column_name='unloading_completed_at') THEN ALTER TABLE fl_container_booking ADD COLUMN unloading_completed_at DATETIME(3) NULL COMMENT '海外仓卸柜完成时间(UTC)' AFTER actual_warehouse_arrival_at; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='fl_container_booking' AND column_name='unloading_photo_uploaded_at') THEN ALTER TABLE fl_container_booking ADD COLUMN unloading_photo_uploaded_at DATETIME(3) NULL COMMENT '海外仓卸柜照片上传成功时间(UTC)' AFTER unloading_completed_at; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='fl_container_booking' AND column_name='estimated_empty_reported_at') THEN ALTER TABLE fl_container_booking ADD COLUMN estimated_empty_reported_at DATETIME(3) NULL COMMENT '预计报空时间(卸柜照片上传成功时间加两天,UTC)' AFTER unloading_photo_uploaded_at; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='fl_container_booking' AND column_name='actual_empty_reported_at') THEN ALTER TABLE fl_container_booking ADD COLUMN actual_empty_reported_at DATETIME(3) NULL COMMENT '实际报空时间(UTC)' AFTER estimated_empty_reported_at; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='fl_container_booking' AND column_name='empty_report_source_version') THEN ALTER TABLE fl_container_booking ADD COLUMN empty_report_source_version BIGINT UNSIGNED NULL COMMENT '海外货柜事件聚合版本,用于阻止乱序覆盖' AFTER actual_empty_reported_at; END IF; END$$ DELIMITER ; CALL add_container_empty_report_fields(); DROP PROCEDURE IF EXISTS add_container_empty_report_fields; CREATE TABLE IF NOT EXISTS fl_container_event_inbox ( event_id VARCHAR(64) NOT NULL COMMENT '跨区事件全局唯一ID', event_type VARCHAR(64) NOT NULL COMMENT '事件类型', event_version INT UNSIGNED NOT NULL COMMENT '事件契约版本', aggregate_id VARCHAR(64) NOT NULL COMMENT '跨区业务聚合ID', aggregate_version BIGINT UNSIGNED NOT NULL COMMENT '跨区业务聚合版本', payload_hash VARCHAR(80) NOT NULL COMMENT '规范化负载SHA-256摘要', payload_json JSON NOT NULL COMMENT '原始事件负载', status VARCHAR(16) NOT NULL COMMENT 'RECEIVED或PROCESSED', received_at DATETIME(3) NOT NULL COMMENT '接收时间(UTC)', processed_at DATETIME(3) NULL COMMENT '处理完成时间(UTC)', PRIMARY KEY (event_id), KEY idx_container_event_aggregate (aggregate_id,aggregate_version) ) ENGINE=InnoDB COMMENT='海外到国内业务事件消费Inbox'; DROP PROCEDURE IF EXISTS add_inbound_overseas_hold_version; DELIMITER $$ CREATE PROCEDURE add_inbound_overseas_hold_version() BEGIN IF NOT EXISTS ( SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='fl_inbound_order' AND column_name='last_overseas_hold_version' ) THEN ALTER TABLE fl_inbound_order ADD COLUMN last_overseas_hold_version BIGINT UNSIGNED NULL COMMENT '海外暂扣事件最新聚合版本,用于阻止乱序覆盖' AFTER current_hold_reason; END IF; ALTER TABLE fl_container_event_inbox MODIFY COLUMN aggregate_id VARCHAR(64) NOT NULL COMMENT '跨区业务聚合ID', MODIFY COLUMN aggregate_version BIGINT UNSIGNED NOT NULL COMMENT '跨区业务聚合版本', COMMENT='海外到国内业务事件消费Inbox'; END$$ DELIMITER ; CALL add_inbound_overseas_hold_version(); DROP PROCEDURE IF EXISTS add_inbound_overseas_hold_version; CALL queue_column_comment('baiyue_firstleg', 'fl_inbound_order', 'last_overseas_hold_version', '海外暂扣事件最新聚合版本,用于阻止乱序覆盖'); -- 标准跨区事件摘要包含 sha256: 前缀,共 71 字符;兼容既有 Finance 64 位摘要。 ALTER TABLE fl_inbound_outbox MODIFY COLUMN payload_hash VARCHAR(71) NULL COMMENT '事件规范化负载SHA-256;标准跨区事件含sha256:前缀'; ALTER TABLE fl_inbound_outbox MODIFY COLUMN payload_json LONGTEXT NOT NULL COMMENT '事件负载快照;文本类型保留跨区事件摘要所依赖的数值词法'; -- 海外权限统一目录(来源:iam/db/lastleg-permissions.sql) USE baiyue_iam; -- 四期 Admin 海外权限只使用 platform:overseas-*;列表权限包含详情只读。 DELETE rp FROM iam_role_permission rp JOIN iam_permission p ON p.id=rp.permission_id WHERE p.permission_code LIKE 'platform:lastleg:%'; UPDATE iam_permission SET status='INACTIVE', deleted=1 WHERE permission_code LIKE 'platform:lastleg:%'; 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 (5107000,0,'PLATFORM','MENU',0,'platform:overseas-warehouse:last-mile-order:list','海外尾程订单','/overseas-warehouse/last-mile-orders','GET',700,'ACTIVE',1,0), (5107001,0,'PLATFORM','BUTTON',5107000,'platform:overseas-warehouse:last-mile-order:edit','编辑海外尾程订单',NULL,'PUT',701,'ACTIVE',1,0), (5107002,0,'PLATFORM','BUTTON',5107000,'platform:overseas-warehouse:last-mile-order:export','导出海外尾程订单',NULL,'GET',702,'ACTIVE',1,0), (5107010,0,'PLATFORM','MENU',0,'platform:overseas-warehouse:inbound-order:list','海外入仓单','/overseas-warehouse/inbound-orders','GET',710,'ACTIVE',1,0), (5107011,0,'PLATFORM','BUTTON',5107010,'platform:overseas-warehouse:inbound-order:create','创建海外入仓计划',NULL,'POST',711,'ACTIVE',1,0), (5107012,0,'PLATFORM','BUTTON',5107010,'platform:overseas-warehouse:inbound-order:edit','编辑海外入仓计划',NULL,'POST',712,'ACTIVE',1,0), (5107013,0,'PLATFORM','BUTTON',5107010,'platform:overseas-warehouse:inbound-order:fba-appointment','FBA预约',NULL,'POST',713,'ACTIVE',1,0), (5107014,0,'PLATFORM','BUTTON',5107010,'platform:overseas-warehouse:inbound-order:smart-dispatch','智能排车',NULL,'POST',714,'ACTIVE',1,0), (5107015,0,'PLATFORM','BUTTON',5107010,'lastleg:pda:operate','海外仓PDA入仓作业',NULL,'POST',715,'ACTIVE',1,0), (5107020,0,'PLATFORM','MENU',0,'platform:overseas-warehouse:relabel-order:list','海外换标单','/overseas-warehouse/relabel-orders','GET',720,'ACTIVE',1,0), (5107021,0,'PLATFORM','BUTTON',5107020,'platform:overseas-warehouse:relabel-order:create','创建海外换标单',NULL,'POST',721,'ACTIVE',1,0), (5107022,0,'PLATFORM','BUTTON',5107020,'platform:overseas-warehouse:relabel-order:status','变更海外换标单状态',NULL,'PUT',722,'ACTIVE',1,0), (5107030,0,'PLATFORM','MENU',0,'platform:overseas-warehouse:pallet:list','海外托盘','/overseas-warehouse/pallets','GET',730,'ACTIVE',1,0), (5107031,0,'PLATFORM','BUTTON',5107030,'platform:overseas-warehouse:pallet:create','创建海外托盘',NULL,'POST',731,'ACTIVE',1,0), (5107032,0,'PLATFORM','BUTTON',5107030,'platform:overseas-warehouse:pallet:edit','编辑海外托盘',NULL,'PUT',732,'ACTIVE',1,0), (5107033,0,'PLATFORM','BUTTON',5107030,'platform:overseas-warehouse:pallet:delete','删除海外托盘',NULL,'DELETE',733,'ACTIVE',1,0), (5107040,0,'PLATFORM','MENU',0,'platform:overseas-warehouse:inventory:list','海外库存','/overseas-warehouse/inventory','GET',740,'ACTIVE',1,0), (5107041,0,'PLATFORM','BUTTON',5107040,'platform:overseas-warehouse:inventory:export','导出海外库存',NULL,'GET',741,'ACTIVE',1,0), (5107042,0,'PLATFORM','BUTTON',5107040,'platform:overseas-warehouse:inventory:edit','编辑海外库存状态',NULL,'POST',742,'ACTIVE',1,0), (5107043,0,'PLATFORM','BUTTON',5107040,'platform:overseas-warehouse:inventory:adjust','调整海外库存',NULL,'POST',743,'ACTIVE',1,0), (5107050,0,'PLATFORM','MENU',0,'platform:overseas-warehouse:warehouse:list','海外仓资源','/overseas-warehouse/warehouses','GET',750,'ACTIVE',1,0), (5107051,0,'PLATFORM','BUTTON',5107050,'platform:overseas-warehouse:warehouse:create','创建海外仓资源',NULL,'POST',751,'ACTIVE',1,0), (5107052,0,'PLATFORM','BUTTON',5107050,'platform:overseas-warehouse:warehouse:edit','编辑海外仓资源',NULL,'PUT',752,'ACTIVE',1,0), (5107053,0,'PLATFORM','BUTTON',5107050,'platform:overseas-warehouse:warehouse:status','变更海外仓状态',NULL,'PATCH',753,'ACTIVE',1,0), (5107060,0,'PLATFORM','MENU',0,'platform:overseas-warehouse:location:list','海外库位','/overseas-warehouse/locations','GET',760,'ACTIVE',1,0), (5107061,0,'PLATFORM','BUTTON',5107060,'platform:overseas-warehouse:location:create','创建海外库位',NULL,'POST',761,'ACTIVE',1,0), (5107062,0,'PLATFORM','BUTTON',5107060,'platform:overseas-warehouse:location:edit','编辑海外库位',NULL,'PUT',762,'ACTIVE',1,0), (5107063,0,'PLATFORM','BUTTON',5107060,'platform:overseas-warehouse:location:delete','删除海外库位',NULL,'DELETE',763,'ACTIVE',1,0), (5107090,0,'PLATFORM','MENU',0,'platform:overseas-warehouse:outbound:list','海外出库单','/overseas-warehouse/outbounds','GET',765,'ACTIVE',1,0), (5107091,0,'PLATFORM','BUTTON',5107090,'platform:overseas-warehouse:outbound:create','创建海外出库单',NULL,'POST',766,'ACTIVE',1,0), (5107092,0,'PLATFORM','MENU',0,'platform:overseas-warehouse:delivery:list','海外派送单','/overseas-warehouse/delivery-orders','GET',767,'ACTIVE',1,0), (5107100,0,'PLATFORM','MENU',0,'platform:overseas-business:purchase:list','海外采购订单','/overseas-business/purchases','GET',800,'ACTIVE',1,0), (5107101,0,'PLATFORM','BUTTON',5107100,'platform:overseas-business:purchase:edit','编辑海外采购订单',NULL,'PATCH',801,'ACTIVE',1,0), (5107102,0,'PLATFORM','BUTTON',5107100,'platform:overseas-business:purchase:review','审核海外采购订单',NULL,'POST',802,'ACTIVE',1,0), (5107110,0,'PLATFORM','MENU',0,'platform:overseas-business:truck:list','本土卡车','/overseas-business/trucks','GET',810,'ACTIVE',1,0), (5107111,0,'PLATFORM','BUTTON',5107110,'platform:overseas-business:truck:create','创建本土卡车单',NULL,'POST',811,'ACTIVE',1,0), (5107112,0,'PLATFORM','BUTTON',5107110,'platform:overseas-business:truck:edit','编辑本土卡车单',NULL,'PUT',812,'ACTIVE',1,0), (5107113,0,'PLATFORM','BUTTON',5107110,'platform:overseas-business:truck:status','变更本土卡车状态',NULL,'PATCH',813,'ACTIVE',1,0), (5107120,0,'PLATFORM','MENU',0,'platform:overseas-business:looseboard:list','本土散板','/overseas-business/looseboards','GET',820,'ACTIVE',1,0), (5107121,0,'PLATFORM','BUTTON',5107120,'platform:overseas-business:looseboard:create','创建本土散板单',NULL,'POST',821,'ACTIVE',1,0), (5107122,0,'PLATFORM','BUTTON',5107120,'platform:overseas-business:looseboard:edit','编辑本土散板单',NULL,'PUT',822,'ACTIVE',1,0), (5107123,0,'PLATFORM','BUTTON',5107120,'platform:overseas-business:looseboard:status','变更本土散板状态',NULL,'PATCH',823,'ACTIVE',1,0), (5107200,0,'PLATFORM','MENU',0,'platform:overseas-master-data:quote:list','海外仓报价','/overseas-master-data/quotes','GET',900,'ACTIVE',1,0), (5107201,0,'PLATFORM','BUTTON',5107200,'platform:overseas-master-data:quote:create','创建海外仓报价',NULL,'POST',901,'ACTIVE',1,0), (5107202,0,'PLATFORM','BUTTON',5107200,'platform:overseas-master-data:quote:edit','编辑海外仓报价',NULL,'PUT',902,'ACTIVE',1,0), (5107203,0,'PLATFORM','BUTTON',5107200,'platform:overseas-master-data:quote:status','变更海外仓报价状态',NULL,'PATCH',903,'ACTIVE',1,0), (5107204,0,'PLATFORM','BUTTON',5107200,'platform:overseas-master-data:quote:export','导出海外仓报价',NULL,'GET',904,'ACTIVE',1,0), (5107210,0,'PLATFORM','MENU',0,'platform:overseas-master-data:exchange-rate:list','月度汇率','/overseas-master-data/exchange-rates','GET',910,'ACTIVE',1,0), (5107211,0,'PLATFORM','BUTTON',5107210,'platform:overseas-master-data:exchange-rate:create','创建月度汇率',NULL,'POST',911,'ACTIVE',1,0), (5107212,0,'PLATFORM','BUTTON',5107210,'platform:overseas-master-data:exchange-rate:edit','编辑月度汇率',NULL,'PUT',912,'ACTIVE',1,0), (5107213,0,'PLATFORM','BUTTON',5107210,'platform:overseas-master-data:exchange-rate:status','变更月度汇率状态',NULL,'PATCH',913,'ACTIVE',1,0), (5107214,0,'PLATFORM','BUTTON',5107210,'platform:overseas-master-data:exchange-rate:export','导出月度汇率',NULL,'GET',914,'ACTIVE',1,0), (5107220,0,'PLATFORM','MENU',0,'platform:overseas-master-data:vehicle:list','车辆主数据','/overseas-master-data/vehicles','GET',920,'ACTIVE',1,0), (5107221,0,'PLATFORM','BUTTON',5107220,'platform:overseas-master-data:vehicle:create','创建车辆主数据',NULL,'POST',921,'ACTIVE',1,0), (5107222,0,'PLATFORM','BUTTON',5107220,'platform:overseas-master-data:vehicle:edit','编辑车辆主数据',NULL,'PUT',922,'ACTIVE',1,0), (5107223,0,'PLATFORM','BUTTON',5107220,'platform:overseas-master-data:vehicle:status','变更车辆状态',NULL,'PATCH',923,'ACTIVE',1,0), (5107224,0,'PLATFORM','BUTTON',5107220,'platform:overseas-master-data:vehicle:export','导出车辆主数据',NULL,'GET',924,'ACTIVE',1,0), (5306100,0,'MERCHANT','MENU',0,'merchant:cross-border:procurement','海外采购','/cross-border/procurement',NULL,70,'ACTIVE',1,0), (5306110,0,'MERCHANT','MENU',0,'merchant:cross-border:domestic-trucking','美国本土卡','/cross-border/domestic-trucking',NULL,80,'ACTIVE',1,0), (5306120,0,'MERCHANT','MENU',0,'merchant:cross-border:pallet-shipping','美本土散板','/cross-border/pallet-shipping',NULL,90,'ACTIVE',1,0), (5306130,0,'MERCHANT','MENU',0,'merchant:warehouse:inbound:list','海外仓入库订单','/orders/inbound',NULL,50,'ACTIVE',1,0) ON DUPLICATE KEY UPDATE 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, role.id, permission.id, NOW(3) FROM iam_role role JOIN iam_permission permission ON permission.tenant_id=0 AND permission.app_code='PLATFORM' AND permission.id BETWEEN 5107000 AND 5107299 AND permission.permission_code<>'lastleg:pda:operate' WHERE role.tenant_id=0 AND role.app_code='PLATFORM' AND role.role_code='PLATFORM_ADMIN' AND role.status='ACTIVE' AND role.deleted=0 AND permission.status='ACTIVE' AND permission.deleted=0; 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 WHERE permission.permission_code='lastleg:pda:operate' AND NOT (role.id=4000005 AND role.tenant_id=0 AND role.app_code='PLATFORM' AND role.role_code='OVERSEAS_WAREHOUSE_PDA_OPERATOR'); INSERT IGNORE INTO iam_role_permission (tenant_id, role_id, permission_id, created_at) SELECT 0, role.id, permission.id, NOW(3) FROM iam_role role JOIN iam_permission permission ON permission.tenant_id=0 AND permission.app_code='PLATFORM' AND permission.permission_code='lastleg:pda:operate' WHERE role.id=4000005 AND role.tenant_id=0 AND role.app_code='PLATFORM' AND role.role_code='OVERSEAS_WAREHOUSE_PDA_OPERATOR' AND role.status='ACTIVE' AND role.deleted=0 AND permission.status='ACTIVE' AND permission.deleted=0; DROP PROCEDURE IF EXISTS assert_overseas_pda_picker_permission; DELIMITER $$ CREATE PROCEDURE assert_overseas_pda_picker_permission() BEGIN IF (SELECT COUNT(*) FROM iam_role_permission grant_row JOIN iam_role role ON role.id=grant_row.role_id AND role.tenant_id=grant_row.tenant_id JOIN iam_permission permission ON permission.id=grant_row.permission_id AND permission.tenant_id=grant_row.tenant_id WHERE permission.permission_code='lastleg:pda:operate' AND role.id=4000005 AND role.app_code='PLATFORM' AND role.role_code='OVERSEAS_WAREHOUSE_PDA_OPERATOR') <> 1 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='海外PDA权限初始化失败:海外仓 PDA 作业员未精确授权'; END IF; IF EXISTS (SELECT 1 FROM iam_role_permission grant_row JOIN iam_role role ON role.id=grant_row.role_id AND role.tenant_id=grant_row.tenant_id JOIN iam_permission permission ON permission.id=grant_row.permission_id AND permission.tenant_id=grant_row.tenant_id WHERE permission.permission_code='lastleg:pda:operate' AND NOT (role.id=4000005 AND role.tenant_id=0 AND role.app_code='PLATFORM' AND role.role_code='OVERSEAS_WAREHOUSE_PDA_OPERATOR')) THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='海外PDA权限初始化失败:存在非海外仓 PDA 作业员授权'; END IF; END$$ DELIMITER ; CALL assert_overseas_pda_picker_permission(); DROP PROCEDURE assert_overseas_pda_picker_permission; -- 四期仓库后台角色严格最小授权:list 同时覆盖列表与详情,只读角色不得获得任何写权限。 DROP TEMPORARY TABLE IF EXISTS tmp_phase4_warehouse_role_permission; CREATE TEMPORARY TABLE tmp_phase4_warehouse_role_permission ( role_code VARCHAR(64) NOT NULL, permission_code VARCHAR(160) NOT NULL, PRIMARY KEY (role_code, permission_code) ); INSERT INTO tmp_phase4_warehouse_role_permission (role_code, permission_code) VALUES ('WAREHOUSE_OPERATOR','platform:overseas-warehouse:last-mile-order:list'), ('WAREHOUSE_OPERATOR','platform:overseas-warehouse:inbound-order:list'), ('WAREHOUSE_OPERATOR','platform:overseas-warehouse:relabel-order:list'), ('WAREHOUSE_OPERATOR','platform:overseas-warehouse:inventory:list'), ('WAREHOUSE_OPERATOR','platform:overseas-warehouse:warehouse:list'), ('WAREHOUSE_OPERATOR','platform:overseas-warehouse:location:list'), ('WAREHOUSE_OPERATOR','platform:overseas-warehouse:pallet:list'), ('WAREHOUSE_OPERATOR','platform:overseas-business:purchase:list'), ('WAREHOUSE_OPERATOR','platform:overseas-business:truck:list'), ('WAREHOUSE_OPERATOR','platform:overseas-business:looseboard:list'), ('WAREHOUSE_TALLY','platform:overseas-warehouse:last-mile-order:list'), ('WAREHOUSE_TALLY','platform:overseas-warehouse:inbound-order:list'), ('WAREHOUSE_TALLY','platform:overseas-warehouse:relabel-order:list'), ('WAREHOUSE_TALLY','platform:overseas-warehouse:inventory:list'), ('WAREHOUSE_TALLY','platform:overseas-warehouse:warehouse:list'), ('WAREHOUSE_TALLY','platform:overseas-warehouse:location:list'), ('WAREHOUSE_TALLY','platform:overseas-warehouse:pallet:list'), ('WAREHOUSE_TALLY','platform:overseas-business:purchase:list'), ('WAREHOUSE_TALLY','platform:overseas-business:truck:list'), ('WAREHOUSE_TALLY','platform:overseas-business:looseboard:list'); DELETE grant_row FROM iam_role_permission grant_row JOIN iam_role role ON role.id=grant_row.role_id AND role.tenant_id=grant_row.tenant_id JOIN iam_permission permission ON permission.id=grant_row.permission_id AND permission.tenant_id=grant_row.tenant_id LEFT JOIN tmp_phase4_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','OVERSEAS_WAREHOUSE_PDA_OPERATOR','WAREHOUSE_TALLY','CN_FIRSTLEG_PDA_OPERATOR') AND permission.permission_code LIKE 'platform:overseas-%' AND expected.permission_code IS NULL; INSERT IGNORE INTO iam_role_permission (tenant_id, role_id, permission_id, created_at) SELECT 0, role.id, permission.id, NOW(3) FROM tmp_phase4_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; DROP PROCEDURE IF EXISTS assert_phase4_warehouse_role_minimum; DELIMITER $$ CREATE PROCEDURE assert_phase4_warehouse_role_minimum() BEGIN IF (SELECT COUNT(*) FROM tmp_phase4_warehouse_role_permission) <> 20 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='四期仓库角色最小权限矩阵数量异常'; END IF; IF EXISTS (SELECT 1 FROM tmp_phase4_warehouse_role_permission WHERE permission_code REGEXP ':(create|edit|status|export|adjust|delete|review)$') THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='四期仓库只读角色错误获得写权限'; END IF; IF EXISTS (SELECT 1 FROM tmp_phase4_warehouse_role_permission expected LEFT 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 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 LEFT JOIN iam_role_permission grant_row ON grant_row.tenant_id=0 AND grant_row.role_id=role.id AND grant_row.permission_id=permission.id WHERE grant_row.role_id IS NULL) THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='四期仓库角色最小权限授权不完整'; END IF; END$$ DELIMITER ; CALL assert_phase4_warehouse_role_minimum(); DROP PROCEDURE assert_phase4_warehouse_role_minimum; DROP TEMPORARY TABLE tmp_phase4_warehouse_role_permission; INSERT IGNORE INTO iam_role_permission (tenant_id, role_id, permission_id, created_at) SELECT 0, role.id, permission.id, NOW(3) FROM iam_role role JOIN iam_permission permission ON permission.tenant_id=0 AND permission.app_code='PLATFORM' AND permission.permission_code IN ( 'platform:overseas-warehouse:inbound-order:fba-appointment', 'platform:overseas-warehouse:inbound-order:smart-dispatch' ) WHERE role.tenant_id=0 AND role.app_code='PLATFORM' AND UPPER(role.role_code)='PLATFORM_CUSTOM_ADMIN' AND role.status='ACTIVE' AND role.deleted=0 AND permission.status='ACTIVE' AND permission.deleted=0; -- 客户默认管理员获得当前客户海外业务入口;其他客户角色按企业实际分工授权。 INSERT IGNORE INTO iam_role_permission (tenant_id, role_id, permission_id, created_at) SELECT 0, role.id, permission.id, NOW(3) FROM iam_role role JOIN iam_permission permission ON permission.tenant_id=0 AND permission.app_code='MERCHANT' AND permission.permission_code IN ( 'merchant:cross-border:procurement', 'merchant:cross-border:domestic-trucking', 'merchant:cross-border:pallet-shipping', 'merchant:warehouse:inbound:list' ) WHERE role.tenant_id=0 AND role.app_code='MERCHANT' AND role.role_code='MERCHANT_OWNER' AND role.status='ACTIVE' AND role.deleted=0 AND permission.status='ACTIVE' AND permission.deleted=0; DROP PROCEDURE IF EXISTS assert_overseas_merchant_permissions; DELIMITER $$ CREATE PROCEDURE assert_overseas_merchant_permissions() BEGIN IF (SELECT COUNT(*) FROM iam_permission WHERE tenant_id=0 AND app_code='MERCHANT' AND status='ACTIVE' AND deleted=0 AND permission_code IN ( 'merchant:cross-border:procurement', 'merchant:cross-border:domestic-trucking', 'merchant:cross-border:pallet-shipping', 'merchant:warehouse:inbound:list')) <> 4 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='海外客户权限初始化失败:权限资源不完整'; END IF; IF (SELECT COUNT(*) FROM iam_role_permission grant_row JOIN iam_role role ON role.id=grant_row.role_id AND role.tenant_id=grant_row.tenant_id JOIN iam_permission permission ON permission.id=grant_row.permission_id WHERE role.tenant_id=0 AND role.app_code='MERCHANT' AND role.role_code='MERCHANT_OWNER' AND role.status='ACTIVE' AND role.deleted=0 AND permission.tenant_id=0 AND permission.app_code='MERCHANT' AND permission.status='ACTIVE' AND permission.deleted=0 AND permission.permission_code IN ( 'merchant:cross-border:procurement', 'merchant:cross-border:domestic-trucking', 'merchant:cross-border:pallet-shipping', 'merchant:warehouse:inbound:list')) <> 4 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='海外客户权限初始化失败:客户默认管理员授权不完整'; END IF; END$$ DELIMITER ; CALL assert_overseas_merchant_permissions(); DROP PROCEDURE assert_overseas_merchant_permissions; -- Tail-leg order actions are available to the current platform administrator role. INSERT IGNORE INTO iam_role_permission (tenant_id, role_id, permission_id, created_at) SELECT 0, role.id, permission.id, NOW(3) FROM iam_role role JOIN iam_permission permission ON permission.tenant_id=0 AND permission.app_code='PLATFORM' AND permission.permission_code IN ( 'platform:overseas-warehouse:last-mile-order:edit', 'platform:overseas-warehouse:inbound-order:create' ) WHERE role.tenant_id=0 AND role.app_code='PLATFORM' AND UPPER(role.role_code) IN ('PLATFORM_ADMIN','PLATFORM_CUSTOM_ADMIN') AND role.status='ACTIVE' AND role.deleted=0 AND permission.status='ACTIVE' AND permission.deleted=0; -- PLATFORM_CUSTOM_ADMIN needs access to the overseas outbound and delivery lists shown in the admin navigation. INSERT IGNORE INTO iam_role_permission (tenant_id, role_id, permission_id, created_at) SELECT 0, role.id, permission.id, NOW(3) FROM iam_role role JOIN iam_permission permission ON permission.tenant_id=0 AND permission.app_code='PLATFORM' AND permission.permission_code IN ( 'platform:overseas-warehouse:outbound:list', 'platform:overseas-warehouse:delivery:list' ) WHERE role.tenant_id=0 AND role.app_code='PLATFORM' AND UPPER(role.role_code)='PLATFORM_CUSTOM_ADMIN' AND role.status='ACTIVE' AND role.deleted=0 AND permission.status='ACTIVE' AND permission.deleted=0; -- 海外排车、出库与派送权限(来源:iam/db/phase5-delivery-permissions.sql) USE baiyue_iam; SET NAMES utf8mb4; -- 五期海外排车、出库与派送权限。列表权限同时覆盖列表和详情只读,写操作保持独立授权。 DROP PROCEDURE IF EXISTS guard_phase5_delivery_permission_ids; DELIMITER $$ CREATE PROCEDURE guard_phase5_delivery_permission_ids() BEGIN IF EXISTS ( SELECT 1 FROM iam_permission WHERE (id = 5107064 AND permission_code <> 'platform:overseas-warehouse:dispatch:list') OR (id = 5107016 AND permission_code <> 'platform:overseas-warehouse:dispatch:edit') OR (id = 5107017 AND permission_code <> 'platform:overseas-warehouse:dispatch:cancel') OR (id = 5107018 AND permission_code <> 'platform:overseas-warehouse:dispatch:transfer-delivery') OR (id = 5107090 AND permission_code <> 'platform:overseas-warehouse:outbound:list') OR (id = 5107091 AND permission_code <> 'platform:overseas-warehouse:outbound:create') OR (id = 5107092 AND permission_code <> 'platform:overseas-warehouse:delivery:list') OR (id = 5107094 AND permission_code <> 'platform:overseas-warehouse:delivery:documents') OR (id = 5107093 AND permission_code <> 'platform:overseas-warehouse:outbound:cancel') OR (id = 5107070 AND permission_code <> 'platform:overseas-warehouse:exception-order:list') OR (id = 5107071 AND permission_code <> 'platform:overseas-warehouse:exception-order:create') OR (id = 5107073 AND permission_code <> 'platform:overseas-warehouse:exception-order:process') OR (id = 5107072 AND permission_code <> 'platform:overseas-warehouse:delivery:return') OR (id = 5107080 AND permission_code <> 'platform:overseas-warehouse:customer-visit:list') OR (id = 5107081 AND permission_code <> 'platform:overseas-warehouse:customer-visit:edit') OR (id = 5107082 AND permission_code <> 'platform:overseas-warehouse:delivery:status') OR (id = 5306000 AND permission_code <> 'merchant:warehouse:delivery:list') OR (id = 5306001 AND permission_code <> 'merchant:warehouse:delivery:create') ) OR EXISTS ( SELECT 1 FROM iam_permission WHERE (permission_code = 'platform:overseas-warehouse:dispatch:list' AND id <> 5107064) OR (permission_code = 'platform:overseas-warehouse:dispatch:edit' AND id <> 5107016) OR (permission_code = 'platform:overseas-warehouse:dispatch:cancel' AND id <> 5107017) OR (permission_code = 'platform:overseas-warehouse:dispatch:transfer-delivery' AND id <> 5107018) OR (permission_code = 'platform:overseas-warehouse:outbound:list' AND id <> 5107090) OR (permission_code = 'platform:overseas-warehouse:outbound:create' AND id <> 5107091) OR (permission_code = 'platform:overseas-warehouse:delivery:list' AND id <> 5107092) OR (permission_code = 'platform:overseas-warehouse:delivery:documents' AND id <> 5107094) OR (permission_code = 'platform:overseas-warehouse:outbound:cancel' AND id <> 5107093) OR (permission_code = 'platform:overseas-warehouse:exception-order:list' AND id <> 5107070) OR (permission_code = 'platform:overseas-warehouse:exception-order:create' AND id <> 5107071) OR (permission_code = 'platform:overseas-warehouse:exception-order:process' AND id <> 5107073) OR (permission_code = 'platform:overseas-warehouse:delivery:return' AND id <> 5107072) OR (permission_code = 'platform:overseas-warehouse:customer-visit:list' AND id <> 5107080) OR (permission_code = 'platform:overseas-warehouse:customer-visit:edit' AND id <> 5107081) OR (permission_code = 'platform:overseas-warehouse:delivery:status' AND id <> 5107082) OR (permission_code = 'merchant:warehouse:delivery:list' AND id <> 5306000) OR (permission_code = 'merchant:warehouse:delivery:create' AND id <> 5306001) ) THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='五期海外派送权限初始化失败:权限ID或编码冲突'; END IF; END$$ DELIMITER ; CALL guard_phase5_delivery_permission_ids(); DROP PROCEDURE guard_phase5_delivery_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 (5107064,0,'PLATFORM','MENU',0,'platform:overseas-warehouse:dispatch:list','海外排车单','/overseas-warehouse/dispatches','GET',764,'ACTIVE',1,0), (5107016,0,'PLATFORM','BUTTON',5107064,'platform:overseas-warehouse:dispatch:edit','编辑海外排车',NULL,'PUT',716,'ACTIVE',1,0), (5107017,0,'PLATFORM','BUTTON',5107064,'platform:overseas-warehouse:dispatch:cancel','取消海外排车',NULL,'DELETE',717,'ACTIVE',1,0), (5107018,0,'PLATFORM','BUTTON',5107064,'platform:overseas-warehouse:dispatch:transfer-delivery','排车转派送',NULL,'POST',718,'ACTIVE',1,0), (5107070,0,'PLATFORM','MENU',0,'platform:overseas-warehouse:exception-order:list','海外异常订单','/overseas-warehouse/exception-orders','GET',770,'ACTIVE',1,0), (5107071,0,'PLATFORM','BUTTON',5107070,'platform:overseas-warehouse:exception-order:create','新增海外异常订单',NULL,'POST',771,'ACTIVE',1,0), (5107073,0,'PLATFORM','BUTTON',5107070,'platform:overseas-warehouse:exception-order:process','处理海外异常订单',NULL,'POST',773,'ACTIVE',1,0), (5107072,0,'PLATFORM','BUTTON',5107092,'platform:overseas-warehouse:delivery:return','派送单标记退货',NULL,'POST',772,'ACTIVE',1,0), (5107080,0,'PLATFORM','MENU',0,'platform:overseas-warehouse:customer-visit:list','客户回访','/overseas-warehouse/customer-visits','GET',780,'ACTIVE',1,0), (5107081,0,'PLATFORM','BUTTON',5107080,'platform:overseas-warehouse:customer-visit:edit','保存客户回访',NULL,'PUT',781,'ACTIVE',1,0), (5107082,0,'PLATFORM','BUTTON',5107092,'platform:overseas-warehouse:delivery:status','编辑派送单状态',NULL,'PUT',782,'ACTIVE',1,0), (5107090,0,'PLATFORM','MENU',0,'platform:overseas-warehouse:outbound:list','海外出库单','/overseas-warehouse/outbounds','GET',765,'ACTIVE',1,0), (5107091,0,'PLATFORM','BUTTON',5107090,'platform:overseas-warehouse:outbound:create','创建海外出库单',NULL,'POST',766,'ACTIVE',1,0), (5107092,0,'PLATFORM','MENU',0,'platform:overseas-warehouse:delivery:list','海外派送单','/overseas-warehouse/delivery-orders','GET',767,'ACTIVE',1,0), (5107094,0,'PLATFORM','BUTTON',5107092,'platform:overseas-warehouse:delivery:documents','管理派送资料',NULL,'POST',769,'ACTIVE',1,0), (5107093,0,'PLATFORM','BUTTON',5107090,'platform:overseas-warehouse:outbound:cancel','取消海外出库单',NULL,'POST',768,'ACTIVE',1,0), (5306000,0,'MERCHANT','MENU',0,'merchant:warehouse:delivery:list','海外仓派送单','/orders/delivery','GET',60,'ACTIVE',1,0), (5306001,0,'MERCHANT','BUTTON',5306000,'merchant:warehouse:delivery:create','创建海外仓派送单',NULL,'POST',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; -- 平台管理员默认拥有五期全部后台动作;已有的平台自定义管理员保持同等五期操作能力。 INSERT IGNORE INTO iam_role_permission (tenant_id, role_id, permission_id, created_at) SELECT 0, role.id, permission.id, CURRENT_TIMESTAMP(3) FROM iam_role role JOIN iam_permission permission ON permission.tenant_id=0 AND permission.app_code='PLATFORM' WHERE role.tenant_id=0 AND role.app_code='PLATFORM' AND UPPER(role.role_code) IN ('PLATFORM_ADMIN','PLATFORM_CUSTOM_ADMIN') AND role.status='ACTIVE' AND role.deleted=0 AND permission.permission_code IN ( 'platform:overseas-warehouse:dispatch:list', 'platform:overseas-warehouse:dispatch:edit', 'platform:overseas-warehouse:dispatch:cancel', 'platform:overseas-warehouse:dispatch:transfer-delivery', 'platform:overseas-warehouse:outbound:list', 'platform:overseas-warehouse:outbound:create', 'platform:overseas-warehouse:outbound:cancel', 'platform:overseas-warehouse:delivery:list', 'platform:overseas-warehouse:exception-order:list', 'platform:overseas-warehouse:exception-order:create', 'platform:overseas-warehouse:exception-order:process', 'platform:overseas-warehouse:delivery:return', 'platform:overseas-warehouse:delivery:status', 'platform:overseas-warehouse:delivery:documents', 'platform:overseas-warehouse:customer-visit:list', 'platform:overseas-warehouse:customer-visit:edit') AND permission.status='ACTIVE' AND permission.deleted=0; -- 仓库后台岗位按最小职责授权;PDA 岗位不得获得后台排车、出库或派送权限。 DROP TEMPORARY TABLE IF EXISTS tmp_phase5_delivery_role_permission; CREATE TEMPORARY TABLE tmp_phase5_delivery_role_permission ( role_code VARCHAR(64) NOT NULL, permission_code VARCHAR(160) NOT NULL, PRIMARY KEY (role_code, permission_code) ) ENGINE=MEMORY; INSERT INTO tmp_phase5_delivery_role_permission (role_code, permission_code) VALUES ('WAREHOUSE_OPERATOR','platform:overseas-warehouse:dispatch:list'), ('WAREHOUSE_OPERATOR','platform:overseas-warehouse:dispatch:edit'), ('WAREHOUSE_OPERATOR','platform:overseas-warehouse:dispatch:cancel'), ('WAREHOUSE_OPERATOR','platform:overseas-warehouse:dispatch:transfer-delivery'), ('WAREHOUSE_OPERATOR','platform:overseas-warehouse:outbound:list'), ('WAREHOUSE_OPERATOR','platform:overseas-warehouse:outbound:create'), ('WAREHOUSE_OPERATOR','platform:overseas-warehouse:outbound:cancel'), ('WAREHOUSE_OPERATOR','platform:overseas-warehouse:delivery:list'), ('WAREHOUSE_OPERATOR','platform:overseas-warehouse:exception-order:list'), ('WAREHOUSE_OPERATOR','platform:overseas-warehouse:exception-order:create'), ('WAREHOUSE_OPERATOR','platform:overseas-warehouse:exception-order:process'), ('WAREHOUSE_OPERATOR','platform:overseas-warehouse:delivery:return'), ('WAREHOUSE_OPERATOR','platform:overseas-warehouse:delivery:status'), ('WAREHOUSE_OPERATOR','platform:overseas-warehouse:delivery:documents'), ('WAREHOUSE_OPERATOR','platform:overseas-warehouse:customer-visit:list'), ('WAREHOUSE_OPERATOR','platform:overseas-warehouse:customer-visit:edit'), ('WAREHOUSE_TALLY','platform:overseas-warehouse:dispatch:list'), ('WAREHOUSE_TALLY','platform:overseas-warehouse:outbound:list'), ('WAREHOUSE_TALLY','platform:overseas-warehouse:delivery:list'), ('WAREHOUSE_TALLY','platform:overseas-warehouse:exception-order:list'), ('WAREHOUSE_TALLY','platform:overseas-warehouse:customer-visit:list'); DELETE grant_row FROM iam_role_permission grant_row JOIN iam_role role ON role.id=grant_row.role_id AND role.tenant_id=grant_row.tenant_id JOIN iam_permission permission ON permission.id=grant_row.permission_id AND permission.tenant_id=grant_row.tenant_id LEFT JOIN tmp_phase5_delivery_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_TALLY','OVERSEAS_WAREHOUSE_PDA_OPERATOR','CN_FIRSTLEG_PDA_OPERATOR') AND permission.permission_code IN ( 'platform:overseas-warehouse:dispatch:list', 'platform:overseas-warehouse:dispatch:edit', 'platform:overseas-warehouse:dispatch:cancel', 'platform:overseas-warehouse:dispatch:transfer-delivery', 'platform:overseas-warehouse:outbound:list', 'platform:overseas-warehouse:outbound:create', 'platform:overseas-warehouse:outbound:cancel', 'platform:overseas-warehouse:delivery:list', 'platform:overseas-warehouse:exception-order:list', 'platform:overseas-warehouse:exception-order:create', 'platform:overseas-warehouse:exception-order:process', 'platform:overseas-warehouse:delivery:return', 'platform:overseas-warehouse:delivery:status', 'platform:overseas-warehouse:delivery:documents', 'platform:overseas-warehouse:customer-visit:list', 'platform:overseas-warehouse:customer-visit:edit') AND expected.permission_code IS NULL; INSERT IGNORE INTO iam_role_permission (tenant_id, role_id, permission_id, created_at) SELECT 0, role.id, permission.id, CURRENT_TIMESTAMP(3) FROM tmp_phase5_delivery_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 IGNORE INTO iam_role_permission (tenant_id, role_id, permission_id, created_at) SELECT 0, role.id, permission.id, CURRENT_TIMESTAMP(3) FROM iam_role role JOIN iam_permission permission ON permission.tenant_id=0 AND permission.app_code='MERCHANT' WHERE role.tenant_id=0 AND role.app_code='MERCHANT' AND role.role_code='MERCHANT_OWNER' AND role.status='ACTIVE' AND role.deleted=0 AND permission.permission_code IN ( 'merchant:warehouse:delivery:list', 'merchant:warehouse:delivery:create') AND permission.status='ACTIVE' AND permission.deleted=0; DROP PROCEDURE IF EXISTS assert_phase5_delivery_permissions; DELIMITER $$ CREATE PROCEDURE assert_phase5_delivery_permissions() BEGIN DECLARE v_warehouse_operator_permission_count INT DEFAULT 0; DECLARE v_warehouse_tally_permission_count INT DEFAULT 0; IF (SELECT COUNT(*) FROM iam_permission WHERE tenant_id=0 AND status='ACTIVE' AND deleted=0 AND permission_code IN ( 'platform:overseas-warehouse:dispatch:list', 'platform:overseas-warehouse:dispatch:edit', 'platform:overseas-warehouse:dispatch:cancel', 'platform:overseas-warehouse:dispatch:transfer-delivery', 'platform:overseas-warehouse:outbound:list', 'platform:overseas-warehouse:outbound:create', 'platform:overseas-warehouse:outbound:cancel', 'platform:overseas-warehouse:delivery:list', 'platform:overseas-warehouse:exception-order:list', 'platform:overseas-warehouse:exception-order:create', 'platform:overseas-warehouse:exception-order:process', 'platform:overseas-warehouse:delivery:return', 'platform:overseas-warehouse:delivery:status', 'platform:overseas-warehouse:delivery:documents', 'platform:overseas-warehouse:customer-visit:list', 'platform:overseas-warehouse:customer-visit:edit', 'merchant:warehouse:delivery:list', 'merchant:warehouse:delivery:create')) <> 18 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='五期海外派送权限初始化失败:权限目录不完整'; END IF; IF (SELECT COUNT(*) FROM iam_role_permission grant_row JOIN iam_role role ON role.id=grant_row.role_id AND role.tenant_id=grant_row.tenant_id JOIN iam_permission permission ON permission.id=grant_row.permission_id WHERE role.role_code='PLATFORM_ADMIN' AND permission.permission_code IN ( 'platform:overseas-warehouse:dispatch:list', 'platform:overseas-warehouse:dispatch:edit', 'platform:overseas-warehouse:dispatch:cancel', 'platform:overseas-warehouse:dispatch:transfer-delivery', 'platform:overseas-warehouse:outbound:list', 'platform:overseas-warehouse:outbound:create', 'platform:overseas-warehouse:outbound:cancel', 'platform:overseas-warehouse:delivery:list', 'platform:overseas-warehouse:exception-order:list', 'platform:overseas-warehouse:exception-order:create', 'platform:overseas-warehouse:exception-order:process', 'platform:overseas-warehouse:delivery:return', 'platform:overseas-warehouse:delivery:status', 'platform:overseas-warehouse:delivery:documents', 'platform:overseas-warehouse:customer-visit:list', 'platform:overseas-warehouse:customer-visit:edit')) <> 16 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='五期海外派送权限初始化失败:平台管理员授权不完整'; END IF; IF EXISTS (SELECT 1 FROM iam_role role WHERE role.tenant_id=0 AND role.app_code='PLATFORM' AND UPPER(role.role_code)='PLATFORM_CUSTOM_ADMIN' AND role.status='ACTIVE' AND role.deleted=0 AND (SELECT COUNT(*) FROM iam_role_permission grant_row JOIN iam_permission permission ON permission.id=grant_row.permission_id WHERE grant_row.role_id=role.id AND grant_row.tenant_id=role.tenant_id AND permission.permission_code IN ( 'platform:overseas-warehouse:dispatch:list', 'platform:overseas-warehouse:dispatch:edit', 'platform:overseas-warehouse:dispatch:cancel', 'platform:overseas-warehouse:dispatch:transfer-delivery', 'platform:overseas-warehouse:outbound:list', 'platform:overseas-warehouse:outbound:create', 'platform:overseas-warehouse:outbound:cancel', 'platform:overseas-warehouse:delivery:list', 'platform:overseas-warehouse:exception-order:list', 'platform:overseas-warehouse:exception-order:create', 'platform:overseas-warehouse:exception-order:process', 'platform:overseas-warehouse:delivery:return', 'platform:overseas-warehouse:delivery:status', 'platform:overseas-warehouse:delivery:documents', 'platform:overseas-warehouse:customer-visit:list', 'platform:overseas-warehouse:customer-visit:edit')) <> 16) THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='五期海外派送权限初始化失败:平台自定义管理员授权不完整'; END IF; SELECT COALESCE(SUM(role_code='WAREHOUSE_OPERATOR'),0), COALESCE(SUM(role_code='WAREHOUSE_TALLY'),0) INTO v_warehouse_operator_permission_count, v_warehouse_tally_permission_count FROM tmp_phase5_delivery_role_permission; IF v_warehouse_operator_permission_count <> 16 OR v_warehouse_tally_permission_count <> 5 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='五期海外派送权限初始化失败:仓库岗位最小权限矩阵异常'; END IF; IF EXISTS (SELECT 1 FROM iam_role_permission grant_row JOIN iam_role role ON role.id=grant_row.role_id AND role.tenant_id=grant_row.tenant_id JOIN iam_permission permission ON permission.id=grant_row.permission_id WHERE role.role_code IN ('OVERSEAS_WAREHOUSE_PDA_OPERATOR','CN_FIRSTLEG_PDA_OPERATOR') AND permission.permission_code IN ( 'platform:overseas-warehouse:dispatch:list', 'platform:overseas-warehouse:dispatch:edit', 'platform:overseas-warehouse:dispatch:cancel', 'platform:overseas-warehouse:dispatch:transfer-delivery', 'platform:overseas-warehouse:outbound:list', 'platform:overseas-warehouse:outbound:create', 'platform:overseas-warehouse:outbound:cancel', 'platform:overseas-warehouse:delivery:list', 'platform:overseas-warehouse:exception-order:list', 'platform:overseas-warehouse:exception-order:create', 'platform:overseas-warehouse:exception-order:process', 'platform:overseas-warehouse:delivery:return', 'platform:overseas-warehouse:delivery:status', 'platform:overseas-warehouse:delivery:documents', 'platform:overseas-warehouse:customer-visit:list', 'platform:overseas-warehouse:customer-visit:edit')) THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='五期海外派送权限初始化失败:PDA岗位错误获得后台权限'; END IF; IF (SELECT COUNT(*) FROM iam_role_permission grant_row JOIN iam_role role ON role.id=grant_row.role_id AND role.tenant_id=grant_row.tenant_id JOIN iam_permission permission ON permission.id=grant_row.permission_id WHERE role.role_code='MERCHANT_OWNER' AND role.app_code='MERCHANT' AND permission.permission_code IN ( 'merchant:warehouse:delivery:list', 'merchant:warehouse:delivery:create')) <> 2 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='五期海外派送权限初始化失败:客户默认管理员授权不完整'; END IF; END$$ DELIMITER ; CALL assert_phase5_delivery_permissions(); DROP PROCEDURE assert_phase5_delivery_permissions; DROP TEMPORARY TABLE tmp_phase5_delivery_role_permission; -- IAM 客户提交即成功,默认尺寸通过持久化任务后台初始化;已有客户按历史事实标记 READY。 USE baiyue_iam; SET @customer_dimension_status_ddl = (SELECT IF( EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='iam_tenant' AND column_name='dimension_initialization_status'), 'SELECT 1', 'ALTER TABLE iam_tenant ADD COLUMN dimension_initialization_status VARCHAR(16) NOT NULL DEFAULT ''READY'' COMMENT ''默认尺寸初始化状态:PENDING待处理、READY已就绪、FAILED失败'' AFTER status')); PREPARE customer_dimension_status_stmt FROM @customer_dimension_status_ddl; EXECUTE customer_dimension_status_stmt; DEALLOCATE PREPARE customer_dimension_status_stmt; CREATE TABLE IF NOT EXISTS iam_customer_dimension_initialization_task ( tenant_id BIGINT NOT NULL COMMENT '客户ID及任务聚合ID', operator_id BIGINT NOT NULL COMMENT '触发初始化的操作人ID;系统补偿为0', initialization_key VARCHAR(128) NOT NULL COMMENT '稳定初始化幂等键:iam-customer-create:{tenantId}', action VARCHAR(16) NOT NULL DEFAULT 'INITIALIZE' COMMENT '目标动作:INITIALIZE初始化、COMPENSATE补偿', status VARCHAR(16) NOT NULL DEFAULT 'PENDING' COMMENT '投递状态:PENDING、SENDING、READY、FAILED、CANCELLED', attempt_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_code VARCHAR(32) NULL COMMENT '最后下游错误码', last_error_message VARCHAR(1000) NULL COMMENT '最后诊断错误,不保存敏感凭证', remote_config_id BIGINT NULL COMMENT 'Firstleg返回的首条默认尺寸配置ID', task_version INT 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 '任务更新时间', completed_at DATETIME(3) NULL COMMENT 'READY或CANCELLED完成时间', PRIMARY KEY (tenant_id), KEY idx_customer_dimension_task_dispatch (status,next_retry_at,lease_until), KEY idx_customer_dimension_task_diagnosis (status,updated_at) ) ENGINE=InnoDB COMMENT='IAM客户默认尺寸可靠后台初始化任务及诊断状态'; -- 兼容已存在的旧表:CREATE TABLE IF NOT EXISTS 不会为旧表补充新字段。 SET @customer_dimension_lease_ddl = (SELECT IF( EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='iam_customer_dimension_initialization_task' AND column_name='lease_until'), 'SELECT 1', 'ALTER TABLE iam_customer_dimension_initialization_task ADD COLUMN lease_until DATETIME(3) NULL COMMENT ''当前任务租约截止时间'' AFTER lease_owner')); PREPARE customer_dimension_lease_stmt FROM @customer_dimension_lease_ddl; EXECUTE customer_dimension_lease_stmt; DEALLOCATE PREPARE customer_dimension_lease_stmt; USE baiyue_firstleg; ALTER TABLE fl_loading_pick_task MODIFY COLUMN task_status VARCHAR(24) NOT NULL DEFAULT 'LOADING' COMMENT 'PDA任务状态:LOADING、COMPLETING、COMPLETION_FAILED、COMPLETED'; ALTER TABLE fl_loading_pick_task DROP CHECK chk_loading_pick_task_status; ALTER TABLE fl_loading_pick_task ADD CONSTRAINT chk_loading_pick_task_status CHECK (task_status IN ('LOADING','COMPLETING','COMPLETION_FAILED','COMPLETED')); CREATE TABLE IF NOT EXISTS fl_loading_completion_command ( id BIGINT UNSIGNED NOT NULL COMMENT '可靠装柜完成命令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 '规范化请求摘要', command_status VARCHAR(16) NOT NULL COMMENT '命令状态', expected_task_version INT UNSIGNED NOT NULL COMMENT '受理时客户端任务版本', remark VARCHAR(500) NULL COMMENT '装柜完成备注', operator_id BIGINT UNSIGNED NOT NULL COMMENT '操作人ID', operator_name VARCHAR(64) NULL COMMENT '操作人名称快照', attempt_count INT UNSIGNED 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 '后台租约截止时间', failure_stage VARCHAR(40) NULL COMMENT '脱敏失败阶段', failure_code VARCHAR(32) NULL COMMENT '业务或下游错误码', failure_request_id VARCHAR(64) NULL COMMENT '下游请求ID', failure_trace_id VARCHAR(64) 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 '命令更新时间', completed_at DATETIME(3) NULL COMMENT '命令完成时间', PRIMARY KEY(id),UNIQUE KEY uk_loading_completion_operation(operation_id), KEY idx_loading_completion_dispatch(command_status,next_retry_at,lease_until), CONSTRAINT fk_loading_completion_task FOREIGN KEY(pick_task_id) REFERENCES fl_loading_pick_task(id), CONSTRAINT chk_loading_completion_status CHECK(command_status IN ('PROCESSING','RETRYABLE','FAILED','COMPLETED')) ) ENGINE=InnoDB COMMENT='PDA装柜完成可靠异步命令'; CREATE TABLE IF NOT EXISTS fl_loading_completion_checkpoint ( command_id BIGINT UNSIGNED NOT NULL COMMENT '可靠装柜完成命令ID', tenant_id BIGINT UNSIGNED NOT NULL COMMENT '入库单客户ID', inbound_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID', owner_sequence INT UNSIGNED NOT NULL COMMENT '稳定处理顺序', checkpoint_stage VARCHAR(24) NOT 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(command_id,tenant_id,inbound_id),KEY idx_loading_completion_owner(command_id,owner_sequence), CONSTRAINT fk_loading_completion_checkpoint_command FOREIGN KEY(command_id) REFERENCES fl_loading_completion_command(id), CONSTRAINT chk_loading_completion_checkpoint CHECK(checkpoint_stage IN ('READY','SETTLED','INBOUND_COMPLETED')) ) ENGINE=InnoDB COMMENT='PDA装柜完成按入库单恢复检查点'; 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','COMPLETING')) OR (OLD.task_status='COMPLETING' AND NEW.task_status IN ('COMPLETING','COMPLETION_FAILED','COMPLETED')) OR (OLD.task_status='COMPLETION_FAILED' AND NEW.task_status='COMPLETING')) THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='PDA拣货任务状态流转非法'; END IF; END$$ DELIMITER ; -- 同柜且不可拆组:跨客户但同国内仓、渠道和目的仓港,整组锁定/解锁/消费。 USE baiyue_firstleg; CREATE TABLE IF NOT EXISTS fl_coload_group ( id BIGINT UNSIGNED NOT NULL COMMENT '同柜不可拆组ID',group_no VARCHAR(40) NOT NULL COMMENT '稳定组号CLG-{id}', group_status VARCHAR(16) NOT NULL COMMENT '组状态:ACTIVE、LOCKED、CONSUMED、CANCELLED', warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '国内收货仓ID快照',channel_code VARCHAR(64) NOT NULL COMMENT '物流渠道编码快照', locked_plan_id BIGINT UNSIGNED NULL COMMENT 'LOCKED/CONSUMED绑定装柜计划ID', version INT UNSIGNED NOT NULL DEFAULT 0 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_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间', locked_by BIGINT UNSIGNED NULL COMMENT '锁定操作人ID',locked_by_name VARCHAR(64) NULL COMMENT '锁定操作人名称快照',locked_at DATETIME(3) NULL COMMENT '锁定时间', consumed_by BIGINT UNSIGNED NULL COMMENT '消费操作人ID',consumed_by_name VARCHAR(64) NULL COMMENT '消费操作人名称快照',consumed_at DATETIME(3) NULL COMMENT '消费完成时间', cancelled_by BIGINT UNSIGNED NULL COMMENT '取消操作人ID',cancelled_by_name VARCHAR(64) NULL COMMENT '取消操作人名称快照',cancelled_at DATETIME(3) NULL COMMENT '取消时间', PRIMARY KEY(id),UNIQUE KEY uk_coload_group_no(group_no),KEY idx_coload_group_status(group_status,updated_at,id), KEY idx_coload_group_plan(locked_plan_id,group_status), CONSTRAINT fk_coload_group_plan FOREIGN KEY(locked_plan_id) REFERENCES fl_loading_plan(id), CONSTRAINT chk_coload_group_status CHECK(group_status IN('ACTIVE','LOCKED','CONSUMED','CANCELLED')), CONSTRAINT chk_coload_group_lock CHECK((group_status IN('ACTIVE','CANCELLED') AND locked_plan_id IS NULL) OR(group_status IN('LOCKED','CONSUMED') AND locked_plan_id IS NOT NULL)) )ENGINE=InnoDB COMMENT='同柜且不可拆业务组聚合根'; CREATE TABLE IF NOT EXISTS fl_coload_group_member ( id BIGINT UNSIGNED NOT NULL COMMENT '组成员ID',group_id BIGINT UNSIGNED NOT NULL COMMENT '同柜不可拆组ID',tenant_id BIGINT UNSIGNED NOT NULL COMMENT '成员客户ID;允许跨客户成组', order_id BIGINT UNSIGNED NOT NULL COMMENT '订单ID',inbound_id BIGINT UNSIGNED NOT NULL COMMENT '创建时权威入库单ID',full_package_count INT UNSIGNED NOT NULL COMMENT '必须整组分配的满箱数快照', active_flag TINYINT UNSIGNED NULL DEFAULT 1 COMMENT '当前约束唯一标识;取消或消费后置NULL', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '成员创建时间',closed_at DATETIME(3) NULL COMMENT '成员约束关闭时间', PRIMARY KEY(id),UNIQUE KEY uk_coload_group_member(group_id,inbound_id), UNIQUE KEY uk_coload_active_inbound(tenant_id,inbound_id,active_flag),KEY idx_coload_member_order(order_id,created_at,group_id), CONSTRAINT fk_coload_member_group FOREIGN KEY(group_id) REFERENCES fl_coload_group(id), CONSTRAINT fk_coload_member_inbound FOREIGN KEY(inbound_id) REFERENCES fl_inbound_order(id), CONSTRAINT chk_coload_member_packages CHECK(full_package_count>0), CONSTRAINT chk_coload_member_active CHECK(active_flag IS NULL OR active_flag=1) )ENGINE=InnoDB COMMENT='同柜不可拆组成员与满箱数快照'; CREATE TABLE IF NOT EXISTS fl_coload_group_history ( id BIGINT UNSIGNED NOT NULL COMMENT '状态历史ID',group_id BIGINT UNSIGNED NOT NULL COMMENT '同柜不可拆组ID',action_type VARCHAR(16) NOT NULL COMMENT '操作类型:CREATE、CANCEL、LOCK、UNLOCK、CONSUME', from_status VARCHAR(16) NULL COMMENT '变更前组状态',to_status VARCHAR(16) NOT NULL COMMENT '变更后组状态',plan_id BIGINT UNSIGNED NULL COMMENT '关联装柜计划ID', operation_id VARCHAR(128) NOT NULL COMMENT '稳定幂等操作ID',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), UNIQUE KEY uk_coload_history_operation(group_id,action_type,operation_id),KEY idx_coload_history_group(group_id,created_at,id), CONSTRAINT fk_coload_history_group FOREIGN KEY(group_id) REFERENCES fl_coload_group(id), CONSTRAINT chk_coload_history_action CHECK(action_type IN('CREATE','CANCEL','LOCK','UNLOCK','CONSUME')) )ENGINE=InnoDB COMMENT='同柜不可拆组不可变状态历史'; CREATE TABLE IF NOT EXISTS fl_coload_command ( id BIGINT UNSIGNED NOT NULL COMMENT '幂等命令ID',actor_id BIGINT UNSIGNED NOT NULL COMMENT '平台或内部操作人ID',action_type VARCHAR(16) NOT NULL COMMENT '命令动作类型', idempotency_key VARCHAR(128) NOT NULL COMMENT '幂等键',request_hash CHAR(64) NOT NULL COMMENT '请求摘要',command_status VARCHAR(16) NOT NULL COMMENT '命令状态:PROCESSING、SUCCESS', result_group_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_coload_command(actor_id,action_type,idempotency_key), CONSTRAINT fk_coload_command_group FOREIGN KEY(result_group_id) REFERENCES fl_coload_group(id), CONSTRAINT chk_coload_command_status CHECK(command_status IN('PROCESSING','SUCCESS')) )ENGINE=InnoDB COMMENT='同柜不可拆组幂等写命令'; -- 一件代发测量完成后的独立 PDA 合托;不代表国内仓库存入库。 USE baiyue_firstleg; CREATE TABLE IF NOT EXISTS fl_dropship_pallet_task ( id BIGINT UNSIGNED NOT NULL COMMENT '合托任务ID', tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户隔离键', inbound_id BIGINT UNSIGNED NOT NULL COMMENT '一件代发入仓单ID', warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '作业仓库ID', task_status VARCHAR(24) NOT NULL COMMENT '任务状态:PENDING待领取、CLAIMED已领取、COMPLETED已完成', total_cartons INT UNSIGNED NOT NULL COMMENT '测量确认总箱数', allocated_cartons INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '已上托箱数', assignee_id BIGINT UNSIGNED NULL COMMENT '任务领取人ID', assignee_name VARCHAR(64) NULL COMMENT '任务领取人姓名快照', claimed_at DATETIME(3) NULL COMMENT '任务领取时间', completed_at DATETIME(3) NULL COMMENT '任务完成时间', version INT NOT NULL DEFAULT 1 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_dropship_task_inbound (tenant_id,inbound_id), KEY idx_dropship_task_operator (task_status,assignee_id,warehouse_id,created_at), CONSTRAINT fk_dropship_task_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order(id), CONSTRAINT chk_dropship_task_status CHECK (task_status IN ('PENDING','CLAIMED','COMPLETED')), CONSTRAINT chk_dropship_task_cartons CHECK (allocated_cartons<=total_cartons) ) ENGINE=InnoDB COMMENT='一件代发测量后PDA合托任务'; CREATE TABLE IF NOT EXISTS fl_dropship_pallet ( id BIGINT UNSIGNED NOT NULL COMMENT '合托托盘ID', task_id BIGINT UNSIGNED NOT NULL COMMENT '所属合托任务ID', tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户隔离键', inbound_id BIGINT UNSIGNED NOT NULL COMMENT '一件代发入仓单ID', warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '作业仓库ID', pallet_no VARCHAR(32) NULL COMMENT '封托时生成的DP托盘号', pallet_status VARCHAR(16) NOT NULL COMMENT '托盘状态:OPEN合托中、COMPLETED已封托', open_operation_id VARCHAR(128) NOT NULL COMMENT '创建托盘幂等操作ID', seal_operation_id VARCHAR(128) NULL COMMENT '封托幂等操作ID', 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 '封托完成时间', version INT NOT NULL DEFAULT 1 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_dropship_pallet_no (pallet_no), UNIQUE KEY uk_dropship_pallet_open_op (open_operation_id), UNIQUE KEY uk_dropship_pallet_seal_op (seal_operation_id), KEY idx_dropship_pallet_inbound (tenant_id,inbound_id,pallet_status), CONSTRAINT fk_dropship_pallet_task FOREIGN KEY (task_id) REFERENCES fl_dropship_pallet_task(id), CONSTRAINT chk_dropship_pallet_status CHECK (pallet_status IN ('OPEN','COMPLETED')) ) ENGINE=InnoDB COMMENT='一件代发合托形成的可打印托盘'; CALL queue_column_comment('baiyue_firstleg','fl_loading_cabinet_evidence','id','柜次证据记录ID'); CALL queue_column_comment('baiyue_firstleg','fl_loading_cabinet_evidence','file_id','File服务文件ID'); CALL queue_column_comment('baiyue_firstleg','fl_loading_cabinet_evidence','file_name','文件名称快照'); CALL queue_column_comment('baiyue_firstleg','fl_loading_cabinet_evidence','seal_no','带锁码照对应锁码'); CALL queue_column_comment('baiyue_firstleg','fl_loading_cabinet_evidence','remark','证据备注'); CALL queue_column_comment('baiyue_firstleg','fl_loading_cabinet_evidence','status','证据状态:ACTIVE有效、VOID作废'); CALL queue_column_comment('baiyue_firstleg','fl_loading_cabinet_evidence','void_reason','作废原因'); CALL queue_column_comment('baiyue_firstleg','fl_loading_cabinet_evidence','voided_by','作废操作人ID'); CALL queue_column_comment('baiyue_firstleg','fl_loading_cabinet_evidence','voided_by_name','作废操作人名称快照'); CALL queue_column_comment('baiyue_firstleg','fl_loading_cabinet_evidence','voided_at','作废时间'); CALL queue_column_comment('baiyue_firstleg','fl_loading_cabinet_evidence','version','乐观锁版本号'); CALL queue_column_comment('baiyue_firstleg','fl_loading_cabinet_evidence','created_by','创建操作人ID'); CALL queue_column_comment('baiyue_firstleg','fl_loading_cabinet_evidence','created_by_name','创建操作人名称快照'); CALL queue_column_comment('baiyue_firstleg','fl_loading_cabinet_evidence','updated_by','最后修改操作人ID'); CALL queue_column_comment('baiyue_firstleg','fl_loading_cabinet_evidence','updated_by_name','最后修改操作人名称快照'); CALL queue_column_comment('baiyue_firstleg','fl_loading_cabinet_evidence','created_at','创建时间'); CALL queue_column_comment('baiyue_firstleg','fl_loading_cabinet_evidence','updated_at','最后更新时间'); CALL queue_column_comment('baiyue_firstleg','fl_order_declaration_event','tenant_id','客户ID和隔离键'); CALL queue_column_comment('baiyue_firstleg','fl_order_declaration_event','event_id','永久保留的Order Outbox事件ID'); CALL queue_column_comment('baiyue_firstleg','fl_order_declaration_event','order_id','Order订单ID'); CALL queue_column_comment('baiyue_firstleg','fl_order_declaration_event','order_version','事件携带的Order聚合版本'); CALL queue_column_comment('baiyue_firstleg','fl_order_declaration_event','request_hash','完整事件SHA-256,用于异参重放拒绝'); CALL queue_column_comment('baiyue_firstleg','fl_order_declaration_event','consumed_at','事件消费完成时间'); CALL queue_column_comment('baiyue_firstleg','fl_order_declaration_snapshot','tenant_id','客户ID和隔离键'); CALL queue_column_comment('baiyue_firstleg','fl_order_declaration_snapshot','order_id','Order订单ID'); CALL queue_column_comment('baiyue_firstleg','fl_order_declaration_snapshot','event_id','Order Outbox事件ID'); CALL queue_column_comment('baiyue_firstleg','fl_order_declaration_snapshot','order_version','Order聚合版本,严格单调消费'); CALL queue_column_comment('baiyue_firstleg','fl_order_declaration_snapshot','request_hash','完整事件SHA-256'); CALL queue_column_comment('baiyue_firstleg','fl_order_declaration_snapshot','customer_remark','客户备注快照'); CALL queue_column_comment('baiyue_firstleg','fl_order_declaration_snapshot','expected_arrival_at','预计到仓时间快照'); CALL queue_column_comment('baiyue_firstleg','fl_order_declaration_snapshot','channel_code','渠道编码快照'); CALL queue_column_comment('baiyue_firstleg','fl_order_declaration_snapshot','channel_name','渠道名称快照'); CALL queue_column_comment('baiyue_firstleg','fl_order_declaration_snapshot','cargoes_json','客户申报货物行完整快照,不覆盖实测事实'); CALL queue_column_comment('baiyue_firstleg','fl_order_declaration_snapshot','changes_json','本版本申报字段变化摘要'); CALL queue_column_comment('baiyue_firstleg','fl_order_declaration_snapshot','occurred_at','Order事件发生时间'); CALL queue_column_comment('baiyue_firstleg','fl_order_declaration_snapshot','created_at','快照创建时间'); CALL queue_column_comment('baiyue_firstleg','fl_order_declaration_snapshot','updated_at','快照更新时间'); SET @unified_ddl = IF( (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='baiyue_order' AND TABLE_NAME='ord_order' AND COLUMN_NAME='estimated_volume_cbm') = 0, 'ALTER TABLE baiyue_order.ord_order ADD COLUMN estimated_volume_cbm DECIMAL(18,4) NULL COMMENT ''平台运营预估体积(立方米),独立于客户下单及实测体积'' AFTER expected_arrival_at', 'SELECT 1' ); PREPARE unified_stmt FROM @unified_ddl; EXECUTE unified_stmt; DEALLOCATE PREPARE unified_stmt; CALL queue_column_comment('baiyue_order','ord_order','estimated_volume_cbm','平台运营预估体积(立方米),独立于客户下单及实测体积'); CALL queue_column_comment('baiyue_order','ord_customer_address','created_by','创建人ID'); CALL queue_column_comment('baiyue_order','ord_customer_address','created_at','创建时间'); CALL queue_column_comment('baiyue_order','ord_customer_address','updated_by','最后修改人ID'); CALL queue_column_comment('baiyue_order','ord_customer_address','updated_at','更新时间'); CALL queue_column_comment('baiyue_order','ord_customer_address','deleted','逻辑删除标记:0未删除、1已删除'); CALL queue_column_comment('baiyue_order','ord_customer_address','version','乐观锁版本号'); 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; CREATE TABLE IF NOT EXISTS fl_dropship_pallet_carton ( id BIGINT UNSIGNED NOT NULL COMMENT '托盘箱明细ID', task_id BIGINT UNSIGNED NOT NULL COMMENT '所属合托任务ID', pallet_id BIGINT UNSIGNED NOT NULL COMMENT '所属合托托盘ID', tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户隔离键', inbound_id BIGINT UNSIGNED NOT NULL COMMENT '一件代发入仓单ID', inbound_detail_id BIGINT UNSIGNED NOT NULL COMMENT '入仓货物明细ID', carton_label_id BIGINT UNSIGNED NOT NULL COMMENT '稳定箱唛记录ID', sku_code VARCHAR(128) NOT NULL COMMENT '箱唛冻结SKU编码', cargo_name VARCHAR(255) NULL COMMENT '货物名称快照', box_code VARCHAR(64) NOT NULL COMMENT '稳定箱码', operation_id VARCHAR(128) NOT NULL COMMENT '上托幂等操作ID', bound_by BIGINT UNSIGNED NOT NULL COMMENT '上托操作人ID', bound_by_name VARCHAR(64) NULL COMMENT '上托操作人姓名快照', bound_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '上托时间', PRIMARY KEY (id), UNIQUE KEY uk_dropship_carton_task_label (task_id,carton_label_id), KEY idx_dropship_carton_pallet_sku (pallet_id,sku_code,id), KEY idx_dropship_carton_inbound (tenant_id,inbound_id,pallet_id), KEY idx_dropship_carton_operation (operation_id), CONSTRAINT fk_dropship_carton_task FOREIGN KEY (task_id) REFERENCES fl_dropship_pallet_task(id), CONSTRAINT fk_dropship_carton_pallet FOREIGN KEY (pallet_id) REFERENCES fl_dropship_pallet(id), CONSTRAINT fk_dropship_carton_label FOREIGN KEY (carton_label_id) REFERENCES fl_inbound_carton_label(id) ) ENGINE=InnoDB COMMENT='一件代发托盘内逐箱SKU和入仓单明细'; -- Auth 登录手机号只从 IAM 中唯一归属的中国大陆手机号回填;存量重复号保持 NULL。 USE baiyue_auth; DROP PROCEDURE IF EXISTS auth_add_login_phone; DELIMITER $$ CREATE PROCEDURE auth_add_login_phone() BEGIN IF NOT EXISTS ( SELECT 1 FROM information_schema.columns WHERE table_schema = 'baiyue_auth' AND table_name = 'auth_account' AND column_name = 'phone') THEN ALTER TABLE auth_account ADD COLUMN phone VARCHAR(32) NULL COMMENT '全局唯一中国大陆登录手机号' AFTER username; END IF; END$$ DELIMITER ; CALL auth_add_login_phone(); DROP PROCEDURE auth_add_login_phone; DROP PROCEDURE IF EXISTS auth_backfill_login_phone; DELIMITER $$ CREATE PROCEDURE auth_backfill_login_phone() BEGIN UPDATE auth_account SET phone = NULL WHERE phone IS NOT NULL AND phone NOT REGEXP '^1[3-9][0-9]{9}$'; UPDATE auth_account account JOIN baiyue_iam.iam_user user ON user.auth_user_id = account.id AND user.deleted = 0 JOIN ( SELECT phone FROM baiyue_iam.iam_user WHERE deleted = 0 AND phone REGEXP '^1[3-9][0-9]{9}$' GROUP BY phone HAVING COUNT(DISTINCT auth_user_id) = 1 ) unique_phone ON unique_phone.phone = user.phone SET account.phone = user.phone WHERE account.phone IS NULL AND user.phone REGEXP '^1[3-9][0-9]{9}$'; IF NOT EXISTS ( SELECT 1 FROM information_schema.statistics WHERE table_schema = 'baiyue_auth' AND table_name = 'auth_account' AND index_name = 'uk_auth_account_phone') THEN ALTER TABLE auth_account ADD UNIQUE KEY uk_auth_account_phone (phone); END IF; END$$ DELIMITER ; CALL auth_backfill_login_phone(); DROP PROCEDURE auth_backfill_login_phone; USE baiyue_firstleg; DROP PROCEDURE IF EXISTS baiyue_firstleg.ensure_smart_loading_candidate_indexes; DELIMITER $$ CREATE PROCEDURE baiyue_firstleg.ensure_smart_loading_candidate_indexes() BEGIN IF NOT EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema='baiyue_firstleg' AND table_name='fl_inbound_order' AND index_name='idx_inbound_smart_loading') THEN ALTER TABLE baiyue_firstleg.fl_inbound_order ADD KEY idx_inbound_smart_loading (warehouse_id,process_status,channel_code,expected_arrival_at,id); END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema='baiyue_firstleg' AND table_name='fl_inbound_measurement' AND index_name='idx_measure_inbound_status_id') THEN ALTER TABLE baiyue_firstleg.fl_inbound_measurement ADD KEY idx_measure_inbound_status_id (tenant_id,inbound_id,status,id); END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema='baiyue_firstleg' AND table_name='fl_inbound_measurement_item' AND index_name='idx_measure_item_candidate') THEN ALTER TABLE baiyue_firstleg.fl_inbound_measurement_item ADD KEY idx_measure_item_candidate (tenant_id,measurement_id,inbound_detail_id); END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema='baiyue_firstleg' AND table_name='fl_loading_plan_item' AND index_name='idx_loading_item_candidate') THEN ALTER TABLE baiyue_firstleg.fl_loading_plan_item ADD KEY idx_loading_item_candidate (account_id,inbound_id,inbound_detail_id,deleted); END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema='baiyue_firstleg' AND table_name='fl_inbound_split_item' AND index_name='idx_inbound_split_candidate') THEN ALTER TABLE baiyue_firstleg.fl_inbound_split_item ADD KEY idx_inbound_split_candidate (tenant_id,parent_inbound_id,source_inbound_detail_id); END IF; END$$ DELIMITER ; CALL baiyue_firstleg.ensure_smart_loading_candidate_indexes(); DROP PROCEDURE IF EXISTS baiyue_firstleg.ensure_smart_loading_candidate_indexes; -- 一件代发客户资金账户:实际余额优先,授信额度补足;冻结后结算或解冻。 USE baiyue_finance; CREATE TABLE IF NOT EXISTS fin_wallet_account ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID', customer_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和隔离键', currency_code CHAR(3) NOT NULL DEFAULT 'CNY' COMMENT '当前仅CNY', actual_balance DECIMAL(20,2) NOT NULL DEFAULT 0 COMMENT '账户实际余额,包含冻结资金', frozen_balance DECIMAL(20,2) NOT NULL DEFAULT 0 COMMENT '冻结总额,包含实际资金与授信冻结', available_balance DECIMAL(20,2) NOT NULL DEFAULT 0 COMMENT '总可用余额=实际余额+授信额度-已用授信-冻结总额', credit_limit DECIMAL(20,2) NOT NULL DEFAULT 0 COMMENT '授信额度', credit_used DECIMAL(20,2) NOT NULL DEFAULT 0 COMMENT '已结算使用的授信', credit_frozen DECIMAL(20,2) NOT NULL DEFAULT 0 COMMENT '冻结总额中占用的授信', version INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '余额乐观锁版本', created_by BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建操作人ID', updated_by BIGINT UNSIGNED NOT NULL DEFAULT 0 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_wallet_account_customer (customer_id, currency_code), CONSTRAINT chk_fin_wallet_account_customer CHECK (customer_id > 0), CONSTRAINT chk_fin_wallet_account_currency CHECK (currency_code = 'CNY'), CONSTRAINT chk_fin_wallet_account_balance CHECK (actual_balance >= 0 AND frozen_balance >= 0 AND available_balance >= 0 AND credit_limit >= 0 AND credit_used >= 0 AND credit_frozen >= 0 AND frozen_balance >= credit_frozen AND actual_balance >= frozen_balance-credit_frozen AND credit_limit >= credit_used+credit_frozen AND available_balance = actual_balance+credit_limit-credit_used-frozen_balance) ) ENGINE=InnoDB COMMENT='客户CNY资金账户主账'; CREATE TABLE IF NOT EXISTS fin_recharge_request ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID', customer_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和隔离键', request_no VARCHAR(32) NOT NULL COMMENT '充值申请业务编号RC-yyMMdd-####', amount DECIMAL(20,2) NOT NULL COMMENT '申请充值金额', payment_method VARCHAR(16) NOT NULL COMMENT 'WECHAT或ALIPAY', voucher_file_id BIGINT UNSIGNED NOT NULL COMMENT '付款截图File ID', voucher_file_name VARCHAR(255) NOT NULL COMMENT '付款截图文件名快照', status VARCHAR(16) NOT NULL DEFAULT 'PENDING' COMMENT '审核状态:PENDING待审核、APPROVED已通过、REJECTED已驳回', version INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '审核乐观锁版本', submitted_by BIGINT UNSIGNED NOT NULL COMMENT '客户提交人ID', submitted_by_name VARCHAR(128) NULL COMMENT '客户提交人名称快照', reviewed_by BIGINT UNSIGNED NULL COMMENT '平台审核人ID', reviewed_by_name VARCHAR(128) NULL COMMENT '平台审核人名称快照', review_remark VARCHAR(500) NULL COMMENT '审核说明或驳回原因', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '记录创建时间', reviewed_at DATETIME(3) NULL COMMENT '平台审核完成时间', updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '最后更新时间', PRIMARY KEY (id), UNIQUE KEY uk_fin_recharge_request_no (request_no), KEY idx_fin_recharge_customer_status (customer_id, status, created_at, id), CONSTRAINT chk_fin_recharge_customer CHECK (customer_id > 0), CONSTRAINT chk_fin_recharge_amount CHECK (amount > 0), CONSTRAINT chk_fin_recharge_method CHECK (payment_method IN ('WECHAT','ALIPAY')), CONSTRAINT chk_fin_recharge_status CHECK (status IN ('PENDING','APPROVED','REJECTED')) ) ENGINE=InnoDB COMMENT='客户付款截图充值申请;平台审核通过后入账'; CREATE TABLE IF NOT EXISTS fin_wallet_ledger ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID', customer_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和隔离键', business_no VARCHAR(32) NOT NULL COMMENT '账户变动业务编号AC-yyMMdd-####', change_type VARCHAR(16) NOT NULL COMMENT '变动类型:充值、扣款、冻结、解冻、消费或授信调整', amount DECIMAL(20,2) NOT NULL COMMENT '带方向变动金额,充值为正、扣款为负', actual_balance_after DECIMAL(20,2) NOT NULL COMMENT '变动后实际余额', frozen_balance_after DECIMAL(20,2) NOT NULL COMMENT '变动后冻结金额', available_balance_after DECIMAL(20,2) NOT NULL COMMENT '变动后可用余额', credit_limit_after DECIMAL(20,2) NOT NULL DEFAULT 0 COMMENT '变动后授信额度', credit_used_after DECIMAL(20,2) NOT NULL DEFAULT 0 COMMENT '变动后已用授信', available_credit_after DECIMAL(20,2) NOT NULL DEFAULT 0 COMMENT '变动后可用授信', source_type VARCHAR(32) NOT NULL COMMENT 'CLIENT_RECHARGE/MANUAL_RECHARGE/MANUAL_DEDUCTION等', source_no VARCHAR(128) NULL COMMENT '充值申请号或外部凭证号', occurred_date DATE NOT NULL COMMENT '业务发生日期', payment_method VARCHAR(32) NULL COMMENT '充值或扣款方式', reference_no VARCHAR(128) NULL COMMENT '收款凭证号或关联单据号', operator_id BIGINT UNSIGNED NOT NULL COMMENT '操作人ID', operator_name VARCHAR(128) NULL COMMENT '操作人名称快照', remark VARCHAR(500) NULL COMMENT '变动说明', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '记录创建时间', PRIMARY KEY (id), UNIQUE KEY uk_fin_wallet_ledger_no (business_no), KEY idx_fin_wallet_ledger_customer_time (customer_id, created_at, id), KEY idx_fin_wallet_ledger_source (customer_id, source_type, source_no), CONSTRAINT chk_fin_wallet_ledger_customer CHECK (customer_id > 0), CONSTRAINT chk_fin_wallet_ledger_amount CHECK (amount <> 0), CONSTRAINT chk_fin_wallet_ledger_balance CHECK (actual_balance_after >= 0 AND frozen_balance_after >= 0 AND available_balance_after >= 0 AND credit_limit_after >= 0 AND credit_used_after >= 0 AND available_credit_after >= 0 AND credit_limit_after >= credit_used_after+available_credit_after AND available_balance_after = actual_balance_after-frozen_balance_after+credit_limit_after-credit_used_after) ) ENGINE=InnoDB COMMENT='客户资金账户不可变余额流水'; CREATE TABLE IF NOT EXISTS fin_wallet_hold ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID', customer_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和隔离键', business_type VARCHAR(32) NOT NULL COMMENT '调用方业务类型', business_no VARCHAR(128) NOT NULL COMMENT '调用方业务单号', amount DECIMAL(20,2) NOT NULL COMMENT '冻结总额', actual_frozen_amount DECIMAL(20,2) NOT NULL COMMENT '冻结时占用实际资金', credit_frozen_amount DECIMAL(20,2) NOT NULL COMMENT '冻结时占用授信', status VARCHAR(16) NOT NULL COMMENT '冻结状态:FROZEN冻结中、SETTLED已结算、RELEASED已释放', freeze_ledger_id BIGINT UNSIGNED NULL COMMENT '冻结流水ID', action_ledger_id BIGINT UNSIGNED NULL COMMENT '结算或解冻流水ID', remark VARCHAR(500) NULL COMMENT '业务说明', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '冻结记录创建时间', completed_at DATETIME(3) NULL COMMENT '结算或释放完成时间', updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '最后更新时间', PRIMARY KEY (id), UNIQUE KEY uk_fin_wallet_hold_business (customer_id,business_type,business_no), KEY idx_fin_wallet_hold_status (customer_id,status,created_at,id), CONSTRAINT chk_fin_wallet_hold_customer CHECK (customer_id > 0), CONSTRAINT chk_fin_wallet_hold_amount CHECK (amount > 0 AND actual_frozen_amount >= 0 AND credit_frozen_amount >= 0 AND amount=actual_frozen_amount+credit_frozen_amount), CONSTRAINT chk_fin_wallet_hold_status CHECK (status IN ('FROZEN','SETTLED','RELEASED')) ) ENGINE=InnoDB COMMENT='客户资金冻结及结算生命周期'; DROP PROCEDURE IF EXISTS migrate_fin_wallet_credit; DELIMITER $$ CREATE PROCEDURE migrate_fin_wallet_credit() BEGIN IF NOT EXISTS (SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='baiyue_finance' AND TABLE_NAME='fin_wallet_account' AND COLUMN_NAME='credit_limit') THEN ALTER TABLE fin_wallet_account ADD COLUMN credit_limit DECIMAL(20,2) NOT NULL DEFAULT 0 COMMENT '授信额度' AFTER available_balance, ADD COLUMN credit_used DECIMAL(20,2) NOT NULL DEFAULT 0 COMMENT '已结算使用的授信' AFTER credit_limit, ADD COLUMN credit_frozen DECIMAL(20,2) NOT NULL DEFAULT 0 COMMENT '冻结总额中占用的授信' AFTER credit_used; END IF; IF EXISTS (SELECT 1 FROM information_schema.TABLE_CONSTRAINTS WHERE CONSTRAINT_SCHEMA='baiyue_finance' AND TABLE_NAME='fin_wallet_account' AND CONSTRAINT_NAME='chk_fin_wallet_account_balance' AND CONSTRAINT_TYPE='CHECK') THEN ALTER TABLE fin_wallet_account DROP CHECK chk_fin_wallet_account_balance; END IF; UPDATE fin_wallet_account SET available_balance=actual_balance+credit_limit-credit_used-frozen_balance; ALTER TABLE fin_wallet_account ADD CONSTRAINT chk_fin_wallet_account_balance CHECK (actual_balance >= 0 AND frozen_balance >= 0 AND available_balance >= 0 AND credit_limit >= 0 AND credit_used >= 0 AND credit_frozen >= 0 AND frozen_balance >= credit_frozen AND actual_balance >= frozen_balance-credit_frozen AND credit_limit >= credit_used+credit_frozen AND available_balance = actual_balance+credit_limit-credit_used-frozen_balance); IF NOT EXISTS (SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='baiyue_finance' AND TABLE_NAME='fin_wallet_ledger' AND COLUMN_NAME='credit_limit_after') THEN ALTER TABLE fin_wallet_ledger ADD COLUMN credit_limit_after DECIMAL(20,2) NOT NULL DEFAULT 0 COMMENT '变动后授信额度' AFTER available_balance_after, ADD COLUMN credit_used_after DECIMAL(20,2) NOT NULL DEFAULT 0 COMMENT '变动后已用授信' AFTER credit_limit_after, ADD COLUMN available_credit_after DECIMAL(20,2) NOT NULL DEFAULT 0 COMMENT '变动后可用授信' AFTER credit_used_after; END IF; IF EXISTS (SELECT 1 FROM information_schema.TABLE_CONSTRAINTS WHERE CONSTRAINT_SCHEMA='baiyue_finance' AND TABLE_NAME='fin_wallet_ledger' AND CONSTRAINT_NAME='chk_fin_wallet_ledger_balance' AND CONSTRAINT_TYPE='CHECK') THEN ALTER TABLE fin_wallet_ledger DROP CHECK chk_fin_wallet_ledger_balance; END IF; ALTER TABLE fin_wallet_ledger ADD CONSTRAINT chk_fin_wallet_ledger_balance CHECK (actual_balance_after >= 0 AND frozen_balance_after >= 0 AND available_balance_after >= 0 AND credit_limit_after >= 0 AND credit_used_after >= 0 AND available_credit_after >= 0 AND credit_limit_after >= credit_used_after+available_credit_after AND available_balance_after = actual_balance_after-frozen_balance_after+credit_limit_after-credit_used_after); END$$ DELIMITER ; CALL migrate_fin_wallet_credit(); DROP PROCEDURE IF EXISTS migrate_fin_wallet_credit; -- 客户资金账户权限初始化。 -- 客户资金账户与充值权限。只读访问和资金写操作使用独立权限。 USE baiyue_iam; SET NAMES utf8mb4; DROP PROCEDURE IF EXISTS migrate_wallet_permissions; DELIMITER $$ CREATE PROCEDURE migrate_wallet_permissions() BEGIN IF EXISTS ( SELECT 1 FROM iam_permission WHERE (id = 5105056 AND (tenant_id <> 0 OR app_code <> 'PLATFORM' OR permission_type <> 'BUTTON' OR permission_code <> 'platform:finance:recharge:review')) OR (id = 5105057 AND (tenant_id <> 0 OR app_code <> 'PLATFORM' OR permission_type <> 'BUTTON' OR permission_code <> 'platform:finance:customer-account:adjust')) OR (id = 5305020 AND (tenant_id <> 0 OR app_code <> 'MERCHANT' OR permission_type <> 'MENU' OR permission_code <> 'merchant:finance:account:view')) OR (id = 5305021 AND (tenant_id <> 0 OR app_code <> 'MERCHANT' OR permission_type <> 'BUTTON' OR permission_code <> 'merchant:finance:recharge:create')) OR (permission_code = 'platform:finance:recharge:review' AND (id <> 5105056 OR tenant_id <> 0 OR app_code <> 'PLATFORM')) OR (permission_code = 'platform:finance:customer-account:adjust' AND (id <> 5105057 OR tenant_id <> 0 OR app_code <> 'PLATFORM')) OR (permission_code = 'merchant:finance:account:view' AND (id <> 5305020 OR tenant_id <> 0 OR app_code <> 'MERCHANT')) OR (permission_code = 'merchant:finance:recharge:create' AND (id <> 5305021 OR tenant_id <> 0 OR app_code <> 'MERCHANT')) ) THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='资金账户权限初始化失败:权限ID、编码或类型冲突'; 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 (5105056,0,'PLATFORM','BUTTON',5105040,'platform:finance:recharge:review', '审核客户充值',NULL,'POST',100,'ACTIVE',1,0), (5105057,0,'PLATFORM','BUTTON',5105040,'platform:finance:customer-account:adjust', '调整客户资金账户',NULL,'POST',110,'ACTIVE',1,0), (5305020,0,'MERCHANT','MENU',5300000,'merchant:finance:account:view', '资金账户','/finance/account','GET',50,'ACTIVE',1,0), (5305021,0,'MERCHANT','BUTTON',5305020,'merchant:finance:recharge:create', '提交充值申请',NULL,'POST',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), 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, role.id, permission.id, CURRENT_TIMESTAMP(3) FROM iam_role role JOIN iam_permission permission ON permission.tenant_id=0 AND permission.app_code='PLATFORM' WHERE role.tenant_id=0 AND role.app_code='PLATFORM' AND role.role_code='PLATFORM_ADMIN' AND role.status='ACTIVE' AND role.deleted=0 AND permission.permission_code IN ( 'platform:finance:recharge:review', 'platform:finance:customer-account:adjust') AND permission.status='ACTIVE' AND permission.deleted=0; INSERT IGNORE INTO iam_role_permission (tenant_id, role_id, permission_id, created_at) SELECT 0, role.id, permission.id, CURRENT_TIMESTAMP(3) FROM iam_role role JOIN iam_permission permission ON permission.tenant_id=0 AND permission.app_code='MERCHANT' WHERE role.tenant_id=0 AND role.app_code='MERCHANT' AND role.role_code='MERCHANT_OWNER' AND role.status='ACTIVE' AND role.deleted=0 AND permission.permission_code IN ( 'merchant:finance:account:view', 'merchant:finance:recharge:create') AND permission.status='ACTIVE' AND permission.deleted=0; IF (SELECT COUNT(*) FROM iam_permission WHERE tenant_id=0 AND app_code='PLATFORM' AND permission_type='BUTTON' AND parent_id=5105040 AND status='ACTIVE' AND built_in=1 AND deleted=0 AND permission_code IN ( 'platform:finance:recharge:review', 'platform:finance:customer-account:adjust')) <> 2 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='资金账户权限初始化失败:平台写权限资源不完整'; END IF; IF (SELECT COUNT(*) FROM iam_permission WHERE tenant_id=0 AND app_code='MERCHANT' AND status='ACTIVE' AND built_in=1 AND deleted=0 AND ((id=5305020 AND permission_type='MENU' AND parent_id=5300000 AND permission_code='merchant:finance:account:view') OR (id=5305021 AND permission_type='BUTTON' AND parent_id=5305020 AND permission_code='merchant:finance:recharge:create'))) <> 2 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='资金账户权限初始化失败:客户权限资源不完整'; END IF; IF (SELECT COUNT(*) FROM iam_role_permission grant_row JOIN iam_role role ON role.id=grant_row.role_id AND role.tenant_id=grant_row.tenant_id JOIN iam_permission permission ON permission.id=grant_row.permission_id WHERE role.tenant_id=0 AND role.app_code='PLATFORM' AND role.role_code='PLATFORM_ADMIN' AND role.status='ACTIVE' AND role.deleted=0 AND permission.tenant_id=0 AND permission.app_code='PLATFORM' AND permission.status='ACTIVE' AND permission.deleted=0 AND permission.permission_code IN ( 'platform:finance:recharge:review', 'platform:finance:customer-account:adjust')) <> 2 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='资金账户权限初始化失败:平台超级管理员授权不完整'; END IF; IF (SELECT COUNT(*) FROM iam_role_permission grant_row JOIN iam_role role ON role.id=grant_row.role_id AND role.tenant_id=grant_row.tenant_id JOIN iam_permission permission ON permission.id=grant_row.permission_id WHERE role.tenant_id=0 AND role.app_code='MERCHANT' AND role.role_code='MERCHANT_OWNER' AND role.status='ACTIVE' AND role.deleted=0 AND permission.tenant_id=0 AND permission.app_code='MERCHANT' AND permission.status='ACTIVE' AND permission.deleted=0 AND permission.permission_code IN ( 'merchant:finance:account:view', 'merchant:finance:recharge:create')) <> 2 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='资金账户权限初始化失败:企业负责人授权不完整'; END IF; END$$ DELIMITER ; CALL migrate_wallet_permissions(); DROP PROCEDURE IF EXISTS migrate_wallet_permissions; -- 钱包写权限属于 Finance 权限总数的一部分,必须先完成钱包权限迁移再做总量断言。 CALL assert_finance_permissions(); DROP PROCEDURE IF EXISTS assert_finance_permissions; USE baiyue_finance; CREATE TABLE IF NOT EXISTS fin_wallet_command_inbox ( event_id VARCHAR(64) NOT NULL COMMENT '海外资金指令事件ID', event_type VARCHAR(96) NOT NULL COMMENT '事件类型', payload_hash VARCHAR(71) NOT NULL COMMENT '规范化载荷SHA-256,包含sha256:前缀', payload_json JSON NOT NULL COMMENT '冻结事件信封', status VARCHAR(16) NOT NULL COMMENT 'PROCESSING、SUCCEEDED或FAILED', result_json JSON NULL COMMENT '最终资金执行结果', received_at DATETIME(3) NOT NULL COMMENT '接收时间', processed_at DATETIME(3) NULL COMMENT '处理完成时间', PRIMARY KEY (event_id), KEY idx_fin_wallet_command_status (status,received_at) ) ENGINE=InnoDB COMMENT='跨区客户资金指令幂等收件箱'; ALTER TABLE fin_wallet_command_inbox MODIFY COLUMN payload_hash VARCHAR(71) NOT NULL COMMENT '规范化载荷SHA-256,包含sha256:前缀'; CREATE TABLE IF NOT EXISTS fin_wallet_event_outbox ( event_id VARCHAR(64) NOT NULL COMMENT '资金结果事件ID', event_key VARCHAR(160) NOT NULL COMMENT '业务幂等键', payload_json JSON NOT NULL COMMENT '完整事件信封', status VARCHAR(16) NOT NULL DEFAULT 'PENDING' COMMENT 'PENDING、SENDING、PUBLISHED或DEAD', retry_count INT NOT NULL DEFAULT 0 COMMENT '失败次数', next_retry_at DATETIME(3) NOT NULL COMMENT '下次投递时间', locked_by VARCHAR(128) NULL COMMENT '租约持有者', locked_at DATETIME(3) NULL COMMENT '租约时间', published_at DATETIME(3) NULL COMMENT '投递完成时间', last_error 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 (event_id), UNIQUE KEY uk_fin_wallet_event_key (event_key), KEY idx_fin_wallet_event_publish (status,next_retry_at,created_at) ) ENGINE=InnoDB COMMENT='国内Finance到海外资金结果可靠发送箱'; SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='baiyue_order' AND table_name='ord_order' AND column_name='lx_code'), 'SELECT 1', 'ALTER TABLE baiyue_order.ord_order ADD COLUMN lx_code VARCHAR(128) NULL COMMENT ''一件代发领星入库单代码'' AFTER fulfillment_type')); PREPARE lx_order_ddl FROM @ddl; EXECUTE lx_order_ddl; DEALLOCATE PREPARE lx_order_ddl; SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='baiyue_firstleg' AND table_name='fl_inbound_order' AND column_name='lx_code'), 'SELECT 1', 'ALTER TABLE baiyue_firstleg.fl_inbound_order ADD COLUMN lx_code VARCHAR(128) NULL COMMENT ''一件代发领星入库单代码'' AFTER fulfillment_type')); PREPARE lx_inbound_ddl FROM @ddl; EXECUTE lx_inbound_ddl; DEALLOCATE PREPARE lx_inbound_ddl; SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='baiyue_lastleg' AND table_name='ll_outbound_order' AND column_name='lx_codes'), 'SELECT 1', 'ALTER TABLE baiyue_lastleg.ll_outbound_order ADD COLUMN lx_codes VARCHAR(1000) NULL COMMENT ''关联一件代发领星入库单代码汇总'' AFTER outbound_type')); PREPARE lx_outbound_ddl FROM @ddl; EXECUTE lx_outbound_ddl; DEALLOCATE PREPARE lx_outbound_ddl; ALTER TABLE baiyue_firstleg.fl_inbound_order MODIFY COLUMN current_hold_reason VARCHAR(1000) NULL COMMENT '当前扣货理由快照'; USE baiyue_iam; DROP PROCEDURE IF EXISTS migrate_inbound_hold_permission; DELIMITER $$ CREATE PROCEDURE migrate_inbound_hold_permission() BEGIN IF EXISTS ( SELECT 1 FROM iam_permission WHERE (id = 5102032 AND (tenant_id <> 0 OR app_code <> 'PLATFORM' OR permission_type <> 'BUTTON' OR permission_code <> 'platform:firstleg:inbound:hold')) OR (permission_code = 'platform:firstleg:inbound:hold' AND (id <> 5102032 OR tenant_id <> 0 OR app_code <> 'PLATFORM')) ) THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='入库单扣货权限初始化失败:权限ID、编码或类型冲突'; 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 (5102032, 0, 'PLATFORM', 'BUTTON', 5102030, 'platform:firstleg:inbound:hold', '扣货与解除扣货', NULL, 'POST', 20, '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), 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, role.id, permission.id, CURRENT_TIMESTAMP(3) FROM iam_role role JOIN iam_permission permission ON permission.tenant_id=0 AND permission.app_code='PLATFORM' AND permission.permission_code='platform:firstleg:inbound:hold' WHERE role.tenant_id=0 AND role.app_code='PLATFORM' AND role.role_code='PLATFORM_ADMIN' AND role.status='ACTIVE' AND role.deleted=0 AND permission.status='ACTIVE' AND permission.deleted=0; IF (SELECT COUNT(*) FROM iam_permission WHERE id=5102032 AND tenant_id=0 AND app_code='PLATFORM' AND permission_type='BUTTON' AND parent_id=5102030 AND permission_code='platform:firstleg:inbound:hold' AND http_method='POST' AND status='ACTIVE' AND built_in=1 AND deleted=0) <> 1 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='入库单扣货权限初始化失败:权限资源不完整'; END IF; IF (SELECT COUNT(*) FROM iam_role_permission grant_row JOIN iam_role role ON role.id=grant_row.role_id AND role.tenant_id=grant_row.tenant_id JOIN iam_permission permission ON permission.id=grant_row.permission_id AND permission.tenant_id=grant_row.tenant_id WHERE role.tenant_id=0 AND role.app_code='PLATFORM' AND role.role_code='PLATFORM_ADMIN' AND role.status='ACTIVE' AND role.deleted=0 AND permission.permission_code='platform:firstleg:inbound:hold' AND permission.status='ACTIVE' AND permission.deleted=0) <> 1 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='入库单扣货权限初始化失败:平台超级管理员授权不完整'; END IF; END$$ DELIMITER ; CALL migrate_inbound_hold_permission(); DROP PROCEDURE IF EXISTS migrate_inbound_hold_permission; DROP PROCEDURE IF EXISTS assert_wallet_event_schema; DELIMITER $$ CREATE PROCEDURE assert_wallet_event_schema() BEGIN IF (SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA='baiyue_finance' AND TABLE_NAME IN ('fin_wallet_command_inbox','fin_wallet_event_outbox')) <> 2 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='统一迁移失败:客户资金跨区事件表不完整'; END IF; IF (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA='baiyue_finance' AND TABLE_NAME='fin_wallet_event_outbox' AND INDEX_NAME='uk_fin_wallet_event_key' AND NON_UNIQUE=0) <> 1 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='统一迁移失败:客户资金结果事件幂等索引不完整'; END IF; END$$ DELIMITER ; CALL assert_wallet_event_schema(); DROP PROCEDURE IF EXISTS assert_wallet_event_schema; -- 目的港码头拖车、提柜与还空闭环(模块来源:phase3-terminal-drayage-schema.sql) USE baiyue_firstleg; CREATE TABLE IF NOT EXISTS fl_terminal_drayage ( id BIGINT UNSIGNED NOT NULL COMMENT '目的港码头拖车聚合ID', container_booking_id BIGINT UNSIGNED NOT NULL COMMENT '唯一归属物理货柜ID', fleet_supplier_id BIGINT UNSIGNED NULL COMMENT 'Warehouse ACTIVE/TRUCKING供应商ID快照', fleet_supplier_name VARCHAR(128) NULL COMMENT 'Warehouse供应商名称快照', pickup_appointment_at DATETIME(3) NULL COMMENT '预约提柜时间', actual_pickup_at DATETIME(3) NULL COMMENT '实际提柜时间', estimated_empty_return_at DATETIME(3) NULL COMMENT '服务端按卸柜完成时间加72小时计算的预计还柜时间', actual_empty_return_at DATETIME(3) NULL COMMENT '实际还空柜时间', version INT UNSIGNED NOT NULL DEFAULT 1 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 '更新时间', PRIMARY KEY(id), UNIQUE KEY uk_terminal_drayage_container(container_booking_id), KEY idx_terminal_drayage_warning(actual_empty_return_at,estimated_empty_return_at), CONSTRAINT fk_terminal_drayage_container FOREIGN KEY(container_booking_id) REFERENCES fl_container_booking(id), CONSTRAINT chk_terminal_drayage_times CHECK((actual_pickup_at IS NULL OR pickup_appointment_at IS NOT NULL) AND (actual_empty_return_at IS NULL OR actual_pickup_at IS NULL OR actual_empty_return_at >= actual_pickup_at)) ) ENGINE=InnoDB COMMENT='目的港货柜级码头拖车与还空事实'; CREATE TABLE IF NOT EXISTS fl_terminal_drayage_mail ( id BIGINT UNSIGNED NOT NULL COMMENT '邮件任务ID', container_booking_id BIGINT UNSIGNED NOT NULL COMMENT '归属物理货柜ID', fleet_supplier_id BIGINT UNSIGNED NOT NULL COMMENT '车队供应商ID快照', fleet_name VARCHAR(128) NOT NULL COMMENT '车队名称快照', recipient VARCHAR(254) NOT NULL COMMENT '收件人邮箱', kind VARCHAR(32) NOT NULL COMMENT '邮件类型:REQUEST_PICKUP提柜请求、CANCEL_PICKUP取消提柜', subject VARCHAR(255) NOT NULL COMMENT '邮件主题', body TEXT NOT NULL COMMENT '邮件正文', status VARCHAR(16) NOT NULL DEFAULT 'PENDING' COMMENT '投递状态:PENDING待发送、SENDING发送中、SENT已发送、FAILED失败', attempts INT NOT NULL DEFAULT 0 COMMENT '发送尝试次数', failure_reason VARCHAR(500) NULL 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 '更新时间', sent_at DATETIME(3) NULL COMMENT '发送成功时间', PRIMARY KEY(id), KEY idx_terminal_mail_dispatch(status,id), KEY idx_terminal_mail_container(container_booking_id,id,status), CONSTRAINT fk_terminal_mail_container FOREIGN KEY(container_booking_id) REFERENCES fl_container_booking(id), CONSTRAINT chk_terminal_mail_kind CHECK(kind IN ('REQUEST_PICKUP','CANCEL_PICKUP')), CONSTRAINT chk_terminal_mail_status CHECK(status IN ('PENDING','SENDING','SENT','FAILED')) ) ENGINE=InnoDB COMMENT='码头车队邮件任务与投递记录'; CREATE TABLE IF NOT EXISTS fl_terminal_drayage_history ( id BIGINT UNSIGNED NOT NULL COMMENT '历史ID', drayage_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) NOT NULL COMMENT '操作前状态', to_status VARCHAR(32) NOT NULL COMMENT '操作后状态', from_version INT UNSIGNED NOT NULL COMMENT '操作前版本', to_version INT UNSIGNED NOT 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_terminal_drayage_history(container_booking_id,created_at,id), CONSTRAINT fk_terminal_drayage_history FOREIGN KEY(drayage_id) REFERENCES fl_terminal_drayage(id), CONSTRAINT chk_terminal_drayage_history_action CHECK(action IN ('SELECT_FLEET','SEND_PICKUP_EMAIL','SCHEDULE_PICKUP','RECORD_PICKUP','RECORD_EMPTY_RETURN')) ) ENGINE=InnoDB COMMENT='目的港码头拖车不可变操作历史'; CREATE TABLE IF NOT EXISTS fl_terminal_drayage_operation ( id BIGINT UNSIGNED NOT NULL COMMENT '幂等操作ID', container_booking_id BIGINT UNSIGNED NOT NULL COMMENT '货柜ID', operation VARCHAR(32) NOT NULL COMMENT '业务动作', idempotency_key VARCHAR(255) NOT NULL COMMENT '客户端幂等键', request_hash CHAR(64) NOT NULL COMMENT '请求载荷SHA-256', result_version INT UNSIGNED NOT NULL COMMENT '成功结果版本', operator_id BIGINT UNSIGNED NOT NULL COMMENT '操作人ID', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '完成时间', PRIMARY KEY(id), UNIQUE KEY uk_terminal_drayage_operation(container_booking_id,idempotency_key), KEY idx_terminal_drayage_operation(container_booking_id,created_at), CONSTRAINT fk_terminal_drayage_operation_container FOREIGN KEY(container_booking_id) REFERENCES fl_container_booking(id) ) ENGINE=InnoDB COMMENT='目的港码头拖车成功幂等操作'; -- MySQL DDL 会隐式提交;如果前次执行在末段校验失败,重跑时需把已创建表的列注释收敛到当前基线。 ALTER TABLE fl_terminal_drayage DROP CHECK chk_terminal_drayage_times, ADD CONSTRAINT chk_terminal_drayage_times CHECK( (actual_pickup_at IS NULL OR pickup_appointment_at IS NOT NULL) AND (actual_empty_return_at IS NULL OR actual_pickup_at IS NULL OR actual_empty_return_at >= actual_pickup_at)), MODIFY COLUMN id BIGINT UNSIGNED NOT NULL COMMENT '目的港码头拖车聚合ID', MODIFY COLUMN container_booking_id BIGINT UNSIGNED NOT NULL COMMENT '唯一归属物理货柜ID', MODIFY COLUMN fleet_supplier_id BIGINT UNSIGNED NULL COMMENT 'Warehouse ACTIVE/TRUCKING供应商ID快照', MODIFY COLUMN fleet_supplier_name VARCHAR(128) NULL COMMENT 'Warehouse供应商名称快照', MODIFY COLUMN pickup_appointment_at DATETIME(3) NULL COMMENT '预约提柜时间', MODIFY COLUMN actual_pickup_at DATETIME(3) NULL COMMENT '实际提柜时间', MODIFY COLUMN estimated_empty_return_at DATETIME(3) NULL COMMENT '服务端按卸柜完成时间加72小时计算的预计还柜时间', MODIFY COLUMN actual_empty_return_at DATETIME(3) NULL COMMENT '实际还空柜时间', MODIFY COLUMN version INT UNSIGNED NOT NULL DEFAULT 1 COMMENT '乐观锁版本号', MODIFY COLUMN created_by BIGINT UNSIGNED NOT NULL COMMENT '创建人ID', MODIFY COLUMN created_by_name VARCHAR(64) NULL COMMENT '创建人名称快照', MODIFY COLUMN created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', MODIFY COLUMN updated_by BIGINT UNSIGNED NOT NULL COMMENT '最后更新人ID', MODIFY COLUMN updated_by_name VARCHAR(64) NULL COMMENT '最后更新人名称快照', MODIFY COLUMN updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间'; ALTER TABLE fl_terminal_drayage_history DROP CHECK chk_terminal_drayage_history_action, ADD CONSTRAINT chk_terminal_drayage_history_action CHECK(action IN ('SELECT_FLEET','SEND_PICKUP_EMAIL','SCHEDULE_PICKUP','RECORD_PICKUP','RECORD_EMPTY_RETURN')), MODIFY COLUMN id BIGINT UNSIGNED NOT NULL COMMENT '历史ID', MODIFY COLUMN drayage_id BIGINT UNSIGNED NOT NULL COMMENT '码头拖车聚合ID', MODIFY COLUMN container_booking_id BIGINT UNSIGNED NOT NULL COMMENT '货柜ID快照', MODIFY COLUMN action VARCHAR(32) NOT NULL COMMENT '业务动作', MODIFY COLUMN from_status VARCHAR(32) NOT NULL COMMENT '操作前状态', MODIFY COLUMN to_status VARCHAR(32) NOT NULL COMMENT '操作后状态', MODIFY COLUMN from_version INT UNSIGNED NOT NULL COMMENT '操作前版本', MODIFY COLUMN to_version INT UNSIGNED NOT NULL COMMENT '操作后版本', MODIFY COLUMN reason VARCHAR(500) NULL COMMENT '操作说明', MODIFY COLUMN operator_id BIGINT UNSIGNED NOT NULL COMMENT '操作人ID', MODIFY COLUMN operator_name VARCHAR(64) NULL COMMENT '操作人名称快照', MODIFY COLUMN created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '操作时间'; ALTER TABLE fl_terminal_drayage_operation MODIFY COLUMN id BIGINT UNSIGNED NOT NULL COMMENT '幂等操作ID', MODIFY COLUMN container_booking_id BIGINT UNSIGNED NOT NULL COMMENT '货柜ID', MODIFY COLUMN operation VARCHAR(32) NOT NULL COMMENT '业务动作', MODIFY COLUMN idempotency_key VARCHAR(255) NOT NULL COMMENT '客户端幂等键', MODIFY COLUMN request_hash CHAR(64) NOT NULL COMMENT '请求载荷SHA-256', MODIFY COLUMN result_version INT UNSIGNED NOT NULL COMMENT '成功结果版本', MODIFY COLUMN operator_id BIGINT UNSIGNED NOT NULL COMMENT '操作人ID', MODIFY COLUMN created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '完成时间'; DROP PROCEDURE IF EXISTS assert_terminal_drayage_schema; DELIMITER $$ CREATE PROCEDURE assert_terminal_drayage_schema() BEGIN IF (SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA='baiyue_firstleg' AND TABLE_NAME='fl_terminal_drayage_mail') <> 1 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='统一基线校验失败:码头车队邮件任务表缺失'; END IF; IF (SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA='baiyue_firstleg' AND TABLE_NAME IN ('fl_terminal_drayage','fl_terminal_drayage_history','fl_terminal_drayage_operation')) <> 3 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_terminal_drayage' AND INDEX_NAME='uk_terminal_drayage_container' AND NON_UNIQUE=0) <> 1 OR (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA='baiyue_firstleg' AND TABLE_NAME='fl_terminal_drayage_operation' AND INDEX_NAME='uk_terminal_drayage_operation' AND NON_UNIQUE=0) <> 2 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='统一迁移失败:目的港码头拖车唯一约束不完整'; END IF; END$$ DELIMITER ; CALL assert_terminal_drayage_schema(); DROP PROCEDURE IF EXISTS assert_terminal_drayage_schema; -- 货柜双工作台权限开始(与 iam/db/container-workspace-permissions.sql 保持一致) -- 国内送柜报关与国外清关提柜双工作台权限;国内沿用现有货柜权限,国外直接收敛到独立权限族。 USE baiyue_iam; SET NAMES utf8mb4; DROP PROCEDURE IF EXISTS migrate_container_workspace_permissions; DELIMITER $$ CREATE PROCEDURE migrate_container_workspace_permissions() BEGIN IF EXISTS ( SELECT 1 FROM iam_permission WHERE (id = 5102090 AND permission_code <> 'platform:firstleg:container:list') OR (id = 5102092 AND permission_code <> 'platform:firstleg:container:create') OR (id = 5102093 AND permission_code <> 'platform:firstleg:container:edit') OR (id = 5102200 AND permission_code <> 'platform:firstleg:terminal-drayage:list') OR (id = 5102201 AND permission_code <> 'platform:firstleg:terminal-drayage:document:edit') OR (id = 5102202 AND permission_code <> 'platform:firstleg:terminal-drayage:fleet:edit') OR (id = 5102203 AND permission_code <> 'platform:firstleg:terminal-drayage:pickup-appointment:edit') OR (id = 5102204 AND permission_code <> 'platform:firstleg:terminal-drayage:pickup:confirm') OR (id = 5102205 AND permission_code <> 'platform:firstleg:terminal-drayage:empty-return:confirm') ) OR EXISTS ( SELECT 1 FROM iam_permission WHERE (permission_code = 'platform:firstleg:container:list' AND id <> 5102090) OR (permission_code = 'platform:firstleg:container:create' AND id <> 5102092) OR (permission_code = 'platform:firstleg:container:edit' AND id <> 5102093) OR (permission_code = 'platform:firstleg:terminal-drayage:list' AND id <> 5102200) OR (permission_code = 'platform:firstleg:terminal-drayage:document:edit' AND id <> 5102201) OR (permission_code = 'platform:firstleg:terminal-drayage:fleet:edit' AND id <> 5102202) OR (permission_code = 'platform:firstleg:terminal-drayage:pickup-appointment:edit' AND id <> 5102203) OR (permission_code = 'platform:firstleg:terminal-drayage:pickup:confirm' AND id <> 5102204) OR (permission_code = 'platform:firstleg:terminal-drayage:empty-return:confirm' AND id <> 5102205) ) THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='货柜双工作台权限初始化失败:权限ID或编码冲突'; 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 (5102090,0,'PLATFORM','MENU',5102000,'platform:firstleg:container:list', '国内送柜报关','/container-loading/domestic','GET',40,'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), (5102200,0,'PLATFORM','MENU',5102000,'platform:firstleg:terminal-drayage:list', '国外清关提柜','/container-loading/overseas','GET',41,'ACTIVE',1,0), (5102201,0,'PLATFORM','BUTTON',5102200,'platform:firstleg:terminal-drayage:document:edit', '维护目的港清关与DO/PU',NULL,'POST',10,'ACTIVE',1,0), (5102202,0,'PLATFORM','BUTTON',5102200,'platform:firstleg:terminal-drayage:fleet:edit', '维护目的港码头车队',NULL,'POST',20,'ACTIVE',1,0), (5102203,0,'PLATFORM','BUTTON',5102200,'platform:firstleg:terminal-drayage:pickup-appointment:edit', '预约目的港提柜',NULL,'POST',30,'ACTIVE',1,0), (5102204,0,'PLATFORM','BUTTON',5102200,'platform:firstleg:terminal-drayage:pickup:confirm', '确认目的港实际提柜',NULL,'POST',40,'ACTIVE',1,0), (5102205,0,'PLATFORM','BUTTON',5102200,'platform:firstleg:terminal-drayage:empty-return:confirm', '确认目的港实际还柜',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; -- list 已包含详情只读;旧 container:view 不再作为别名或双权限入口。 DELETE grant_row FROM iam_role_permission grant_row JOIN iam_permission permission ON permission.id=grant_row.permission_id AND permission.tenant_id=grant_row.tenant_id WHERE permission.tenant_id=0 AND permission.permission_code='platform:firstleg:container:view'; UPDATE iam_permission SET status='INACTIVE', deleted=1 WHERE tenant_id=0 AND permission_code='platform:firstleg:container:view'; -- 平台超级管理员和现有订单运营保持货柜全链路能力;其他角色按两个菜单独立授权。 INSERT IGNORE INTO iam_role_permission (tenant_id, role_id, permission_id, created_at) SELECT 0, role.id, permission.id, CURRENT_TIMESTAMP(3) FROM iam_role role JOIN iam_permission permission ON permission.tenant_id=0 AND permission.app_code='PLATFORM' AND permission.permission_code IN ( 'platform:firstleg:container:list', 'platform:firstleg:container:create', 'platform:firstleg:container:edit', 'platform:firstleg:terminal-drayage:list', 'platform:firstleg:terminal-drayage:document:edit', 'platform:firstleg:terminal-drayage:fleet:edit', 'platform:firstleg:terminal-drayage:pickup-appointment:edit', 'platform:firstleg:terminal-drayage:pickup:confirm', 'platform:firstleg:terminal-drayage:empty-return:confirm') WHERE role.tenant_id=0 AND role.app_code='PLATFORM' AND role.role_code IN ('PLATFORM_ADMIN','ORDER_OPERATOR') AND role.status='ACTIVE' AND role.deleted=0 AND permission.status='ACTIVE' AND permission.deleted=0; 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:firstleg:container:list', 'platform:firstleg:container:create', 'platform:firstleg:container:edit', 'platform:firstleg:terminal-drayage:list', 'platform:firstleg:terminal-drayage:document:edit', 'platform:firstleg:terminal-drayage:fleet:edit', 'platform:firstleg:terminal-drayage:pickup-appointment:edit', 'platform:firstleg:terminal-drayage:pickup:confirm', 'platform:firstleg:terminal-drayage:empty-return:confirm')) <> 9 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='货柜双工作台权限初始化失败:权限目录不完整'; END IF; IF (SELECT COUNT(DISTINCT CONCAT(role.role_code, ':', permission.permission_code)) FROM iam_role_permission grant_row JOIN iam_role role ON role.id=grant_row.role_id AND role.tenant_id=grant_row.tenant_id JOIN iam_permission permission ON permission.id=grant_row.permission_id AND permission.tenant_id=grant_row.tenant_id WHERE role.tenant_id=0 AND role.app_code='PLATFORM' AND role.role_code IN ('PLATFORM_ADMIN','ORDER_OPERATOR') AND permission.permission_code IN ( 'platform:firstleg:container:list', 'platform:firstleg:container:create', 'platform:firstleg:container:edit', 'platform:firstleg:terminal-drayage:list', 'platform:firstleg:terminal-drayage:document:edit', 'platform:firstleg:terminal-drayage:fleet:edit', 'platform:firstleg:terminal-drayage:pickup-appointment:edit', 'platform:firstleg:terminal-drayage:pickup:confirm', 'platform:firstleg:terminal-drayage:empty-return:confirm')) <> 18 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='货柜双工作台权限初始化失败:平台默认角色授权不完整'; END IF; IF EXISTS (SELECT 1 FROM iam_permission WHERE tenant_id=0 AND permission_code='platform:firstleg:container:view' AND (status='ACTIVE' OR deleted=0)) THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='货柜双工作台权限初始化失败:旧查看权限仍然有效'; END IF; END$$ DELIMITER ; CALL migrate_container_workspace_permissions(); DROP PROCEDURE IF EXISTS migrate_container_workspace_permissions; -- 货柜双工作台权限结束,恢复头程数据库上下文 USE baiyue_firstleg; -- 财务 P0:海外费用原币、计费依据与钱包结算关联 DROP PROCEDURE IF EXISTS add_finance_p0_column; DELIMITER $$ CREATE PROCEDURE add_finance_p0_column( IN target_schema VARCHAR(64), IN target_table VARCHAR(64), IN target_column VARCHAR(64), IN column_definition TEXT ) BEGIN IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=target_schema AND table_name=target_table) AND NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=target_schema AND table_name=target_table AND column_name=target_column) THEN SET @ddl = CONCAT('ALTER TABLE `', target_schema, '`.`', target_table, '` ADD COLUMN `', target_column, '` ', column_definition); PREPARE statement FROM @ddl; EXECUTE statement; DEALLOCATE PREPARE statement; END IF; END$$ DELIMITER ; CALL add_finance_p0_column('baiyue_lastleg','ll_finance_fee_source','original_currency',"CHAR(3) NOT NULL DEFAULT 'CNY' COMMENT '原始业务币种' AFTER amount"); CALL add_finance_p0_column('baiyue_lastleg','ll_finance_fee_source','original_amount',"DECIMAL(20,6) NOT NULL DEFAULT 0 COMMENT '原币金额' AFTER original_currency"); CALL add_finance_p0_column('baiyue_lastleg','ll_finance_fee_source','exchange_rate',"DECIMAL(20,8) NOT NULL DEFAULT 1 COMMENT '原币折算CNY汇率' AFTER original_amount"); CALL add_finance_p0_column('baiyue_lastleg','ll_finance_fee_source','exchange_rate_month',"CHAR(7) NOT NULL DEFAULT '1970-01' COMMENT '汇率月份YYYY-MM' AFTER exchange_rate"); CALL add_finance_p0_column('baiyue_lastleg','ll_finance_fee_source','chargeable_quantity',"DECIMAL(20,6) NULL COMMENT '计费数量' AFTER exchange_rate_month"); CALL add_finance_p0_column('baiyue_lastleg','ll_finance_fee_source','quantity_unit',"VARCHAR(32) NULL COMMENT '计费单位' AFTER chargeable_quantity"); CALL add_finance_p0_column('baiyue_lastleg','ll_finance_fee_source','unit_price',"DECIMAL(20,6) NULL COMMENT '原币单价' AFTER quantity_unit"); CALL add_finance_p0_column('baiyue_lastleg','ll_finance_fee_source','period_start',"DATE NULL COMMENT '计费期间开始日' AFTER unit_price"); CALL add_finance_p0_column('baiyue_lastleg','ll_finance_fee_source','period_end',"DATE NULL COMMENT '计费期间结束日' AFTER period_start"); CALL add_finance_p0_column('baiyue_finance','fin_fee_source','wallet_hold_id',"BIGINT UNSIGNED NULL COMMENT '已由客户资金账户结算时关联的资金冻结记录ID' AFTER operation_version"); DROP PROCEDURE IF EXISTS add_finance_p0_column; SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema='baiyue_finance' AND table_name='fin_fee_source' AND index_name='idx_fin_fee_source_wallet_hold'), 'SELECT 1', 'ALTER TABLE baiyue_finance.fin_fee_source ADD KEY idx_fin_fee_source_wallet_hold(wallet_hold_id)')); PREPARE statement FROM @ddl; EXECUTE statement; DEALLOCATE PREPARE statement; CALL baiyue_firstleg.assert_unified_final_schema(); DROP PROCEDURE IF EXISTS baiyue_firstleg.assert_unified_final_schema; SELECT 'PASS' AS migration_status, '统一增量全部执行完成;父子订单、入库扣货、履约分片、装柜门禁、订舱拖车、报关船期、船司查询、仓库角色、费用、Finance人工财务、客户资金账户、公共资源、可靠审计及IAM安全事件结构校验通过' AS message;