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,221 @@
|
||||
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.FertGasEmission;
|
||||
import com.kaidi.oa.repository.FertGasEmissionRepository;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
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.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 生物质肥料制造中心·EHS 废气排放监测(需求 §8,补深审计 PARTIAL[med])。
|
||||
*
|
||||
* <ul>
|
||||
* <li>按排放口录入监测数据(NH₃/H₂S/臭气指数/粉尘浓度),与限值对标后自动判定超标并拼装超标详情;</li>
|
||||
* <li>GET /alerts → 超标未整改记录列表(环保预警核心);</li>
|
||||
* <li>POST /{id}/rectify → 填整改措施,标记已整改;</li>
|
||||
* <li>GET /trend?outlet=&from=&to= → 指定排放口按日期区间折线趋势(NH₃/H₂S/臭气历史数据);</li>
|
||||
* <li>GET /summary → 各排放口超标次数汇总(EHS 看板)。</li>
|
||||
* </ul>
|
||||
*
|
||||
* 写口受 AuthInterceptor default-deny(ADMIN/APPROVER) 保护,前缀已登记入 FINANCE/SENSITIVE_READ。
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/oa/fert-gas-emissions")
|
||||
public class FertGasEmissionController {
|
||||
|
||||
private final FertGasEmissionRepository repo;
|
||||
|
||||
public FertGasEmissionController(FertGasEmissionRepository repo) {
|
||||
this.repo = repo;
|
||||
}
|
||||
|
||||
// ---------- 列表 ----------
|
||||
|
||||
@GetMapping
|
||||
public ApiResp<List<FertGasEmission>> list(@RequestParam(required = false) String outlet,
|
||||
@RequestParam(required = false) Boolean overOnly) {
|
||||
if (Boolean.TRUE.equals(overOnly)) return ApiResp.ok(repo.findByOverLimitTrue());
|
||||
if (outlet != null && !outlet.isBlank()) return ApiResp.ok(repo.findByOutletCode(outlet.trim()));
|
||||
return ApiResp.ok(repo.findAll());
|
||||
}
|
||||
|
||||
/** 超标未整改列表(环保预警)。 */
|
||||
@GetMapping("/alerts")
|
||||
public ApiResp<List<FertGasEmission>> alerts() {
|
||||
return ApiResp.ok(repo.findByRectifiedFalseAndOverLimitTrue());
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ApiResp<FertGasEmission> get(@PathVariable Long id) {
|
||||
return ApiResp.ok(find(id));
|
||||
}
|
||||
|
||||
// ---------- 录入 / 更新 ----------
|
||||
|
||||
public record EmissionRequest(String monitorDate, String timePeriod, String outletCode, String outletDesc,
|
||||
Double nh3Concentration, Double nh3Limit,
|
||||
Double h2sConcentration, Double h2sLimit,
|
||||
Double odorIndex, Double odorLimit,
|
||||
Double dustConcentration, Double dustLimit,
|
||||
String dataSource, Boolean deodorizerNormal,
|
||||
String recorder, String remark) {
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ApiResp<FertGasEmission> create(@RequestBody EmissionRequest req) {
|
||||
if (req.outletCode() == null || req.outletCode().isBlank()) {
|
||||
throw new ApiException(400, "排放口编号(outletCode) 不能为空");
|
||||
}
|
||||
FertGasEmission e = new FertGasEmission();
|
||||
e.setCreatedAt(Instant.now());
|
||||
apply(e, req);
|
||||
return ApiResp.ok(repo.save(e));
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ApiResp<FertGasEmission> update(@PathVariable Long id, @RequestBody EmissionRequest req) {
|
||||
FertGasEmission e = find(id);
|
||||
apply(e, req);
|
||||
return ApiResp.ok(repo.save(e));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ApiResp<Void> delete(@PathVariable Long id) {
|
||||
repo.delete(find(id));
|
||||
return ApiResp.ok();
|
||||
}
|
||||
|
||||
private void apply(FertGasEmission e, EmissionRequest req) {
|
||||
e.setMonitorDate(req.monitorDate() == null || req.monitorDate().isBlank()
|
||||
? LocalDate.now().toString() : req.monitorDate());
|
||||
e.setTimePeriod(req.timePeriod());
|
||||
if (req.outletCode() != null && !req.outletCode().isBlank()) e.setOutletCode(req.outletCode().trim());
|
||||
e.setOutletDesc(req.outletDesc());
|
||||
e.setNh3Concentration(bd(req.nh3Concentration()));
|
||||
e.setNh3Limit(bd(req.nh3Limit()));
|
||||
e.setH2sConcentration(bd(req.h2sConcentration()));
|
||||
e.setH2sLimit(bd(req.h2sLimit()));
|
||||
e.setOdorIndex(bd(req.odorIndex()));
|
||||
e.setOdorLimit(bd(req.odorLimit()));
|
||||
e.setDustConcentration(bd(req.dustConcentration()));
|
||||
e.setDustLimit(bd(req.dustLimit()));
|
||||
e.setDataSource(req.dataSource() == null || req.dataSource().isBlank() ? "手工录入" : req.dataSource());
|
||||
e.setDeodorizerNormal(req.deodorizerNormal() == null ? true : req.deodorizerNormal());
|
||||
e.setRecorder(req.recorder());
|
||||
e.setRemark(req.remark());
|
||||
// 自动判定超标。
|
||||
judgeOverLimit(e);
|
||||
}
|
||||
|
||||
/** 逐项比对限值,自动设 overLimit + overDetail。 */
|
||||
private static void judgeOverLimit(FertGasEmission e) {
|
||||
List<String> items = new ArrayList<>();
|
||||
if (e.getNh3Limit().signum() > 0 && e.getNh3Concentration().compareTo(e.getNh3Limit()) > 0) {
|
||||
items.add("NH3超标:" + e.getNh3Concentration() + ">" + e.getNh3Limit() + "mg/m³");
|
||||
}
|
||||
if (e.getH2sLimit().signum() > 0 && e.getH2sConcentration().compareTo(e.getH2sLimit()) > 0) {
|
||||
items.add("H2S超标:" + e.getH2sConcentration() + ">" + e.getH2sLimit() + "mg/m³");
|
||||
}
|
||||
if (e.getOdorLimit().signum() > 0 && e.getOdorIndex().compareTo(e.getOdorLimit()) > 0) {
|
||||
items.add("臭气超标:" + e.getOdorIndex() + ">" + e.getOdorLimit());
|
||||
}
|
||||
if (e.getDustLimit().signum() > 0 && e.getDustConcentration().compareTo(e.getDustLimit()) > 0) {
|
||||
items.add("粉尘超标:" + e.getDustConcentration() + ">" + e.getDustLimit() + "mg/m³");
|
||||
}
|
||||
e.setOverLimit(!items.isEmpty());
|
||||
e.setOverDetail(items.isEmpty() ? null : String.join("; ", items));
|
||||
if (!items.isEmpty()) e.setRectified(false);
|
||||
}
|
||||
|
||||
// ---------- 整改 ----------
|
||||
|
||||
public record RectifyRequest(String rectifyNote) {
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/rectify")
|
||||
public ApiResp<FertGasEmission> rectify(@PathVariable Long id, @RequestBody RectifyRequest req) {
|
||||
FertGasEmission e = find(id);
|
||||
if (!Boolean.TRUE.equals(e.getOverLimit())) {
|
||||
throw new ApiException(409, "该记录未超标,无需整改");
|
||||
}
|
||||
if (req.rectifyNote() == null || req.rectifyNote().isBlank()) {
|
||||
throw new ApiException(400, "整改措施(rectifyNote) 不能为空");
|
||||
}
|
||||
e.setRectifyNote(req.rectifyNote().trim());
|
||||
e.setRectified(true);
|
||||
return ApiResp.ok(repo.save(e));
|
||||
}
|
||||
|
||||
// ---------- 趋势(按排放口 + 日期区间) ----------
|
||||
|
||||
public record TrendPoint(String monitorDate, BigDecimal nh3, BigDecimal h2s, BigDecimal odorIndex,
|
||||
BigDecimal dust, Boolean overLimit) {
|
||||
}
|
||||
|
||||
@GetMapping("/trend")
|
||||
public ApiResp<List<TrendPoint>> trend(@RequestParam(required = false) String outlet,
|
||||
@RequestParam(required = false) String from,
|
||||
@RequestParam(required = false) String to) {
|
||||
String f = from == null || from.isBlank() ? LocalDate.now().minusMonths(1).toString() : from;
|
||||
String t = to == null || to.isBlank() ? LocalDate.now().toString() : to;
|
||||
List<FertGasEmission> data = repo.findByMonitorDateBetweenOrderByMonitorDateDesc(f, t);
|
||||
return ApiResp.ok(data.stream()
|
||||
.filter(e -> outlet == null || outlet.isBlank() || outlet.trim().equals(e.getOutletCode()))
|
||||
.map(e -> new TrendPoint(e.getMonitorDate(), e.getNh3Concentration(),
|
||||
e.getH2sConcentration(), e.getOdorIndex(), e.getDustConcentration(), e.getOverLimit()))
|
||||
.toList());
|
||||
}
|
||||
|
||||
// ---------- 各排放口超标汇总(EHS 看板) ----------
|
||||
|
||||
public record OutletSummary(String outletCode, String outletDesc, long totalRecords,
|
||||
long overCount, long pendingRectify) {
|
||||
}
|
||||
|
||||
@GetMapping("/summary")
|
||||
public ApiResp<List<OutletSummary>> summary() {
|
||||
Map<String, long[]> acc = new LinkedHashMap<>(); // [total, over, pendingRectify]
|
||||
Map<String, String> descMap = new LinkedHashMap<>();
|
||||
for (FertGasEmission e : repo.findAll()) {
|
||||
String code = e.getOutletCode() == null ? "未知" : e.getOutletCode();
|
||||
long[] v = acc.computeIfAbsent(code, k -> new long[]{0, 0, 0});
|
||||
v[0]++;
|
||||
if (Boolean.TRUE.equals(e.getOverLimit())) {
|
||||
v[1]++;
|
||||
if (!Boolean.TRUE.equals(e.getRectified())) v[2]++;
|
||||
}
|
||||
descMap.putIfAbsent(code, e.getOutletDesc());
|
||||
}
|
||||
return ApiResp.ok(acc.entrySet().stream()
|
||||
.map(en -> new OutletSummary(en.getKey(), descMap.get(en.getKey()),
|
||||
en.getValue()[0], en.getValue()[1], en.getValue()[2]))
|
||||
.toList());
|
||||
}
|
||||
|
||||
// ---------- helpers ----------
|
||||
|
||||
private FertGasEmission find(Long id) {
|
||||
return repo.findById(id).orElseThrow(() -> new NotFoundException("废气排放记录不存在:" + id));
|
||||
}
|
||||
|
||||
private static BigDecimal bd(Double v) {
|
||||
return v == null ? BigDecimal.ZERO : BigDecimal.valueOf(v);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user