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])。
*
*
* - 按排放口录入监测数据(NH₃/H₂S/臭气指数/粉尘浓度),与限值对标后自动判定超标并拼装超标详情;
* - GET /alerts → 超标未整改记录列表(环保预警核心);
* - POST /{id}/rectify → 填整改措施,标记已整改;
* - GET /trend?outlet=&from=&to= → 指定排放口按日期区间折线趋势(NH₃/H₂S/臭气历史数据);
* - GET /summary → 各排放口超标次数汇总(EHS 看板)。
*
*
* 写口受 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(@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> alerts() {
return ApiResp.ok(repo.findByRectifiedFalseAndOverLimitTrue());
}
@GetMapping("/{id}")
public ApiResp 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 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 update(@PathVariable Long id, @RequestBody EmissionRequest req) {
FertGasEmission e = find(id);
apply(e, req);
return ApiResp.ok(repo.save(e));
}
@DeleteMapping("/{id}")
public ApiResp 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 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 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> 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 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> summary() {
Map acc = new LinkedHashMap<>(); // [total, over, pendingRectify]
Map 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);
}
}