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.FertEnergyLog; import com.kaidi.oa.repository.FertEnergyLogRepository; 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.math.RoundingMode; import java.time.Instant; import java.time.LocalDate; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * 生物质肥料制造中心·能源消耗逐班记录 + 分摊批次 + 单位能耗对标(需求 §7,补深审计 PARTIAL[med])。 * * * 综合能耗折标系数:电 1kWh≈0.1229kgce;燃料按填写的折标系数 fuelCoalFactor × fuelQty × 1000(吨标煤→kgce)。 * 写口受 AuthInterceptor default-deny(ADMIN/APPROVER) 保护,前缀已登记入 FINANCE/SENSITIVE_READ。 */ @RestController @RequestMapping("/api/oa/fert-energy-logs") public class FertEnergyLogController { /** 电力折标煤系数 kgce/kWh(当量值 0.1229)。 */ private static final BigDecimal ELEC_FACTOR = new BigDecimal("0.1229"); private final FertEnergyLogRepository repo; public FertEnergyLogController(FertEnergyLogRepository repo) { this.repo = repo; } @GetMapping public ApiResp> list(@RequestParam(required = false) String process, @RequestParam(required = false) Boolean overOnly) { if (Boolean.TRUE.equals(overOnly)) return ApiResp.ok(repo.findByOverBenchmarkTrue()); if (process != null && !process.isBlank()) return ApiResp.ok(repo.findByProcess(process)); return ApiResp.ok(repo.findAll()); } @GetMapping("/{id}") public ApiResp get(@PathVariable Long id) { return ApiResp.ok(find(id)); } public record EnergyRequest(String logDate, String shift, String process, String batchRef, Double outputQty, Double electricityKwh, String fuelType, Double fuelQty, Double fuelCoalFactor, Double waterTon, Double benchmarkKgcePerTon, String recorder, String remark) { } @PostMapping public ApiResp create(@RequestBody EnergyRequest req) { FertEnergyLog l = new FertEnergyLog(); l.setCreatedAt(Instant.now()); apply(l, req); return ApiResp.ok(repo.save(l)); } @PutMapping("/{id}") public ApiResp update(@PathVariable Long id, @RequestBody EnergyRequest req) { FertEnergyLog l = find(id); apply(l, req); return ApiResp.ok(repo.save(l)); } @DeleteMapping("/{id}") public ApiResp delete(@PathVariable Long id) { repo.delete(find(id)); return ApiResp.ok(); } private void apply(FertEnergyLog l, EnergyRequest req) { l.setLogDate(req.logDate() == null || req.logDate().isBlank() ? LocalDate.now().toString() : req.logDate()); l.setShift(req.shift() == null || req.shift().isBlank() ? "全天" : req.shift()); l.setProcess(req.process()); l.setBatchRef(req.batchRef()); l.setOutputQty(bd(req.outputQty())); l.setElectricityKwh(bd(req.electricityKwh())); l.setFuelType(req.fuelType()); l.setFuelQty(bd(req.fuelQty())); l.setFuelCoalFactor(bd(req.fuelCoalFactor())); l.setWaterTon(bd(req.waterTon())); l.setBenchmarkKgcePerTon(bd(req.benchmarkKgcePerTon())); l.setRecorder(req.recorder()); l.setRemark(req.remark()); recompute(l); } /** 自动计算综合能耗、单位能耗、超标判定。 */ private void recompute(FertEnergyLog l) { // 电力折标:kWh × 0.1229 = kgce。 BigDecimal elecKgce = l.getElectricityKwh().multiply(ELEC_FACTOR); // 燃料折标:fuelQty(吨等) × fuelCoalFactor(吨标煤/单位) × 1000(吨→千克标煤)。 BigDecimal fuelKgce = l.getFuelQty().multiply(l.getFuelCoalFactor()).multiply(new BigDecimal("1000")); BigDecimal total = elecKgce.add(fuelKgce).setScale(2, RoundingMode.HALF_UP); l.setTotalKgce(total); if (l.getOutputQty().signum() > 0) { BigDecimal unit = total.divide(l.getOutputQty(), 2, RoundingMode.HALF_UP); l.setUnitKgcePerTon(unit); l.setOverBenchmark(l.getBenchmarkKgcePerTon().signum() > 0 && unit.compareTo(l.getBenchmarkKgcePerTon()) > 0); } else { l.setUnitKgcePerTon(BigDecimal.ZERO); l.setOverBenchmark(false); } } // ---------- 批次能耗分摊 ---------- public record BatchEnergy(String batchRef, BigDecimal totalKgce, BigDecimal totalOutput, BigDecimal unitKgcePerTon, BigDecimal electricityKwh, BigDecimal waterTon, long logCount) { } @GetMapping("/by-batch") public ApiResp byBatch(@RequestParam String batchRef) { if (batchRef == null || batchRef.isBlank()) { throw new ApiException(400, "批次/工单号(batchRef) 不能为空"); } List logs = repo.findByBatchRef(batchRef.trim()); BigDecimal kgce = BigDecimal.ZERO, out = BigDecimal.ZERO, elec = BigDecimal.ZERO, water = BigDecimal.ZERO; for (FertEnergyLog l : logs) { kgce = kgce.add(nz(l.getTotalKgce())); out = out.add(nz(l.getOutputQty())); elec = elec.add(nz(l.getElectricityKwh())); water = water.add(nz(l.getWaterTon())); } BigDecimal unit = out.signum() > 0 ? kgce.divide(out, 2, RoundingMode.HALF_UP) : BigDecimal.ZERO; return ApiResp.ok(new BatchEnergy(batchRef.trim(), scale(kgce), scale(out), unit, scale(elec), scale(water), logs.size())); } // ---------- 工序单耗对标看板 ---------- public record ProcessBench(String process, BigDecimal totalKgce, BigDecimal totalOutput, BigDecimal unitKgcePerTon, long overCount, long logCount) { } @GetMapping("/benchmark") public ApiResp> benchmark() { Map acc = new LinkedHashMap<>(); // [kgce, output, over, count] for (FertEnergyLog l : repo.findAll()) { String p = l.getProcess() == null || l.getProcess().isBlank() ? "未分类" : l.getProcess(); BigDecimal[] v = acc.computeIfAbsent(p, k -> new BigDecimal[]{BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO}); v[0] = v[0].add(nz(l.getTotalKgce())); v[1] = v[1].add(nz(l.getOutputQty())); if (Boolean.TRUE.equals(l.getOverBenchmark())) v[2] = v[2].add(BigDecimal.ONE); v[3] = v[3].add(BigDecimal.ONE); } List rows = new ArrayList<>(); for (Map.Entry e : acc.entrySet()) { BigDecimal[] v = e.getValue(); BigDecimal unit = v[1].signum() > 0 ? v[0].divide(v[1], 2, RoundingMode.HALF_UP) : BigDecimal.ZERO; rows.add(new ProcessBench(e.getKey(), scale(v[0]), scale(v[1]), unit, v[2].longValue(), v[3].longValue())); } rows.sort((a, b) -> b.unitKgcePerTon().compareTo(a.unitKgcePerTon())); return ApiResp.ok(rows); } // ---------- helpers ---------- private FertEnergyLog 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).setScale(2, RoundingMode.HALF_UP); } private static BigDecimal nz(BigDecimal v) { return v == null ? BigDecimal.ZERO : v; } private static BigDecimal scale(BigDecimal v) { return nz(v).setScale(2, RoundingMode.HALF_UP); } }