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>
This commit is contained in:
@@ -0,0 +1,243 @@
|
||||
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.SvSequenceInspect;
|
||||
import com.kaidi.oa.repository.SupervisionProjectRepository;
|
||||
import com.kaidi.oa.repository.SvSequenceInspectRepository;
|
||||
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.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 工程监理部 · 工序验收工单(施工过程质量控制 Gap 3 核心补齐)。
|
||||
*
|
||||
* 完整工单状态机:
|
||||
* 施工方提交申请(POST /) → 待接单
|
||||
* 监理接单(POST /{id}/accept)→ 检查中
|
||||
* 现场检查录入 + 出结论(POST /{id}/conclude)→ 已出结论
|
||||
* 合格→自动生成验收记录表编号 + status=已生成记录
|
||||
* 不合格/整改→自动生成整改通知单编号
|
||||
*
|
||||
* 另提供:GET /compare-analysis(平行检验偏差对比聚合,补 Gap 3 平行检验对比逻辑)。
|
||||
*
|
||||
* 资源路径 /api/oa/sv-sequence-inspects。
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/oa/sv-sequence-inspects")
|
||||
public class SvSequenceInspectController {
|
||||
|
||||
private static final List<String> INSPECT_LEVELS = List.of("检验批", "分项工程", "分部工程");
|
||||
private static final List<String> CONCLUSIONS = List.of("合格", "整改", "不合格");
|
||||
|
||||
private final SvSequenceInspectRepository repo;
|
||||
private final SupervisionProjectRepository projectRepo;
|
||||
|
||||
public SvSequenceInspectController(SvSequenceInspectRepository repo,
|
||||
SupervisionProjectRepository projectRepo) {
|
||||
this.repo = repo;
|
||||
this.projectRepo = projectRepo;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ApiResp<List<SvSequenceInspect>> list(
|
||||
@RequestParam(required = false) Long projectId,
|
||||
@RequestParam(required = false) String status,
|
||||
@RequestParam(required = false) String inspectLevel) {
|
||||
if (projectId != null) {
|
||||
return ApiResp.ok(repo.findBySupervisionProjectIdOrderByIdDesc(projectId));
|
||||
}
|
||||
if (status != null && !status.isBlank()) {
|
||||
return ApiResp.ok(repo.findByStatus(status));
|
||||
}
|
||||
if (inspectLevel != null && !inspectLevel.isBlank()) {
|
||||
return ApiResp.ok(repo.findByInspectLevel(inspectLevel));
|
||||
}
|
||||
return ApiResp.ok(repo.findAll());
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ApiResp<SvSequenceInspect> get(@PathVariable Long id) {
|
||||
return ApiResp.ok(find(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 平行检验偏差对比分析(Gap 3 补齐)。
|
||||
* 聚合指定监理项目的验收结论统计:合格数/整改数/不合格数 + 合格率,
|
||||
* 为监理与施工自检比对偏差分析提供数据基础。
|
||||
*/
|
||||
@GetMapping("/compare-analysis")
|
||||
public ApiResp<Map<String, Object>> compareAnalysis(@RequestParam Long projectId) {
|
||||
if (!projectRepo.existsById(projectId)) {
|
||||
throw new NotFoundException("supervision project not found: " + projectId);
|
||||
}
|
||||
List<SvSequenceInspect> all = repo.findBySupervisionProjectIdOrderByIdDesc(projectId);
|
||||
long total = all.size();
|
||||
long qualified = all.stream().filter(r -> "合格".equals(r.getConclusion())).count();
|
||||
long rectify = all.stream().filter(r -> "整改".equals(r.getConclusion())).count();
|
||||
long unqualified = all.stream().filter(r -> "不合格".equals(r.getConclusion())).count();
|
||||
long pending = all.stream().filter(r -> r.getConclusion() == null).count();
|
||||
double qualifiedRate = total > 0 ? Math.round(qualified * 10000.0 / total) / 100.0 : 0.0;
|
||||
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("projectId", projectId);
|
||||
result.put("total", total);
|
||||
result.put("qualified", qualified);
|
||||
result.put("rectify", rectify);
|
||||
result.put("unqualified", unqualified);
|
||||
result.put("pending", pending);
|
||||
result.put("qualifiedRate", qualifiedRate);
|
||||
result.put("deviationAlert", unqualified > 0 || (total > 0 && qualifiedRate < 80.0));
|
||||
return ApiResp.ok(result);
|
||||
}
|
||||
|
||||
public record ApplyRequest(
|
||||
Long supervisionProjectId, String inspectLevel, String partName, String location,
|
||||
String applyDate, String applicant, String selfCheckRecord) {
|
||||
}
|
||||
|
||||
/** 施工方提交验收申请,生成工单,状态=待接单。 */
|
||||
@PostMapping
|
||||
@Transactional
|
||||
public ApiResp<SvSequenceInspect> apply(@RequestBody ApplyRequest 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.partName() == null || req.partName().isBlank()) {
|
||||
throw new ApiException(400, "分部/分项名称(partName) 不能为空");
|
||||
}
|
||||
String level = req.inspectLevel() == null || req.inspectLevel().isBlank()
|
||||
? "检验批" : req.inspectLevel();
|
||||
if (!INSPECT_LEVELS.contains(level)) {
|
||||
throw new ApiException(400, "验收层级无效,允许:" + INSPECT_LEVELS);
|
||||
}
|
||||
SvSequenceInspect s = new SvSequenceInspect();
|
||||
s.setSupervisionProjectId(parent.getId());
|
||||
s.setProjectCode(parent.getCode());
|
||||
s.setOrderNo("GX-" + (repo.count() + 1));
|
||||
s.setInspectLevel(level);
|
||||
s.setPartName(req.partName());
|
||||
s.setLocation(req.location());
|
||||
s.setApplyDate(req.applyDate() == null || req.applyDate().isBlank()
|
||||
? LocalDate.now().toString() : req.applyDate());
|
||||
s.setApplicant(req.applicant());
|
||||
s.setSelfCheckRecord(req.selfCheckRecord());
|
||||
s.setRecordGenerated(false);
|
||||
s.setStatus("待接单");
|
||||
s.setCreatedAt(Instant.now());
|
||||
return ApiResp.ok(repo.save(s));
|
||||
}
|
||||
|
||||
@PatchMapping("/{id}")
|
||||
public ApiResp<SvSequenceInspect> update(@PathVariable Long id, @RequestBody ApplyRequest req) {
|
||||
SvSequenceInspect s = find(id);
|
||||
if (!"待接单".equals(s.getStatus())) {
|
||||
throw new ApiException(409, "仅「待接单」的工单可修改申请信息,当前:" + s.getStatus());
|
||||
}
|
||||
if (req.partName() != null && !req.partName().isBlank()) s.setPartName(req.partName());
|
||||
if (req.location() != null) s.setLocation(req.location());
|
||||
if (req.applicant() != null) s.setApplicant(req.applicant());
|
||||
if (req.selfCheckRecord() != null) s.setSelfCheckRecord(req.selfCheckRecord());
|
||||
return ApiResp.ok(repo.save(s));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ApiResp<Void> delete(@PathVariable Long id) {
|
||||
SvSequenceInspect s = find(id);
|
||||
if (!"待接单".equals(s.getStatus())) {
|
||||
throw new ApiException(409, "仅「待接单」的工单可撤回删除");
|
||||
}
|
||||
repo.deleteById(id);
|
||||
return ApiResp.ok(null);
|
||||
}
|
||||
|
||||
// ---------- 业务动作 ----------
|
||||
|
||||
public record AcceptRequest(String inspector, String acceptDate) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 监理接单:待接单 → 检查中,记录接单监理员和接单日期。
|
||||
*/
|
||||
@PostMapping("/{id}/accept")
|
||||
@Transactional
|
||||
public ApiResp<SvSequenceInspect> accept(@PathVariable Long id, @RequestBody AcceptRequest req) {
|
||||
SvSequenceInspect s = find(id);
|
||||
if (!"待接单".equals(s.getStatus())) {
|
||||
throw new ApiException(409, "仅「待接单」状态可接单,当前:" + s.getStatus());
|
||||
}
|
||||
if (req.inspector() == null || req.inspector().isBlank()) {
|
||||
throw new ApiException(400, "接单监理员(inspector) 不能为空");
|
||||
}
|
||||
s.setInspector(req.inspector());
|
||||
s.setAcceptDate(req.acceptDate() == null || req.acceptDate().isBlank()
|
||||
? LocalDate.now().toString() : req.acceptDate());
|
||||
s.setStatus("检查中");
|
||||
return ApiResp.ok(repo.save(s));
|
||||
}
|
||||
|
||||
public record ConcludeRequest(
|
||||
String checkDate, String checkContent, String photoRefs,
|
||||
String conclusion, String conclusionRemark) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 出结论:检查中 → 已出结论。
|
||||
* 合格:自动生成验收记录表编号(YSJ-GX-工单号),recordGenerated=true,status=已生成记录。
|
||||
* 整改/不合格:自动生成整改通知单编号(ZG-工单号)。
|
||||
*/
|
||||
@PostMapping("/{id}/conclude")
|
||||
@Transactional
|
||||
public ApiResp<SvSequenceInspect> conclude(@PathVariable Long id, @RequestBody ConcludeRequest req) {
|
||||
SvSequenceInspect s = find(id);
|
||||
if (!"检查中".equals(s.getStatus())) {
|
||||
throw new ApiException(409, "仅「检查中」状态可出结论,当前:" + s.getStatus());
|
||||
}
|
||||
if (req.conclusion() == null || req.conclusion().isBlank()) {
|
||||
throw new ApiException(400, "验收结论(conclusion) 不能为空");
|
||||
}
|
||||
if (!CONCLUSIONS.contains(req.conclusion())) {
|
||||
throw new ApiException(400, "结论无效,允许:" + CONCLUSIONS);
|
||||
}
|
||||
s.setCheckDate(req.checkDate() == null || req.checkDate().isBlank()
|
||||
? LocalDate.now().toString() : req.checkDate());
|
||||
if (req.checkContent() != null) s.setCheckContent(req.checkContent());
|
||||
if (req.photoRefs() != null) s.setPhotoRefs(req.photoRefs());
|
||||
s.setConclusion(req.conclusion());
|
||||
if (req.conclusionRemark() != null) s.setConclusionRemark(req.conclusionRemark());
|
||||
|
||||
if ("合格".equals(req.conclusion())) {
|
||||
// 自动生成验收记录表
|
||||
s.setRecordGenerated(true);
|
||||
s.setRecordNo("YSJ-" + s.getOrderNo());
|
||||
s.setStatus("已生成记录");
|
||||
} else {
|
||||
// 整改或不合格:生成整改通知单编号
|
||||
s.setRectifyNoticeNo("ZG-" + s.getOrderNo());
|
||||
s.setStatus("已出结论");
|
||||
}
|
||||
return ApiResp.ok(repo.save(s));
|
||||
}
|
||||
|
||||
private SvSequenceInspect find(Long id) {
|
||||
return repo.findById(id)
|
||||
.orElseThrow(() -> new NotFoundException("sv-sequence-inspect not found: " + id));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user