package com.ruoyi.cost.service.impl; import com.ruoyi.cost.bean.vo.CostEnergyVo; import com.ruoyi.cost.bean.vo.CostLaborVo; import com.ruoyi.cost.bean.vo.CostMaterialInputVo; import com.ruoyi.cost.bean.vo.CostMaterialPriceVo; import com.ruoyi.cost.bean.vo.CostMissingPriceVo; import com.ruoyi.cost.bean.vo.CostOrderVo; import com.ruoyi.cost.bean.vo.CostSalePriceVo; import com.ruoyi.cost.bean.vo.CostStatVo; import com.ruoyi.cost.bean.vo.CostSummaryVo; import com.ruoyi.cost.mapper.CostAccountingMapper; import com.ruoyi.cost.service.CostAccountingService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @Service @RequiredArgsConstructor public class CostAccountingServiceImpl implements CostAccountingService { private static final BigDecimal HUNDRED = BigDecimal.valueOf(100); private static final DateTimeFormatter MONTH_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM"); private final CostAccountingMapper costAccountingMapper; @Override public List stat(LocalDate startDate, LocalDate endDate, String npsNo, String productName) { List orders = costAccountingMapper.listOrders(startDate, endDate, npsNo, productName); if (orders.isEmpty()) { return new ArrayList<>(); } Map> inputsByOrder = costAccountingMapper .listMaterialInputs(startDate, endDate, npsNo, productName).stream() .filter(i -> i.getProductionOrderId() != null) .collect(Collectors.groupingBy(CostMaterialInputVo::getProductionOrderId)); Map priceByModel = priceByModel(); Map laborByOrder = costAccountingMapper .listLaborCosts(startDate, endDate, npsNo, productName).stream() .filter(l -> l.getProductionOrderId() != null) .collect(Collectors.toMap(CostLaborVo::getProductionOrderId, l -> l, (a, b) -> a)); Map salePriceByOrder = new LinkedHashMap<>(); for (CostSalePriceVo sale : costAccountingMapper.listSalePrices(startDate, endDate, npsNo, productName)) { if (sale.getProductionOrderId() != null) { //SQL 已按 slp.id 倒序,保留最新一张销售单的单价 salePriceByOrder.putIfAbsent(sale.getProductionOrderId(), sale.getSalePrice()); } } CostEnergyVo energy = nvlEnergy(costAccountingMapper.sumEnergyCost(startDate, endDate)); BigDecimal energyTotal = nvl(energy.getEnergyCost()); BigDecimal totalOutput = orders.stream().map(this::outputQty).reduce(BigDecimal.ZERO, BigDecimal::add); List result = new ArrayList<>(); for (CostOrderVo order : orders) { BigDecimal output = outputQty(order); //材料成本:Σ(投入量×单价),同时统计已计价消耗量与缺价物料数 BigDecimal materialCost = BigDecimal.ZERO; BigDecimal pricedQty = BigDecimal.ZERO; BigDecimal inputQty = BigDecimal.ZERO; int missingKinds = 0; for (CostMaterialInputVo input : inputsByOrder.getOrDefault(order.getProductionOrderId(), new ArrayList<>())) { BigDecimal qty = nvl(input.getInputQty()); inputQty = inputQty.add(qty); CostMaterialPriceVo price = input.getProductModelId() == null ? null : priceByModel.get(input.getProductModelId()); if (price == null || nvl(price.getPrice()).compareTo(BigDecimal.ZERO) <= 0) { if (qty.compareTo(BigDecimal.ZERO) != 0) { missingKinds++; } continue; } pricedQty = pricedQty.add(qty); materialCost = materialCost.add(qty.multiply(nvl(price.getPrice()))); } materialCost = scale(materialCost, 2); //人工成本 CostLaborVo labor = laborByOrder.get(order.getProductionOrderId()); BigDecimal laborCost = labor == null ? BigDecimal.ZERO : scale(nvl(labor.getLaborCost()), 2); //能耗成本:区间电费按产量占比分摊 BigDecimal energyCost = energyTotal.compareTo(BigDecimal.ZERO) > 0 && totalOutput.compareTo(BigDecimal.ZERO) > 0 ? scale(energyTotal.multiply(output).divide(totalOutput, 6, RoundingMode.HALF_UP), 2) : BigDecimal.ZERO; BigDecimal totalCost = materialCost.add(laborCost).add(energyCost); BigDecimal unitCost = output.compareTo(BigDecimal.ZERO) > 0 ? totalCost.divide(output, 4, RoundingMode.HALF_UP) : null; BigDecimal salePrice = salePriceByOrder.get(order.getProductionOrderId()); CostStatVo vo = new CostStatVo(); vo.setProductionOrderId(order.getProductionOrderId()); vo.setNpsNo(order.getNpsNo()); vo.setFinishedProduct(order.getFinishedProduct()); vo.setUnit(order.getUnit()); vo.setOrderDate(order.getCreateTime() == null ? null : order.getCreateTime().toLocalDate()); vo.setOutputQty(output); vo.setMaterialCost(materialCost); vo.setLaborCost(laborCost); vo.setEnergyCost(energyCost); vo.setTotalCost(scale(totalCost, 2)); vo.setUnitCost(unitCost); vo.setSalePrice(salePrice); if (salePrice != null && unitCost != null) { BigDecimal profit = salePrice.subtract(unitCost).multiply(output); vo.setGrossProfit(scale(profit, 2)); BigDecimal revenue = salePrice.multiply(output); if (revenue.compareTo(BigDecimal.ZERO) != 0) { vo.setGrossProfitRate(profit.multiply(HUNDRED).divide(revenue, 2, RoundingMode.HALF_UP)); } } if (inputQty.compareTo(BigDecimal.ZERO) > 0) { vo.setPriceCoverageRate(pricedQty.multiply(HUNDRED).divide(inputQty, 2, RoundingMode.HALF_UP)); } vo.setMissingPriceKinds(missingKinds); vo.setNoRateRows(labor == null ? 0 : labor.getNoRateRows()); result.add(vo); } return result; } @Override public List summary(LocalDate startDate, LocalDate endDate, String npsNo, String productName, String groupBy) { List stats = stat(startDate, endDate, npsNo, productName); boolean byProduct = "product".equals(groupBy); Map grouped = new LinkedHashMap<>(); for (CostStatVo stat : stats) { String key = byProduct ? nvlText(stat.getFinishedProduct()) : (stat.getOrderDate() == null ? "未填日期" : stat.getOrderDate().format(MONTH_FORMAT)); CostSummaryVo vo = grouped.computeIfAbsent(key, k -> { CostSummaryVo created = new CostSummaryVo(); created.setGroupKey(k); created.setOrderCount(0); created.setOutputQty(BigDecimal.ZERO); created.setMaterialCost(BigDecimal.ZERO); created.setLaborCost(BigDecimal.ZERO); created.setEnergyCost(BigDecimal.ZERO); created.setTotalCost(BigDecimal.ZERO); created.setGrossProfit(BigDecimal.ZERO); return created; }); vo.setOrderCount(vo.getOrderCount() + 1); vo.setOutputQty(vo.getOutputQty().add(nvl(stat.getOutputQty()))); vo.setMaterialCost(vo.getMaterialCost().add(nvl(stat.getMaterialCost()))); vo.setLaborCost(vo.getLaborCost().add(nvl(stat.getLaborCost()))); vo.setEnergyCost(vo.getEnergyCost().add(nvl(stat.getEnergyCost()))); vo.setTotalCost(vo.getTotalCost().add(nvl(stat.getTotalCost()))); vo.setGrossProfit(vo.getGrossProfit().add(nvl(stat.getGrossProfit()))); } List result = new ArrayList<>(grouped.values()); for (CostSummaryVo vo : result) { BigDecimal total = vo.getTotalCost(); vo.setMaterialCost(scale(vo.getMaterialCost(), 2)); vo.setLaborCost(scale(vo.getLaborCost(), 2)); vo.setEnergyCost(scale(vo.getEnergyCost(), 2)); vo.setTotalCost(scale(total, 2)); vo.setGrossProfit(scale(vo.getGrossProfit(), 2)); if (vo.getOutputQty().compareTo(BigDecimal.ZERO) > 0) { vo.setUnitCost(total.divide(vo.getOutputQty(), 4, RoundingMode.HALF_UP)); } if (total.compareTo(BigDecimal.ZERO) > 0) { vo.setMaterialRate(rate(vo.getMaterialCost(), total)); vo.setLaborRate(rate(vo.getLaborCost(), total)); vo.setEnergyRate(rate(vo.getEnergyCost(), total)); } } return result; } @Override public List missingPrice(LocalDate startDate, LocalDate endDate, String npsNo, String productName) { Map priceByModel = priceByModel(); return costAccountingMapper.listConsumedMaterials(startDate, endDate, npsNo, productName).stream() .filter(m -> m.getProductModelId() != null && !priceByModel.containsKey(m.getProductModelId())) .collect(Collectors.toList()); } /** 单价来源按优先级取第一条(SQL 已按 productModelId、priority 排序) */ private Map priceByModel() { Map map = new HashMap<>(); for (CostMaterialPriceVo price : costAccountingMapper.listMaterialPrices()) { if (price.getProductModelId() != null) { map.putIfAbsent(price.getProductModelId(), price); } } return map; } /** 产量口径:完工数量优先,未完工回退计划数量 */ private BigDecimal outputQty(CostOrderVo order) { return nvl(order.getCompleteQty()).compareTo(BigDecimal.ZERO) > 0 ? order.getCompleteQty() : nvl(order.getPlanQty()); } private BigDecimal rate(BigDecimal part, BigDecimal total) { return part.multiply(HUNDRED).divide(total, 2, RoundingMode.HALF_UP); } private CostEnergyVo nvlEnergy(CostEnergyVo energy) { if (energy == null) { energy = new CostEnergyVo(); } if (energy.getKwh() == null) { energy.setKwh(BigDecimal.ZERO); } if (energy.getEnergyCost() == null) { energy.setEnergyCost(BigDecimal.ZERO); } return energy; } private BigDecimal nvl(BigDecimal value) { return value == null ? BigDecimal.ZERO : value; } private String nvlText(String value) { return value == null ? "" : value; } private BigDecimal scale(BigDecimal value, int scale) { return nvl(value).setScale(scale, RoundingMode.HALF_UP); } }