SNAPSHOT W7 已部署稳定态 — 凯迪ERP+OA一体化平台 (MET 73.3%)

恢复点(restore point)。别人改崩后可 git reset --hard 回到此提交。

== 此快照内容 ==
- 后端 oa-backend: 734 控制器 / 711 实体 (Spring Boot 3.2.5 + SQLite, 端口8091)
- 前端 modern-ui/app: Vue3+Vite, 约700页 (构建产物已在 oa-backend/src/main/resources/static)
- 数据库 oa-backend/data/oa.db: 含全部演示数据 (强制入库, 6.6MB)
- 交接文档 go.md + go-code-reference/endpoints/entities/database.md
- 多代理建设脚本 .claude/wf-*.js

== 状态 ==
- 对 凯迪科技ERP_20260507.xlsx 合规 MET ~73.3% (PARTIAL 75: 34可建+6种子/bug+35外部硬天花板)
- 安全: 5轮红队+5轮复检, default-deny分级鉴权, 连续零可利用
- W3~W7 累计补完436缺口; W8末轮(40缺口)为半成品(源码树可编译但未集成)
- 运行: cd oa-backend; java -jar build/libs/oa-backend-0.1.0.jar --server.port=8091; admin/123456

== 排除(gitignore, 可再生) ==
node_modules / oa-backend/build / .jdks / *.log / Backup-ERP-* / 弃用的OFBiz核心(只保留modern-ui)
完整文件夹备份见同目录 Backup-ERP-20260615-191517/ (含上述全部, 仅缺 node_modules)

时间戳: 20260615-191517

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Qiufeng
2026-06-15 19:19:15 +08:00
co-authored by Claude Opus 4.8
commit 5e51dc3f56
10584 changed files with 2501339 additions and 0 deletions
@@ -0,0 +1,336 @@
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 org.springframework.stereotype.Component;
import java.time.Instant;
import java.time.LocalDate;
/**
* 行政·综合部——招聘与人才管理 + AI文档初稿 演示种子。
*
* 幂等:各表已有数据则跳过。
* 覆盖:
* - HrRecruitJob 3 条岗位(招聘中/面试中/已录用)
* - HrRecruitCandidate 6 条候选人(多状态,含 AI 评分)
* - HrInterviewQuestion 5 条面试题(AI 生成标记)
* - HrAiDocDraft 4 条 AI 生成文档(入职资料/岗位说明/员工手册/转正考核,多状态)
*/
@Component
@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 1AI生成)
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);
}
}