fix(aihr): version candidate interview review migration

This commit is contained in:
2026-07-15 00:59:08 +08:00
parent ef21d4d61b
commit 1ca5d1eca4
5 changed files with 233 additions and 3 deletions
@@ -0,0 +1,196 @@
-- AIHR 候选人资料、面试复核和入职关联迁移(MySQL 8.x)
-- 仅补齐生产结构,不写入业务数据;可重复执行。
CREATE TABLE IF NOT EXISTS `aihr_candidate_material` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`tenant_id` varchar(20) DEFAULT '000000' COMMENT '租户编号',
`candidate_id` varchar(80) NOT NULL COMMENT '候选人外部ID',
`candidate_name` varchar(100) DEFAULT NULL COMMENT '候选人姓名',
`material_type` varchar(50) DEFAULT NULL COMMENT '资料类型',
`file_name` varchar(255) NOT NULL COMMENT '文件名',
`file_size` bigint DEFAULT 0 COMMENT '文件大小',
`content_type` varchar(100) DEFAULT NULL COMMENT '文件MIME',
`oss_id` bigint NOT NULL COMMENT '对象存储ID',
`status` varchar(20) DEFAULT '待审核' COMMENT '审核状态',
`reviewer` varchar(100) DEFAULT NULL COMMENT '审核人',
`reviewed_time` datetime DEFAULT NULL COMMENT '审核时间',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_aihr_candidate_material_candidate` (`tenant_id`, `candidate_id`, `id`),
KEY `idx_aihr_candidate_material_review` (`tenant_id`, `status`, `id`),
KEY `idx_aihr_candidate_material_oss` (`oss_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='AI HR 候选人补充资料';
SET @aihr_candidate_material_reviewer_exists := (
SELECT COUNT(*)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'aihr_candidate_material'
AND COLUMN_NAME = 'reviewer'
);
SET @aihr_candidate_material_reviewer_ddl := IF(
@aihr_candidate_material_reviewer_exists = 0,
'ALTER TABLE aihr_candidate_material ADD COLUMN `reviewer` varchar(100) DEFAULT NULL COMMENT ''审核人''',
'SELECT 1'
);
PREPARE aihr_candidate_material_reviewer_stmt FROM @aihr_candidate_material_reviewer_ddl;
EXECUTE aihr_candidate_material_reviewer_stmt;
DEALLOCATE PREPARE aihr_candidate_material_reviewer_stmt;
SET @aihr_candidate_material_reviewed_time_exists := (
SELECT COUNT(*)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'aihr_candidate_material'
AND COLUMN_NAME = 'reviewed_time'
);
SET @aihr_candidate_material_reviewed_time_ddl := IF(
@aihr_candidate_material_reviewed_time_exists = 0,
'ALTER TABLE aihr_candidate_material ADD COLUMN `reviewed_time` datetime DEFAULT NULL COMMENT ''审核时间''',
'SELECT 1'
);
PREPARE aihr_candidate_material_reviewed_time_stmt FROM @aihr_candidate_material_reviewed_time_ddl;
EXECUTE aihr_candidate_material_reviewed_time_stmt;
DEALLOCATE PREPARE aihr_candidate_material_reviewed_time_stmt;
CREATE TABLE IF NOT EXISTS `aihr_interview_result` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`tenant_id` varchar(20) DEFAULT '000000' COMMENT '租户编号',
`session_id` varchar(100) NOT NULL COMMENT '面试会话ID',
`candidate_id` varchar(80) DEFAULT NULL COMMENT '候选人ID',
`candidate_name` varchar(100) DEFAULT NULL COMMENT '候选人姓名',
`position_code` varchar(120) DEFAULT NULL COMMENT '岗位',
`total_score` int DEFAULT NULL COMMENT '总分',
`reviewed_score` int DEFAULT NULL COMMENT '人工复核分',
`review_status` varchar(20) DEFAULT '待复核' COMMENT '人工复核状态',
`reviewer` varchar(100) DEFAULT NULL COMMENT '复核人',
`review_note` varchar(1000) DEFAULT NULL COMMENT '人工复核意见',
`reviewed_time` datetime DEFAULT NULL COMMENT '复核时间',
`result` varchar(40) DEFAULT NULL COMMENT '结论',
`suggestion` varchar(500) DEFAULT NULL COMMENT '建议',
`ai_summary` varchar(1000) DEFAULT NULL COMMENT 'AI摘要',
`evidence` varchar(1000) DEFAULT NULL COMMENT '评分依据',
`source` varchar(40) DEFAULT NULL COMMENT '评分来源',
`answers_json` text DEFAULT NULL COMMENT '候选人回答JSON',
`questions_json` text DEFAULT NULL COMMENT '面试问题与回答快照JSON',
`dimensions_json` text DEFAULT NULL COMMENT '维度评分JSON',
`finished_time` datetime DEFAULT NULL COMMENT '完成时间',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_aihr_interview_session` (`tenant_id`, `session_id`),
KEY `idx_aihr_interview_candidate` (`tenant_id`, `candidate_id`, `finished_time`),
KEY `idx_aihr_interview_finished` (`tenant_id`, `finished_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='AI HR 面试结果';
SET @aihr_interview_result_questions_exists := (
SELECT COUNT(*)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'aihr_interview_result'
AND COLUMN_NAME = 'questions_json'
);
SET @aihr_interview_result_questions_ddl := IF(
@aihr_interview_result_questions_exists = 0,
'ALTER TABLE aihr_interview_result ADD COLUMN `questions_json` text DEFAULT NULL COMMENT ''面试问题与回答快照JSON''',
'SELECT 1'
);
PREPARE aihr_interview_result_questions_stmt FROM @aihr_interview_result_questions_ddl;
EXECUTE aihr_interview_result_questions_stmt;
DEALLOCATE PREPARE aihr_interview_result_questions_stmt;
SET @aihr_interview_reviewed_score_exists := (
SELECT COUNT(*)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'aihr_interview_result'
AND COLUMN_NAME = 'reviewed_score'
);
SET @aihr_interview_reviewed_score_ddl := IF(
@aihr_interview_reviewed_score_exists = 0,
'ALTER TABLE aihr_interview_result ADD COLUMN `reviewed_score` int DEFAULT NULL COMMENT ''人工复核分''',
'SELECT 1'
);
PREPARE aihr_interview_reviewed_score_stmt FROM @aihr_interview_reviewed_score_ddl;
EXECUTE aihr_interview_reviewed_score_stmt;
DEALLOCATE PREPARE aihr_interview_reviewed_score_stmt;
SET @aihr_interview_review_status_exists := (
SELECT COUNT(*)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'aihr_interview_result'
AND COLUMN_NAME = 'review_status'
);
SET @aihr_interview_review_status_ddl := IF(
@aihr_interview_review_status_exists = 0,
'ALTER TABLE aihr_interview_result ADD COLUMN `review_status` varchar(20) DEFAULT ''待复核'' COMMENT ''人工复核状态''',
'SELECT 1'
);
PREPARE aihr_interview_review_status_stmt FROM @aihr_interview_review_status_ddl;
EXECUTE aihr_interview_review_status_stmt;
DEALLOCATE PREPARE aihr_interview_review_status_stmt;
SET @aihr_interview_reviewer_exists := (
SELECT COUNT(*)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'aihr_interview_result'
AND COLUMN_NAME = 'reviewer'
);
SET @aihr_interview_reviewer_ddl := IF(
@aihr_interview_reviewer_exists = 0,
'ALTER TABLE aihr_interview_result ADD COLUMN `reviewer` varchar(100) DEFAULT NULL COMMENT ''复核人''',
'SELECT 1'
);
PREPARE aihr_interview_reviewer_stmt FROM @aihr_interview_reviewer_ddl;
EXECUTE aihr_interview_reviewer_stmt;
DEALLOCATE PREPARE aihr_interview_reviewer_stmt;
SET @aihr_interview_review_note_exists := (
SELECT COUNT(*)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'aihr_interview_result'
AND COLUMN_NAME = 'review_note'
);
SET @aihr_interview_review_note_ddl := IF(
@aihr_interview_review_note_exists = 0,
'ALTER TABLE aihr_interview_result ADD COLUMN `review_note` varchar(1000) DEFAULT NULL COMMENT ''人工复核意见''',
'SELECT 1'
);
PREPARE aihr_interview_review_note_stmt FROM @aihr_interview_review_note_ddl;
EXECUTE aihr_interview_review_note_stmt;
DEALLOCATE PREPARE aihr_interview_review_note_stmt;
SET @aihr_interview_reviewed_time_exists := (
SELECT COUNT(*)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'aihr_interview_result'
AND COLUMN_NAME = 'reviewed_time'
);
SET @aihr_interview_reviewed_time_ddl := IF(
@aihr_interview_reviewed_time_exists = 0,
'ALTER TABLE aihr_interview_result ADD COLUMN `reviewed_time` datetime DEFAULT NULL COMMENT ''复核时间''',
'SELECT 1'
);
PREPARE aihr_interview_reviewed_time_stmt FROM @aihr_interview_reviewed_time_ddl;
EXECUTE aihr_interview_reviewed_time_stmt;
DEALLOCATE PREPARE aihr_interview_reviewed_time_stmt;
CREATE TABLE IF NOT EXISTS `aihr_candidate_employee_link` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`tenant_id` varchar(20) DEFAULT '000000' COMMENT '租户编号',
`candidate_id` varchar(80) NOT NULL COMMENT '本地候选人ID',
`ext_party_id` varchar(100) NOT NULL COMMENT '外部员工主体ID',
`status` varchar(20) DEFAULT '已关联' COMMENT '关联状态',
`linked_by` varchar(100) DEFAULT NULL COMMENT '关联操作人',
`linked_time` datetime DEFAULT NULL COMMENT '关联时间',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_aihr_candidate_employee_link_candidate` (`tenant_id`, `candidate_id`),
UNIQUE KEY `uk_aihr_candidate_employee_link_party` (`tenant_id`, `ext_party_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='AI HR 候选人与员工主体关联';
+2
View File
@@ -1,5 +1,7 @@
# BRD 功能审查与未完成项记录
- 2026-07-15 BRD 候选人/面试生产迁移缺口修复:生产只读复核发现 `aihr_candidate_material` 缺少 `reviewer/reviewed_time`,`aihr_interview_result` 缺少人工复核字段和 `questions_json`,`aihr_candidate_employee_link` 尚不存在;这些路径此前依赖运行时懒迁移。现新增可重复执行的 `aihr_20260715_candidate_interview_review_mysql8.sql`,兼容空库、旧表和重复执行,纳入生产迁移 Runbook、schema 回归和 `demo-check` marker;仅补结构,不写生产业务数据,正式迁移仍需发布窗口、备份和回滚责任。
- 2026-07-15 BRD 4.3/4.4/4.5 生产迁移缺口修复:严格试点门禁要求 `aihr_practice_session` 的 8 个证据字段,但版本化迁移此前只覆盖三项五维字段,`annotations_json`、主管复盘建议、人工贡献分和满意度字段仍可能依赖运行时懒迁移。现新增幂等 MySQL 8 迁移补齐其余 5 列,纳入 Runbook、schema 回归和 `demo-check` marker;仅补结构,不写业务数据,尚未执行生产迁移。
- 2026-07-15 BRD G3/4.7 生产迁移缺口修复:`aihr_knowledge_gap` 与 `aihr_knowledge_answer_feedback` 此前只在 `AihrSopSeedService` 运行时懒建,生产迁移 Runbook 没有对应版本化脚本,可能出现应用启动前结构缺失、反馈/知识缺口审计不可用。现新增幂等 MySQL 8 迁移,兼容旧反馈表补齐 `review_id/reviewer/reviewed_time`,纳入迁移 Runbook、迁移回归测试和 `demo-check` marker;仅补结构,不写业务数据,尚未执行生产迁移。
+7 -2
View File
@@ -10,6 +10,7 @@
- `aihr_case_record` 缺 `media_oss_id`、`media_url`;
- 对练五维、每日三题评分模式、SOP 提问主体/版本、组织入职日期字段尚未全部迁移;
- `aihr_prompt_template` 表存在,但内置模板数量为 `0`;
- `aihr_candidate_material` 缺少 `reviewer/reviewed_time`,`aihr_interview_result` 缺少人工复核字段和 `questions_json`,`aihr_candidate_employee_link` 尚不存在;这些候选人/HR 复核链路不能依赖首次请求触发懒迁移;
- 这些事实不能由应用启动时的懒迁移或页面 fallback 代替。
## 发布前置条件
@@ -28,6 +29,7 @@ mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aih
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260714_practice_brd_scenarios_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260714_brd_sop_seed_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260714_prompt_template_seed_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260715_candidate_interview_review_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260714_practice_evidence_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260714_case_media_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260714_case_learning_mysql8.sql
@@ -52,7 +54,8 @@ WHERE table_schema = DATABASE()
AND table_name IN (
'aihr_practice_scenario', 'aihr_practice_rubric', 'aihr_practice_rubric_dimension',
'aihr_practice_audio', 'aihr_practice_audio_upload', 'aihr_practice_calibration',
'aihr_knowledge_gap', 'aihr_knowledge_answer_feedback'
'aihr_knowledge_gap', 'aihr_knowledge_answer_feedback',
'aihr_candidate_material', 'aihr_interview_result', 'aihr_candidate_employee_link'
)
ORDER BY table_name;
@@ -65,6 +68,8 @@ WHERE table_schema = DATABASE()
OR (table_name = 'aihr_practice_assignment' AND column_name IN ('score', 'score_mode'))
OR (table_name = 'aihr_sop_answer_review' AND column_name IN ('prompt_version', 'requester_ext_party_id'))
OR (table_name = 'aihr_org_snapshot' AND column_name = 'hire_date')
OR (table_name = 'aihr_candidate_material' AND column_name IN ('reviewer', 'reviewed_time'))
OR (table_name = 'aihr_interview_result' AND column_name IN ('questions_json', 'reviewed_score', 'review_status', 'reviewer', 'review_note', 'reviewed_time'))
)
ORDER BY table_name, column_name;
@@ -74,7 +79,7 @@ WHERE tenant_id = '000000'
AND code IN ('summary_card', 'sop_answer', 'mentor_rewrite', 'review_annotation', 'prep_card', 'supervision_image_analysis_maintenance');
```
预期是三张场景/Rubric 表、三张证据表和两张知识治理表均存在,会话证据字段为 `8/8`,字段查询覆盖所有列,内置 Prompt 数量为 `6`。这只证明 schema/seed 迁移完成,不证明正式试点完成。
预期是三张场景/Rubric 表、三张证据表、两张知识治理表和三张候选人/面试表均存在,会话证据字段为 `8/8`,候选资料审核字段为 `2/2`,面试复核字段为 `6/6`,字段查询覆盖所有列,内置 Prompt 数量为 `6`。这只证明 schema/seed 迁移完成,不证明正式试点完成。
## 迁移后应用回归
+3
View File
@@ -579,6 +579,9 @@ contains backend/script/sql/update/aihr_20260714_org_hire_date_mysql8.sql "hire_
contains backend/script/sql/update/aihr_20260714_org_hire_date_mysql8.sql "aihr_org_snapshot_table_exists"
contains backend/script/sql/update/aihr_20260714_sop_requester_identity_mysql8.sql "aihr_sop_source_exists"
contains backend/script/sql/update/aihr_20260714_sop_requester_identity_mysql8.sql "aihr_sop_prompt_version_exists"
contains backend/script/sql/update/aihr_20260715_candidate_interview_review_mysql8.sql "aihr_candidate_material"
contains backend/script/sql/update/aihr_20260715_candidate_interview_review_mysql8.sql "aihr_interview_result"
contains backend/script/sql/update/aihr_20260715_candidate_interview_review_mysql8.sql "aihr_candidate_employee_link"
contains scripts/tests/aihr-schema-migrations.test.sh "AIHR legacy schema migrations are idempotent"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrOrgSyncService.java "hireDate"
contains frontend/src/api/aihr/practice.ts '/api/train/practice/scenarios/${id}/enabled'
+25 -1
View File
@@ -9,6 +9,7 @@ fi
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
MYSQL_CONTAINER="wygj-mysql"
DB="wygj_aihr_schema_test_$$"
LEGACY_DB="wygj_aihr_candidate_legacy_test_$$"
mysql() {
docker exec -i "$MYSQL_CONTAINER" mysql -uroot -proot --default-character-set=utf8mb4 "$@"
@@ -16,6 +17,7 @@ mysql() {
cleanup() {
mysql -e "DROP DATABASE IF EXISTS \`$DB\`;" >/dev/null 2>&1 || true
mysql -e "DROP DATABASE IF EXISTS \`$LEGACY_DB\`;" >/dev/null 2>&1 || true
}
trap cleanup EXIT
@@ -140,4 +142,26 @@ mysql "$DB" < "$ROOT_DIR/backend/script/sql/update/aihr_20260714_org_hire_date_m
org_columns="$(mysql "$DB" -N -B -e "SELECT GROUP_CONCAT(column_name ORDER BY ordinal_position) FROM information_schema.columns WHERE table_schema='$DB' AND table_name='aihr_org_snapshot' AND column_name='hire_date';")"
test "$org_columns" = 'hire_date'
echo "PASS: AIHR legacy schema migrations are idempotent, including practice evidence and knowledge feedback fields"
mysql -e "CREATE DATABASE \`$LEGACY_DB\` CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;"
mysql "$LEGACY_DB" <<'SQL'
CREATE TABLE aihr_candidate_material (
id bigint NOT NULL AUTO_INCREMENT,
status varchar(20) DEFAULT '待审核',
PRIMARY KEY (id)
) ENGINE=InnoDB;
CREATE TABLE aihr_interview_result (
id bigint NOT NULL AUTO_INCREMENT,
session_id varchar(100) NOT NULL,
total_score int,
answers_json text,
PRIMARY KEY (id)
) ENGINE=InnoDB;
SQL
mysql "$LEGACY_DB" < "$ROOT_DIR/backend/script/sql/update/aihr_20260715_candidate_interview_review_mysql8.sql"
mysql "$LEGACY_DB" < "$ROOT_DIR/backend/script/sql/update/aihr_20260715_candidate_interview_review_mysql8.sql"
candidate_interview_tables="$(mysql "$LEGACY_DB" -N -B -e "SELECT GROUP_CONCAT(table_name ORDER BY table_name) FROM information_schema.tables WHERE table_schema='$LEGACY_DB' AND table_name IN ('aihr_candidate_material','aihr_interview_result','aihr_candidate_employee_link');")"
test "$candidate_interview_tables" = 'aihr_candidate_employee_link,aihr_candidate_material,aihr_interview_result'
candidate_interview_columns="$(mysql "$LEGACY_DB" -N -B -e "SELECT COUNT(*) FROM information_schema.columns WHERE table_schema='$LEGACY_DB' AND ((table_name = 'aihr_candidate_material' AND column_name IN ('reviewer','reviewed_time')) OR (table_name = 'aihr_interview_result' AND column_name IN ('questions_json','reviewed_score','review_status','reviewer','review_note','reviewed_time')));")"
test "$candidate_interview_columns" = '8'
echo "PASS: AIHR legacy schema migrations are idempotent, including practice evidence, knowledge feedback, and candidate interview review fields"