恢复点(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>
195 lines
9.6 KiB
Java
195 lines
9.6 KiB
Java
package com.kaidi.oa.web;
|
||
|
||
import com.kaidi.oa.common.ApiException;
|
||
import com.kaidi.oa.common.ApiResp;
|
||
import com.kaidi.oa.common.Money;
|
||
import com.kaidi.oa.common.NotFoundException;
|
||
import com.kaidi.oa.domain.CompletionAcceptance;
|
||
import com.kaidi.oa.domain.MeasurementPayment;
|
||
import com.kaidi.oa.domain.SupervisionInspection;
|
||
import com.kaidi.oa.domain.SupervisionProject;
|
||
import com.kaidi.oa.repository.CompletionAcceptanceRepository;
|
||
import com.kaidi.oa.repository.MeasurementPaymentRepository;
|
||
import com.kaidi.oa.repository.SupervisionInspectionRepository;
|
||
import com.kaidi.oa.repository.SupervisionProjectRepository;
|
||
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.math.BigDecimal;
|
||
import java.time.Instant;
|
||
import java.util.List;
|
||
|
||
/**
|
||
* 工程监理部·监理项目主档。一条 SupervisionProject 代表一个监理合同项下受监工程,
|
||
* 监理记录(旁站/巡视/平行检验/监理通知)、计量支付(期次核定/支付证书)、竣工验收挂其下。
|
||
*
|
||
* 本控制器只承载监理项目本身的 CRUD + 状态流转 + 跨子表汇总驾驶舱({@code /{id}/overview}),
|
||
* 不覆盖既有 SupervisionLogController(/api/oa/supervision-logs)。新资源走 /api/oa/supervision-projects。
|
||
*
|
||
* 含监理合同额,写口受 AuthInterceptor default-deny(ADMIN/APPROVER) 保护;
|
||
* 合同额/计量金额读侧已登记进 SENSITIVE_READ_PREFIXES(由主代理中央合并前缀)。
|
||
*/
|
||
@RestController
|
||
@RequestMapping("/api/oa/supervision-projects")
|
||
public class SupervisionProjectController {
|
||
|
||
private final SupervisionProjectRepository projectRepo;
|
||
private final SupervisionInspectionRepository inspectionRepo;
|
||
private final MeasurementPaymentRepository paymentRepo;
|
||
private final CompletionAcceptanceRepository acceptanceRepo;
|
||
|
||
public SupervisionProjectController(SupervisionProjectRepository projectRepo,
|
||
SupervisionInspectionRepository inspectionRepo,
|
||
MeasurementPaymentRepository paymentRepo,
|
||
CompletionAcceptanceRepository acceptanceRepo) {
|
||
this.projectRepo = projectRepo;
|
||
this.inspectionRepo = inspectionRepo;
|
||
this.paymentRepo = paymentRepo;
|
||
this.acceptanceRepo = acceptanceRepo;
|
||
}
|
||
|
||
// ---------- 监理项目 CRUD ----------
|
||
|
||
@GetMapping
|
||
public ApiResp<List<SupervisionProject>> list(@RequestParam(required = false) String status,
|
||
@RequestParam(required = false) String stage) {
|
||
if (status != null && !status.isBlank()) {
|
||
return ApiResp.ok(projectRepo.findByStatus(status));
|
||
}
|
||
if (stage != null && !stage.isBlank()) {
|
||
return ApiResp.ok(projectRepo.findByStage(stage));
|
||
}
|
||
return ApiResp.ok(projectRepo.findAll());
|
||
}
|
||
|
||
@GetMapping("/{id}")
|
||
public ApiResp<SupervisionProject> get(@PathVariable Long id) {
|
||
return ApiResp.ok(projectRepo.findById(id)
|
||
.orElseThrow(() -> new NotFoundException("supervision project not found: " + id)));
|
||
}
|
||
|
||
public record ProjectRequest(
|
||
String code, String projectName, Long projectId, String contractNo, Long contractId,
|
||
String owner, String constructor, String chiefSupervisor, String supervisionUnit,
|
||
String scale, Double contractAmount, String startDate, String endDate,
|
||
String stage, String status, String remark) {
|
||
}
|
||
|
||
@PostMapping
|
||
public ApiResp<SupervisionProject> create(@RequestBody ProjectRequest req) {
|
||
if (req.projectName() == null || req.projectName().isBlank()) {
|
||
throw new ApiException(400, "工程名称(projectName) 不能为空");
|
||
}
|
||
SupervisionProject p = new SupervisionProject();
|
||
p.setCode(req.code() == null || req.code().isBlank()
|
||
? "JL-" + (projectRepo.count() + 1) : req.code());
|
||
p.setProjectName(req.projectName());
|
||
p.setProjectId(req.projectId());
|
||
p.setContractNo(req.contractNo());
|
||
p.setContractId(req.contractId());
|
||
p.setOwner(req.owner());
|
||
p.setConstructor(req.constructor());
|
||
p.setChiefSupervisor(req.chiefSupervisor());
|
||
p.setSupervisionUnit(req.supervisionUnit());
|
||
p.setScale(req.scale());
|
||
p.setContractAmount(Money.of(req.contractAmount()));
|
||
p.setStartDate(req.startDate());
|
||
p.setEndDate(req.endDate());
|
||
p.setStage(req.stage() == null || req.stage().isBlank() ? "施工准备" : req.stage());
|
||
p.setStatus(req.status() == null || req.status().isBlank() ? "筹备" : req.status());
|
||
p.setRemark(req.remark());
|
||
p.setCreatedAt(Instant.now());
|
||
return ApiResp.ok(projectRepo.save(p));
|
||
}
|
||
|
||
@PatchMapping("/{id}")
|
||
public ApiResp<SupervisionProject> update(@PathVariable Long id, @RequestBody ProjectRequest req) {
|
||
SupervisionProject p = projectRepo.findById(id)
|
||
.orElseThrow(() -> new NotFoundException("supervision project not found: " + id));
|
||
if (req.projectName() != null && !req.projectName().isBlank()) p.setProjectName(req.projectName());
|
||
if (req.projectId() != null) p.setProjectId(req.projectId());
|
||
if (req.contractNo() != null) p.setContractNo(req.contractNo());
|
||
if (req.contractId() != null) p.setContractId(req.contractId());
|
||
if (req.owner() != null) p.setOwner(req.owner());
|
||
if (req.constructor() != null) p.setConstructor(req.constructor());
|
||
if (req.chiefSupervisor() != null) p.setChiefSupervisor(req.chiefSupervisor());
|
||
if (req.supervisionUnit() != null) p.setSupervisionUnit(req.supervisionUnit());
|
||
if (req.scale() != null) p.setScale(req.scale());
|
||
if (req.contractAmount() != null) p.setContractAmount(Money.of(req.contractAmount()));
|
||
if (req.startDate() != null) p.setStartDate(req.startDate());
|
||
if (req.endDate() != null) p.setEndDate(req.endDate());
|
||
if (req.stage() != null && !req.stage().isBlank()) p.setStage(req.stage());
|
||
if (req.status() != null && !req.status().isBlank()) p.setStatus(req.status());
|
||
if (req.remark() != null) p.setRemark(req.remark());
|
||
return ApiResp.ok(projectRepo.save(p));
|
||
}
|
||
|
||
@DeleteMapping("/{id}")
|
||
@Transactional
|
||
public ApiResp<Void> delete(@PathVariable Long id) {
|
||
if (!projectRepo.existsById(id)) {
|
||
throw new NotFoundException("supervision project not found: " + id);
|
||
}
|
||
// 级联清理本监理项目的监理记录 / 计量支付 / 竣工验收,杜绝悬挂子表行。
|
||
inspectionRepo.deleteBySupervisionProjectId(id);
|
||
paymentRepo.deleteBySupervisionProjectId(id);
|
||
acceptanceRepo.deleteBySupervisionProjectId(id);
|
||
projectRepo.deleteById(id);
|
||
return ApiResp.ok(null);
|
||
}
|
||
|
||
// ---------- 监理驾驶舱(跨子表汇总) ----------
|
||
|
||
public record Overview(
|
||
SupervisionProject project,
|
||
int inspectionCount, int openRectifyCount,
|
||
int paymentCount, double declaredTotal, double approvedTotal, double certificateTotal,
|
||
int acceptanceCount, boolean completed) {
|
||
}
|
||
|
||
/**
|
||
* 监理项目驾驶舱:汇总监理记录数 / 未闭环整改数、计量期数与申报/核定/支付证书金额合计、
|
||
* 竣工验收次数与是否已通过。给项目卡片/详情抽屉一次取数。
|
||
*/
|
||
@GetMapping("/{id}/overview")
|
||
public ApiResp<Overview> overview(@PathVariable Long id) {
|
||
SupervisionProject p = projectRepo.findById(id)
|
||
.orElseThrow(() -> new NotFoundException("supervision project not found: " + id));
|
||
|
||
List<SupervisionInspection> inspections = inspectionRepo.findBySupervisionProjectIdOrderByIdDesc(id);
|
||
int openRectify = (int) inspections.stream()
|
||
.filter(x -> {
|
||
String rs = x.getRectifyStatus();
|
||
return rs != null && !"无需整改".equals(rs) && !"已复核闭环".equals(rs);
|
||
})
|
||
.count();
|
||
|
||
List<MeasurementPayment> payments = paymentRepo.findBySupervisionProjectIdOrderByPeriodNoAsc(id);
|
||
BigDecimal declared = BigDecimal.ZERO;
|
||
BigDecimal approved = BigDecimal.ZERO;
|
||
BigDecimal certificate = BigDecimal.ZERO;
|
||
for (MeasurementPayment mp : payments) {
|
||
declared = Money.add(declared, Money.nz(mp.getDeclaredAmount()));
|
||
approved = Money.add(approved, Money.nz(mp.getApprovedAmount()));
|
||
certificate = Money.add(certificate, Money.nz(mp.getCertificateAmount()));
|
||
}
|
||
|
||
List<CompletionAcceptance> acceptances = acceptanceRepo.findBySupervisionProjectIdOrderByIdDesc(id);
|
||
boolean completed = acceptances.stream()
|
||
.anyMatch(a -> "正式竣工验收".equals(a.getAcceptType()) && "已通过".equals(a.getStatus()));
|
||
|
||
return ApiResp.ok(new Overview(p,
|
||
inspections.size(), openRectify,
|
||
payments.size(), declared.doubleValue(), approved.doubleValue(), certificate.doubleValue(),
|
||
acceptances.size(), completed));
|
||
}
|
||
}
|