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.EmergencyDrill; import com.kaidi.oa.repository.EmergencyDrillRepository; 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.LocalDate; import java.util.List; /** * 质安部·应急预案与演练(需求 §2 应急管理)。补深审计 high 缺口:应急预案 + 演练计划与记录 + 评估效果。 * * 状态机:计划 → 已演练(record,记录过程/参演人)→ 已评估(evaluate,记录评估结论/评分/发现问题)。 * 写口默认受 AuthInterceptor default-deny(ADMIN/APPROVER) 保护。 */ @RestController @RequestMapping("/api/oa/emergency-drills") public class EmergencyDrillController { private final EmergencyDrillRepository repo; public EmergencyDrillController(EmergencyDrillRepository repo) { this.repo = repo; } @GetMapping public ApiResp> list(@RequestParam(required = false) String status, @RequestParam(required = false) String planType) { if (status != null && !status.isBlank()) { return ApiResp.ok(repo.findByStatus(status)); } if (planType != null && !planType.isBlank()) { return ApiResp.ok(repo.findByPlanType(planType)); } return ApiResp.ok(repo.findAll()); } @GetMapping("/{id}") public ApiResp get(@PathVariable Long id) { return ApiResp.ok(repo.findById(id) .orElseThrow(() -> new NotFoundException("drill not found: " + id))); } public record DrillRequest(String code, String planName, String planType, String title, String drillType, String scenario, String dept, String planDate) { } @PostMapping public ApiResp create(@RequestBody DrillRequest req) { if (req.title() == null || req.title().isBlank()) { throw new ApiException(400, "演练主题不能为空"); } EmergencyDrill d = new EmergencyDrill(); d.setCode(req.code() == null || req.code().isBlank() ? "YJ-" + (repo.count() + 1) : req.code()); d.setPlanName(req.planName()); d.setPlanType(req.planType() == null || req.planType().isBlank() ? "专项预案" : req.planType()); d.setTitle(req.title()); d.setDrillType(req.drillType() == null || req.drillType().isBlank() ? "实战演练" : req.drillType()); d.setScenario(req.scenario()); d.setDept(req.dept()); d.setPlanDate(req.planDate()); d.setStatus("计划"); d.setCreatedAt(Instant.now()); return ApiResp.ok(repo.save(d)); } @PatchMapping("/{id}") public ApiResp update(@PathVariable Long id, @RequestBody DrillRequest req) { EmergencyDrill d = repo.findById(id) .orElseThrow(() -> new NotFoundException("drill not found: " + id)); if ("已评估".equals(d.getStatus())) { throw new ApiException(409, "已评估演练不可编辑"); } if (req.planName() != null) d.setPlanName(req.planName()); if (req.planType() != null && !req.planType().isBlank()) d.setPlanType(req.planType()); if (req.title() != null && !req.title().isBlank()) d.setTitle(req.title()); if (req.drillType() != null && !req.drillType().isBlank()) d.setDrillType(req.drillType()); if (req.scenario() != null) d.setScenario(req.scenario()); if (req.dept() != null) d.setDept(req.dept()); if (req.planDate() != null) d.setPlanDate(req.planDate()); return ApiResp.ok(repo.save(d)); } @DeleteMapping("/{id}") public ApiResp delete(@PathVariable Long id) { if (!repo.existsById(id)) { throw new NotFoundException("drill not found: " + id); } repo.deleteById(id); return ApiResp.ok(null); } public record RecordRequest(String drillDate, Integer participants, String process) { } /** 记录演练:计划 → 已演练(记录过程/参演人)。 */ @PostMapping("/{id}/record") public ApiResp record(@PathVariable Long id, @RequestBody RecordRequest req) { EmergencyDrill d = repo.findById(id) .orElseThrow(() -> new NotFoundException("drill not found: " + id)); if (!"计划".equals(d.getStatus())) { throw new ApiException(409, "仅计划中的演练可记录(当前:" + d.getStatus() + ")"); } d.setDrillDate(req.drillDate() == null || req.drillDate().isBlank() ? LocalDate.now().toString() : req.drillDate()); d.setParticipants(req.participants()); d.setProcess(req.process()); d.setStatus("已演练"); return ApiResp.ok(repo.save(d)); } public record EvaluateRequest(String evaluation, Integer score, String problems) { } /** 评估演练:已演练 → 已评估(记录评估结论/评分/发现问题)。 */ @PostMapping("/{id}/evaluate") public ApiResp evaluate(@PathVariable Long id, @RequestBody EvaluateRequest req) { EmergencyDrill d = repo.findById(id) .orElseThrow(() -> new NotFoundException("drill not found: " + id)); if (!"已演练".equals(d.getStatus())) { throw new ApiException(409, "仅已演练的演练可评估(当前:" + d.getStatus() + ")"); } if (req.evaluation() == null || req.evaluation().isBlank()) { throw new ApiException(400, "评估结论不能为空"); } d.setEvaluation(req.evaluation()); if (req.score() != null) { d.setScore(Math.max(0, Math.min(100, req.score()))); } d.setProblems(req.problems()); d.setStatus("已评估"); return ApiResp.ok(repo.save(d)); } }