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.FormInstance; import com.kaidi.oa.domain.LegalPolicyRevision; import com.kaidi.oa.repository.FormInstanceRepository; import com.kaidi.oa.repository.LegalPolicyRevisionRepository; import com.kaidi.oa.service.WorkflowService; 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.time.Year; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * 制度修订审批(内控部 / 法务风险部 · 制度与授权管理)。 * * 补齐缺口 8:制度修订审批流接入 OA 引擎不完整(仅 submit/revise 状态机,无 FormInstance 联动)。 * * 核心流程: * - CRUD 草稿:创建制度修订申请(新建/修订/废止); * - POST /{id}/submit-approval:接入 WorkflowService,创建 FormInstance,状态推进到「审批中」; * - POST /{id}/approve:审批通过后生效(状态 → 已批准 → 已生效); * - POST /{id}/reject:审批驳回(状态 → 已驳回); * - POST /{id}/activate:批准后通知生效(状态 → 已生效); * - POST /{id}/deprecate:已生效制度废止(状态 → 已废止)。 * * 读口含内部治理制度,已登记进 SENSITIVE_READ_PREFIXES;写口受 default-deny 保护。 */ @RestController @RequestMapping("/api/oa/legal-policy-revisions") public class LegalPolicyRevisionController { /** OA 审批引擎模板 ID(制度修订审批流)。 */ private static final String POLICY_REVISION_TEMPLATE_ID = "policy-revision-apply"; private final LegalPolicyRevisionRepository repo; private final FormInstanceRepository instanceRepo; private final WorkflowService workflowService; public LegalPolicyRevisionController(LegalPolicyRevisionRepository repo, FormInstanceRepository instanceRepo, WorkflowService workflowService) { this.repo = repo; this.instanceRepo = instanceRepo; this.workflowService = workflowService; } @GetMapping public ApiResp> list( @RequestParam(required = false) String status, @RequestParam(required = false) String revisionType, @RequestParam(required = false) String category) { if (status != null && !status.isBlank()) return ApiResp.ok(repo.findByStatus(status)); if (revisionType != null && !revisionType.isBlank()) return ApiResp.ok(repo.findByRevisionType(revisionType)); if (category != null && !category.isBlank()) return ApiResp.ok(repo.findByCategory(category)); return ApiResp.ok(repo.findAll()); } @GetMapping("/{id}") public ApiResp get(@PathVariable Long id) { return ApiResp.ok(load(id)); } public record RevisionRequest( String policyNo, String title, String revisionType, String category, String content, String reason, String changePoints, String applicantDept, String applicant, String effectiveDate, String deprecationDate, String remark) { } @PostMapping @Transactional public ApiResp create(@RequestBody RevisionRequest req) { if (req.title() == null || req.title().isBlank()) { throw new ApiException(400, "制度名称(title) 不能为空"); } LegalPolicyRevision r = new LegalPolicyRevision(); r.setRevisionNo("PR-" + Year.now().getValue() + "-" + String.format("%03d", repo.count() + 1)); r.setPolicyNo(req.policyNo()); r.setTitle(req.title()); r.setRevisionType(req.revisionType() == null || req.revisionType().isBlank() ? "修订" : req.revisionType()); r.setCategory(req.category() == null || req.category().isBlank() ? "管理制度" : req.category()); r.setContent(req.content()); r.setReason(req.reason()); r.setChangePoints(req.changePoints()); r.setApplicantDept(req.applicantDept()); r.setApplicant(req.applicant() == null || req.applicant().isBlank() ? "admin" : req.applicant()); r.setEffectiveDate(req.effectiveDate()); r.setDeprecationDate(req.deprecationDate()); r.setRemark(req.remark()); r.setStatus("草稿"); r.setCreatedAt(Instant.now()); r.setUpdatedAt(Instant.now()); return ApiResp.ok(repo.save(r)); } @PatchMapping("/{id}") @Transactional public ApiResp update(@PathVariable Long id, @RequestBody RevisionRequest req) { LegalPolicyRevision r = load(id); if (List.of("已批准", "已生效", "已废止").contains(r.getStatus())) { throw new ApiException(409, "当前状态 [" + r.getStatus() + "] 不允许修改"); } if (req.policyNo() != null) r.setPolicyNo(req.policyNo()); if (req.title() != null && !req.title().isBlank()) r.setTitle(req.title()); if (req.revisionType() != null) r.setRevisionType(req.revisionType()); if (req.category() != null) r.setCategory(req.category()); if (req.content() != null) r.setContent(req.content()); if (req.reason() != null) r.setReason(req.reason()); if (req.changePoints() != null) r.setChangePoints(req.changePoints()); if (req.applicantDept() != null) r.setApplicantDept(req.applicantDept()); if (req.applicant() != null) r.setApplicant(req.applicant()); if (req.effectiveDate() != null) r.setEffectiveDate(req.effectiveDate()); if (req.deprecationDate() != null) r.setDeprecationDate(req.deprecationDate()); if (req.remark() != null) r.setRemark(req.remark()); r.setUpdatedAt(Instant.now()); return ApiResp.ok(repo.save(r)); } @DeleteMapping("/{id}") @Transactional public ApiResp delete(@PathVariable Long id) { LegalPolicyRevision r = load(id); if (!"草稿".equals(r.getStatus()) && !"已驳回".equals(r.getStatus())) { throw new ApiException(409, "只有草稿/已驳回状态可以删除"); } repo.deleteById(id); return ApiResp.ok(null); } // ==================== 审批流接入 OA 引擎 ==================== public record ApprovalSubmitRequest(String applicant) {} /** * 提交制度修订申请进入 OA 审批引擎(法务审核 → 合规 → 高管审批)。 * 接入 WorkflowService.submit(),创建 FormInstance,状态推进到「审批中」。 * 同一申请只能提交一次(已有进行中实例则拒绝重复提交)。 * * 补齐缺口 8 核心:制度修订审批流与 FormInstance 联动。 */ @PostMapping("/{id}/submit-approval") @Transactional public ApiResp submitApproval(@PathVariable Long id, @RequestBody(required = false) ApprovalSubmitRequest req) { LegalPolicyRevision r = load(id); if (!List.of("草稿", "已驳回").contains(r.getStatus())) { throw new ApiException(409, "当前状态 [" + r.getStatus() + "] 不允许提交审批"); } String applicant = (req != null && req.applicant() != null && !req.applicant().isBlank()) ? req.applicant() : nvl(r.getApplicant()); // 防止重复提交:检查是否已有未驳回的审批实例 boolean alreadyPending = instanceRepo.findAll().stream() .anyMatch(inst -> POLICY_REVISION_TEMPLATE_ID.equals(inst.getTemplateId()) && inst.getTitle() != null && inst.getTitle().contains(r.getRevisionNo()) && !List.of("已驳回", "已撤回").contains(inst.getStatus())); if (alreadyPending) { throw new ApiException(409, "该申请已存在进行中的审批流程,请勿重复提交"); } // 构建审批表单数据 JSON String dataJson = String.format( "{\"revisionNo\":\"%s\",\"policyNo\":\"%s\",\"title\":\"%s\"," + "\"revisionType\":\"%s\",\"category\":\"%s\",\"reason\":\"%s\"," + "\"applicantDept\":\"%s\",\"applicant\":\"%s\"}", nvl(r.getRevisionNo()), nvl(r.getPolicyNo()), escape(r.getTitle()), nvl(r.getRevisionType()), nvl(r.getCategory()), escape(r.getReason()), nvl(r.getApplicantDept()), applicant); String title = "制度修订申请-" + r.getRevisionNo() + " 《" + r.getTitle() + "》[" + r.getRevisionType() + "]"; FormInstance inst = workflowService.submit(POLICY_REVISION_TEMPLATE_ID, dataJson, title, applicant); r.setFormInstanceId(inst.getId()); r.setStatus("审批中"); r.setUpdatedAt(Instant.now()); repo.save(r); return ApiResp.ok(inst); } /** 审批通过:状态 → 已批准。 */ @PostMapping("/{id}/approve") @Transactional public ApiResp approve(@PathVariable Long id, @RequestBody(required = false) ApproveRequest req) { LegalPolicyRevision r = load(id); if (!"审批中".equals(r.getStatus())) { throw new ApiException(409, "当前状态 [" + r.getStatus() + "] 不允许审批"); } if (req != null && req.effectiveDate() != null && !req.effectiveDate().isBlank()) { r.setEffectiveDate(req.effectiveDate()); } r.setStatus("已批准"); r.setUpdatedAt(Instant.now()); return ApiResp.ok(repo.save(r)); } public record ApproveRequest(String effectiveDate) {} /** 审批驳回:状态 → 已驳回。 */ @PostMapping("/{id}/reject") @Transactional public ApiResp reject(@PathVariable Long id) { LegalPolicyRevision r = load(id); if (!"审批中".equals(r.getStatus())) { throw new ApiException(409, "当前状态 [" + r.getStatus() + "] 不允许驳回"); } r.setStatus("已驳回"); r.setUpdatedAt(Instant.now()); return ApiResp.ok(repo.save(r)); } /** 通知生效:已批准 → 已生效。 */ @PostMapping("/{id}/activate") @Transactional public ApiResp activate(@PathVariable Long id) { LegalPolicyRevision r = load(id); if (!"已批准".equals(r.getStatus())) { throw new ApiException(409, "只有已批准状态可以通知生效"); } r.setStatus("已生效"); r.setUpdatedAt(Instant.now()); return ApiResp.ok(repo.save(r)); } /** 废止:已生效 → 已废止。 */ @PostMapping("/{id}/deprecate") @Transactional public ApiResp deprecate(@PathVariable Long id) { LegalPolicyRevision r = load(id); if (!"已生效".equals(r.getStatus())) { throw new ApiException(409, "只有已生效的制度可以废止"); } r.setStatus("已废止"); r.setUpdatedAt(Instant.now()); return ApiResp.ok(repo.save(r)); } @GetMapping("/stats/overview") public ApiResp> stats() { List all = repo.findAll(); Map m = new LinkedHashMap<>(); m.put("total", all.size()); Map byStatus = new LinkedHashMap<>(); for (LegalPolicyRevision r : all) { byStatus.merge(r.getStatus(), 1L, Long::sum); } m.put("byStatus", byStatus); m.put("active", all.stream().filter(r -> "已生效".equals(r.getStatus())).count()); m.put("pending", all.stream().filter(r -> "审批中".equals(r.getStatus())).count()); return ApiResp.ok(m); } private LegalPolicyRevision load(Long id) { return repo.findById(id).orElseThrow(() -> new NotFoundException("制度修订申请不存在: " + id)); } private static String nvl(String s) { return s == null ? "" : s; } private static String escape(String s) { return s == null ? "" : s.replace("\"", "'").replace("\n", " "); } }