-- OnboardX 公司、部门、岗位、合同模板及入职模板层级升级脚本 -- 适用数据库:MySQL 8.0+ -- 说明: -- 1. 可重复执行,不会覆盖已经人工维护的归属数据。 -- 2. 仅对能够从现有岗位/入职模板关系中唯一确定的数据执行安全回填。 -- 3. 脚本末尾会输出仍需人工处理的数据,请先处理完再启用相关模板。 SET NAMES utf8mb4; DROP PROCEDURE IF EXISTS `ob_scope_add_column_if_missing`; DELIMITER $$ CREATE PROCEDURE `ob_scope_add_column_if_missing`( IN p_table_name VARCHAR(128), IN p_column_name VARCHAR(128), IN p_column_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 @ddl = CONCAT( 'ALTER TABLE `', p_table_name, '` ADD COLUMN `', p_column_name, '` ', p_column_definition ); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt; END IF; END$$ DELIMITER ; DROP PROCEDURE IF EXISTS `ob_scope_add_index_if_missing`; DELIMITER $$ CREATE PROCEDURE `ob_scope_add_index_if_missing`( IN p_table_name VARCHAR(128), IN p_index_name VARCHAR(128), IN p_index_definition VARCHAR(1000) ) BEGIN IF NOT EXISTS ( SELECT 1 FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = p_table_name AND INDEX_NAME = p_index_name ) THEN SET @ddl = CONCAT( 'ALTER TABLE `', p_table_name, '` ADD ', p_index_definition ); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt; END IF; END$$ DELIMITER ; DROP PROCEDURE IF EXISTS `ob_scope_add_unique_index_if_clean`; DELIMITER $$ CREATE PROCEDURE `ob_scope_add_unique_index_if_clean`( IN p_table_name VARCHAR(128), IN p_column_name VARCHAR(128), IN p_index_name VARCHAR(128) ) 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 @duplicate_count = 0; SET @check_sql = CONCAT( 'SELECT COUNT(*) INTO @duplicate_count FROM (', 'SELECT `', p_column_name, '` FROM `', p_table_name, '` ', 'WHERE `', p_column_name, '` IS NOT NULL ', 'AND LENGTH(TRIM(`', p_column_name, '`)) > 0 ', 'GROUP BY `', p_column_name, '` HAVING COUNT(*) > 1', ') duplicated_values' ); PREPARE stmt FROM @check_sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; IF @duplicate_count = 0 THEN SET @ddl = CONCAT( 'ALTER TABLE `', p_table_name, '` ADD UNIQUE INDEX `', p_index_name, '` (`', p_column_name, '`)' ); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt; END IF; END IF; END$$ DELIMITER ; -- 入职模板增加所属部门。 CALL `ob_scope_add_column_if_missing`( 'ob_onboarding_template', 'DepartmentId', 'BIGINT NULL COMMENT ''所属部门Id'' AFTER `CompanyId`' ); -- 合同模板增加所属公司和部门。暂时允许 NULL,以兼容待人工归类的历史模板。 CALL `ob_scope_add_column_if_missing`( 'ob_contract_template', 'CompanyId', 'BIGINT NULL COMMENT ''所属公司Id'' AFTER `Id`' ); CALL `ob_scope_add_column_if_missing`( 'ob_contract_template', 'DepartmentId', 'BIGINT NULL COMMENT ''所属部门Id'' AFTER `CompanyId`' ); -- 兼容尚未执行上一版部门迁移的数据库,重复调用不会新增重复字段。 CALL `ob_scope_add_column_if_missing`( 'ob_position', 'DepartmentId', 'BIGINT NULL COMMENT ''所属部门Id'' AFTER `CompanyId`' ); CALL `ob_scope_add_column_if_missing`( 'ob_onboarding_task', 'DepartmentId', 'BIGINT NULL COMMENT ''所属部门Id'' AFTER `CompanyId`' ); CALL `ob_scope_add_column_if_missing`( 'ob_employee', 'DepartmentId', 'BIGINT NULL COMMENT ''所属部门Id'' AFTER `CompanyId`' ); -- 合同模板与岗位为多对多关系,一份通用合同可同时适用于多个岗位。 CREATE TABLE IF NOT EXISTS `ob_contract_template_position` ( `Id` BIGINT NOT NULL COMMENT '主键', `ContractTemplateId` BIGINT NOT NULL COMMENT '合同模板Id', `PositionId` BIGINT NOT NULL COMMENT '适用岗位Id', `ExtJson` LONGTEXT NULL COMMENT '扩展信息', `Status` VARCHAR(20) NULL DEFAULT 'enable' COMMENT '启用状态', `CreateTime` DATETIME(3) NULL COMMENT '创建时间', `UpdateTime` DATETIME(3) NULL COMMENT '更新时间', `CreateUserId` BIGINT NULL COMMENT '创建者Id', `UpdateUserId` BIGINT NULL COMMENT '修改者Id', `CreateUser` VARCHAR(200) NULL COMMENT '创建人', `UpdateUser` VARCHAR(200) NULL COMMENT '更新人', `IsDelete` TINYINT(1) NULL DEFAULT 0 COMMENT '软删除', PRIMARY KEY (`Id`), UNIQUE KEY `uk_ob_contract_template_position` (`ContractTemplateId`, `PositionId`), KEY `idx_ob_contract_template_position_position` (`PositionId`, `ContractTemplateId`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='合同模板适用岗位'; CALL `ob_scope_add_index_if_missing`( 'ob_position', 'idx_ob_position_department_status', 'INDEX `idx_ob_position_department_status` (`DepartmentId`, `Status`, `IsDelete`)' ); CALL `ob_scope_add_index_if_missing`( 'ob_onboarding_template', 'idx_ob_onboarding_template_scope', 'INDEX `idx_ob_onboarding_template_scope` (`CompanyId`, `DepartmentId`, `Status`, `IsDelete`)' ); CALL `ob_scope_add_index_if_missing`( 'ob_contract_template', 'idx_ob_contract_template_scope', 'INDEX `idx_ob_contract_template_scope` (`CompanyId`, `DepartmentId`, `Status`, `IsDelete`)' ); CALL `ob_scope_add_index_if_missing`( 'ob_onboarding_task', 'idx_ob_onboarding_task_department', 'INDEX `idx_ob_onboarding_task_department` (`DepartmentId`, `TaskStatus`, `IsDelete`)' ); CALL `ob_scope_add_index_if_missing`( 'ob_employee', 'idx_ob_employee_department', 'INDEX `idx_ob_employee_department` (`DepartmentId`, `IsDelete`)' ); -- 身份证号和手机号是正式员工防重复的数据库最终防线。 -- 若历史数据已经重复,本次会安全跳过建索引,并由脚本末尾查询列出冲突数据。 CALL `ob_scope_add_unique_index_if_clean`( 'ob_employee', 'IdCardNumber', 'uk_ob_employee_id_card' ); CALL `ob_scope_add_unique_index_if_clean`( 'ob_employee', 'Phone', 'uk_ob_employee_phone' ); -- 安全回填入职模板部门:只有全部适用岗位均属于同一个非空部门时才回填。 UPDATE `ob_onboarding_template` template INNER JOIN ( SELECT relation.`OnboardingTemplateId`, MIN(position.`DepartmentId`) AS `DepartmentId` FROM `ob_onboarding_template_position` relation INNER JOIN `ob_position` position ON position.`Id` = relation.`PositionId` WHERE COALESCE(relation.`IsDelete`, 0) = 0 AND COALESCE(position.`IsDelete`, 0) = 0 GROUP BY relation.`OnboardingTemplateId` HAVING COUNT(*) > 0 AND COUNT(DISTINCT position.`DepartmentId`) = 1 AND SUM(position.`DepartmentId` IS NULL) = 0 ) source ON source.`OnboardingTemplateId` = template.`Id` SET template.`DepartmentId` = source.`DepartmentId` WHERE template.`DepartmentId` IS NULL; -- 安全回填合同模板公司、部门:只有历史引用的入职模板归属完全一致时才回填。 UPDATE `ob_contract_template` contract_template INNER JOIN ( SELECT relation.`ContractTemplateId`, MIN(template.`CompanyId`) AS `CompanyId`, MIN(template.`DepartmentId`) AS `DepartmentId` FROM `ob_onboarding_template_contract` relation INNER JOIN `ob_onboarding_template` template ON template.`Id` = relation.`OnboardingTemplateId` WHERE COALESCE(relation.`IsDelete`, 0) = 0 AND COALESCE(template.`IsDelete`, 0) = 0 GROUP BY relation.`ContractTemplateId` HAVING COUNT(DISTINCT template.`CompanyId`) = 1 AND COUNT(DISTINCT template.`DepartmentId`) = 1 AND SUM(template.`DepartmentId` IS NULL) = 0 ) source ON source.`ContractTemplateId` = contract_template.`Id` SET contract_template.`CompanyId` = COALESCE(contract_template.`CompanyId`, source.`CompanyId`), contract_template.`DepartmentId` = COALESCE(contract_template.`DepartmentId`, source.`DepartmentId`) WHERE contract_template.`CompanyId` IS NULL OR contract_template.`DepartmentId` IS NULL; -- 根据历史入职模板关系,回填合同模板已经实际使用过的岗位范围。 INSERT IGNORE INTO `ob_contract_template_position` ( `Id`, `ContractTemplateId`, `PositionId`, `Status`, `CreateTime`, `IsDelete` ) SELECT DISTINCT CAST(CONV(SUBSTRING(MD5(CONCAT( 'obctp:', contract_relation.`ContractTemplateId`, ':', position_relation.`PositionId` )), 1, 15), 16, 10) AS UNSIGNED) AS `Id`, contract_relation.`ContractTemplateId`, position_relation.`PositionId`, 'enable', NOW(3), 0 FROM `ob_onboarding_template_contract` contract_relation INNER JOIN `ob_onboarding_template_position` position_relation ON position_relation.`OnboardingTemplateId` = contract_relation.`OnboardingTemplateId` INNER JOIN `ob_contract_template` contract_template ON contract_template.`Id` = contract_relation.`ContractTemplateId` INNER JOIN `ob_position` position ON position.`Id` = position_relation.`PositionId` WHERE COALESCE(contract_relation.`IsDelete`, 0) = 0 AND COALESCE(position_relation.`IsDelete`, 0) = 0 AND contract_template.`CompanyId` = position.`CompanyId` AND contract_template.`DepartmentId` = position.`DepartmentId`; DROP PROCEDURE IF EXISTS `ob_scope_add_unique_index_if_clean`; DROP PROCEDURE IF EXISTS `ob_scope_add_index_if_missing`; DROP PROCEDURE IF EXISTS `ob_scope_add_column_if_missing`; -- 结果 1:仍未明确部门归属的岗位。必须在岗位管理中补齐。 SELECT position.`Id`, position.`PositionCode`, position.`PositionName`, position.`CompanyId` FROM `ob_position` position WHERE position.`DepartmentId` IS NULL AND COALESCE(position.`IsDelete`, 0) = 0; -- 结果 2:仍未明确部门归属的入职模板。必须编辑并重新保存。 SELECT template.`Id`, template.`TemplateCode`, template.`TemplateName`, template.`CompanyId` FROM `ob_onboarding_template` template WHERE template.`DepartmentId` IS NULL AND COALESCE(template.`IsDelete`, 0) = 0; -- 结果 3:仍未明确公司/部门或没有适用岗位的合同模板。必须编辑并重新保存。 SELECT contract_template.`Id`, contract_template.`TemplateCode`, contract_template.`TemplateName`, contract_template.`CompanyId`, contract_template.`DepartmentId`, COUNT(position_relation.`Id`) AS `PositionCount` FROM `ob_contract_template` contract_template LEFT JOIN `ob_contract_template_position` position_relation ON position_relation.`ContractTemplateId` = contract_template.`Id` AND COALESCE(position_relation.`IsDelete`, 0) = 0 WHERE COALESCE(contract_template.`IsDelete`, 0) = 0 GROUP BY contract_template.`Id`, contract_template.`TemplateCode`, contract_template.`TemplateName`, contract_template.`CompanyId`, contract_template.`DepartmentId` HAVING contract_template.`CompanyId` IS NULL OR contract_template.`DepartmentId` IS NULL OR COUNT(position_relation.`Id`) = 0; -- 结果 4:检查层级不一致的数据,正常情况下应返回 0 行。 SELECT contract_template.`Id` AS `ContractTemplateId`, contract_template.`TemplateName`, position.`Id` AS `PositionId`, position.`PositionName` FROM `ob_contract_template` contract_template INNER JOIN `ob_contract_template_position` relation ON relation.`ContractTemplateId` = contract_template.`Id` INNER JOIN `ob_position` position ON position.`Id` = relation.`PositionId` WHERE contract_template.`CompanyId` <> position.`CompanyId` OR contract_template.`DepartmentId` <> position.`DepartmentId`; -- 结果 5:正式员工身份证号或手机号重复时,唯一索引会被安全跳过;请清理后重新运行脚本。 SELECT 'IdCardNumber' AS `ConflictField`, employee.`IdCardNumber` AS `ConflictValue`, COUNT(*) AS `ConflictCount` FROM `ob_employee` employee WHERE NULLIF(TRIM(employee.`IdCardNumber`), '') IS NOT NULL GROUP BY employee.`IdCardNumber` HAVING COUNT(*) > 1 UNION ALL SELECT 'Phone' AS `ConflictField`, employee.`Phone` AS `ConflictValue`, COUNT(*) AS `ConflictCount` FROM `ob_employee` employee WHERE NULLIF(TRIM(employee.`Phone`), '') IS NOT NULL GROUP BY employee.`Phone` HAVING COUNT(*) > 1;