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.SupervisionProject; import com.kaidi.oa.domain.SvAccident; import com.kaidi.oa.repository.SupervisionProjectRepository; import com.kaidi.oa.repository.SvAccidentRepository; 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.LocalDate; import java.util.List; /** * 工程监理部 · 安全事故快速上报(补 Gap 6:SafetyCheckController 无 accident/伤亡专属流程)。 * * 完整流程: * 快速上报(POST /)→ 已上报 * 启动应急预案(POST /{id}/activate-emergency)→ 应急处置中 * 移交调查(POST /{id}/investigate)→ 调查中 * 归档整改措施(POST /{id}/archive)→ 整改归档 * 确认闭环(POST /{id}/close)→ 已闭环 * * 资源路径 /api/oa/sv-accidents。 */ @RestController @RequestMapping("/api/oa/sv-accidents") public class SvAccidentController { private static final List ACCIDENT_TYPES = List.of("伤亡事故", "财产损失", "险肇事故", "环境污染"); private static final List LEVELS = List.of("一般事故", "较大事故", "重大事故", "特别重大事故"); private final SvAccidentRepository accidentRepo; private final SupervisionProjectRepository projectRepo; public SvAccidentController(SvAccidentRepository accidentRepo, SupervisionProjectRepository projectRepo) { this.accidentRepo = accidentRepo; this.projectRepo = projectRepo; } @GetMapping public ApiResp> list( @RequestParam(required = false) Long projectId, @RequestParam(required = false) String status, @RequestParam(required = false) String level) { if (projectId != null) { return ApiResp.ok(accidentRepo.findBySupervisionProjectIdOrderByIdDesc(projectId)); } if (status != null && !status.isBlank()) { return ApiResp.ok(accidentRepo.findByStatus(status)); } if (level != null && !level.isBlank()) { return ApiResp.ok(accidentRepo.findByLevel(level)); } return ApiResp.ok(accidentRepo.findAll()); } @GetMapping("/{id}") public ApiResp get(@PathVariable Long id) { return ApiResp.ok(find(id)); } public record ReportRequest( Long supervisionProjectId, String occurTime, String location, String accidentType, String level, String casualtyInfo, String preliminaryCause, String reporter, String attachmentRefs) { } /** * 快速上报安全事故:填基本信息 + 伤亡情况 + 初步原因,状态置「已上报」。 * 重大/特别重大事故自动标记预警(level 字段)。 */ @PostMapping @Transactional public ApiResp report(@RequestBody ReportRequest req) { if (req.supervisionProjectId() == null) { throw new ApiException(400, "所属监理项目(supervisionProjectId) 不能为空"); } SupervisionProject parent = projectRepo.findById(req.supervisionProjectId()) .orElseThrow(() -> new NotFoundException("supervision project not found: " + req.supervisionProjectId())); if (req.occurTime() == null || req.occurTime().isBlank()) { throw new ApiException(400, "事故发生时间(occurTime) 不能为空"); } if (req.accidentType() != null && !req.accidentType().isBlank() && !ACCIDENT_TYPES.contains(req.accidentType())) { throw new ApiException(400, "事故类型无效,允许:" + ACCIDENT_TYPES); } if (req.level() != null && !req.level().isBlank() && !LEVELS.contains(req.level())) { throw new ApiException(400, "事故等级无效,允许:" + LEVELS); } SvAccident a = new SvAccident(); a.setSupervisionProjectId(parent.getId()); a.setProjectCode(parent.getCode()); a.setAccidentNo("SGT-" + (accidentRepo.count() + 1)); a.setOccurTime(req.occurTime()); a.setLocation(req.location()); a.setAccidentType(req.accidentType() == null || req.accidentType().isBlank() ? "伤亡事故" : req.accidentType()); a.setLevel(req.level() == null || req.level().isBlank() ? "一般事故" : req.level()); a.setCasualtyInfo(req.casualtyInfo()); a.setPreliminaryCause(req.preliminaryCause()); a.setReporter(req.reporter()); a.setReportTime(Instant.now()); a.setEmergencyActivated(false); a.setAttachmentRefs(req.attachmentRefs()); a.setStatus("已上报"); a.setCreatedAt(Instant.now()); return ApiResp.ok(accidentRepo.save(a)); } @PatchMapping("/{id}") public ApiResp update(@PathVariable Long id, @RequestBody ReportRequest req) { SvAccident a = find(id); if ("已闭环".equals(a.getStatus())) { throw new ApiException(409, "已闭环的事故记录不可修改"); } if (req.occurTime() != null && !req.occurTime().isBlank()) a.setOccurTime(req.occurTime()); if (req.location() != null) a.setLocation(req.location()); if (req.accidentType() != null && !req.accidentType().isBlank()) { if (!ACCIDENT_TYPES.contains(req.accidentType())) throw new ApiException(400, "事故类型无效:" + ACCIDENT_TYPES); a.setAccidentType(req.accidentType()); } if (req.level() != null && !req.level().isBlank()) { if (!LEVELS.contains(req.level())) throw new ApiException(400, "事故等级无效:" + LEVELS); a.setLevel(req.level()); } if (req.casualtyInfo() != null) a.setCasualtyInfo(req.casualtyInfo()); if (req.preliminaryCause() != null) a.setPreliminaryCause(req.preliminaryCause()); if (req.attachmentRefs() != null) a.setAttachmentRefs(req.attachmentRefs()); return ApiResp.ok(accidentRepo.save(a)); } @DeleteMapping("/{id}") public ApiResp delete(@PathVariable Long id) { SvAccident a = find(id); if (!"已上报".equals(a.getStatus())) { throw new ApiException(409, "仅「已上报」状态的事故记录可删除(撤回)"); } accidentRepo.deleteById(id); return ApiResp.ok(null); } // ---------- 业务动作 ---------- public record EmergencyRequest(String emergencyPlanRef, String emergencyDesc) { } /** * 启动应急预案:已上报 → 应急处置中。 * 记录应急预案引用(关联 SvPlan.planType=监理应急预案)。 */ @PostMapping("/{id}/activate-emergency") @Transactional public ApiResp activateEmergency(@PathVariable Long id, @RequestBody EmergencyRequest req) { SvAccident a = find(id); if (!"已上报".equals(a.getStatus())) { throw new ApiException(409, "仅「已上报」状态可启动应急,当前:" + a.getStatus()); } if (Boolean.TRUE.equals(a.getEmergencyActivated())) { throw new ApiException(409, "应急预案已启动,请勿重复"); } a.setEmergencyActivated(true); a.setEmergencyPlanRef(req.emergencyPlanRef()); a.setEmergencyDesc(req.emergencyDesc()); a.setStatus("应急处置中"); return ApiResp.ok(accidentRepo.save(a)); } public record InvestigateRequest(String investigationResult) { } /** 移交/进入事故调查:应急处置中 → 调查中。 */ @PostMapping("/{id}/investigate") @Transactional public ApiResp investigate(@PathVariable Long id, @RequestBody InvestigateRequest req) { SvAccident a = find(id); if (!"应急处置中".equals(a.getStatus())) { throw new ApiException(409, "请先启动应急预案,再进入调查阶段"); } if (req.investigationResult() != null) a.setInvestigationResult(req.investigationResult()); a.setStatus("调查中"); return ApiResp.ok(accidentRepo.save(a)); } public record ArchiveRequest(String correctiveMeasures, String correctiveDate, String investigationResult) { } /** 归档整改措施:调查中 → 整改归档。 */ @PostMapping("/{id}/archive") @Transactional public ApiResp archive(@PathVariable Long id, @RequestBody ArchiveRequest req) { SvAccident a = find(id); if (!"调查中".equals(a.getStatus())) { throw new ApiException(409, "请先进入调查阶段,再归档整改措施"); } if (req.correctiveMeasures() == null || req.correctiveMeasures().isBlank()) { throw new ApiException(400, "整改措施(correctiveMeasures) 不能为空"); } if (req.investigationResult() != null && !req.investigationResult().isBlank()) { a.setInvestigationResult(req.investigationResult()); } a.setCorrectiveMeasures(req.correctiveMeasures()); a.setCorrectiveDate(req.correctiveDate() == null || req.correctiveDate().isBlank() ? LocalDate.now().toString() : req.correctiveDate()); a.setStatus("整改归档"); return ApiResp.ok(accidentRepo.save(a)); } /** 确认闭环:整改归档 → 已闭环。 */ @PostMapping("/{id}/close") @Transactional public ApiResp close(@PathVariable Long id) { SvAccident a = find(id); if (!"整改归档".equals(a.getStatus())) { throw new ApiException(409, "请先完成调查归档,再闭环"); } a.setStatus("已闭环"); return ApiResp.ok(accidentRepo.save(a)); } private SvAccident find(Long id) { return accidentRepo.findById(id) .orElseThrow(() -> new NotFoundException("sv-accident not found: " + id)); } }