Files
ERP/oa-backend/src/main/java/com/kaidi/oa/web/SvAccidentController.java
T
QiufengandClaude Opus 4.8 5e51dc3f56 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>
2026-06-15 19:19:15 +08:00

237 lines
10 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 6SafetyCheckController 无 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<String> ACCIDENT_TYPES = List.of("伤亡事故", "财产损失", "险肇事故", "环境污染");
private static final List<String> 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<SvAccident>> 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<SvAccident> 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<SvAccident> 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<SvAccident> 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<Void> 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<SvAccident> 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<SvAccident> 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<SvAccident> 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<SvAccident> 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));
}
}