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])。
*
*
* - 逐班录入电/燃料/水耗,系统自动算综合能耗(kgce)与单位产品能耗(kgce/吨),并与行业基准对标超标预警;
* - GET /by-batch?batchRef= 把同一批次/工单各班能耗合计,得到该批次单位能耗(分摊到生产批次);
* - GET /benchmark 按工序汇总单耗与超标条数,干燥工序重点监控。
*
* 综合能耗折标系数:电 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);
}
}