删除增量文件

This commit is contained in:
2026-08-05 10:54:38 +08:00
parent 496fc58a90
commit 9678ee0f1f
5 changed files with 0 additions and 4506 deletions
@@ -1,930 +0,0 @@
-- 一期回归测试环境增量迁移汇总
-- 适用版本:MySQL 8.0
-- 涉及数据库:baiyue_iam、baiyue_warehouse、baiyue_order、baiyue_firstleg、baiyue_file
-- 安全边界:不创建数据库、不清表、不删除业务数据、不写入演示租户及演示业务数据。
-- 可重复执行:所有新增字段、表、索引和权限均带存在性判断或幂等写入。
-- 重要说明:MySQL DDL 会隐式提交,本脚本不能作为一个跨库事务整体回滚。
SET NAMES utf8mb4;
-- ============================================================================
-- 1. IAM:权限资源结构、平台/商户按钮权限及管理员授权
-- ============================================================================
USE baiyue_iam;
DROP PROCEDURE IF EXISTS regression_assert_table;
DELIMITER $$
CREATE PROCEDURE regression_assert_table(IN p_table_name VARCHAR(64))
BEGIN
IF NOT EXISTS (
SELECT 1
FROM information_schema.tables
WHERE table_schema = DATABASE()
AND table_name = p_table_name
) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'baiyue_iam 缺少基础表,请先执行 IAM 基线建表脚本';
END IF;
END$$
DELIMITER ;
CALL regression_assert_table('iam_permission');
CALL regression_assert_table('iam_role');
CALL regression_assert_table('iam_role_permission');
CALL regression_assert_table('iam_tenant');
CALL regression_assert_table('iam_tenant_review');
CALL regression_assert_table('iam_tenant_qualification');
CALL regression_assert_table('iam_membership');
DROP PROCEDURE regression_assert_table;
ALTER TABLE iam_tenant
MODIFY COLUMN status VARCHAR(20) NOT NULL DEFAULT 'PROFILE_REQUIRED'
COMMENT '租户状态:PROFILE_REQUIRED、PENDING、ACTIVE、REJECTED、DISABLED';
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;
-- 审核通过自动开户依赖全局企业管理员模板。通过自然键幂等补齐,避免存量环境
-- 已存在同编码但 ID 不同的角色时,把权限错误授予固定 ID。
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
role_name = VALUES(role_name), role_type = 'SYSTEM', status = 'ACTIVE',
description = VALUES(description), 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
)
SELECT permission.id + 100000, 0, permission.app_code, permission.permission_type,
CASE WHEN permission.parent_id = 0 THEN 0 ELSE permission.parent_id + 100000 END,
permission.permission_code, permission.permission_name, permission.route_path,
permission.http_method, permission.sort_no, 'ACTIVE', 1, 0
FROM iam_permission permission
WHERE permission.tenant_id = 10001 AND permission.app_code = 'MERCHANT'
AND permission.permission_type IN ('MENU', 'BUTTON')
AND permission.deleted = 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 owner_role.id, permission.id, 0
FROM iam_role owner_role
JOIN iam_permission permission
ON permission.tenant_id = 0 AND permission.app_code = 'MERCHANT'
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);
-- 一期客户档案字段;与 iam/db/phase1-customer-profile.sql 保持一致。
DROP PROCEDURE IF EXISTS regression_add_iam_column;
DELIMITER $$
CREATE PROCEDURE regression_add_iam_column(
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 = 'iam_tenant'
AND column_name = p_column_name
) THEN
SET @regression_iam_sql = CONCAT(
'ALTER TABLE iam_tenant ADD COLUMN `', p_column_name, '` ', p_definition
);
PREPARE regression_iam_stmt FROM @regression_iam_sql;
EXECUTE regression_iam_stmt;
DEALLOCATE PREPARE regression_iam_stmt;
END IF;
END$$
DELIMITER ;
CALL regression_add_iam_column('contact_wechat', 'VARCHAR(64) NULL COMMENT ''联系微信'' AFTER contact_email');
CALL regression_add_iam_column('legal_representative', 'VARCHAR(64) NULL COMMENT ''法定代表人'' AFTER legal_name');
CALL regression_add_iam_column('sales_owner_name', 'VARCHAR(64) NULL COMMENT ''销售负责人姓名'' AFTER contact_wechat');
CALL regression_add_iam_column('payment_term_days', 'SMALLINT UNSIGNED NULL COMMENT ''账期天数'' AFTER sales_owner_name');
CALL regression_add_iam_column('enterprise_type', 'VARCHAR(64) NULL COMMENT ''企业类型'' AFTER payment_term_days');
CALL regression_add_iam_column('established_date', 'DATE NULL COMMENT ''成立日期'' AFTER enterprise_type');
CALL regression_add_iam_column('registered_capital', 'DECIMAL(18,2) NULL COMMENT ''注册资本'' AFTER established_date');
CALL regression_add_iam_column('registered_capital_currency', 'CHAR(3) NULL COMMENT ''注册资本币种 ISO 4217'' AFTER registered_capital');
CALL regression_add_iam_column('registered_address', 'VARCHAR(500) NULL COMMENT ''注册地址文本'' AFTER registered_capital_currency');
CALL regression_add_iam_column('internal_remark', 'VARCHAR(1000) NULL COMMENT ''平台内部备注'' AFTER registered_address');
CALL regression_add_iam_column('approved_at', 'DATETIME(3) NULL COMMENT ''审核通过时间'' AFTER internal_remark');
CALL regression_add_iam_column('approved_by', 'BIGINT NULL COMMENT ''审核人ID'' AFTER approved_at');
CALL regression_add_iam_column('reject_reason', 'VARCHAR(500) NULL COMMENT ''驳回原因'' AFTER approved_by');
DROP PROCEDURE regression_add_iam_column;
-- 注册审核和资质替换依赖的辅助字段;存量测试库也必须具备同一契约。
DROP PROCEDURE IF EXISTS regression_add_iam_related_column;
DELIMITER $$
CREATE PROCEDURE regression_add_iam_related_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 @regression_iam_related_sql = CONCAT(
'ALTER TABLE `', p_table_name, '` ADD COLUMN `', p_column_name, '` ', p_definition
);
PREPARE regression_iam_related_stmt FROM @regression_iam_related_sql;
EXECUTE regression_iam_related_stmt;
DEALLOCATE PREPARE regression_iam_related_stmt;
END IF;
END$$
DELIMITER ;
CALL regression_add_iam_related_column('iam_tenant_qualification', 'review_comment', 'VARCHAR(500) NULL COMMENT ''审核意见''');
CALL regression_add_iam_related_column('iam_membership', 'created_by', 'BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT ''创建人ID''');
CALL regression_add_iam_related_column('iam_membership', 'updated_by', 'BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT ''最后修改人ID''');
DROP PROCEDURE regression_add_iam_related_column;
-- 先写入父菜单,再写入按钮,避免新增环境出现悬空父节点。
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
(5100000, 0, 'PLATFORM', 'MENU', 0, 'platform:customer', '客户管理', '/customers', NULL, 10, 'ACTIVE', 1, 0),
(5101000, 0, 'PLATFORM', 'MENU', 0, 'platform:system', '系统管理', '/system', NULL, 20, 'ACTIVE', 1, 0),
(5102000, 0, 'PLATFORM', 'MENU', 0, 'platform:order', '头程订单', '/first-leg-orders', NULL, 30, 'ACTIVE', 1, 0),
(5103000, 0, 'PLATFORM', 'MENU', 0, 'platform:master-data', '基础资料', '/master-data', NULL, 40, 'ACTIVE', 1, 0),
(5100010, 0, 'PLATFORM', 'MENU', 5100000, 'platform:customer:list', '企业客户', '/customers/list', NULL, 10, 'ACTIVE', 1, 0),
(5101010, 0, 'PLATFORM', 'MENU', 5101000, 'platform:user:list', '内部用户', '/system/users', NULL, 10, 'ACTIVE', 1, 0),
(5101020, 0, 'PLATFORM', 'MENU', 5101000, 'platform:role:list', '角色管理', '/system/roles', NULL, 20, 'ACTIVE', 1, 0),
(5101030, 0, 'PLATFORM', 'MENU', 5101000, 'platform:permission:list', '菜单按钮', '/system/permissions', NULL, 30, 'ACTIVE', 1, 0),
(5101040, 0, 'PLATFORM', 'MENU', 5101000, 'platform:member:list', '平台成员', '/system/members', NULL, 40, 'ACTIVE', 1, 0),
(5102010, 0, 'PLATFORM', 'MENU', 5102000, 'platform:order:list', '订单列表', '/first-leg-orders/list', NULL, 10, 'ACTIVE', 1, 0),
(5102020, 0, 'PLATFORM', 'MENU', 5102000, 'platform:firstleg:task:list', '履约任务', '/first-leg-tasks/list', NULL, 20, 'ACTIVE', 1, 0),
(5103020, 0, 'PLATFORM', 'MENU', 5103000, 'platform:express:list', '快递公司', '/master-data/express', NULL, 20, 'ACTIVE', 1, 0),
(5103030, 0, 'PLATFORM', 'MENU', 5103000, 'platform:counterparty:list', '往来单位', '/master-data/counterparties', NULL, 30, 'ACTIVE', 1, 0),
(5103040, 0, 'PLATFORM', 'MENU', 5103000, 'platform:download:list', '下载中心', '/downloads', 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),
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
(5100011, 0, 'PLATFORM', 'BUTTON', 5100010, 'platform:customer:create', '新增企业客户', NULL, NULL, 10, 'ACTIVE', 1, 0),
(5100014, 0, 'PLATFORM', 'BUTTON', 5100010, 'platform:customer:view', '查看企业客户档案', NULL, NULL, 15, 'ACTIVE', 1, 0),
(5100012, 0, 'PLATFORM', 'BUTTON', 5100010, 'platform:customer:edit', '编辑企业客户', NULL, NULL, 20, 'ACTIVE', 1, 0),
(5100013, 0, 'PLATFORM', 'BUTTON', 5100010, 'platform:customer:status', '启停企业客户', NULL, NULL, 30, 'ACTIVE', 1, 0),
(5100015, 0, 'PLATFORM', 'BUTTON', 5100010, 'platform:customer:export', '导出企业客户', NULL, NULL, 40, 'ACTIVE', 1, 0),
(5100020, 0, 'PLATFORM', 'BUTTON', 5100010, 'platform:customer:review', '客户审核', NULL, NULL, 50, 'ACTIVE', 1, 0),
(5100021, 0, 'PLATFORM', 'BUTTON', 5100010, 'platform:customer:approve', '审核通过', NULL, NULL, 60, 'ACTIVE', 1, 0),
(5100022, 0, 'PLATFORM', 'BUTTON', 5100010, 'platform:customer:reject', '审核驳回', NULL, NULL, 70, 'ACTIVE', 1, 0),
(5101011, 0, 'PLATFORM', 'BUTTON', 5101010, 'platform:user:create', '新增内部用户', NULL, NULL, 10, 'ACTIVE', 1, 0),
(5101012, 0, 'PLATFORM', 'BUTTON', 5101010, 'platform:user:edit', '编辑内部用户', NULL, NULL, 20, 'ACTIVE', 1, 0),
(5101013, 0, 'PLATFORM', 'BUTTON', 5101010, 'platform:user:status', '启停内部用户', NULL, NULL, 30, 'ACTIVE', 1, 0),
(5101014, 0, 'PLATFORM', 'BUTTON', 5101010, 'platform:user:reset-password', '重置内部用户密码', NULL, NULL, 40, 'ACTIVE', 1, 0),
(5101015, 0, 'PLATFORM', 'BUTTON', 5101010, 'platform:user:grant-role', '分配内部用户角色', NULL, NULL, 50, 'ACTIVE', 1, 0),
(5101021, 0, 'PLATFORM', 'BUTTON', 5101020, 'platform:role:create', '新增角色', NULL, NULL, 10, 'ACTIVE', 1, 0),
(5101022, 0, 'PLATFORM', 'BUTTON', 5101020, 'platform:role:grant', '角色授权', NULL, NULL, 20, 'ACTIVE', 1, 0),
(5101023, 0, 'PLATFORM', 'BUTTON', 5101020, 'platform:role:edit', '编辑角色', NULL, NULL, 30, 'ACTIVE', 1, 0),
(5101024, 0, 'PLATFORM', 'BUTTON', 5101020, 'platform:role:status', '启停角色', NULL, NULL, 40, 'ACTIVE', 1, 0),
(5101025, 0, 'PLATFORM', 'BUTTON', 5101020, 'platform:role:delete', '删除角色', NULL, NULL, 50, 'ACTIVE', 1, 0),
(5101031, 0, 'PLATFORM', 'BUTTON', 5101030, 'platform:permission:create', '新增菜单按钮', NULL, NULL, 10, 'ACTIVE', 1, 0),
(5101032, 0, 'PLATFORM', 'BUTTON', 5101030, 'platform:permission:edit', '编辑权限资源', NULL, NULL, 20, 'ACTIVE', 1, 0),
(5101033, 0, 'PLATFORM', 'BUTTON', 5101030, 'platform:permission:status', '启停权限资源', NULL, NULL, 30, 'ACTIVE', 1, 0),
(5101034, 0, 'PLATFORM', 'BUTTON', 5101030, 'platform:permission:delete', '删除权限资源', NULL, NULL, 40, 'ACTIVE', 1, 0),
(5101041, 0, 'PLATFORM', 'BUTTON', 5101040, 'platform:member:grant-role', '分配平台成员角色', NULL, NULL, 10, 'ACTIVE', 1, 0),
(5102019, 0, 'PLATFORM', 'BUTTON', 5102010, 'platform:order:create', '平台代客创建订单', NULL, NULL, 90, 'ACTIVE', 1, 0),
(5102024, 0, 'PLATFORM', 'BUTTON', 5102010, 'platform:order:edit', '平台编辑订单草稿', NULL, NULL, 100, 'ACTIVE', 1, 0),
(5102025, 0, 'PLATFORM', 'BUTTON', 5102010, 'platform:order:submit', '平台代客提交订单', NULL, NULL, 110, 'ACTIVE', 1, 0),
(5102021, 0, 'PLATFORM', 'BUTTON', 5102020, 'platform:firstleg:task:view', '查看履约任务', NULL, NULL, 10, 'ACTIVE', 1, 0),
(5102022, 0, 'PLATFORM', 'BUTTON', 5102020, 'platform:firstleg:assignment:history', '查看分配历史', NULL, NULL, 20, 'ACTIVE', 1, 0),
(5102023, 0, 'PLATFORM', 'BUTTON', 5102020, 'platform:firstleg:assignment:assign', '分配履约任务', NULL, NULL, 30, 'ACTIVE', 1, 0),
(5103021, 0, 'PLATFORM', 'BUTTON', 5103020, 'platform:express:create', '新增快递公司', NULL, NULL, 10, 'ACTIVE', 1, 0),
(5103022, 0, 'PLATFORM', 'BUTTON', 5103020, 'platform:express:edit', '编辑快递公司', NULL, NULL, 20, 'ACTIVE', 1, 0),
(5103023, 0, 'PLATFORM', 'BUTTON', 5103020, 'platform:express:status', '启停快递公司', NULL, NULL, 30, 'ACTIVE', 1, 0),
(5103031, 0, 'PLATFORM', 'BUTTON', 5103030, 'platform:counterparty:create', '新增往来单位', NULL, NULL, 10, 'ACTIVE', 1, 0),
(5103032, 0, 'PLATFORM', 'BUTTON', 5103030, 'platform:counterparty:edit', '编辑往来单位', NULL, NULL, 20, 'ACTIVE', 1, 0),
(5103033, 0, 'PLATFORM', 'BUTTON', 5103030, 'platform:counterparty:status', '启停往来单位', NULL, NULL, 30, '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),
sort_no = VALUES(sort_no),
status = 'ACTIVE',
built_in = 1,
deleted = 0;
-- 商户角色维护权限仅在对应父菜单已存在时补齐,不创建演示租户或角色。
INSERT INTO iam_permission
(id, tenant_id, app_code, permission_type, parent_id, permission_code,
permission_name, sort_no, status, built_in, deleted)
SELECT 5200033, 10001, 'MERCHANT', 'BUTTON', 5200030, 'merchant:role:edit', '编辑角色', 30, 'ACTIVE', 1, 0
WHERE EXISTS (SELECT 1 FROM iam_permission WHERE id = 5200030 AND tenant_id = 10001)
UNION ALL
SELECT 5200034, 10001, 'MERCHANT', 'BUTTON', 5200030, 'merchant:role:status', '启停角色', 40, 'ACTIVE', 1, 0
WHERE EXISTS (SELECT 1 FROM iam_permission WHERE id = 5200030 AND tenant_id = 10001)
UNION ALL
SELECT 5200035, 10001, 'MERCHANT', 'BUTTON', 5200030, 'merchant:role:delete', '删除角色', 50, 'ACTIVE', 1, 0
WHERE EXISTS (SELECT 1 FROM iam_permission WHERE id = 5200030 AND tenant_id = 10001)
UNION ALL
SELECT 5300033, 0, 'MERCHANT', 'BUTTON', 5300030, 'merchant:role:edit', '编辑角色', 30, 'ACTIVE', 1, 0
WHERE EXISTS (SELECT 1 FROM iam_permission WHERE id = 5300030 AND tenant_id = 0)
UNION ALL
SELECT 5300034, 0, 'MERCHANT', 'BUTTON', 5300030, 'merchant:role:status', '启停角色', 40, 'ACTIVE', 1, 0
WHERE EXISTS (SELECT 1 FROM iam_permission WHERE id = 5300030 AND tenant_id = 0)
UNION ALL
SELECT 5300035, 0, 'MERCHANT', 'BUTTON', 5300030, 'merchant:role:delete', '删除角色', 50, 'ACTIVE', 1, 0
WHERE EXISTS (SELECT 1 FROM iam_permission WHERE id = 5300030 AND tenant_id = 0)
ON DUPLICATE KEY UPDATE
permission_name = VALUES(permission_name),
parent_id = VALUES(parent_id),
sort_no = VALUES(sort_no),
status = 'ACTIVE',
built_in = 1,
deleted = 0;
UPDATE iam_permission
SET built_in = 1
WHERE id BETWEEN 5000000 AND 5399999
AND built_in = 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'
WHERE r.tenant_id = 0
AND r.app_code = 'PLATFORM'
AND r.role_code = 'PLATFORM_ADMIN'
AND r.deleted = 0
AND p.permission_code IN (
'platform:customer', 'platform:customer:list', 'platform:customer:create',
'platform:customer:view', 'platform:customer:edit', 'platform:customer:status', 'platform:customer:export',
'platform:customer:review', 'platform:customer:approve', 'platform:customer:reject',
'platform:system', 'platform:user:list', 'platform:user:create', 'platform:user:edit',
'platform:user:status', 'platform:user:reset-password', 'platform:user:grant-role',
'platform:role:list', 'platform:role:create', 'platform:role:grant',
'platform:role:edit', 'platform:role:status', 'platform:role:delete',
'platform:member:list', 'platform:member:grant-role',
'platform:permission:list', 'platform:permission:create', 'platform:permission:edit',
'platform:permission:status', 'platform:permission:delete',
'platform:order', 'platform:order:list', 'platform:order:create',
'platform:order:edit', 'platform:order:submit',
'platform:firstleg:task:list', 'platform:firstleg:task:view',
'platform:firstleg:assignment:history', 'platform:firstleg:assignment:assign',
'platform:master-data', 'platform:express:list', 'platform:express:create',
'platform:express:edit', 'platform:express:status',
'platform:counterparty:list', 'platform:counterparty:create',
'platform:counterparty:edit', 'platform:counterparty:status',
'platform:download:list', 'platform:download:all'
)
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);
-- 已有客户列表或审核角色获得客户导出及本人下载任务查询,不继承查看全部任务权限。
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 r.id, p.id, r.tenant_id
FROM iam_role r
JOIN iam_permission p
ON p.tenant_id = r.tenant_id
AND p.app_code = 'MERCHANT'
WHERE r.app_code = 'MERCHANT'
AND r.role_code IN ('MERCHANT_OWNER', 'MERCHANT_ADMIN')
AND r.deleted = 0
AND p.permission_code IN ('merchant:role:edit', 'merchant:role:status', 'merchant:role:delete')
ON DUPLICATE KEY UPDATE tenant_id = VALUES(tenant_id);
-- ============================================================================
-- 2. Warehouse:仓库、地址、能力、往来单位、快递公司结构兼容迁移
-- ============================================================================
USE baiyue_warehouse;
CREATE TABLE IF NOT EXISTS wh_partner (
id BIGINT UNSIGNED NOT NULL,
tenant_id BIGINT UNSIGNED NOT NULL,
partner_code VARCHAR(50) NOT NULL,
partner_name VARCHAR(128) NOT NULL,
partner_type VARCHAR(20) NOT NULL COMMENT 'CUSTOMER、SUPPLIER或BOTH',
contact_name VARCHAR(64) NOT NULL,
contact_phone VARCHAR(32) NOT NULL,
contact_email VARCHAR(128) NULL,
country_code CHAR(2) NOT NULL,
province VARCHAR(64) NULL,
city VARCHAR(64) NOT NULL,
district VARCHAR(64) NULL,
address_line VARCHAR(255) NOT NULL,
postal_code VARCHAR(20) NULL,
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
created_by BIGINT UNSIGNED NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_by BIGINT UNSIGNED NULL,
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
deleted TINYINT(1) NOT NULL DEFAULT 0,
version INT NOT NULL DEFAULT 0,
PRIMARY KEY (id),
UNIQUE KEY uk_wh_partner_tenant_code (tenant_id, partner_code, deleted),
KEY idx_wh_partner_tenant_status (tenant_id, status, deleted)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='仓储域内的租户往来单位配置';
CREATE TABLE IF NOT EXISTS wh_express_company (
id BIGINT UNSIGNED NOT NULL COMMENT '快递公司记录ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '所属租户ID',
company_code VARCHAR(50) NOT NULL COMMENT '租户内唯一快递公司编码',
company_name VARCHAR(128) NOT NULL COMMENT '快递公司名称',
service_phone VARCHAR(32) NULL COMMENT '客服电话',
website VARCHAR(255) NULL COMMENT '官方网站',
tracking_url_template VARCHAR(500) NULL COMMENT '轨迹查询地址模板,可包含{trackingNo}',
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE' 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_wh_express_tenant_code (tenant_id, company_code, deleted),
KEY idx_wh_express_tenant_status (tenant_id, status, deleted)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='租户维度快递公司主数据';
DROP PROCEDURE IF EXISTS regression_assert_warehouse_table;
DELIMITER $$
CREATE PROCEDURE regression_assert_warehouse_table(IN p_table_name VARCHAR(64))
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.tables
WHERE table_schema = DATABASE() AND table_name = p_table_name
) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'baiyue_warehouse 缺少仓库基础表,请先执行 warehouse.sql';
END IF;
END$$
DELIMITER ;
CALL regression_assert_warehouse_table('wh_warehouse');
CALL regression_assert_warehouse_table('wh_warehouse_address');
CALL regression_assert_warehouse_table('wh_service_capability');
DROP PROCEDURE regression_assert_warehouse_table;
DROP PROCEDURE IF EXISTS regression_add_column;
DELIMITER $$
CREATE PROCEDURE regression_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 @regression_column_sql = CONCAT(
'ALTER TABLE `', REPLACE(p_table_name, '`', '``'),
'` ADD COLUMN `', REPLACE(p_column_name, '`', '``'),
'` ', p_column_definition
);
PREPARE regression_column_stmt FROM @regression_column_sql;
EXECUTE regression_column_stmt;
DEALLOCATE PREPARE regression_column_stmt;
END IF;
END$$
DELIMITER ;
CALL regression_add_column('wh_warehouse', 'warehouse_type', 'VARCHAR(32) NOT NULL DEFAULT ''DOMESTIC'' COMMENT ''仓库类型'' AFTER warehouse_name');
CALL regression_add_column('wh_warehouse', 'country_code', 'CHAR(2) NOT NULL DEFAULT ''CN'' COMMENT ''所在国家二字码'' AFTER warehouse_type');
CALL regression_add_column('wh_warehouse', 'timezone', 'VARCHAR(64) NOT NULL DEFAULT ''Asia/Shanghai'' COMMENT ''IANA时区'' AFTER country_code');
CALL regression_add_column('wh_warehouse', 'contact_name', 'VARCHAR(64) NULL AFTER timezone');
CALL regression_add_column('wh_warehouse', 'contact_phone', 'VARCHAR(32) NULL AFTER contact_name');
CALL regression_add_column('wh_warehouse', 'contact_email', 'VARCHAR(128) NULL AFTER contact_phone');
CALL regression_add_column('wh_warehouse', 'status', 'VARCHAR(20) NOT NULL DEFAULT ''ACTIVE'' AFTER contact_email');
CALL regression_add_column('wh_warehouse', 'remark', 'VARCHAR(500) NULL AFTER status');
CALL regression_add_column('wh_warehouse', 'created_by', 'BIGINT UNSIGNED NULL');
CALL regression_add_column('wh_warehouse', 'created_at', 'DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)');
CALL regression_add_column('wh_warehouse', 'updated_by', 'BIGINT UNSIGNED NULL');
CALL regression_add_column('wh_warehouse', 'updated_at', 'DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)');
CALL regression_add_column('wh_warehouse', 'deleted', 'TINYINT(1) NOT NULL DEFAULT 0');
CALL regression_add_column('wh_warehouse', 'version', 'INT NOT NULL DEFAULT 0');
CALL regression_add_column('wh_warehouse_address', 'tenant_id', 'BIGINT UNSIGNED NOT NULL DEFAULT 0 AFTER id');
CALL regression_add_column('wh_warehouse_address', 'warehouse_id', 'BIGINT UNSIGNED NOT NULL DEFAULT 0 AFTER tenant_id');
CALL regression_add_column('wh_warehouse_address', 'address_type', 'VARCHAR(20) NOT NULL DEFAULT ''OPERATING'' AFTER warehouse_id');
CALL regression_add_column('wh_warehouse_address', 'country_code', 'CHAR(2) NOT NULL DEFAULT ''CN'' AFTER address_type');
CALL regression_add_column('wh_warehouse_address', 'state_province', 'VARCHAR(100) NULL');
CALL regression_add_column('wh_warehouse_address', 'city', 'VARCHAR(100) NOT NULL DEFAULT '''' AFTER state_province');
CALL regression_add_column('wh_warehouse_address', 'district', 'VARCHAR(100) NULL');
CALL regression_add_column('wh_warehouse_address', 'postal_code', 'VARCHAR(20) NULL');
CALL regression_add_column('wh_warehouse_address', 'address_line1', 'VARCHAR(255) NOT NULL DEFAULT '''' AFTER postal_code');
CALL regression_add_column('wh_warehouse_address', 'address_line2', 'VARCHAR(255) NULL');
CALL regression_add_column('wh_warehouse_address', 'latitude', 'DECIMAL(10,7) NULL');
CALL regression_add_column('wh_warehouse_address', 'longitude', 'DECIMAL(10,7) NULL');
CALL regression_add_column('wh_warehouse_address', 'created_by', 'BIGINT UNSIGNED NULL');
CALL regression_add_column('wh_warehouse_address', 'created_at', 'DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)');
CALL regression_add_column('wh_warehouse_address', 'updated_by', 'BIGINT UNSIGNED NULL');
CALL regression_add_column('wh_warehouse_address', 'updated_at', 'DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)');
CALL regression_add_column('wh_warehouse_address', 'deleted', 'TINYINT(1) NOT NULL DEFAULT 0');
CALL regression_add_column('wh_warehouse_address', 'version', 'INT NOT NULL DEFAULT 0');
CALL regression_add_column('wh_service_capability', 'tenant_id', 'BIGINT UNSIGNED NOT NULL DEFAULT 0 AFTER id');
CALL regression_add_column('wh_service_capability', 'warehouse_id', 'BIGINT UNSIGNED NOT NULL DEFAULT 0 AFTER tenant_id');
CALL regression_add_column('wh_service_capability', 'capability_code', 'VARCHAR(50) NOT NULL DEFAULT '''' AFTER warehouse_id');
CALL regression_add_column('wh_service_capability', 'capability_name', 'VARCHAR(100) NOT NULL DEFAULT '''' AFTER capability_code');
CALL regression_add_column('wh_service_capability', 'enabled', 'TINYINT(1) NOT NULL DEFAULT 1');
CALL regression_add_column('wh_service_capability', 'cutoff_time', 'TIME NULL');
CALL regression_add_column('wh_service_capability', 'description', 'VARCHAR(500) NULL');
CALL regression_add_column('wh_service_capability', 'created_by', 'BIGINT UNSIGNED NULL');
CALL regression_add_column('wh_service_capability', 'created_at', 'DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)');
CALL regression_add_column('wh_service_capability', 'updated_by', 'BIGINT UNSIGNED NULL');
CALL regression_add_column('wh_service_capability', 'updated_at', 'DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)');
CALL regression_add_column('wh_service_capability', 'deleted', 'TINYINT(1) NOT NULL DEFAULT 0');
CALL regression_add_column('wh_service_capability', 'version', 'INT NOT NULL DEFAULT 0');
CALL regression_add_column('wh_partner', 'tenant_id', 'BIGINT UNSIGNED NOT NULL DEFAULT 0 AFTER id');
CALL regression_add_column('wh_partner', 'partner_code', 'VARCHAR(50) NOT NULL DEFAULT '''' AFTER tenant_id');
CALL regression_add_column('wh_partner', 'partner_name', 'VARCHAR(128) NOT NULL DEFAULT '''' AFTER partner_code');
CALL regression_add_column('wh_partner', 'partner_type', 'VARCHAR(20) NOT NULL DEFAULT ''CUSTOMER'' AFTER partner_name');
CALL regression_add_column('wh_partner', 'contact_name', 'VARCHAR(64) NOT NULL DEFAULT '''' AFTER partner_type');
CALL regression_add_column('wh_partner', 'contact_phone', 'VARCHAR(32) NOT NULL DEFAULT '''' AFTER contact_name');
CALL regression_add_column('wh_partner', 'contact_email', 'VARCHAR(128) NULL');
CALL regression_add_column('wh_partner', 'country_code', 'CHAR(2) NOT NULL DEFAULT ''CN'' AFTER contact_email');
CALL regression_add_column('wh_partner', 'province', 'VARCHAR(64) NULL');
CALL regression_add_column('wh_partner', 'city', 'VARCHAR(64) NOT NULL DEFAULT '''' AFTER province');
CALL regression_add_column('wh_partner', 'district', 'VARCHAR(64) NULL');
CALL regression_add_column('wh_partner', 'address_line', 'VARCHAR(255) NOT NULL DEFAULT '''' AFTER district');
CALL regression_add_column('wh_partner', 'postal_code', 'VARCHAR(20) NULL');
CALL regression_add_column('wh_partner', 'status', 'VARCHAR(20) NOT NULL DEFAULT ''ACTIVE'' AFTER postal_code');
CALL regression_add_column('wh_partner', 'created_by', 'BIGINT UNSIGNED NULL');
CALL regression_add_column('wh_partner', 'created_at', 'DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)');
CALL regression_add_column('wh_partner', 'updated_by', 'BIGINT UNSIGNED NULL');
CALL regression_add_column('wh_partner', 'updated_at', 'DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)');
CALL regression_add_column('wh_partner', 'deleted', 'TINYINT(1) NOT NULL DEFAULT 0');
CALL regression_add_column('wh_partner', 'version', 'INT NOT NULL DEFAULT 0');
CALL regression_add_column('wh_express_company', 'tenant_id', 'BIGINT UNSIGNED NOT NULL DEFAULT 0 AFTER id');
CALL regression_add_column('wh_express_company', 'company_code', 'VARCHAR(50) NOT NULL DEFAULT '''' AFTER tenant_id');
CALL regression_add_column('wh_express_company', 'company_name', 'VARCHAR(128) NOT NULL DEFAULT '''' AFTER company_code');
CALL regression_add_column('wh_express_company', 'service_phone', 'VARCHAR(32) NULL');
CALL regression_add_column('wh_express_company', 'website', 'VARCHAR(255) NULL');
CALL regression_add_column('wh_express_company', 'tracking_url_template', 'VARCHAR(500) NULL');
CALL regression_add_column('wh_express_company', 'status', 'VARCHAR(20) NOT NULL DEFAULT ''ACTIVE'' AFTER tracking_url_template');
CALL regression_add_column('wh_express_company', 'remark', 'VARCHAR(500) NULL');
CALL regression_add_column('wh_express_company', 'created_by', 'BIGINT UNSIGNED NULL');
CALL regression_add_column('wh_express_company', 'created_at', 'DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)');
CALL regression_add_column('wh_express_company', 'updated_by', 'BIGINT UNSIGNED NULL');
CALL regression_add_column('wh_express_company', 'updated_at', 'DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)');
CALL regression_add_column('wh_express_company', 'deleted', 'TINYINT(1) NOT NULL DEFAULT 0');
CALL regression_add_column('wh_express_company', 'version', 'INT NOT NULL DEFAULT 0');
DROP PROCEDURE regression_add_column;
DROP PROCEDURE IF EXISTS regression_add_index;
DELIMITER $$
CREATE PROCEDURE regression_add_index(
IN p_table_name VARCHAR(64),
IN p_index_name VARCHAR(64),
IN p_index_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 @regression_index_sql = CONCAT(
'ALTER TABLE `', REPLACE(p_table_name, '`', '``'),
'` ADD ', p_index_definition
);
PREPARE regression_index_stmt FROM @regression_index_sql;
EXECUTE regression_index_stmt;
DEALLOCATE PREPARE regression_index_stmt;
END IF;
END$$
DELIMITER ;
CALL regression_add_index('wh_warehouse', 'idx_wh_tenant_status', 'KEY idx_wh_tenant_status (tenant_id, status, deleted)');
CALL regression_add_index('wh_warehouse_address', 'idx_address_warehouse', 'KEY idx_address_warehouse (tenant_id, warehouse_id, deleted)');
CALL regression_add_index('wh_service_capability', 'idx_capability_query', 'KEY idx_capability_query (tenant_id, warehouse_id, enabled, deleted)');
CALL regression_add_index('wh_partner', 'idx_wh_partner_tenant_status', 'KEY idx_wh_partner_tenant_status (tenant_id, status, deleted)');
CALL regression_add_index('wh_partner', 'uk_wh_partner_tenant_code', 'UNIQUE KEY uk_wh_partner_tenant_code (tenant_id, partner_code, deleted)');
CALL regression_add_index('wh_express_company', 'idx_wh_express_tenant_status', 'KEY idx_wh_express_tenant_status (tenant_id, status, deleted)');
CALL regression_add_index('wh_express_company', 'uk_wh_express_tenant_code', 'UNIQUE KEY uk_wh_express_tenant_code (tenant_id, company_code, deleted)');
DROP PROCEDURE regression_add_index;
-- ============================================================================
-- 3. Order:一期订单快照字段、货物字段和持久化幂等记录
-- ============================================================================
USE baiyue_order;
DROP PROCEDURE IF EXISTS regression_assert_order_table;
DELIMITER $$
CREATE PROCEDURE regression_assert_order_table(IN p_table_name VARCHAR(64))
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.tables
WHERE table_schema = DATABASE() AND table_name = p_table_name
) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'baiyue_order 缺少订单基础表,请先执行 order.sql';
END IF;
END$$
DELIMITER ;
CALL regression_assert_order_table('ord_order');
CALL regression_assert_order_table('ord_order_cargo');
DROP PROCEDURE regression_assert_order_table;
DROP PROCEDURE IF EXISTS regression_add_order_column;
DELIMITER $$
CREATE PROCEDURE regression_add_order_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 @regression_order_sql = CONCAT(
'ALTER TABLE `', p_table_name, '` ADD COLUMN `', p_column_name, '` ', p_definition
);
PREPARE regression_order_stmt FROM @regression_order_sql;
EXECUTE regression_order_stmt;
DEALLOCATE PREPARE regression_order_stmt;
END IF;
END$$
DELIMITER ;
CALL regression_add_order_column('ord_order', 'pickup_method', 'VARCHAR(24) NULL COMMENT ''提货方式''');
CALL regression_add_order_column('ord_order', 'receiving_warehouse_id', 'BIGINT UNSIGNED NULL COMMENT ''国内收货仓ID''');
CALL regression_add_order_column('ord_order', 'receiving_warehouse_name', 'VARCHAR(128) NULL COMMENT ''国内收货仓名称快照''');
CALL regression_add_order_column('ord_order', 'receiving_warehouse_address', 'VARCHAR(512) NULL COMMENT ''国内收货仓完整地址快照''');
CALL regression_add_order_column('ord_order', 'receiving_time', 'VARCHAR(128) NULL COMMENT ''国内收货仓收货时间快照''');
CALL regression_add_order_column('ord_order', 'channel_code', 'VARCHAR(64) NULL COMMENT ''运输渠道编码''');
CALL regression_add_order_column('ord_order', 'channel_name', 'VARCHAR(128) NULL COMMENT ''运输渠道名称快照''');
CALL regression_add_order_column('ord_order', 'tax_mode', 'VARCHAR(24) NULL COMMENT ''报关买单或退税模式''');
CALL regression_add_order_column('ord_order', 'container_mode', 'VARCHAR(24) NULL COMMENT ''整柜或散货模式''');
CALL regression_add_order_column('ord_order', 'customs_inspection', 'TINYINT(1) NULL COMMENT ''是否需要海关查验''');
CALL regression_add_order_column('ord_order', 'destination_type', 'VARCHAR(24) NULL COMMENT ''FBA、海外仓或私人地址''');
CALL regression_add_order_column('ord_order', 'amazon_warehouse_code', 'VARCHAR(32) NULL COMMENT ''亚马逊仓库编码''');
CALL regression_add_order_column('ord_order', 'shipment_id', 'VARCHAR(64) NULL COMMENT ''亚马逊Shipment ID''');
CALL regression_add_order_column('ord_order', 'reference_id', 'VARCHAR(64) NULL COMMENT ''亚马逊Reference ID''');
CALL regression_add_order_column('ord_order', 'express_company', 'VARCHAR(64) NULL COMMENT ''履约快递公司名称快照''');
CALL regression_add_order_column('ord_order', 'express_tracking_no', 'VARCHAR(128) NULL COMMENT ''履约快递单号''');
CALL regression_add_order_column('ord_order_cargo', 'sku_no', 'VARCHAR(64) NULL COMMENT ''SKU编号''');
CALL regression_add_order_column('ord_order_cargo', 'upc_no', 'VARCHAR(64) NULL COMMENT ''UPC编号''');
CALL regression_add_order_column('ord_order_cargo', 'cargo_name_en', 'VARCHAR(128) NULL COMMENT ''货物英文名称''');
CALL regression_add_order_column('ord_order_cargo', 'customs_hs_code', 'VARCHAR(20) NULL COMMENT ''目的国海关HS编码''');
CALL regression_add_order_column('ord_order_cargo', 'units_per_package', 'INT UNSIGNED NULL COMMENT ''每箱件数''');
CALL regression_add_order_column('ord_order_cargo', 'total_quantity', 'INT UNSIGNED NULL COMMENT ''总件数''');
CALL regression_add_order_column('ord_order_cargo', 'net_weight_kg', 'DECIMAL(18,3) NULL COMMENT ''净重千克''');
CALL regression_add_order_column('ord_order_cargo', 'unit_declared_value', 'DECIMAL(18,2) NULL COMMENT ''单件申报价值''');
CALL regression_add_order_column('ord_order_cargo', 'material', 'VARCHAR(128) NULL COMMENT ''材质''');
CALL regression_add_order_column('ord_order_cargo', 'product_usage', 'VARCHAR(128) NULL COMMENT ''用途''');
CALL regression_add_order_column('ord_order_cargo', 'model', 'VARCHAR(64) NULL COMMENT ''型号''');
CALL regression_add_order_column('ord_order_cargo', 'brand', 'VARCHAR(64) NULL COMMENT ''品牌''');
DROP PROCEDURE regression_add_order_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',
idempotency_key VARCHAR(128) NOT NULL COMMENT '客户端幂等键',
request_hash CHAR(64) NOT NULL COMMENT '请求内容摘要',
status VARCHAR(16) NOT NULL COMMENT '处理状态',
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 DEFAULT CHARSET=utf8mb4 COMMENT='订单写接口持久化幂等记录';
-- 兼容曾经只建表但未建立完整索引的测试库。
SET @order_idempotency_unique_sql = IF(
EXISTS(
SELECT 1 FROM information_schema.statistics
WHERE table_schema = DATABASE()
AND table_name = 'ord_idempotency_record'
AND index_name = 'uk_order_idempotency_scope'
),
'SELECT 1',
'ALTER TABLE ord_idempotency_record ADD UNIQUE KEY uk_order_idempotency_scope (tenant_id, operator_id, operation, idempotency_key)'
);
PREPARE order_idempotency_unique_stmt FROM @order_idempotency_unique_sql;
EXECUTE order_idempotency_unique_stmt;
DEALLOCATE PREPARE order_idempotency_unique_stmt;
SET @order_idempotency_created_sql = IF(
EXISTS(
SELECT 1 FROM information_schema.statistics
WHERE table_schema = DATABASE()
AND table_name = 'ord_idempotency_record'
AND index_name = 'idx_order_idempotency_created'
),
'SELECT 1',
'ALTER TABLE ord_idempotency_record ADD KEY idx_order_idempotency_created (created_at)'
);
PREPARE order_idempotency_created_stmt FROM @order_idempotency_created_sql;
EXECUTE order_idempotency_created_stmt;
DEALLOCATE PREPARE order_idempotency_created_stmt;
-- ============================================================================
-- 4. Firstleg:履约任务分页索引
-- ============================================================================
USE baiyue_firstleg;
DROP PROCEDURE IF EXISTS regression_assert_firstleg_table;
DELIMITER $$
CREATE PROCEDURE regression_assert_firstleg_table()
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.tables
WHERE table_schema = DATABASE() AND table_name = 'fl_fulfillment_task'
) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'baiyue_firstleg 缺少履约任务基础表,请先执行 firstleg.sql';
END IF;
END$$
DELIMITER ;
CALL regression_assert_firstleg_table();
DROP PROCEDURE regression_assert_firstleg_table;
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;
-- ============================================================================
-- 5. File:公共导出与下载中心任务表
-- ============================================================================
USE baiyue_file;
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 '申请端',
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 '任务状态',
total_rows BIGINT UNSIGNED NULL,
processed_rows BIGINT UNSIGNED NOT NULL DEFAULT 0,
file_name VARCHAR(255) NULL,
object_key VARCHAR(512) NULL,
content_type VARCHAR(128) NULL,
size_bytes BIGINT UNSIGNED NULL,
failure_message VARCHAR(1000) NULL,
requested_by BIGINT UNSIGNED NOT NULL,
requested_username VARCHAR(128) NULL,
requested_at DATETIME(3) NOT NULL,
started_at DATETIME(3) NULL,
completed_at DATETIME(3) NULL,
expires_at DATETIME(3) NULL,
created_by BIGINT UNSIGNED NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_by BIGINT UNSIGNED NULL,
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
deleted TINYINT(1) NOT NULL DEFAULT 0,
version INT NOT NULL DEFAULT 0,
PRIMARY KEY (id),
UNIQUE KEY uk_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_expiry (status, expires_at)
) ENGINE=InnoDB COMMENT='公共异步导出任务表';
-- ============================================================================
-- 6. 执行后只读核验
-- ============================================================================
SELECT
'IAM权限资源' AS check_item,
COUNT(*) AS actual_count,
47 AS expected_count,
IF(COUNT(*) = 47, '通过', '请检查缺失权限') AS check_result
FROM baiyue_iam.iam_permission
WHERE deleted = 0
AND permission_code IN (
'platform:customer', 'platform:customer:list', 'platform:customer:create',
'platform:customer:view', 'platform:customer:edit', 'platform:customer:status', 'platform:customer:export',
'platform:customer:review', 'platform:customer:approve', 'platform:customer:reject',
'platform:system', 'platform:user:list', 'platform:user:create', 'platform:user:edit',
'platform:user:status', 'platform:user:reset-password', 'platform:user:grant-role',
'platform:role:list', 'platform:role:create', 'platform:role:grant',
'platform:role:edit', 'platform:role:status', 'platform:role:delete',
'platform:member:list', 'platform:member:grant-role',
'platform:permission:list', 'platform:permission:create', 'platform:permission:edit',
'platform:permission:status', 'platform:permission:delete',
'platform:order', 'platform:order:list', 'platform:order:create',
'platform:order:edit', 'platform:order:submit',
'platform:firstleg:task:list', 'platform:firstleg:task:view',
'platform:firstleg:assignment:history', 'platform:firstleg:assignment:assign',
'platform:master-data', 'platform:express:list', 'platform:express:create',
'platform:express:edit', 'platform:express:status',
'platform:counterparty:list', 'platform:counterparty:create',
'platform:counterparty:edit', 'platform:counterparty:status',
'platform:download:list', 'platform:download:all'
);
SELECT
'IAM客户档案字段' AS check_item,
COUNT(*) AS actual_count,
10 AS expected_count,
IF(COUNT(*) = 10, '通过', '请检查客户档案字段迁移') AS check_result
FROM information_schema.columns
WHERE table_schema = 'baiyue_iam'
AND table_name = 'iam_tenant'
AND column_name IN (
'legal_representative', 'contact_wechat', 'sales_owner_name', 'payment_term_days', 'enterprise_type',
'established_date', 'registered_capital', 'registered_capital_currency',
'registered_address', 'internal_remark'
);
SELECT
'IAM注册审核字段' AS check_item,
COUNT(*) AS actual_count,
4 AS expected_count,
IF(COUNT(*) = 4, '通过', '请检查租户审核状态和结果字段') AS check_result
FROM information_schema.columns
WHERE table_schema = 'baiyue_iam'
AND table_name = 'iam_tenant'
AND column_name IN ('status', 'approved_at', 'approved_by', 'reject_reason');
SELECT
'IAM资质与成员审计字段' AS check_item,
(
SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = 'baiyue_iam'
AND table_name = 'iam_tenant_qualification'
AND column_name = 'review_comment'
) + (
SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = 'baiyue_iam'
AND table_name = 'iam_membership'
AND column_name IN ('created_by', 'updated_by')
) AS actual_count,
3 AS expected_count,
IF(
(
SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = 'baiyue_iam'
AND table_name = 'iam_tenant_qualification'
AND column_name = 'review_comment'
) + (
SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = 'baiyue_iam'
AND table_name = 'iam_membership'
AND column_name IN ('created_by', 'updated_by')
) = 3,
'通过', '请检查资质审核备注和成员审计字段'
) AS check_result;
SELECT
'IAM全局企业管理员模板' AS check_item,
COUNT(DISTINCT owner_role.id) AS role_count,
COUNT(DISTINCT role_permission.permission_id) AS actual_permission_count,
(SELECT COUNT(*) FROM baiyue_iam.iam_permission permission
WHERE permission.tenant_id = 0 AND permission.app_code = 'MERCHANT'
AND permission.permission_type IN ('MENU', 'BUTTON')
AND permission.status = 'ACTIVE' AND permission.deleted = 0) AS expected_permission_count,
IF(
COUNT(DISTINCT owner_role.id) = 1
AND COUNT(DISTINCT role_permission.permission_id) = (
SELECT COUNT(*) FROM baiyue_iam.iam_permission permission
WHERE permission.tenant_id = 0 AND permission.app_code = 'MERCHANT'
AND permission.permission_type IN ('MENU', 'BUTTON')
AND permission.status = 'ACTIVE' AND permission.deleted = 0
)
AND COUNT(DISTINCT role_permission.permission_id) > 0,
'通过', '请检查全局MERCHANT_OWNER角色及授权'
) AS check_result
FROM baiyue_iam.iam_role owner_role
LEFT JOIN baiyue_iam.iam_role_permission role_permission
ON role_permission.role_id = owner_role.id AND role_permission.tenant_id = 0
WHERE owner_role.tenant_id = 0 AND owner_role.app_code = 'MERCHANT'
AND owner_role.role_code = 'MERCHANT_OWNER'
AND owner_role.role_type = 'SYSTEM' AND owner_role.status = 'ACTIVE' AND owner_role.deleted = 0;
SELECT
table_name,
COUNT(*) AS column_count
FROM information_schema.columns
WHERE table_schema = 'baiyue_warehouse'
AND table_name IN (
'wh_warehouse', 'wh_warehouse_address', 'wh_service_capability',
'wh_partner', 'wh_express_company'
)
GROUP BY table_name
ORDER BY table_name;
SELECT
IF(COUNT(*) = 1, '通过', '缺失') AS idempotency_table,
'baiyue_order.ord_idempotency_record' AS object_name
FROM information_schema.tables
WHERE table_schema = 'baiyue_order'
AND table_name = 'ord_idempotency_record';
SELECT
IF(COUNT(*) > 0, '通过', '缺失') AS idempotency_unique_index,
'uk_order_idempotency_scope' AS object_name
FROM information_schema.statistics
WHERE table_schema = 'baiyue_order'
AND table_name = 'ord_idempotency_record'
AND index_name = 'uk_order_idempotency_scope';
SELECT
IF(COUNT(*) > 0, '通过', '缺失') AS firstleg_task_index,
'idx_task_tenant_created' AS object_name
FROM information_schema.statistics
WHERE table_schema = 'baiyue_firstleg'
AND table_name = 'fl_fulfillment_task'
AND index_name = 'idx_task_tenant_created';
SELECT
IF(COUNT(*) = 1, '通过', '缺失') AS export_task_table,
'baiyue_file.file_export_task' AS object_name
FROM information_schema.tables
WHERE table_schema = 'baiyue_file'
AND table_name = 'file_export_task';
SELECT 'MIGRATION_COMPLETED' AS migration_status,
'请确认上方所有核验结果均为通过后再启动服务回归' AS message;
@@ -1,202 +0,0 @@
-- 一期头程下单前置增量迁移。
-- 适用:旧版测试库已存在基础表,但缺少客户档案、往来单位或订单一期字段。
-- 特性:仅新增缺失字段/表,可重复执行;不删除数据,不写入订单业务数据。
SET NAMES utf8mb4;
-- 1. IAM:下单资格校验会查询以下租户档案字段。
USE baiyue_iam;
SET @ddl = IF(
EXISTS(SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'iam_tenant' AND column_name = 'service_package'),
'SELECT 1', 'ALTER TABLE iam_tenant ADD COLUMN service_package VARCHAR(64) NOT NULL DEFAULT ''基础版'' 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 = '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 ''注册资本币种'' 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;
-- 2. Warehouse:订单主数据校验依赖往来单位及其 SHIPPER 类型。
USE baiyue_warehouse;
DROP PROCEDURE IF EXISTS order_prerequisite_add_partner_column;
DELIMITER $$
CREATE PROCEDURE order_prerequisite_add_partner_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 = 'wh_partner' AND column_name = p_column
) THEN
SET @sql = CONCAT('ALTER TABLE wh_partner ADD COLUMN `', p_column, '` ', p_definition);
PREPARE add_column_stmt FROM @sql;
EXECUTE add_column_stmt;
DEALLOCATE PREPARE add_column_stmt;
END IF;
END$$
DELIMITER ;
CALL order_prerequisite_add_partner_column('tenant_id', 'BIGINT UNSIGNED NOT NULL DEFAULT 0');
CALL order_prerequisite_add_partner_column('data_scope_id', 'BIGINT UNSIGNED NOT NULL DEFAULT 0');
CALL order_prerequisite_add_partner_column('partner_code', 'VARCHAR(50) NOT NULL DEFAULT ''''');
CALL order_prerequisite_add_partner_column('partner_name', 'VARCHAR(128) NOT NULL DEFAULT ''''');
CALL order_prerequisite_add_partner_column('partner_type', 'VARCHAR(20) NOT NULL DEFAULT ''CUSTOMER''');
CALL order_prerequisite_add_partner_column('contact_name', 'VARCHAR(64) NOT NULL DEFAULT ''''');
CALL order_prerequisite_add_partner_column('contact_phone', 'VARCHAR(32) NOT NULL DEFAULT ''''');
CALL order_prerequisite_add_partner_column('contact_email', 'VARCHAR(128) NULL');
CALL order_prerequisite_add_partner_column('country_code', 'CHAR(2) NOT NULL DEFAULT ''CN''');
CALL order_prerequisite_add_partner_column('province', 'VARCHAR(64) NULL');
CALL order_prerequisite_add_partner_column('city', 'VARCHAR(64) NOT NULL DEFAULT ''''');
CALL order_prerequisite_add_partner_column('district', 'VARCHAR(64) NULL');
CALL order_prerequisite_add_partner_column('address_line', 'VARCHAR(255) NOT NULL DEFAULT ''''');
CALL order_prerequisite_add_partner_column('postal_code', 'VARCHAR(20) NULL');
CALL order_prerequisite_add_partner_column('status', 'VARCHAR(20) NOT NULL DEFAULT ''ACTIVE''');
CALL order_prerequisite_add_partner_column('remark', 'VARCHAR(500) NULL');
CALL order_prerequisite_add_partner_column('created_by', 'BIGINT UNSIGNED NULL');
CALL order_prerequisite_add_partner_column('created_at', 'DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)');
CALL order_prerequisite_add_partner_column('updated_by', 'BIGINT UNSIGNED NULL');
CALL order_prerequisite_add_partner_column('updated_at', 'DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)');
CALL order_prerequisite_add_partner_column('deleted', 'TINYINT(1) NOT NULL DEFAULT 0');
CALL order_prerequisite_add_partner_column('version', 'INT NOT NULL DEFAULT 0');
DROP PROCEDURE IF EXISTS order_prerequisite_add_partner_column;
UPDATE wh_partner SET data_scope_id = tenant_id WHERE data_scope_id = 0;
CREATE TABLE IF NOT EXISTS wh_partner_type (
partner_id BIGINT UNSIGNED NOT NULL,
tenant_id BIGINT UNSIGNED NOT NULL,
data_scope_id BIGINT UNSIGNED NOT NULL,
partner_type VARCHAR(32) NOT NULL,
PRIMARY KEY (partner_id, partner_type),
KEY idx_wh_partner_type_scope (tenant_id, data_scope_id, partner_type, partner_id)
) ENGINE=InnoDB COMMENT='往来单位业务类型';
-- 将既有有效往来单位的兼容单值类型回填到类型关联表。
INSERT IGNORE INTO wh_partner_type(partner_id, tenant_id, data_scope_id, partner_type)
SELECT id, tenant_id, data_scope_id, partner_type
FROM wh_partner
WHERE deleted = 0 AND partner_type <> 'BOTH';
INSERT IGNORE INTO wh_partner_type(partner_id, tenant_id, data_scope_id, partner_type)
SELECT id, tenant_id, data_scope_id, 'CUSTOMER'
FROM wh_partner
WHERE deleted = 0 AND partner_type = 'BOTH';
INSERT IGNORE INTO wh_partner_type(partner_id, tenant_id, data_scope_id, partner_type)
SELECT id, tenant_id, data_scope_id, 'SUPPLIER'
FROM wh_partner
WHERE deleted = 0 AND partner_type = 'BOTH';
-- 一期演示商户的既有客户同时作为发货单位,供头程订单 customerId=191000000000000001 使用。
INSERT IGNORE INTO wh_partner_type(partner_id, tenant_id, data_scope_id, partner_type)
SELECT id, tenant_id, data_scope_id, 'SHIPPER'
FROM wh_partner
WHERE id = 191000000000000001 AND tenant_id = 10001 AND status = 'ACTIVE' AND deleted = 0;
-- 3. Order:草稿创建、货物快照和 Idempotency-Key 持久化。
USE baiyue_order;
DROP PROCEDURE IF EXISTS order_prerequisite_add_order_column;
DELIMITER $$
CREATE PROCEDURE order_prerequisite_add_order_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 add_column_stmt FROM @sql;
EXECUTE add_column_stmt;
DEALLOCATE PREPARE add_column_stmt;
END IF;
END$$
DELIMITER ;
CALL order_prerequisite_add_order_column('ord_order', 'pickup_method', 'VARCHAR(24) NULL');
CALL order_prerequisite_add_order_column('ord_order', 'receiving_warehouse_id', 'BIGINT UNSIGNED NULL');
CALL order_prerequisite_add_order_column('ord_order', 'receiving_warehouse_name', 'VARCHAR(128) NULL');
CALL order_prerequisite_add_order_column('ord_order', 'receiving_warehouse_address', 'VARCHAR(600) NULL');
CALL order_prerequisite_add_order_column('ord_order', 'receiving_time', 'VARCHAR(128) NULL');
CALL order_prerequisite_add_order_column('ord_order', 'channel_code', 'VARCHAR(64) NULL');
CALL order_prerequisite_add_order_column('ord_order', 'channel_name', 'VARCHAR(128) NULL');
CALL order_prerequisite_add_order_column('ord_order', 'tax_mode', 'VARCHAR(24) NULL');
CALL order_prerequisite_add_order_column('ord_order', 'container_mode', 'VARCHAR(24) NULL');
CALL order_prerequisite_add_order_column('ord_order', 'customs_inspection', 'TINYINT(1) NULL');
CALL order_prerequisite_add_order_column('ord_order', 'destination_type', 'VARCHAR(24) NULL');
CALL order_prerequisite_add_order_column('ord_order', 'amazon_warehouse_code', 'VARCHAR(32) NULL');
CALL order_prerequisite_add_order_column('ord_order', 'shipment_id', 'VARCHAR(64) NULL');
CALL order_prerequisite_add_order_column('ord_order', 'reference_id', 'VARCHAR(64) NULL');
CALL order_prerequisite_add_order_column('ord_order', 'express_company', 'VARCHAR(64) NULL');
CALL order_prerequisite_add_order_column('ord_order', 'express_tracking_no', 'VARCHAR(128) NULL');
CALL order_prerequisite_add_order_column('ord_order_cargo', 'sku_no', 'VARCHAR(64) NULL');
CALL order_prerequisite_add_order_column('ord_order_cargo', 'upc_no', 'VARCHAR(64) NULL');
CALL order_prerequisite_add_order_column('ord_order_cargo', 'cargo_name_en', 'VARCHAR(128) NULL');
CALL order_prerequisite_add_order_column('ord_order_cargo', 'customs_hs_code', 'VARCHAR(20) NULL');
CALL order_prerequisite_add_order_column('ord_order_cargo', 'units_per_package', 'INT UNSIGNED NULL');
CALL order_prerequisite_add_order_column('ord_order_cargo', 'total_quantity', 'INT UNSIGNED NULL');
CALL order_prerequisite_add_order_column('ord_order_cargo', 'net_weight_kg', 'DECIMAL(18,3) NULL');
CALL order_prerequisite_add_order_column('ord_order_cargo', 'unit_declared_value', 'DECIMAL(18,2) NULL');
CALL order_prerequisite_add_order_column('ord_order_cargo', 'material', 'VARCHAR(128) NULL');
CALL order_prerequisite_add_order_column('ord_order_cargo', 'product_usage', 'VARCHAR(128) NULL');
CALL order_prerequisite_add_order_column('ord_order_cargo', 'model', 'VARCHAR(64) NULL');
CALL order_prerequisite_add_order_column('ord_order_cargo', 'brand', 'VARCHAR(64) NULL');
DROP PROCEDURE IF EXISTS order_prerequisite_add_order_column;
CREATE TABLE IF NOT EXISTS ord_idempotency_record (
id BIGINT UNSIGNED NOT NULL,
tenant_id BIGINT UNSIGNED NOT NULL,
operator_id BIGINT UNSIGNED NOT NULL,
operation VARCHAR(32) NOT NULL,
resource_id BIGINT UNSIGNED NOT NULL DEFAULT 0,
idempotency_key VARCHAR(128) NOT NULL,
request_hash CHAR(64) NOT NULL,
status VARCHAR(16) NOT NULL,
result_order_id BIGINT UNSIGNED NULL,
completed_at DATETIME(3) NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (id),
UNIQUE KEY uk_order_idempotency_scope (tenant_id, operator_id, operation, idempotency_key),
KEY idx_order_idempotency_created (created_at)
) ENGINE=InnoDB COMMENT='订单幂等记录';
@@ -1,699 +0,0 @@
-- 柏悦头程二期入库表结构:TEST环境一键执行脚本
-- 生成日期:2026-08-03
-- 适用版本:MySQL 8.0及以上
-- 执行范围:只新增二期表、字段、索引和约束,不插入、更新或删除业务数据
-- 目标数据库:baiyue_firstleg、baiyue_warehouse
-- 前置条件:一期基线表 fl_fulfillment_task、wh_warehouse 已存在
-- 注意:MySQL DDL会自动提交,请使用具有目标库建表权限的测试环境账号执行
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';
SELECT VERSION() AS mysql_version,
'开始执行二期入库表结构' AS execution_message;
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 '入库流程状态',
order_status_snapshot VARCHAR(24) NOT NULL COMMENT '订单状态快照',
measure_review_status VARCHAR(24) NOT NULL DEFAULT 'NOT_SUBMITTED' COMMENT '测量数据审核状态',
fee_status VARCHAR(24) NOT NULL DEFAULT 'UNPRICED' COMMENT '费用业务状态',
fee_review_status VARCHAR(24) NOT NULL DEFAULT 'NOT_SUBMITTED' COMMENT '费用审核状态',
notification_status VARCHAR(24) NOT NULL DEFAULT 'NOT_CREATED' COMMENT '客户通知状态',
expected_arrival_at DATETIME(3) NULL COMMENT '预计到达时间',
customer_id BIGINT UNSIGNED NULL COMMENT '客户逻辑ID',
customer_name VARCHAR(128) NOT NULL COMMENT '客户名称快照',
company_id BIGINT UNSIGNED NULL COMMENT '运营公司逻辑ID',
company_name VARCHAR(128) NULL COMMENT '运营公司名称快照',
warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '实际作业仓库ID',
warehouse_name VARCHAR(128) NOT NULL COMMENT '实际作业仓库名称快照',
delivery_warehouse_id BIGINT UNSIGNED NULL COMMENT '交货仓逻辑ID',
delivery_warehouse_name VARCHAR(128) NULL COMMENT '交货仓名称快照',
business_type VARCHAR(32) NOT NULL COMMENT '业务类型',
channel_code VARCHAR(64) NULL COMMENT '渠道编码',
channel_name VARCHAR(128) NULL COMMENT '渠道名称快照',
tax_mode VARCHAR(24) NULL COMMENT '买单或退税模式',
container_mode VARCHAR(24) NULL COMMENT '整柜或散货模式',
customs_inspection TINYINT(1) NULL COMMENT '是否商检',
destination_type VARCHAR(24) NULL COMMENT '目的仓类型',
destination_warehouse_id BIGINT UNSIGNED NULL COMMENT '目的仓逻辑ID',
destination_code VARCHAR(32) NULL COMMENT '目的仓编码,例如FBA仓编码',
destination_name VARCHAR(128) NULL COMMENT '目的仓名称快照',
hold_flag TINYINT(1) NOT NULL DEFAULT 0 COMMENT '当前是否扣货',
current_hold_reason VARCHAR(500) NULL COMMENT '当前扣货理由快照',
total_package_count INT UNSIGNED NULL COMMENT '确认后的总箱数',
actual_gross_weight_kg DECIMAL(18,3) NULL COMMENT '确认后的实际总毛重kg',
chargeable_weight_kg DECIMAL(18,3) NULL COMMENT '当前费用快照的计费重量kg',
actual_volume_cbm DECIMAL(18,4) NULL COMMENT '确认后的实际总体积m3',
chargeable_volume_cbm DECIMAL(18,4) NULL COMMENT '当前费用快照的计费体积m3',
pallet_count INT UNSIGNED NULL COMMENT '已分配总托盘数快照',
volume_weight_kg DECIMAL(18,3) NULL COMMENT '总材积重kg',
actual_net_weight_kg DECIMAL(18,3) NULL COMMENT '总净重kg',
measure_completed_at DATETIME(3) NULL COMMENT '测量完成时间',
inbounded_at DATETIME(3) NULL COMMENT '入库完成时间',
express_company_code VARCHAR(64) NULL COMMENT '快递公司编码快照',
express_company_name VARCHAR(128) NULL COMMENT '快递公司名称快照',
express_tracking_no VARCHAR(128) NULL COMMENT '快递单号快照',
public_remark VARCHAR(1000) NULL COMMENT '客户可见备注',
created_by BIGINT UNSIGNED NOT NULL COMMENT '创建人ID',
created_by_name VARCHAR(64) NULL COMMENT '创建人名称快照',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_by BIGINT UNSIGNED NOT NULL COMMENT '最后修改人ID',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id),
UNIQUE KEY uk_inbound_tenant_code (tenant_id, inbound_code),
UNIQUE KEY uk_inbound_tenant_order (tenant_id, order_id),
KEY idx_inbound_process_time (tenant_id, process_status, created_at, id),
KEY idx_inbound_measure_time (tenant_id, measure_review_status, created_at, id),
KEY idx_inbound_fee_time (tenant_id, fee_review_status, created_at, id),
KEY idx_inbound_warehouse_status (tenant_id, warehouse_id, process_status, created_at),
KEY idx_inbound_customer_time (tenant_id, customer_id, created_at),
KEY idx_inbound_company_time (tenant_id, company_id, created_at),
KEY idx_inbound_arrival (tenant_id, expected_arrival_at),
KEY idx_inbound_destination (tenant_id, destination_code, created_at),
KEY idx_inbound_channel (tenant_id, channel_code, created_at),
CONSTRAINT fk_inbound_task FOREIGN KEY (task_id) REFERENCES fl_fulfillment_task (id)
) ENGINE=InnoDB COMMENT='二期头程入库单聚合根和列表查询快照';
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 '第三方事件号',
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 '测量批次状态',
raw_payload_json JSON 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',
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 '更新时间',
PRIMARY KEY (id),
UNIQUE KEY uk_measure_external_event (tenant_id, source_code, external_event_no),
KEY idx_measure_inbound_time (tenant_id, inbound_id, created_at),
CONSTRAINT fk_measure_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id)
) ENGINE=InnoDB COMMENT='入库长宽高测量批次和第三方原始数据';
CREATE TABLE IF NOT EXISTS fl_inbound_measurement_item (
id BIGINT UNSIGNED NOT NULL COMMENT '测量明细ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID',
measurement_id BIGINT UNSIGNED NOT NULL COMMENT '测量批次ID',
item_no VARCHAR(32) 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',
volume_raw_cbm DECIMAL(18,6) NOT NULL COMMENT '原始体积m3',
volume_cbm DECIMAL(18,4) NOT NULL COMMENT '业务体积m3',
calculation_rule_version VARCHAR(32) NOT NULL COMMENT '体积计算和舍入规则版本',
source_image_file_id BIGINT UNSIGNED NULL COMMENT '测量设备图片文件ID',
remark VARCHAR(500) NULL COMMENT '测量明细备注',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
PRIMARY KEY (id),
UNIQUE KEY uk_measure_item_no (tenant_id, measurement_id, item_no),
CONSTRAINT fk_measure_item_batch FOREIGN KEY (measurement_id) REFERENCES fl_inbound_measurement (id)
) ENGINE=InnoDB COMMENT='入库长宽高和箱数测量明细';
CREATE TABLE IF NOT EXISTS fl_inbound_weight_result (
id BIGINT UNSIGNED NOT NULL COMMENT 'OCR重量结果ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID',
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID',
file_id BIGINT UNSIGNED NOT NULL COMMENT '称重图片文件ID',
ocr_provider VARCHAR(64) NULL COMMENT 'OCR服务商编码',
ocr_raw_text VARCHAR(1000) NULL COMMENT 'OCR原始文本',
ocr_weight_value DECIMAL(18,3) NULL COMMENT 'OCR识别原始重量值',
ocr_weight_unit VARCHAR(8) NULL COMMENT 'OCR识别原始单位',
confidence DECIMAL(6,5) NULL COMMENT '识别置信度',
adjusted_weight_value DECIMAL(18,3) NULL COMMENT '人工修正后的原单位重量',
adjusted_weight_unit VARCHAR(8) NULL COMMENT '人工修正后的单位',
final_weight_kg DECIMAL(18,3) NULL COMMENT '确认并统一换算后的重量kg',
recognition_status VARCHAR(24) NOT NULL DEFAULT 'PENDING' COMMENT '识别和确认状态',
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_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(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id),
UNIQUE KEY uk_weight_inbound_file (tenant_id, inbound_id, file_id),
KEY idx_weight_inbound_status (tenant_id, inbound_id, recognition_status),
CONSTRAINT fk_weight_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id)
) ENGINE=InnoDB COMMENT='称重图片OCR原值、人工修正值和确认重量';
CREATE TABLE IF NOT EXISTS fl_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),
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 '规则状态',
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 '逻辑删除标记',
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',
rate_code VARCHAR(50) NOT NULL COMMENT '租户内价格编码',
rate_version INT UNSIGNED NOT NULL COMMENT '价格版本号',
origin_warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '起运仓逻辑ID',
origin_warehouse_name VARCHAR(128) NOT NULL COMMENT '起运仓名称快照',
channel_code VARCHAR(64) NULL COMMENT '渠道编码',
carrier_id BIGINT UNSIGNED NULL COMMENT '船司逻辑ID',
carrier_name VARCHAR(128) NOT NULL COMMENT '船司名称快照',
destination_code VARCHAR(32) NOT NULL COMMENT 'FBA目的仓编码',
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 '固定附加费',
effective_from DATETIME(3) NOT NULL COMMENT '生效开始时间',
effective_to DATETIME(3) NULL COMMENT '生效结束时间',
status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE' 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 '逻辑删除标记',
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, channel_code, destination_code, charge_unit, status, effective_from)
) ENGINE=InnoDB COMMENT='船司FBA目的仓价格版本';
CREATE TABLE IF NOT EXISTS fl_inbound_fee (
id BIGINT UNSIGNED NOT NULL COMMENT '入库费用ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID',
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID',
fee_no VARCHAR(40) NOT NULL COMMENT '租户内唯一费用单号',
fee_version INT UNSIGNED NOT NULL DEFAULT 1 COMMENT '费用快照版本',
charge_rule_id BIGINT UNSIGNED NOT NULL COMMENT '客户计费规则ID',
charge_rule_version INT UNSIGNED NOT NULL COMMENT '客户规则版本快照',
carrier_rate_id BIGINT UNSIGNED NOT NULL COMMENT '船司FBA价格ID',
carrier_rate_version INT UNSIGNED NOT NULL COMMENT '船司价格版本快照',
charge_mode VARCHAR(16) NOT NULL COMMENT '计费口径:KG或CBM',
conversion_factor DECIMAL(18,6) NOT NULL COMMENT '使用的换算系数快照',
actual_weight_kg DECIMAL(18,3) NOT NULL COMMENT '试算时实际重量kg',
actual_volume_cbm DECIMAL(18,4) NOT NULL COMMENT '试算时实际体积m3',
converted_quantity DECIMAL(18,4) NOT NULL COMMENT '换算后的重量或体积',
chargeable_quantity DECIMAL(18,4) NOT NULL COMMENT '最终计费量',
charge_unit VARCHAR(16) NOT NULL COMMENT '计费单位',
currency CHAR(3) NOT NULL COMMENT '币种',
subtotal_amount DECIMAL(18,2) NOT NULL COMMENT '基础费用金额',
additional_amount DECIMAL(18,2) NOT NULL DEFAULT 0 COMMENT '附加费金额',
total_amount DECIMAL(18,2) NOT NULL COMMENT '总金额',
fee_status VARCHAR(24) NOT NULL DEFAULT 'CALCULATED' COMMENT '费用状态',
review_status VARCHAR(24) NOT NULL DEFAULT 'NOT_SUBMITTED' COMMENT '费用审核状态',
exception_release TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否基于测量异常放行生成',
calculated_by BIGINT UNSIGNED NOT NULL COMMENT '试算人ID',
calculated_at DATETIME(3) NOT NULL COMMENT '试算时间',
confirmed_by BIGINT UNSIGNED NULL COMMENT '确认人ID',
confirmed_at DATETIME(3) NULL COMMENT '确认时间',
sent_by BIGINT UNSIGNED NULL COMMENT '发送人ID',
sent_at DATETIME(3) NULL COMMENT '发送客户时间',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
PRIMARY KEY (id),
UNIQUE KEY uk_inbound_fee_no (tenant_id, fee_no),
UNIQUE KEY uk_inbound_fee_version (tenant_id, inbound_id, fee_version),
KEY idx_inbound_fee_status (tenant_id, inbound_id, fee_status, review_status),
CONSTRAINT fk_fee_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id),
CONSTRAINT fk_fee_charge_rule FOREIGN KEY (charge_rule_id) REFERENCES fl_customer_charge_rule (id),
CONSTRAINT fk_fee_carrier_rate FOREIGN KEY (carrier_rate_id) REFERENCES fl_carrier_fba_rate (id)
) ENGINE=InnoDB COMMENT='入库头程费用计算和确认快照';
CREATE TABLE IF NOT EXISTS fl_inbound_fee_item (
id BIGINT UNSIGNED NOT NULL COMMENT '费用明细ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID',
fee_id BIGINT UNSIGNED NOT NULL COMMENT '费用头ID',
item_no INT UNSIGNED NOT NULL COMMENT '费用单内序号',
fee_code VARCHAR(50) NOT NULL COMMENT '费用项目编码',
fee_name VARCHAR(128) NOT NULL COMMENT '费用项目名称快照',
quantity DECIMAL(18,4) NOT NULL COMMENT '计费数量',
unit VARCHAR(16) NOT NULL COMMENT '计费单位',
unit_price DECIMAL(18,6) NOT NULL COMMENT '单价',
amount DECIMAL(18,2) NOT NULL COMMENT '金额',
currency CHAR(3) NOT NULL COMMENT '币种',
automatic_flag TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否自动计算',
remark VARCHAR(500) NULL COMMENT '费用备注',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
PRIMARY KEY (id),
UNIQUE KEY uk_fee_item_no (tenant_id, fee_id, item_no),
CONSTRAINT fk_fee_item_header FOREIGN KEY (fee_id) REFERENCES fl_inbound_fee (id)
) ENGINE=InnoDB COMMENT='入库头程费用项目明细';
CREATE TABLE IF NOT EXISTS fl_inbound_pallet_allocation (
id BIGINT UNSIGNED NOT NULL COMMENT '头程托盘分配引用ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID',
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID',
allocation_no INT UNSIGNED NOT NULL COMMENT '入库单内分配序号',
warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '仓库服务仓库ID',
pallet_id BIGINT UNSIGNED NOT NULL COMMENT '仓库服务托盘ID',
pallet_code VARCHAR(50) NOT NULL COMMENT '托盘编码快照',
pallet_capacity_cbm DECIMAL(18,4) NOT NULL COMMENT '托盘可用容积快照',
allocated_volume_cbm DECIMAL(18,4) NOT NULL COMMENT '本托盘分配体积',
allocated_package_count INT UNSIGNED NULL COMMENT '本托盘分配箱数',
location_id BIGINT UNSIGNED NOT NULL COMMENT '仓库服务库位ID',
location_code VARCHAR(50) NOT NULL COMMENT '库位编码快照',
reservation_no VARCHAR(64) NOT NULL COMMENT '仓库库位预占单号',
allocation_status VARCHAR(24) NOT NULL DEFAULT 'RESERVED' COMMENT '分配状态',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
PRIMARY KEY (id),
UNIQUE KEY uk_pallet_allocation_no (tenant_id, inbound_id, allocation_no),
UNIQUE KEY uk_pallet_allocation_pallet (tenant_id, inbound_id, pallet_id),
KEY idx_allocation_location (tenant_id, warehouse_id, location_id, allocation_status),
CONSTRAINT fk_allocation_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id)
) ENGINE=InnoDB COMMENT='头程侧托盘、库位和仓库预占结果引用';
CREATE TABLE IF NOT EXISTS fl_inbound_file (
id BIGINT UNSIGNED NOT NULL COMMENT '入库单文件关系ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID',
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID',
file_id BIGINT UNSIGNED NOT NULL COMMENT '文件服务文件ID',
file_type VARCHAR(24) NOT NULL COMMENT '文件类型:RELATED、MARK、PC_ORDER、MEASURE或WEIGHT',
file_name_snapshot VARCHAR(255) NOT NULL COMMENT '文件名快照',
visibility VARCHAR(16) NOT NULL DEFAULT 'INTERNAL' COMMENT '可见范围',
file_version INT UNSIGNED NOT NULL DEFAULT 1 COMMENT '业务文件版本',
uploaded_by BIGINT UNSIGNED NOT NULL COMMENT '上传人ID',
uploaded_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '上传时间',
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
PRIMARY KEY (id),
UNIQUE KEY uk_inbound_file_relation (tenant_id, inbound_id, file_id, file_type),
KEY idx_inbound_file_type (tenant_id, inbound_id, file_type, deleted),
CONSTRAINT fk_file_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id)
) ENGINE=InnoDB COMMENT='入库单与文件中心对象的分类关系';
CREATE TABLE IF NOT EXISTS fl_inbound_hold_record (
id BIGINT UNSIGNED NOT NULL COMMENT '扣货记录ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID',
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID',
hold_action VARCHAR(16) NOT NULL COMMENT '动作:HOLD或RELEASE',
reason VARCHAR(1000) NOT NULL COMMENT '扣货或解除原因',
evidence_file_id BIGINT UNSIGNED NULL COMMENT '证据文件ID',
operator_id BIGINT UNSIGNED NOT NULL COMMENT '操作人ID',
operator_name VARCHAR(64) NULL COMMENT '操作人名称快照',
operated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '操作时间',
PRIMARY KEY (id),
KEY idx_hold_inbound_time (tenant_id, inbound_id, operated_at),
CONSTRAINT fk_hold_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id)
) ENGINE=InnoDB COMMENT='入库单扣货和解除扣货不可变历史';
CREATE TABLE IF NOT EXISTS fl_inbound_internal_note (
id BIGINT UNSIGNED NOT NULL COMMENT '客服内部备注ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID',
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID',
content VARCHAR(2000) NOT NULL COMMENT '仅内部可见备注',
created_by BIGINT UNSIGNED NOT NULL COMMENT '创建人ID',
created_by_name VARCHAR(64) NULL COMMENT '创建人名称快照',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
PRIMARY KEY (id),
KEY idx_internal_note_time (tenant_id, inbound_id, created_at),
CONSTRAINT fk_internal_note_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id)
) ENGINE=InnoDB COMMENT='入库单客服内部备注历史';
CREATE TABLE IF NOT EXISTS fl_inbound_status_history (
id BIGINT UNSIGNED NOT NULL COMMENT '入库状态历史ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID',
inbound_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID',
from_status VARCHAR(24) NULL COMMENT '变更前状态',
to_status VARCHAR(24) NOT NULL COMMENT '变更后状态',
action VARCHAR(32) NOT NULL COMMENT '触发动作',
reason VARCHAR(1000) NULL COMMENT '变更原因',
operator_id BIGINT UNSIGNED NOT NULL COMMENT '操作人ID',
operator_name VARCHAR(64) NULL COMMENT '操作人名称快照',
occurred_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '发生时间',
PRIMARY KEY (id),
KEY idx_inbound_history_time (tenant_id, inbound_id, occurred_at),
CONSTRAINT fk_history_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id)
) ENGINE=InnoDB COMMENT='入库单流程状态不可变历史';
CREATE TABLE IF NOT EXISTS fl_inbound_outbox (
id BIGINT UNSIGNED NOT NULL COMMENT '领域事件全局ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '事件所属租户ID',
aggregate_type VARCHAR(32) NOT NULL DEFAULT 'INBOUND' COMMENT '聚合类型',
aggregate_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID',
event_type VARCHAR(64) NOT NULL COMMENT '事件类型',
payload_json JSON NOT NULL COMMENT '事件负载快照',
publish_status VARCHAR(16) NOT NULL DEFAULT 'PENDING' COMMENT '发布状态',
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 '处理状态',
result_json JSON 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 '更新时间',
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='第三方测量、审核、费用发送和入库写操作幂等记录';
-- 二期托盘、库位、预占与占用表结构。
-- 本脚本只新增表、字段、索引和约束,不包含业务数据及流程实现。
-- 可在 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 '数据所属租户ID',
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',
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_pallet_tenant_warehouse_code (tenant_id, warehouse_id, pallet_code),
KEY idx_pallet_available (tenant_id, warehouse_id, status, deleted),
KEY idx_pallet_type (tenant_id, warehouse_id, pallet_type, status, deleted),
CONSTRAINT fk_pallet_warehouse FOREIGN KEY (warehouse_id) REFERENCES wh_warehouse (id),
CONSTRAINT ck_pallet_capacity CHECK (capacity_cbm > 0)
) ENGINE=InnoDB COMMENT='二期托盘基础资料和物理托盘实例';
CREATE TABLE IF NOT EXISTS wh_location (
id BIGINT UNSIGNED NOT NULL COMMENT '库位ID(雪花ID)',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID',
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 '最大承重展示值,二期不作容量拦截',
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 '数据所属租户ID',
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 '库位编码快照',
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' 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 '数据所属租户ID',
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 '库位编码快照',
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_occupancy TINYINT GENERATED ALWAYS AS (CASE WHEN occupancy_status = 'OCCUPIED' 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_occupancy),
UNIQUE KEY uk_pallet_active_occupancy (tenant_id, pallet_id, active_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='已确认的一托一库位占用事实记录';
-- 头程入库单属于其他数据库,不建立跨库外键。
-- 执行后结构核对:只输出检查结果,不写入业务数据。
SELECT '头程二期表数量' AS check_item,
COUNT(*) AS actual_value,
19 AS expected_value,
IF(COUNT(*) = 19, 'PASS', 'FAIL') AS check_result
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'baiyue_firstleg'
AND TABLE_NAME IN (
'fl_inbound_order',
'fl_inbound_pickup_snapshot',
'fl_inbound_destination_snapshot',
'fl_inbound_transport_snapshot',
'fl_inbound_measurement',
'fl_inbound_measurement_item',
'fl_inbound_weight_result',
'fl_measure_review_record',
'fl_customer_charge_rule',
'fl_carrier_fba_rate',
'fl_inbound_fee',
'fl_inbound_fee_item',
'fl_inbound_pallet_allocation',
'fl_inbound_file',
'fl_inbound_hold_record',
'fl_inbound_internal_note',
'fl_inbound_status_history',
'fl_inbound_outbox',
'fl_inbound_idempotency'
)
UNION ALL
SELECT '仓库二期表数量' AS check_item,
COUNT(*) AS actual_value,
4 AS expected_value,
IF(COUNT(*) = 4, 'PASS', 'FAIL') AS check_result
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'baiyue_warehouse'
AND TABLE_NAME IN (
'wh_pallet',
'wh_location',
'wh_location_reservation',
'wh_location_occupancy'
);
SELECT '关键唯一索引数量' AS check_item,
COUNT(DISTINCT CONCAT(TABLE_SCHEMA, '.', TABLE_NAME, '.', INDEX_NAME)) AS actual_value,
5 AS expected_value,
IF(COUNT(DISTINCT CONCAT(TABLE_SCHEMA, '.', TABLE_NAME, '.', INDEX_NAME)) = 5, 'PASS', 'FAIL') AS check_result
FROM information_schema.STATISTICS
WHERE (TABLE_SCHEMA = 'baiyue_firstleg'
AND TABLE_NAME = 'fl_inbound_order'
AND INDEX_NAME IN ('uk_inbound_tenant_code', 'uk_inbound_tenant_order'))
OR (TABLE_SCHEMA = 'baiyue_warehouse'
AND TABLE_NAME = 'wh_location_reservation'
AND INDEX_NAME = 'uk_location_active_reservation')
OR (TABLE_SCHEMA = 'baiyue_warehouse'
AND TABLE_NAME = 'wh_location_occupancy'
AND INDEX_NAME IN ('uk_location_active_occupancy', 'uk_pallet_active_occupancy'));
SELECT '关键字段数量' AS check_item,
COUNT(*) AS actual_value,
8 AS expected_value,
IF(COUNT(*) = 8, 'PASS', 'FAIL') AS check_result
FROM information_schema.COLUMNS
WHERE (TABLE_SCHEMA = 'baiyue_firstleg'
AND TABLE_NAME = 'fl_inbound_order'
AND COLUMN_NAME IN (
'inbound_code',
'measure_review_status',
'fee_review_status',
'expected_arrival_at',
'chargeable_weight_kg',
'chargeable_volume_cbm'
))
OR (TABLE_SCHEMA = 'baiyue_warehouse'
AND TABLE_NAME = 'wh_location_reservation'
AND COLUMN_NAME = 'active_reservation')
OR (TABLE_SCHEMA = 'baiyue_warehouse'
AND TABLE_NAME = 'wh_location_occupancy'
AND COLUMN_NAME = 'active_occupancy');
SELECT '二期入库表结构执行完成,请确认以上检查全部为PASS' AS execution_message;
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff