package com.kaidi.oa.seed; import com.kaidi.oa.domain.HrAiDocDraft; import com.kaidi.oa.domain.HrInterviewQuestion; import com.kaidi.oa.domain.HrRecruitCandidate; import com.kaidi.oa.domain.HrRecruitJob; import com.kaidi.oa.repository.HrAiDocDraftRepository; import com.kaidi.oa.repository.HrInterviewQuestionRepository; import com.kaidi.oa.repository.HrRecruitCandidateRepository; import com.kaidi.oa.repository.HrRecruitJobRepository; import org.springframework.boot.CommandLineRunner; import org.springframework.core.annotation.Order; import java.time.Instant; import java.time.LocalDate; /** * 行政·综合部——招聘与人才管理 + AI文档初稿 演示种子。 * * 幂等:各表已有数据则跳过。 * 覆盖: * - HrRecruitJob 3 条岗位(招聘中/面试中/已录用) * - HrRecruitCandidate 6 条候选人(多状态,含 AI 评分) * - HrInterviewQuestion 5 条面试题(AI 生成标记) * - HrAiDocDraft 4 条 AI 生成文档(入职资料/岗位说明/员工手册/转正考核,多状态) */ @DemoSeed @Order(203) public class HrRecruitSeeder implements CommandLineRunner { private final HrRecruitJobRepository jobRepo; private final HrRecruitCandidateRepository candidateRepo; private final HrInterviewQuestionRepository questionRepo; private final HrAiDocDraftRepository aiDocRepo; public HrRecruitSeeder(HrRecruitJobRepository jobRepo, HrRecruitCandidateRepository candidateRepo, HrInterviewQuestionRepository questionRepo, HrAiDocDraftRepository aiDocRepo) { this.jobRepo = jobRepo; this.candidateRepo = candidateRepo; this.questionRepo = questionRepo; this.aiDocRepo = aiDocRepo; } @Override public void run(String... args) { seedJobs(); seedAiDocs(); } // ============ 岗位 + 候选人 ============ private void seedJobs() { if (jobRepo.count() > 0) return; // Job 1:高级工程师(招聘中) HrRecruitJob j1 = new HrRecruitJob(); j1.setPosition("高级结构工程师"); j1.setDept("工程技术部"); j1.setHeadcount(2); j1.setChannel("猎聘"); j1.setRequirement("注册结构工程师,BIM,超高层,地下工程,CAD,10年以上"); j1.setSalaryRange("25k-40k"); j1.setTargetDate(LocalDate.now().plusMonths(1).toString()); j1.setStatus("招聘中"); j1.setHrOwner("王芳"); j1.setRemark("紧急招募,优先有超高层经验"); j1.setCreatedAt(Instant.now().minusSeconds(86400 * 20)); j1.setUpdatedAt(Instant.now().minusSeconds(86400 * 5)); j1 = jobRepo.save(j1); // Job 2:项目经理(面试中) HrRecruitJob j2 = new HrRecruitJob(); j2.setPosition("项目经理(EPC)"); j2.setDept("项目管理部"); j2.setHeadcount(1); j2.setChannel("BOSS直聘"); j2.setRequirement("一级建造师,EPC总承包,项目全周期管理,甲方协调,8年以上"); j2.setSalaryRange("30k-45k"); j2.setTargetDate(LocalDate.now().plusWeeks(3).toString()); j2.setStatus("面试中"); j2.setHrOwner("李娜"); j2.setRemark("要求有大型市政工程经验"); j2.setCreatedAt(Instant.now().minusSeconds(86400 * 30)); j2.setUpdatedAt(Instant.now().minusSeconds(86400 * 3)); j2 = jobRepo.save(j2); // Job 3:财务主管(已录用) HrRecruitJob j3 = new HrRecruitJob(); j3.setPosition("财务主管"); j3.setDept("财务中心"); j3.setHeadcount(1); j3.setChannel("内部推荐"); j3.setRequirement("注册会计师,ERP系统,财务分析,税务筹划,5年以上"); j3.setSalaryRange("18k-25k"); j3.setTargetDate(LocalDate.now().minusWeeks(1).toString()); j3.setStatus("已录用"); j3.setHrOwner("王芳"); j3.setRemark("已完成录用,等待入职"); j3.setCreatedAt(Instant.now().minusSeconds(86400 * 45)); j3.setUpdatedAt(Instant.now().minusSeconds(86400 * 7)); j3 = jobRepo.save(j3); // 候选人 for Job 1 addCandidate(j1.getId(), "张明远", "138****5201", "硕士", 12, "注册结构工程师,超高层钢结构,BIM建模,CAD,地下工程,设计院15年", 75, "推荐(评分:75)。学历:硕士;工作年限:12年。", "初筛通过", "35k", "2026-06-25 10:00", "技术背景强,建议安排技术面试"); addCandidate(j1.getId(), "刘雪梅", "139****6302", "本科", 8, "结构工程,CAD,AutoCAD,工程设计,住宅项目", 62, "推荐(评分:62)。学历:本科;工作年限:8年。", "面试安排", "28k", "2026-06-28 14:00", ""); addCandidate(j1.getId(), "赵国强", "136****4403", "大专", 5, "辅助设计,AutoCAD,钢筋翻样", 51, "不推荐(评分:51)。学历:大专;工作年限:5年。", "已淘汰", "20k", null, "学历和经验不满足要求"); // 候选人 for Job 2 addCandidate(j2.getId(), "陈志远", "137****7504", "本科", 10, "一级建造师,EPC总承包,项目全周期,市政工程,甲方协调,合同管理", 88, "强烈推荐(评分:88)。学历:本科;工作年限:10年。", "面试通过", "40k", "2026-06-20 09:00", "各项能力突出,建议发Offer"); addCandidate(j2.getId(), "孙丽华", "135****8605", "硕士", 7, "项目管理,EPC,一建,工程合同,进度管理", 80, "强烈推荐(评分:80)。学历:硕士;工作年限:7年。", "offer发出", "38k", "2026-06-18 14:00", "综合素质优秀"); // 候选人 for Job 3(已入职) addCandidate(j3.getId(), "吴美玲", "132****9706", "本科", 6, "注册会计师,ERP财务模块,财务分析,税务筹划,预算管理", 90, "强烈推荐(评分:90)。学历:本科;工作年限:6年。", "已入职", "22k", "2026-06-10 10:00", "已正式入职,表现优异"); // 面试题(for Job 1,AI生成) if (questionRepo.count() == 0) { addQuestion(j1.getId(), "高级结构工程师", "技能考察", "请描述您在超高层建筑钢结构设计中遇到的最大技术挑战,以及您的解决方案。", "考察实际技术经验;期望看到:抗风抗震计算、复杂节点处理、BIM协同经验。", "高级"); addQuestion(j1.getId(), "高级结构工程师", "面试话术", "请介绍您主导过的最大单体项目,包括规模、难点和您的贡献。", "关注项目规模、复杂度与个人贡献比重;是否有超过5万平方米单体。", "中级"); addQuestion(j2.getId(), "项目经理(EPC)", "情景题", "假设EPC项目在施工过程中甲方提出重大变更,可能导致工期延误3个月,您如何应对?", "考察变更管理和谈判能力;期望看到:书面确认→工期/费用影响评估→变更谈判→合同签署。", "高级"); addQuestion(j2.getId(), "项目经理(EPC)", "综合素质", "您如何处理团队内部成员工作风格冲突的问题?请举例说明。", "考察领导力与团队管理;期望看到积极沟通、明确责任、结果导向。", "中级"); addQuestion(j3.getId(), "财务主管", "笔试题", "请说明税务筹划与逃税的本质区别,并举例说明合法的增值税筹划方法。", "考察税务专业知识合规意识;期望看到清晰区分合规边界,举例一般纳税人优惠政策。", "高级"); } } private void addCandidate(Long jobId, String name, String phone, String edu, int years, String summary, int score, String aiReport, String status, String salary, String interviewTime, String note) { HrRecruitCandidate c = new HrRecruitCandidate(); c.setJobId(jobId); c.setName(name); c.setPhone(phone); c.setEducation(edu); c.setWorkYears(years); c.setResumeSummary(summary); c.setAiScore(score); c.setAiReport(aiReport); c.setStatus(status); c.setExpectedSalary(salary); c.setInterviewTime(interviewTime); c.setInterviewNote(note); c.setCreatedAt(Instant.now().minusSeconds(86400 * (long)(Math.random() * 15 + 1))); c.setUpdatedAt(Instant.now().minusSeconds(3600)); candidateRepo.save(c); } private void addQuestion(Long jobId, String position, String qType, String content, String refAnswer, String difficulty) { HrInterviewQuestion q = new HrInterviewQuestion(); q.setJobId(jobId); q.setPosition(position); q.setQuestionType(qType); q.setContent(content); q.setReferenceAnswer(refAnswer); q.setAiGenerated(true); q.setDifficulty(difficulty); q.setStatus("启用"); q.setCreator("AI系统"); q.setCreatedAt(Instant.now().minusSeconds(86400 * 10)); questionRepo.save(q); } // ============ AI 文档初稿 ============ private void seedAiDocs() { if (aiDocRepo.count() > 0) return; // 1. 入职资料(已发布) HrAiDocDraft d1 = new HrAiDocDraft(); d1.setDocType("入职资料"); d1.setRelatedPosition(null); d1.setRelatedDept("工程技术部"); d1.setTitle("工程技术部 新员工入职资料包(AI初稿)"); d1.setContent("# 工程技术部 新员工入职资料包\n\n" + "> AI生成初稿,已经HR审核。\n\n" + "## 一、欢迎信\n\n" + "亲爱的新同事,\n\n热烈欢迎您加入凯迪科技工程技术部大家庭!\n\n" + "## 二、报到须知\n\n" + "**报到时间**:请于入职通知书上注明的日期 09:00 前到达人力资源部。\n\n" + "**携带材料**:身份证、学历证书、学位证书、离职证明、近期照片 2 张、银行卡。\n\n" + "## 三、专属材料\n\n" + "技术部员工还需提供:职业资格证书(建造师/结构工程师等)原件复印件。\n\n" + "## 四、入职第一周\n\n" + "- Day1:行政手续 + 系统开通\n- Day2:公司文化宣贯\n" + "- Day3-4:技术规范学习 + 导师结对\n- Day5:与主管沟通试用期目标\n\n" + "---\n*由 AI 辅助生成,HR 已审核。*"); d1.setGenContext("类型=入职资料 岗位= 部门=工程技术部 背景=职业资格证书"); d1.setAiGenerated(true); d1.setStatus("已发布"); d1.setReviewNote("内容准确,补充了专业证书要求,审核通过"); d1.setOperator("HR-王芳"); d1.setCreatedAt(Instant.now().minusSeconds(86400 * 30)); d1.setUpdatedAt(Instant.now().minusSeconds(86400 * 25)); aiDocRepo.save(d1); // 2. 岗位说明(已审核) HrAiDocDraft d2 = new HrAiDocDraft(); d2.setDocType("岗位说明"); d2.setRelatedPosition("高级结构工程师"); d2.setRelatedDept("工程技术部"); d2.setTitle("高级结构工程师 岗位说明书(AI初稿)"); d2.setContent("# 高级结构工程师 岗位说明书\n\n" + "> AI生成初稿,已审核待发布。\n\n" + "## 一、基本信息\n\n" + "| 项目 | 内容 |\n| ---- | ---- |\n" + "| 岗位名称 | 高级结构工程师 |\n" + "| 所属部门 | 工程技术部 |\n" + "| 直接上级 | 技术总工 |\n" + "| 岗位类别 | 专业技术类 |\n" + "| 编制人数 | 2 |\n\n" + "## 二、岗位职责\n\n" + "1. 负责大型建筑结构设计、审核与优化\n" + "2. 参与超高层/复杂结构的技术攻关\n" + "3. 指导初中级工程师完成设计任务\n" + "4. 负责结构设计图纸的质量把控\n" + "5. 参与投标技术方案编写\n\n" + "## 三、任职资格\n\n" + "**学历**:本科及以上,土木/结构工程专业\n\n" + "**证书**:注册结构工程师(一级)优先\n\n" + "**经验**:10年以上相关经验,有超高层项目经验\n\n" + "**技能**:BIM、CAD、SAP2000/PKPM、Revit\n\n" + "---\n*由 AI 辅助生成,已审核待签发。*"); d2.setGenContext("类型=岗位说明 岗位=高级结构工程师 部门=工程技术部 背景=注册结构工程师,BIM,超高层"); d2.setAiGenerated(true); d2.setStatus("已审核"); d2.setReviewNote("内容基本准确,已补充证书要求,待部门负责人签发"); d2.setOperator("HR-李娜"); d2.setCreatedAt(Instant.now().minusSeconds(86400 * 15)); d2.setUpdatedAt(Instant.now().minusSeconds(86400 * 10)); aiDocRepo.save(d2); // 3. 员工手册(草稿) HrAiDocDraft d3 = new HrAiDocDraft(); d3.setDocType("员工手册"); d3.setRelatedPosition(null); d3.setRelatedDept("凯迪科技有限公司"); d3.setTitle("凯迪科技有限公司 员工手册(AI初稿·2026版)"); d3.setContent("# 凯迪科技有限公司 员工手册\n\n" + "> AI生成草稿,待法务及HR审核。\n\n" + "## 第一章 公司简介\n\n" + "凯迪科技有限公司是一家专业从事工程技术、建设管理及 ERP 系统集成的企业," + "秉承「诚信、专业、创新」的核心价值观。\n\n" + "## 第二章 行为规范\n\n" + "- 诚实守信,不弄虚作假\n" + "- 尊重同事,维护良好工作氛围\n" + "- 保守公司机密,不泄露商业信息\n\n" + "## 第三章 考勤制度\n\n" + "- 工作时间:周一至周五 09:00-18:00\n" + "- 打卡方式:APP/指纹识别\n" + "- 迟到:15分钟以上计考勤异常\n\n" + "## 第四章 假期\n\n" + "| 假期 | 时长 |\n| ---- | ---- |\n" + "| 年假 | 5-15天 |\n| 病假 | 按医院证明 |\n| 婚假 | 3天 |\n" + "| 产假 | 按国家规定 |\n\n" + "## 第五章 薪酬福利\n\n" + "每月15日发薪;五险一金按规定缴纳;节日发放礼品/礼金。\n\n" + "---\n*由 AI 辅助生成,待法务审核。*"); d3.setGenContext("类型=员工手册 部门=凯迪科技有限公司"); d3.setAiGenerated(true); d3.setStatus("草稿"); d3.setReviewNote(null); d3.setOperator("HR-王芳"); d3.setCreatedAt(Instant.now().minusSeconds(86400 * 5)); d3.setUpdatedAt(Instant.now().minusSeconds(86400 * 5)); aiDocRepo.save(d3); // 4. 转正考核(草稿) HrAiDocDraft d4 = new HrAiDocDraft(); d4.setDocType("转正考核"); d4.setRelatedPosition("财务主管"); d4.setRelatedDept("财务中心"); d4.setTitle("财务主管 转正考核表(AI初稿)"); d4.setContent("# 财务主管 转正考核表\n\n" + "> AI生成草稿,请直属主管填写评分。\n\n" + "## 基本信息\n\n" + "| 项目 | 内容 |\n| ---- | ---- |\n" + "| 员工姓名 | 吴美玲 |\n" + "| 岗位 | 财务主管 |\n" + "| 入职日期 | 2026-06-10 |\n" + "| 试用期截止 | 2026-09-10 |\n" + "| 直属主管 | 财务总监 |\n\n" + "## 试用期目标\n\n" + "| 目标 | 计划完成 | 实际完成 | 完成率 |\n" + "| ---- | ---- | ---- | ---- |\n" + "| 完成岗位培训 | 入职第1周 | | |\n" + "| 接管月度账务 | 入职第1月 | | |\n" + "| 独立完成税务申报 | 入职第2月 | | |\n" + "| 完成半年财务分析 | 试用期末 | | |\n\n" + "## 综合评分\n\n" + "| 维度 | 权重 | 得分 |\n" + "| ---- | ---- | ---- |\n" + "| 专业能力 | 40% | |\n" + "| 工作态度 | 20% | |\n" + "| 团队协作 | 20% | |\n" + "| 执行力 | 20% | |\n\n" + "**转正建议**:[ ] 正式转正 [ ] 延长 [ ] 不予转正\n\n" + "---\n*由 AI 辅助生成,待主管填写。*"); d4.setGenContext("类型=转正考核 岗位=财务主管 部门=财务中心"); d4.setAiGenerated(true); d4.setStatus("草稿"); d4.setReviewNote(null); d4.setOperator("HR-李娜"); d4.setCreatedAt(Instant.now().minusSeconds(86400 * 3)); d4.setUpdatedAt(Instant.now().minusSeconds(86400 * 3)); aiDocRepo.save(d4); } }