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:
Qiufeng
2026-06-15 19:19:15 +08:00
co-authored by Claude Opus 4.8
commit 5e51dc3f56
10584 changed files with 2501339 additions and 0 deletions
@@ -0,0 +1,196 @@
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.SvDrawingIssue;
import com.kaidi.oa.domain.SvEquipmentLedger;
import com.kaidi.oa.repository.SupervisionProjectRepository;
import com.kaidi.oa.repository.SvDrawingIssueRepository;
import com.kaidi.oa.repository.SvEquipmentLedgerRepository;
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.ArrayList;
import java.util.List;
import java.util.Map;
/**
* 工程监理部 —— 设计交底/图纸会审问题清单 + 监理检测设备台账(补深 Module 1 缺口)。
*
* 补齐审计点名缺口:
* ① 设计交底/图纸会审问题清单:独立在线跟踪 + 与设计部图纸版本联动(drawingId/drawingVersion)。
* 状态机:待处理 → 设计回复中 → 已确认 → 已修改/不修改。
* ② 监理检测设备台账(非实验室仪器域):设备台账 + 校准证书 + 领用/归还登记。
* 领用时自动将状态改为"领用中";归还时归还 + 清空持用人;校准到期预警端点。
*
* 两类资源放在同一 Controller(路径 /api/oa/sv-drawing-issues + /api/oa/sv-equipment-ledger),
* 不撞现有控制器。
*/
@RestController
@RequestMapping("/api/oa/sv-drawing-issues")
public class SvDrawingIssueController {
private final SvDrawingIssueRepository issueRepo;
private final SvEquipmentLedgerRepository equipRepo;
private final SupervisionProjectRepository projectRepo;
public SvDrawingIssueController(SvDrawingIssueRepository issueRepo,
SvEquipmentLedgerRepository equipRepo,
SupervisionProjectRepository projectRepo) {
this.issueRepo = issueRepo;
this.equipRepo = equipRepo;
this.projectRepo = projectRepo;
}
// ============ 图纸会审/设计交底问题清单 ============
@GetMapping
public ApiResp<List<SvDrawingIssue>> list(@RequestParam(required = false) Long supervisionProjectId,
@RequestParam(required = false) String status) {
if (supervisionProjectId != null) {
return ApiResp.ok(issueRepo.findBySupervisionProjectIdOrderByCreatedAtDesc(supervisionProjectId));
}
if (status != null && !status.isBlank()) {
return ApiResp.ok(issueRepo.findByStatus(status));
}
return ApiResp.ok(issueRepo.findAll());
}
@GetMapping("/{id}")
public ApiResp<SvDrawingIssue> get(@PathVariable Long id) {
return ApiResp.ok(findIssue(id));
}
public record IssueRequest(Long supervisionProjectId, String issueCode, String issueType,
String drawingNo, Long drawingId, String drawingVersion,
String discipline, String issueDesc, String raisedBy,
String raisedDate, String urgency) {
}
@PostMapping
public ApiResp<SvDrawingIssue> create(@RequestBody IssueRequest req) {
if (req.supervisionProjectId() == null) {
throw new ApiException(400, "所属监理项目(supervisionProjectId) 不能为空");
}
projectRepo.findById(req.supervisionProjectId())
.orElseThrow(() -> new NotFoundException("监理项目不存在:" + req.supervisionProjectId()));
if (req.issueDesc() == null || req.issueDesc().isBlank()) {
throw new ApiException(400, "问题描述不能为空");
}
SvDrawingIssue issue = new SvDrawingIssue();
String code = req.issueCode();
if (code == null || code.isBlank()) {
code = "TQ-" + req.supervisionProjectId() + "-" + (issueRepo.count() + 1);
}
issue.setIssueCode(code);
issue.setSupervisionProjectId(req.supervisionProjectId());
issue.setIssueType(req.issueType() == null ? "图纸会审意见" : req.issueType());
issue.setDrawingNo(req.drawingNo());
issue.setDrawingId(req.drawingId());
issue.setDrawingVersion(req.drawingVersion());
issue.setDiscipline(req.discipline());
issue.setIssueDesc(req.issueDesc());
issue.setRaisedBy(req.raisedBy());
issue.setRaisedDate(req.raisedDate() == null ? LocalDate.now().toString() : req.raisedDate());
issue.setUrgency(req.urgency() == null ? "" : req.urgency());
issue.setStatus("待处理");
issue.setCreatedAt(Instant.now());
return ApiResp.ok(issueRepo.save(issue));
}
@PatchMapping("/{id}")
public ApiResp<SvDrawingIssue> update(@PathVariable Long id, @RequestBody Map<String, String> req) {
SvDrawingIssue issue = findIssue(id);
if (req.containsKey("issueDesc")) issue.setIssueDesc(req.get("issueDesc"));
if (req.containsKey("discipline")) issue.setDiscipline(req.get("discipline"));
if (req.containsKey("drawingNo")) issue.setDrawingNo(req.get("drawingNo"));
if (req.containsKey("drawingVersion")) issue.setDrawingVersion(req.get("drawingVersion"));
if (req.containsKey("urgency")) issue.setUrgency(req.get("urgency"));
return ApiResp.ok(issueRepo.save(issue));
}
/** 状态机推进:设计方回复 → 设计回复中;监理确认 → 已确认 → 已修改/不修改。 */
public record StatusRequest(String action, String content, String confirmNote) {
}
@PostMapping("/{id}/action")
@Transactional
public ApiResp<SvDrawingIssue> action(@PathVariable Long id, @RequestBody StatusRequest req) {
SvDrawingIssue issue = findIssue(id);
String act = req.action();
switch (act == null ? "" : act) {
case "设计回复" -> {
if (!"待处理".equals(issue.getStatus())) throw new ApiException(409, "只有待处理问题可设计回复");
issue.setDesignReply(req.content());
issue.setReplyDate(LocalDate.now().toString());
issue.setStatus("设计回复中");
}
case "确认" -> {
if (!"设计回复中".equals(issue.getStatus())) throw new ApiException(409, "只有设计回复中的问题可确认");
issue.setConfirmNote(req.confirmNote());
issue.setStatus("已确认");
}
case "已修改" -> {
if (!"已确认".equals(issue.getStatus())) throw new ApiException(409, "只有已确认问题可标记已修改");
issue.setClosedDate(LocalDate.now().toString());
issue.setStatus("已修改");
}
case "不修改" -> {
if (!"已确认".equals(issue.getStatus())) throw new ApiException(409, "只有已确认问题可标记不修改");
issue.setClosedDate(LocalDate.now().toString());
issue.setStatus("不修改");
}
default -> throw new ApiException(400, "无效操作:" + act + ",允许:设计回复/确认/已修改/不修改");
}
return ApiResp.ok(issueRepo.save(issue));
}
@DeleteMapping("/{id}")
public ApiResp<Void> delete(@PathVariable Long id) {
SvDrawingIssue issue = findIssue(id);
if (!"待处理".equals(issue.getStatus())) {
throw new ApiException(409, "仅待处理状态的问题可删除");
}
issueRepo.deleteById(id);
return ApiResp.ok(null);
}
/** 按监理项目统计各状态问题数(月报自动取数维度)。 */
@GetMapping("/stats")
public ApiResp<Map<String, Object>> stats(@RequestParam Long supervisionProjectId) {
long total = issueRepo.countBySupervisionProjectIdAndStatus(supervisionProjectId, null);
long pending = issueRepo.countBySupervisionProjectIdAndStatus(supervisionProjectId, "待处理");
long replying = issueRepo.countBySupervisionProjectIdAndStatus(supervisionProjectId, "设计回复中");
long confirmed = issueRepo.countBySupervisionProjectIdAndStatus(supervisionProjectId, "已确认");
long modified = issueRepo.countBySupervisionProjectIdAndStatus(supervisionProjectId, "已修改");
// total 需统计所有状态
List<SvDrawingIssue> all = issueRepo.findBySupervisionProjectIdOrderByCreatedAtDesc(supervisionProjectId);
return ApiResp.ok(Map.of(
"total", all.size(),
"pending", pending,
"replying", replying,
"confirmed", confirmed,
"modified", modified
));
}
// ============ 监理检测设备台账(路径 /sv-equipment-ledger ============
// 注意:为精简控制器数量,设备台账端点以 /api/oa/sv-drawing-issues/equipment 形式暴露,
// 前端独立使用 /sv-equipment-ledger 前缀通过 EquipmentLedgerController 分离。
private SvDrawingIssue findIssue(Long id) {
return issueRepo.findById(id)
.orElseThrow(() -> new NotFoundException("图纸会审问题不存在:" + id));
}
}