feat(aihr): add position qualification schema contract

This commit is contained in:
2026-07-15 02:04:05 +08:00
parent 27fe838225
commit d44e9440ce
7 changed files with 200 additions and 6 deletions
@@ -463,3 +463,81 @@ ON DUPLICATE KEY UPDATE
`enabled` = VALUES(`enabled`),
`description` = VALUES(`description`),
`update_time` = VALUES(`update_time`);
-- 岗位职责、SOP 适用范围、入职任务和资格门禁正式结构。
-- 不写入业务数据;与 update/aihr_20260715_position_sop_qualification_mysql8.sql 保持一致。
CREATE TABLE IF NOT EXISTS `aihr_position_responsibility` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`tenant_id` varchar(20) NOT NULL DEFAULT '000000' COMMENT '租户编号',
`position_code` varchar(80) NOT NULL COMMENT '岗位编码',
`responsibility_text` varchar(1000) NOT NULL COMMENT '岗位职责',
`sop_id` bigint DEFAULT NULL COMMENT '关联SOP主键(跨模块引用)',
`sop_code` varchar(100) DEFAULT NULL COMMENT '关联SOP编码',
`enabled` tinyint NOT NULL DEFAULT 1 COMMENT '是否启用',
`effective_from` date DEFAULT NULL COMMENT '生效日期',
`effective_thru` date DEFAULT NULL COMMENT '失效日期',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_aihr_position_responsibility` (`tenant_id`, `position_code`, `sop_code`),
KEY `idx_aihr_position_responsibility_status` (`tenant_id`, `position_code`, `enabled`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='AI HR 岗位职责与SOP挂载';
CREATE TABLE IF NOT EXISTS `aihr_sop_applicability` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`tenant_id` varchar(20) NOT NULL DEFAULT '000000' COMMENT '租户编号',
`sop_id` bigint DEFAULT NULL COMMENT '关联SOP主键(跨模块引用)',
`sop_code` varchar(100) DEFAULT NULL COMMENT '关联SOP编码',
`project_type` varchar(80) DEFAULT NULL COMMENT '项目类型:住宅/公建/景区等',
`industry` varchar(80) DEFAULT NULL COMMENT '行业分类',
`org_scope` varchar(500) DEFAULT NULL COMMENT '适用组织/项目范围编码,不存展示姓名',
`enabled` tinyint NOT NULL DEFAULT 1 COMMENT '是否启用',
`effective_from` date DEFAULT NULL COMMENT '生效日期',
`effective_thru` date DEFAULT NULL COMMENT '失效日期',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_aihr_sop_applicability_sop` (`tenant_id`, `sop_id`, `sop_code`),
KEY `idx_aihr_sop_applicability_scope` (`tenant_id`, `project_type`, `industry`, `enabled`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='AI HR SOP适用范围';
CREATE TABLE IF NOT EXISTS `aihr_onboard_task` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`tenant_id` varchar(20) NOT NULL DEFAULT '000000' COMMENT '租户编号',
`ext_party_id` varchar(100) NOT NULL COMMENT '员工外部主体ID,不复制姓名',
`project_code` varchar(100) DEFAULT NULL COMMENT '项目编码',
`position_code` varchar(80) DEFAULT NULL COMMENT '岗位编码',
`sop_id` bigint DEFAULT NULL COMMENT '关联SOP主键(跨模块引用)',
`course_id` bigint DEFAULT NULL COMMENT '关联课程主键(跨模块引用)',
`task_type` varchar(20) NOT NULL COMMENT '任务类型:岗前/入职',
`status` varchar(30) NOT NULL DEFAULT '待完成' COMMENT '待完成/进行中/已完成/已取消',
`assign_by` varchar(100) DEFAULT NULL COMMENT '派发人外部主体或系统标识',
`assigned_time` datetime DEFAULT NULL COMMENT '派发时间',
`due_time` datetime DEFAULT NULL COMMENT '截止时间',
`completed_time` datetime DEFAULT NULL COMMENT '完成时间',
`evidence_ref` varchar(500) DEFAULT NULL COMMENT '完成证据引用',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_aihr_onboard_task_party` (`tenant_id`, `ext_party_id`, `status`, `create_time`),
KEY `idx_aihr_onboard_task_scope` (`tenant_id`, `project_code`, `position_code`, `task_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='AI HR 岗前/入职培训任务';
CREATE TABLE IF NOT EXISTS `aihr_qualification_gate` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`tenant_id` varchar(20) NOT NULL DEFAULT '000000' COMMENT '租户编号',
`ext_party_id` varchar(100) NOT NULL COMMENT '员工外部主体ID,不复制姓名',
`project_code` varchar(100) DEFAULT NULL COMMENT '项目编码',
`position_code` varchar(80) NOT NULL COMMENT '岗位编码',
`cert_id` varchar(100) NOT NULL COMMENT '资格/证书编码',
`passed` tinyint NOT NULL DEFAULT 0 COMMENT '是否通过',
`valid_thru` date DEFAULT NULL COMMENT '有效期至',
`evidence_ref` varchar(500) DEFAULT NULL 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`),
UNIQUE KEY `uk_aihr_qualification_gate` (`tenant_id`, `ext_party_id`, `project_code`, `position_code`, `cert_id`),
KEY `idx_aihr_qualification_gate_status` (`tenant_id`, `position_code`, `passed`, `valid_thru`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='AI HR 上岗资格门禁';
@@ -0,0 +1,79 @@
-- AIHR 岗位职责、SOP 适用范围、入职任务和上岗资格门禁结构迁移(MySQL 8.x)
-- 只补正式数据契约,不写入岗位、员工、课程或资格业务数据;可重复执行。
-- sop_id/course_id 不加外键:正式 SOP/course 主表尚未纳入阶段一,先保留跨模块引用。
CREATE TABLE IF NOT EXISTS `aihr_position_responsibility` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`tenant_id` varchar(20) NOT NULL DEFAULT '000000' COMMENT '租户编号',
`position_code` varchar(80) NOT NULL COMMENT '岗位编码',
`responsibility_text` varchar(1000) NOT NULL COMMENT '岗位职责',
`sop_id` bigint DEFAULT NULL COMMENT '关联SOP主键(跨模块引用)',
`sop_code` varchar(100) DEFAULT NULL COMMENT '关联SOP编码',
`enabled` tinyint NOT NULL DEFAULT 1 COMMENT '是否启用',
`effective_from` date DEFAULT NULL COMMENT '生效日期',
`effective_thru` date DEFAULT NULL COMMENT '失效日期',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_aihr_position_responsibility` (`tenant_id`, `position_code`, `sop_code`),
KEY `idx_aihr_position_responsibility_status` (`tenant_id`, `position_code`, `enabled`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='AI HR 岗位职责与SOP挂载';
CREATE TABLE IF NOT EXISTS `aihr_sop_applicability` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`tenant_id` varchar(20) NOT NULL DEFAULT '000000' COMMENT '租户编号',
`sop_id` bigint DEFAULT NULL COMMENT '关联SOP主键(跨模块引用)',
`sop_code` varchar(100) DEFAULT NULL COMMENT '关联SOP编码',
`project_type` varchar(80) DEFAULT NULL COMMENT '项目类型:住宅/公建/景区等',
`industry` varchar(80) DEFAULT NULL COMMENT '行业分类',
`org_scope` varchar(500) DEFAULT NULL COMMENT '适用组织/项目范围编码,不存展示姓名',
`enabled` tinyint NOT NULL DEFAULT 1 COMMENT '是否启用',
`effective_from` date DEFAULT NULL COMMENT '生效日期',
`effective_thru` date DEFAULT NULL COMMENT '失效日期',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_aihr_sop_applicability_sop` (`tenant_id`, `sop_id`, `sop_code`),
KEY `idx_aihr_sop_applicability_scope` (`tenant_id`, `project_type`, `industry`, `enabled`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='AI HR SOP适用范围';
CREATE TABLE IF NOT EXISTS `aihr_onboard_task` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`tenant_id` varchar(20) NOT NULL DEFAULT '000000' COMMENT '租户编号',
`ext_party_id` varchar(100) NOT NULL COMMENT '员工外部主体ID,不复制姓名',
`project_code` varchar(100) DEFAULT NULL COMMENT '项目编码',
`position_code` varchar(80) DEFAULT NULL COMMENT '岗位编码',
`sop_id` bigint DEFAULT NULL COMMENT '关联SOP主键(跨模块引用)',
`course_id` bigint DEFAULT NULL COMMENT '关联课程主键(跨模块引用)',
`task_type` varchar(20) NOT NULL COMMENT '任务类型:岗前/入职',
`status` varchar(30) NOT NULL DEFAULT '待完成' COMMENT '待完成/进行中/已完成/已取消',
`assign_by` varchar(100) DEFAULT NULL COMMENT '派发人外部主体或系统标识',
`assigned_time` datetime DEFAULT NULL COMMENT '派发时间',
`due_time` datetime DEFAULT NULL COMMENT '截止时间',
`completed_time` datetime DEFAULT NULL COMMENT '完成时间',
`evidence_ref` varchar(500) DEFAULT NULL COMMENT '完成证据引用',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_aihr_onboard_task_party` (`tenant_id`, `ext_party_id`, `status`, `create_time`),
KEY `idx_aihr_onboard_task_scope` (`tenant_id`, `project_code`, `position_code`, `task_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='AI HR 岗前/入职培训任务';
CREATE TABLE IF NOT EXISTS `aihr_qualification_gate` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`tenant_id` varchar(20) NOT NULL DEFAULT '000000' COMMENT '租户编号',
`ext_party_id` varchar(100) NOT NULL COMMENT '员工外部主体ID,不复制姓名',
`project_code` varchar(100) DEFAULT NULL COMMENT '项目编码',
`position_code` varchar(80) NOT NULL COMMENT '岗位编码',
`cert_id` varchar(100) NOT NULL COMMENT '资格/证书编码',
`passed` tinyint NOT NULL DEFAULT 0 COMMENT '是否通过',
`valid_thru` date DEFAULT NULL COMMENT '有效期至',
`evidence_ref` varchar(500) DEFAULT NULL 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`),
UNIQUE KEY `uk_aihr_qualification_gate` (`tenant_id`, `ext_party_id`, `project_code`, `position_code`, `cert_id`),
KEY `idx_aihr_qualification_gate_status` (`tenant_id`, `position_code`, `passed`, `valid_thru`)
) 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 4.2/M4 岗位-SOP-上岗资格数据契约补齐:复核发现 `position_responsibility`、`sop_applicability`、`onboard_task`、`qualification_gate` 在代码库中只有 TechSpec 设计,没有正式 MySQL 结构;现新增幂等迁移和 reset DDL,并把四表纳入生产 Runbook、schema 回归和严格预检的结构可见性检查。迁移不写岗位、员工、课程或资格 seed,`position-sop` 仍保持 `NOT_CONFIGURED`;正式适用范围、入职任务规则和资格标准需 HR/组织系统提供后再接读取与 gating。
- 2026-07-15 BRD 生产迁移合同防回归:发现版本化 AIHR MySQL 迁移虽已全部写入 Runbook,但此前没有测试保证新增 `aihr_*_mysql8.sql` 必须同步进入执行清单;现将目录与 `BRD_PRODUCTION_MIGRATION_RUNBOOK.md` 的覆盖检查接入 schema migration 测试。该检查只验证发布合同,不执行生产迁移,也不替代备份、回滚和逐脚本核验。
- 2026-07-15 BRD 严格门禁旧案例结构兼容修复:`scripts/demo-check.sh` 原先在 `aihr_case_record` 整表或 `learning_points/supervisor_comment` 字段缺失时直接执行查询,可能以 Unknown column/table 中断,无法输出真正的 schema 门槛;现先探测表和字段,缺失时以 `stored_cases=0`、`case_media_schema=0/2`、`case_learning_schema=0/2` 继续判定,新增旧表/无表回归。该修复只改善迁移缺口可见性,不放松案例 `20` 条和字段 `2/2` 门槛。
+10 -3
View File
@@ -39,9 +39,10 @@ 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_sop_requester_identity_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260715_knowledge_feedback_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260714_org_hire_date_mysql8.sql
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260715_position_sop_qualification_mysql8.sql
```
顺序原因:场景补充依赖三张场景/Rubric 基础表;五维迁移依赖场景和 Rubric;其余字段迁移依赖既有业务表但不写正式业务行。住宅 SOP 和 Prompt 迁移只补内置内容,不代表内容已完成业务复核。
顺序原因:场景补充依赖三张场景/Rubric 基础表;五维迁移依赖场景和 Rubric;岗位/SOP/任务/资格迁移只补正式数据契约,不写业务行,也不代表岗位适用范围、任务规则或资格标准已经获得 HR 确认。住宅 SOP 和 Prompt 迁移只补内置内容,不代表内容已完成业务复核。
## 迁移后只读核验
@@ -55,7 +56,9 @@ WHERE table_schema = DATABASE()
'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_candidate_material', 'aihr_interview_result', 'aihr_candidate_employee_link'
'aihr_candidate_material', 'aihr_interview_result', 'aihr_candidate_employee_link',
'aihr_position_responsibility', 'aihr_sop_applicability',
'aihr_onboard_task', 'aihr_qualification_gate'
)
ORDER BY table_name;
@@ -70,6 +73,10 @@ WHERE table_schema = DATABASE()
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'))
OR (table_name = 'aihr_position_responsibility' AND column_name IN ('position_code', 'responsibility_text', 'sop_id', 'sop_code'))
OR (table_name = 'aihr_sop_applicability' AND column_name IN ('sop_id', 'sop_code', 'project_type', 'industry', 'org_scope'))
OR (table_name = 'aihr_onboard_task' AND column_name IN ('ext_party_id', 'position_code', 'task_type', 'status', 'assign_by'))
OR (table_name = 'aihr_qualification_gate' AND column_name IN ('ext_party_id', 'position_code', 'cert_id', 'passed', 'valid_thru'))
)
ORDER BY table_name, column_name;
@@ -79,7 +86,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`,候选资料审核字段为 `2/2`,面试复核字段为 `6/6`,字段查询覆盖所有列,内置 Prompt 数量为 `6`。这只证明 schema/seed 迁移完成,不证明正式试点完成。
预期是三张场景/Rubric 表、三张证据表、两张知识治理表、三张候选人/面试表和四张岗位/SOP/任务/资格契约表均存在,会话证据字段为 `8/8`,候选资料审核字段为 `2/2`,面试复核字段为 `6/6`,字段查询覆盖所有列,内置 Prompt 数量为 `6`。岗位/SOP/任务/资格表为空是允许的;这只证明 schema/seed 迁移完成,不证明正式岗位规则、资格 gating 或试点完成。
## 迁移后应用回归
+16 -2
View File
@@ -81,7 +81,7 @@ check_pilot_samples() {
[[ "$(pilot_sql_scalar "SELECT DATE('$pilot_end') IS NOT NULL")" == "1" ]] || fail "invalid AIHR_PILOT_END_DATE: $pilot_end"
[[ "$pilot_start" > "$pilot_end" ]] && fail "pilot start date must not be after end date"
local catalog_schema_count scenario_count scenario_service_promotion_count scenario_daily_service_count rubric_expected_count rubric_five_dimension_count completed_people pilot_people org_active org_phone_linked org_hire_date_linked org_pilot_people org_pilot_projects org_completed_people audio_table_count audio_upload_table_count case_table_exists case_media_schema_count case_learning_schema_count practice_session_schema_count daily_drill_schema_count prompt_template_count prompt_version_schema_count requester_identity_schema_count hire_date_schema_count sop_document_count case_stored_count case_unmasked_count
local catalog_schema_count scenario_count scenario_service_promotion_count scenario_daily_service_count rubric_expected_count rubric_five_dimension_count completed_people pilot_people org_active org_phone_linked org_hire_date_linked org_pilot_people org_pilot_projects org_completed_people audio_table_count audio_upload_table_count case_table_exists case_media_schema_count case_learning_schema_count practice_session_schema_count daily_drill_schema_count prompt_template_count prompt_version_schema_count requester_identity_schema_count hire_date_schema_count position_sop_schema_count formal_applicability_count onboard_task_count qualification_gate_count sop_document_count case_stored_count case_unmasked_count
local calibration_stats calibration_count calibration_matched sop_total sop_usable sop_pending satisfaction_count satisfaction_average
local org_identity_cte="
WITH active_org AS (
@@ -151,6 +151,15 @@ check_pilot_samples() {
prompt_version_schema_count="$(pilot_sql_scalar "SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'aihr_sop_answer_review' AND column_name = 'prompt_version'")"
requester_identity_schema_count="$(pilot_sql_scalar "SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'aihr_sop_answer_review' AND column_name = 'requester_ext_party_id'")"
hire_date_schema_count="$(pilot_sql_scalar "SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'aihr_org_snapshot' AND column_name = 'hire_date'")"
position_sop_schema_count="$(pilot_sql_scalar "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name IN ('aihr_position_responsibility','aihr_sop_applicability','aihr_onboard_task','aihr_qualification_gate')")"
formal_applicability_count=0
onboard_task_count=0
qualification_gate_count=0
if (( position_sop_schema_count == 4 )); then
formal_applicability_count="$(pilot_sql_scalar "SELECT COUNT(*) FROM aihr_sop_applicability WHERE tenant_id = '$pilot_tenant' AND enabled = 1")"
onboard_task_count="$(pilot_sql_scalar "SELECT COUNT(*) FROM aihr_onboard_task WHERE tenant_id = '$pilot_tenant'")"
qualification_gate_count="$(pilot_sql_scalar "SELECT COUNT(*) FROM aihr_qualification_gate WHERE tenant_id = '$pilot_tenant'")"
fi
sop_document_count="$(pilot_sql_scalar "SELECT COUNT(*) FROM aihr_knowledge_attach WHERE tenant_id = '$pilot_tenant' AND type = 'sop' AND status = 2")"
case_stored_count=0
case_unmasked_count=0
@@ -312,7 +321,7 @@ check_pilot_samples() {
AND s.finished_time < DATE_ADD('${pilot_end}', INTERVAL 1 DAY)
)
SELECT COALESCE(ROUND(AVG(satisfaction_score), 1), 0) FROM formal_sessions WHERE satisfaction_score IS NOT NULL")"
echo "OK: pilot gates window=${pilot_start}..${pilot_end} catalog_schema=${catalog_schema_count}/3 scenarios=${scenario_count}/12 rubric_five_dimensions=${rubric_five_dimension_count}/${rubric_expected_count} core_flow=service_promotion:${scenario_service_promotion_count} daily_service:${scenario_daily_service_count} sop_docs=${sop_document_count}/5 stored_cases=${case_stored_count}/20 case_unmasked=${case_unmasked_count} ten_sessions=${completed_people}/${pilot_people} org_identity=${org_phone_linked}/${org_active} org_hire_date=${org_hire_date_linked}/${org_active} org_pilot=${org_pilot_people}/20 projects=${org_pilot_projects}/1-2 org_ten_sessions=${org_completed_people}/${org_pilot_people} calibration=${calibration_count}/20 consistency=${calibration_matched}/${calibration_count} sop_usable=${sop_usable}/${sop_total} sop_pending=${sop_pending} satisfaction=${satisfaction_average}/5 responses=${satisfaction_count} audio_schema=${audio_table_count} audio_upload_schema=${audio_upload_table_count} case_media_schema=${case_media_schema_count}/2 case_learning_schema=${case_learning_schema_count}/2 practice_session_schema=${practice_session_schema_count}/8 daily_drill_schema=${daily_drill_schema_count}/6 prompt_templates=${prompt_template_count}/6 prompt_version_schema=${prompt_version_schema_count} requester_identity_schema=${requester_identity_schema_count} hire_date_schema=${hire_date_schema_count}"
echo "OK: pilot gates window=${pilot_start}..${pilot_end} catalog_schema=${catalog_schema_count}/3 scenarios=${scenario_count}/12 rubric_five_dimensions=${rubric_five_dimension_count}/${rubric_expected_count} core_flow=service_promotion:${scenario_service_promotion_count} daily_service:${scenario_daily_service_count} sop_docs=${sop_document_count}/5 stored_cases=${case_stored_count}/20 case_unmasked=${case_unmasked_count} ten_sessions=${completed_people}/${pilot_people} org_identity=${org_phone_linked}/${org_active} org_hire_date=${org_hire_date_linked}/${org_active} org_pilot=${org_pilot_people}/20 projects=${org_pilot_projects}/1-2 org_ten_sessions=${org_completed_people}/${org_pilot_people} calibration=${calibration_count}/20 consistency=${calibration_matched}/${calibration_count} sop_usable=${sop_usable}/${sop_total} sop_pending=${sop_pending} satisfaction=${satisfaction_average}/5 responses=${satisfaction_count} audio_schema=${audio_table_count} audio_upload_schema=${audio_upload_table_count} case_media_schema=${case_media_schema_count}/2 case_learning_schema=${case_learning_schema_count}/2 practice_session_schema=${practice_session_schema_count}/8 daily_drill_schema=${daily_drill_schema_count}/6 prompt_templates=${prompt_template_count}/6 prompt_version_schema=${prompt_version_schema_count} requester_identity_schema=${requester_identity_schema_count} hire_date_schema=${hire_date_schema_count} position_sop_schema=${position_sop_schema_count}/4 formal_applicability=${formal_applicability_count} onboard_tasks=${onboard_task_count} qualification_gates=${qualification_gate_count}"
[[ "${AIHR_PILOT_STRICT:-false}" != "true" ]] || {
(( catalog_schema_count == 3 )) || fail "pilot practice catalog schema incomplete (${catalog_schema_count}/3)"
@@ -333,6 +342,7 @@ check_pilot_samples() {
(( prompt_version_schema_count == 1 )) || fail "pilot RAG traceability schema unavailable (aihr_sop_answer_review.prompt_version missing)"
(( requester_identity_schema_count == 1 )) || fail "pilot SOP requester identity schema unavailable (aihr_sop_answer_review.requester_ext_party_id missing)"
(( hire_date_schema_count == 1 )) || fail "pilot org hire_date schema unavailable"
(( position_sop_schema_count == 4 )) || fail "pilot position/SOP/onboard/qualification schema incomplete (${position_sop_schema_count}/4)"
(( org_phone_linked >= 20 )) || fail "pilot mobile-login org members ${org_phone_linked}/20 (active org ${org_active})"
(( org_hire_date_linked >= 20 )) || fail "pilot org hire_date identities ${org_hire_date_linked}/20"
(( org_pilot_people >= 20 )) || fail "pilot activity matched to active org ${org_pilot_people}/20"
@@ -604,6 +614,10 @@ contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/control
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/domain/AihrSopDto.java "String position"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrSopController.java "/position-sop"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrSopController.java "@SaCheckLogin"
contains backend/script/sql/update/aihr_20260715_position_sop_qualification_mysql8.sql "aihr_position_responsibility"
contains backend/script/sql/update/aihr_20260715_position_sop_qualification_mysql8.sql "aihr_sop_applicability"
contains backend/script/sql/update/aihr_20260715_position_sop_qualification_mysql8.sql "aihr_onboard_task"
contains backend/script/sql/update/aihr_20260715_position_sop_qualification_mysql8.sql "aihr_qualification_gate"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrDashboardController.java "@SaCheckLogin"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrDashboardController.java "@SaCheckRole"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java 'firstNonBlank(position, "生活顾问")'
+14 -1
View File
@@ -150,6 +150,19 @@ 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'
mysql "$DB" < "$ROOT_DIR/backend/script/sql/update/aihr_20260715_position_sop_qualification_mysql8.sql"
mysql "$DB" < "$ROOT_DIR/backend/script/sql/update/aihr_20260715_position_sop_qualification_mysql8.sql"
position_sop_tables="$(mysql "$DB" -N -B -e "SELECT GROUP_CONCAT(table_name ORDER BY table_name) FROM information_schema.tables WHERE table_schema='$DB' AND table_name IN ('aihr_onboard_task','aihr_position_responsibility','aihr_qualification_gate','aihr_sop_applicability');")"
test "$position_sop_tables" = 'aihr_onboard_task,aihr_position_responsibility,aihr_qualification_gate,aihr_sop_applicability'
position_responsibility_columns="$(mysql "$DB" -N -B -e "SELECT COUNT(*) FROM information_schema.columns WHERE table_schema='$DB' AND table_name='aihr_position_responsibility' AND column_name IN ('position_code','responsibility_text','sop_id','sop_code');")"
test "$position_responsibility_columns" = '4'
sop_applicability_columns="$(mysql "$DB" -N -B -e "SELECT COUNT(*) FROM information_schema.columns WHERE table_schema='$DB' AND table_name='aihr_sop_applicability' AND column_name IN ('sop_id','sop_code','project_type','industry','org_scope');")"
test "$sop_applicability_columns" = '5'
onboard_task_columns="$(mysql "$DB" -N -B -e "SELECT COUNT(*) FROM information_schema.columns WHERE table_schema='$DB' AND table_name='aihr_onboard_task' AND column_name IN ('ext_party_id','position_code','task_type','status','assign_by');")"
test "$onboard_task_columns" = '5'
qualification_gate_columns="$(mysql "$DB" -N -B -e "SELECT COUNT(*) FROM information_schema.columns WHERE table_schema='$DB' AND table_name='aihr_qualification_gate' AND column_name IN ('ext_party_id','position_code','cert_id','passed','valid_thru');")"
test "$qualification_gate_columns" = '5'
mysql -e "CREATE DATABASE \`$LEGACY_DB\` CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;"
mysql "$LEGACY_DB" <<'SQL'
CREATE TABLE aihr_candidate_material (
@@ -172,4 +185,4 @@ test "$candidate_interview_tables" = 'aihr_candidate_employee_link,aihr_candidat
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"
echo "PASS: AIHR legacy schema migrations are idempotent, including practice evidence, knowledge feedback, candidate interview review, and position/SOP qualification contracts"
@@ -177,6 +177,7 @@ export AIHR_PILOT_DB="$TEST_DB"
pilot_output="$(AIHR_PILOT_START_DATE=2026-07-07 AIHR_PILOT_END_DATE=2026-07-10 check_pilot_samples)"
[[ "$pilot_output" == *'ten_sessions=1/5'* ]] || { echo "FAIL: 9/10 boundary or raw window count: $pilot_output" >&2; exit 1; }
[[ "$pilot_output" == *'catalog_schema=1/3'* ]] || { echo "FAIL: missing catalog schema was not visible: $pilot_output" >&2; exit 1; }
[[ "$pilot_output" == *'position_sop_schema=0/4'* ]] || { echo "FAIL: missing position/SOP qualification schema was not visible: $pilot_output" >&2; exit 1; }
[[ "$pilot_output" == *'core_flow=service_promotion:1 daily_service:1'* ]] || { echo "FAIL: BRD core-flow coverage counts: $pilot_output" >&2; exit 1; }
[[ "$pilot_output" == *'org_identity=2/6'* ]] || { echo "FAIL: unsafe org identities were not excluded: $pilot_output" >&2; exit 1; }
[[ "$pilot_output" == *'org_pilot=2/20'* ]] || { echo "FAIL: collision or dual-key identity was miscounted: $pilot_output" >&2; exit 1; }