package com.kaidi.oa.web; import com.kaidi.oa.common.ApiException; import com.kaidi.oa.common.ApiResp; import com.kaidi.oa.common.NotFoundException; import com.kaidi.oa.domain.HrAiDocDraft; import com.kaidi.oa.repository.HrAiDocDraftRepository; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.time.Instant; import java.util.List; /** * 行政·综合部——AI 文档初稿生成(Module 11 缺口闭合)。 * * 缺口:「员工入职资料整理、岗位说明、员工手册由 AI 生成初稿」(仅题库生成存在,此子功能缺失)。 * * 本控制器提供: * GET /hr-ai-doc — 列表(支持 docType/status 筛选) * GET /hr-ai-doc/{id} — 详情 * POST /hr-ai-doc — 人工录入草稿 * PATCH /hr-ai-doc/{id} — 编辑/审核更新 * DELETE /hr-ai-doc/{id} — 删除草稿 * POST /hr-ai-doc/ai-generate — AI 模拟生成指定类型文档初稿 * POST /hr-ai-doc/{id}/publish — 发布(草稿→已发布) * POST /hr-ai-doc/{id}/review — 审核通过(草稿→已审核) * * 文档类型:入职资料 / 岗位说明 / 员工手册 / 转正考核 / 离职说明 * AI生成:关键词结构化展开生成 Markdown 正文,真实 LLM 接入时替换 buildDraft() 为 AiController 调用。 */ @RestController @RequestMapping("/api/oa/hr-ai-doc") public class HrAiDocController { private final HrAiDocDraftRepository repo; public HrAiDocController(HrAiDocDraftRepository repo) { this.repo = repo; } // ============ 查询 ============ @GetMapping public ApiResp> list( @RequestParam(required = false) String docType, @RequestParam(required = false) String status) { if (docType != null && !docType.isBlank() && status != null && !status.isBlank()) { return ApiResp.ok(repo.findByDocTypeAndStatus(docType, status)); } if (docType != null && !docType.isBlank()) { return ApiResp.ok(repo.findByDocType(docType)); } if (status != null && !status.isBlank()) { return ApiResp.ok(repo.findByStatus(status)); } return ApiResp.ok(repo.findAll()); } @GetMapping("/{id}") public ApiResp get(@PathVariable Long id) { return ApiResp.ok(require(id)); } // ============ 人工录入 ============ public record CreateReq( String docType, String relatedPosition, String relatedDept, String title, String content, String operator) {} @PostMapping @Transactional public ApiResp create(@RequestBody CreateReq req) { if (req.docType() == null || req.docType().isBlank()) { throw new ApiException(400, "文档类型(docType) 不能为空"); } if (req.title() == null || req.title().isBlank()) { throw new ApiException(400, "文档标题(title) 不能为空"); } HrAiDocDraft d = new HrAiDocDraft(); d.setDocType(req.docType().trim()); d.setRelatedPosition(req.relatedPosition()); d.setRelatedDept(req.relatedDept()); d.setTitle(req.title().trim()); d.setContent(req.content()); d.setAiGenerated(false); d.setStatus("草稿"); d.setOperator(req.operator()); d.setCreatedAt(Instant.now()); d.setUpdatedAt(Instant.now()); return ApiResp.ok(repo.save(d)); } // ============ 编辑 ============ public record UpdateReq( String title, String content, String relatedPosition, String relatedDept, String reviewNote, String operator) {} @PatchMapping("/{id}") @Transactional public ApiResp update(@PathVariable Long id, @RequestBody UpdateReq req) { HrAiDocDraft d = require(id); if (req.title() != null && !req.title().isBlank()) d.setTitle(req.title().trim()); if (req.content() != null) d.setContent(req.content()); if (req.relatedPosition() != null) d.setRelatedPosition(req.relatedPosition()); if (req.relatedDept() != null) d.setRelatedDept(req.relatedDept()); if (req.reviewNote() != null) d.setReviewNote(req.reviewNote()); if (req.operator() != null) d.setOperator(req.operator()); d.setUpdatedAt(Instant.now()); return ApiResp.ok(repo.save(d)); } // ============ 删除 ============ @DeleteMapping("/{id}") @Transactional public ApiResp delete(@PathVariable Long id) { if (!repo.existsById(id)) throw new NotFoundException("文档草稿不存在: " + id); repo.deleteById(id); return ApiResp.ok(null); } // ============ 状态推进 ============ public record ReviewReq(String reviewNote) {} @PostMapping("/{id}/review") @Transactional public ApiResp review(@PathVariable Long id, @RequestBody ReviewReq req) { HrAiDocDraft d = require(id); if (!"草稿".equals(d.getStatus())) { throw new ApiException(400, "只有「草稿」状态的文档才能审核"); } d.setStatus("已审核"); if (req.reviewNote() != null) d.setReviewNote(req.reviewNote()); d.setUpdatedAt(Instant.now()); return ApiResp.ok(repo.save(d)); } @PostMapping("/{id}/publish") @Transactional public ApiResp publish(@PathVariable Long id) { HrAiDocDraft d = require(id); if (!"已审核".equals(d.getStatus()) && !"草稿".equals(d.getStatus())) { throw new ApiException(400, "文档状态不符合发布条件"); } d.setStatus("已发布"); d.setUpdatedAt(Instant.now()); return ApiResp.ok(repo.save(d)); } // ============ AI 模拟生成 ============ public record AiGenReq( String docType, String relatedPosition, String relatedDept, String background, String operator) {} /** * AI 模拟生成文档初稿。 * 根据 docType 展开结构化 Markdown 正文,落库后 HR 可人工修订。 * 真实 LLM 接入:将 buildDraft() 替换为 AiController HTTP 调用。 * * 支持类型:入职资料 / 岗位说明 / 员工手册 / 转正考核 / 离职说明 */ @PostMapping("/ai-generate") @Transactional public ApiResp aiGenerate(@RequestBody AiGenReq req) { String docType = req.docType() != null ? req.docType().trim() : "入职资料"; String pos = req.relatedPosition() != null ? req.relatedPosition() : ""; String dept = req.relatedDept() != null ? req.relatedDept() : ""; String bg = req.background() != null ? req.background() : ""; String op = req.operator() != null ? req.operator() : "AI系统"; String title = buildTitle(docType, pos, dept); String content = buildDraft(docType, pos, dept, bg); HrAiDocDraft d = new HrAiDocDraft(); d.setDocType(docType); d.setRelatedPosition(pos.isBlank() ? null : pos); d.setRelatedDept(dept.isBlank() ? null : dept); d.setTitle(title); d.setContent(content); d.setGenContext("类型=" + docType + " 岗位=" + pos + " 部门=" + dept + " 背景=" + bg); d.setAiGenerated(true); d.setStatus("草稿"); d.setOperator(op); d.setCreatedAt(Instant.now()); d.setUpdatedAt(Instant.now()); return ApiResp.ok(repo.save(d)); } // ============ 私有方法 ============ private HrAiDocDraft require(Long id) { return repo.findById(id) .orElseThrow(() -> new NotFoundException("文档草稿不存在: " + id)); } private static String buildTitle(String docType, String pos, String dept) { return switch (docType) { case "岗位说明" -> (pos.isBlank() ? "" : pos + " ") + "岗位说明书(AI初稿)"; case "员工手册" -> (dept.isBlank() ? "凯迪科技" : dept) + "员工手册(AI初稿)"; case "转正考核" -> (pos.isBlank() ? "" : pos + " ") + "转正考核表(AI初稿)"; case "离职说明" -> "员工离职交接说明模板(AI初稿)"; default -> (dept.isBlank() ? "" : dept + " ") + "员工入职资料包(AI初稿)"; }; } /** * 结构化文档正文生成,按 docType 展开不同模板。 * 使用关键词填充,真实场景对接 LLM 后效果更佳。 */ private static String buildDraft(String docType, String pos, String dept, String background) { return switch (docType) { case "岗位说明" -> buildJobDescription(pos, dept, background); case "员工手册" -> buildEmployeeHandbook(dept); case "转正考核" -> buildProbationReview(pos); case "离职说明" -> buildResignationGuide(); default -> buildOnboardingDoc(dept, background); }; } private static String buildOnboardingDoc(String dept, String background) { String deptName = dept.isBlank() ? "凯迪科技" : dept; return "# " + deptName + " 新员工入职资料包\n\n" + "> AI初稿,请HR结合实际情况修订后发布。\n\n" + "## 一、欢迎信\n\n" + "亲爱的新同事,\n\n" + "热烈欢迎您加入" + deptName + "大家庭!我们期待与您共同成长。\n" + "请仔细阅读以下入职指引,确保顺利完成入职手续。\n\n" + "## 二、报到须知\n\n" + "**报到时间**:请于入职通知书上注明的日期,9:00前到达人力资源部。\n\n" + "**报到地点**:公司总部行政中心(具体楼层由HR另行通知)。\n\n" + "**携带材料**(原件及复印件各一份):\n" + "- 本人身份证\n" + "- 最高学历证书\n" + "- 学位证书\n" + "- 上一家单位离职证明\n" + "- 近期一寸免冠照片 2 张\n" + "- 银行卡(工资卡,建议建设银行或工商银行)\n" + "- 社保卡(如有)\n" + (background.isBlank() ? "" : "- " + background + "\n") + "\n## 三、入职手续办理流程\n\n" + "1. 到HR部门提交材料,签署劳动合同\n" + "2. 办理门禁卡、工牌、工位安排\n" + "3. 开通系统账号(OA/ERP)\n" + "4. 由部门主管安排入职培训及岗位引导\n" + "5. 了解考勤、薪酬、报销等制度\n\n" + "## 四、入职第一周安排\n\n" + "| 时间 | 内容 |\n" + "| ---- | ---- |\n" + "| Day 1 | 行政手续办理、系统开通、部门介绍 |\n" + "| Day 2 | 公司文化、规章制度宣贯 |\n" + "| Day 3-4 | 岗位业务学习、导师结对 |\n" + "| Day 5 | 工作计划提交、与主管沟通试用期目标 |\n\n" + "## 五、重要联系人\n\n" + "- HR负责人:请入职后向直属主管确认\n" + "- IT支持:发送邮件至 it-support@kaidi.com\n" + "- 行政综合:综合办公室(内线:XXXX)\n\n" + "## 六、试用期说明\n\n" + "- 试用期一般为 **3 个月**,表现优秀者可申请提前转正\n" + "- 试用期内须完成《转正考核表》中的目标任务\n" + "- 转正后签订正式劳动合同,享受全薪及完整福利\n\n" + "---\n*本文档由 AI 辅助生成,最终版本需由人力资源部审核确认。*\n"; } private static String buildJobDescription(String pos, String dept, String background) { String posName = pos.isBlank() ? "目标岗位" : pos; String deptName = dept.isBlank() ? "凯迪科技" : dept; return "# " + posName + " 岗位说明书\n\n" + "> AI初稿,请部门主管及HR共同修订确认。\n\n" + "## 一、基本信息\n\n" + "| 项目 | 内容 |\n" + "| ---- | ---- |\n" + "| 岗位名称 | " + posName + " |\n" + "| 所属部门 | " + deptName + " |\n" + "| 直接上级 | 部门主管(由实际组织架构填写) |\n" + "| 岗位类别 | 专业技术类 |\n" + "| 编制人数 | 待定 |\n\n" + "## 二、岗位职责\n\n" + (background.isBlank() ? "1. 负责本岗位核心业务的日常执行与管理\n" + "2. 制定并执行工作计划,确保目标按时达成\n" + "3. 与相关部门协调配合,推进跨部门协作事项\n" + "4. 收集整理业务数据,定期提交工作报告\n" + "5. 参与制度建设,持续优化工作流程\n" : "1. " + background + "\n" + "2. 执行上级安排,按时完成目标任务\n" + "3. 与相关部门协调,推动项目落地\n" + "4. 汇报工作进度,提交阶段成果\n" + "5. 提出改进建议,持续优化工作方式\n") + "\n## 三、任职资格\n\n" + "**学历要求**:本科及以上学历,相关专业优先\n\n" + "**工作经验**:3年以上相关工作经验\n\n" + "**技能要求**:\n" + "- 熟练使用 Office 办公软件\n" + "- 良好的沟通协调能力\n" + "- 具备较强的逻辑分析和解决问题能力\n" + (background.isBlank() ? "" : "- " + background + "相关技能\n") + "\n**其他要求**:\n" + "- 工作认真负责,具备良好职业素养\n" + "- 具备团队合作精神\n" + "- 有较强的学习能力和适应能力\n\n" + "## 四、绩效考核维度\n\n" + "| 维度 | 权重 | 考核方式 |\n" + "| ---- | ---- | ---- |\n" + "| 工作完成度 | 40% | KPI达成率 |\n" + "| 工作质量 | 30% | 主管评分 |\n" + "| 协作表现 | 20% | 360度反馈 |\n" + "| 学习发展 | 10% | 培训完成情况 |\n\n" + "## 五、审批\n\n" + "- 起草人:HR / AI辅助生成\n" + "- 审核人:部门主管\n" + "- 批准人:分管领导\n\n" + "---\n*本文档由 AI 辅助生成,最终版本需由部门主管及人力资源部共同确认。*\n"; } private static String buildEmployeeHandbook(String dept) { String company = dept.isBlank() ? "凯迪科技有限公司" : dept; return "# " + company + " 员工手册\n\n" + "> AI初稿,请法务部门及人力资源部审核后正式发布。\n\n" + "## 第一章 公司简介\n\n" + company + "(以下简称「公司」)是一家专注于工程技术、ERP管理的企业," + "致力于为客户提供高质量的解决方案。\n\n" + "## 第二章 行为规范\n\n" + "### 2.1 基本准则\n" + "- 诚实守信,不弄虚作假\n" + "- 尊重他人,维护良好工作氛围\n" + "- 保守公司机密,不泄露商业信息\n" + "- 遵纪守法,不从事违法违规活动\n\n" + "### 2.2 保密义务\n" + "员工在职期间及离职后2年内,对公司以下信息负有保密义务:\n" + "- 客户名单及合同信息\n" + "- 公司财务数据\n" + "- 技术方案及知识产权\n" + "- 内部管理制度\n\n" + "## 第三章 考勤制度\n\n" + "- **工作时间**:每周一至周五,09:00-18:00(午休12:00-13:00)\n" + "- **打卡方式**:APP/指纹/人脸识别,上下班各打卡一次\n" + "- **迟到处理**:迟到15分钟以上计入考勤异常\n" + "- **加班**:需提前申请,按国家规定给予加班补偿\n\n" + "## 第四章 假期制度\n\n" + "| 假期类型 | 时长 | 审批流程 |\n" + "| ---- | ---- | ---- |\n" + "| 年假 | 5-15天(按工龄) | 部门主管审批 |\n" + "| 病假 | 按医院证明 | 提交病假条至HR |\n" + "| 婚假 | 3天 | HR审批 |\n" + "| 产假 | 按国家规定 | HR审批 |\n" + "| 陪产假 | 15天 | HR审批 |\n" + "| 丧假 | 3天 | HR审批 |\n\n" + "## 第五章 薪酬福利\n\n" + "- **薪资**:每月15日发放,转账至员工银行卡\n" + "- **社保**:按国家规定缴纳养老、医疗、失业、工伤、生育保险\n" + "- **住房公积金**:按当地比例缴纳\n" + "- **补贴**:交通补贴、餐饮补贴(具体标准见薪酬方案)\n" + "- **节日福利**:传统节日发放节日礼品/礼金\n\n" + "## 第六章 离职管理\n\n" + "- 试用期离职提前 **3天** 通知\n" + "- 正式员工离职提前 **1个月** 书面通知\n" + "- 完成交接方可办理离职手续,结清薪资\n\n" + "## 第七章 违规处理\n\n" + "- **口头警告**:轻微违规,初次\n" + "- **书面警告**:同类违规再次出现\n" + "- **解除合同**:严重违规(泄密/造假/违法等)\n\n" + "---\n*本手册由 AI 辅助生成,最终版本需法务部门及HR审核签发。*\n"; } private static String buildProbationReview(String pos) { String posName = pos.isBlank() ? "【岗位名称】" : pos; return "# " + posName + " 转正考核表\n\n" + "> AI初稿,请直属主管填写后经HR确认归档。\n\n" + "## 基本信息\n\n" + "| 项目 | 内容 |\n" + "| ---- | ---- |\n" + "| 员工姓名 | |\n" + "| 岗位 | " + posName + " |\n" + "| 入职日期 | |\n" + "| 试用期截止 | |\n" + "| 考核日期 | |\n" + "| 直属主管 | |\n\n" + "## 试用期目标完成情况\n\n" + "| 目标 | 计划完成时间 | 实际完成情况 | 完成率 |\n" + "| ---- | ---- | ---- | ---- |\n" + "| 完成岗位培训 | 入职第1周 | | |\n" + "| 熟悉业务流程 | 入职第2周 | | |\n" + "| 独立完成首个任务 | 入职第1月 | | |\n" + "| 达成季度工作指标 | 试用期末 | | |\n\n" + "## 综合评分(100分)\n\n" + "| 评分维度 | 权重 | 得分 | 说明 |\n" + "| ---- | ---- | ---- | ---- |\n" + "| 工作能力 | 30% | | |\n" + "| 工作态度 | 20% | | |\n" + "| 学习能力 | 20% | | |\n" + "| 团队协作 | 15% | | |\n" + "| 执行力 | 15% | | |\n" + "| **合计** | 100% | | |\n\n" + "## 转正意见\n\n" + "- [ ] 正式转正(总分 ≥ 80)\n" + "- [ ] 延长试用期(60 ≤ 总分 < 80)\n" + "- [ ] 不予转正(总分 < 60 或有违规行为)\n\n" + "**主管评语**:\n\n" + "**员工自评**:\n\n" + "## 签字确认\n\n" + "| 角色 | 签字 | 日期 |\n" + "| ---- | ---- | ---- |\n" + "| 员工本人 | | |\n" + "| 直属主管 | | |\n" + "| 部门负责人 | | |\n" + "| HR负责人 | | |\n\n" + "---\n*本表格由 AI 辅助生成,请主管如实填写评估内容。*\n"; } private static String buildResignationGuide() { return "# 员工离职交接说明模板\n\n" + "> AI初稿,请HR根据公司实际情况修订后使用。\n\n" + "## 一、离职申请流程\n\n" + "1. 员工提交《离职申请书》(正式员工提前1个月,试用期提前3天)\n" + "2. 直属主管确认离职日期,发起离职审批流程\n" + "3. 各部门审批通过后,确认最后工作日\n\n" + "## 二、工作交接清单\n\n" + "### 文档资料交接\n" + "- [ ] 在制项目文档、合同、方案\n" + "- [ ] 客户/供应商联系信息\n" + "- [ ] 内部制度文件、操作手册\n" + "- [ ] 未归档的工作成果\n\n" + "### 系统账号交接\n" + "- [ ] OA系统账号密码(交IT部门注销)\n" + "- [ ] 业务系统(ERP/CRM等)账号处理\n" + "- [ ] 邮件转发设置(至少转发1个月)\n" + "- [ ] 其他外部账号(行业平台、招聘网站等)\n\n" + "### 物品归还\n" + "- [ ] 工牌/门禁卡\n" + "- [ ] 电脑及外设(充电器、鼠标等)\n" + "- [ ] 公司书籍资料\n" + "- [ ] 其他公司财产\n\n" + "## 三、财务结算\n\n" + "- 报销:离职前提交所有未报销凭证\n" + "- 工资:最后工资于离职后次月15日发放\n" + "- 社保/公积金:离职当月停缴,次月起不再缴纳\n" + "- 押金:如有押金,凭结清证明退还\n\n" + "## 四、离职证明\n\n" + "完成全部交接手续并经HR确认后,公司出具《离职证明》,一般3个工作日内完成。\n\n" + "## 五、保密提示\n\n" + "离职后,员工仍需遵守签订的《保密协议》及《竞业限制协议》(如有)," + "不得泄露公司商业秘密或为竞争对手提供服务。\n\n" + "---\n*本文档由 AI 辅助生成,请HR结合实际修订后正式使用。*\n"; } }