恢复点(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>
237 lines
10 KiB
Java
237 lines
10 KiB
Java
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<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));
|
||
}
|
||
}
|