package com.ruoyi.production.service.impl; import com.ruoyi.basic.mapper.ProductMapper; import com.ruoyi.basic.mapper.ProductModelMapper; import com.ruoyi.basic.pojo.Product; import com.ruoyi.basic.pojo.ProductModel; import com.ruoyi.production.bean.vo.MaterialConsumptionActualVo; import com.ruoyi.production.bean.vo.MaterialConsumptionOrderVo; import com.ruoyi.production.bean.vo.MaterialConsumptionStatVo; import com.ruoyi.production.bean.vo.MaterialConsumptionStructureVo; import com.ruoyi.production.mapper.MaterialConsumptionMapper; import com.ruoyi.production.service.MaterialConsumptionService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDate; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; @Service @RequiredArgsConstructor public class MaterialConsumptionServiceImpl implements MaterialConsumptionService { private final MaterialConsumptionMapper materialConsumptionMapper; private final ProductModelMapper productModelMapper; private final ProductMapper productMapper; @Override public List stat(LocalDate startDate, LocalDate endDate, String npsNo, String productName, Boolean onlyDiff) { List orders = materialConsumptionMapper.listOrders(startDate, endDate, npsNo, productName); if (orders.isEmpty()) { return new ArrayList<>(); } List orderIds = orders.stream().map(MaterialConsumptionOrderVo::getProductionOrderId) .filter(Objects::nonNull).distinct().collect(Collectors.toList()); List structures = materialConsumptionMapper.listStructures(orderIds); List actuals = materialConsumptionMapper.listActualInputs(orderIds); Map structureById = structures.stream() .collect(Collectors.toMap(MaterialConsumptionStructureVo::getId, s -> s, (a, b) -> a)); Map actualMap = new HashMap<>(); for (MaterialConsumptionActualVo actual : actuals) { actualMap.merge(actual.getProductionOrderId() + "_" + actual.getProductModelId(), nvl(actual.getActualQty()), BigDecimal::add); } //BOM逐层展开:累计单耗=上级累计×本级单耗,按物料合并 Map> orderStandard = new HashMap<>(); for (MaterialConsumptionStructureVo row : structures) { if (isRoot(row)) { continue; } BigDecimal cumulative = cumulativeUnitQuantity(row, structureById); orderStandard.computeIfAbsent(row.getProductionOrderId(), k -> new LinkedHashMap<>()) .merge(row.getProductModelId(), cumulative, BigDecimal::add); } Set materialModelIds = new HashSet<>(); orderStandard.values().forEach(m -> materialModelIds.addAll(m.keySet())); materialModelIds.addAll(actualMap.keySet().stream() .map(k -> Long.valueOf(k.substring(k.indexOf('_') + 1))).collect(Collectors.toSet())); Map modelMap = modelIds(materialModelIds); Map productMap = products(modelMap.values()); List result = new ArrayList<>(); for (MaterialConsumptionOrderVo order : orders) { Map standardMap = orderStandard.getOrDefault(order.getProductionOrderId(), new LinkedHashMap<>()); //报工投入了但BOM没有的物料也要展示(超标/误领) Set materialIds = new java.util.LinkedHashSet<>(standardMap.keySet()); actualMap.keySet().stream() .filter(k -> k.startsWith(order.getProductionOrderId() + "_")) .forEach(k -> materialIds.add(Long.valueOf(k.substring(k.indexOf('_') + 1)))); if (materialIds.isEmpty()) { continue; } BigDecimal outputQty = nvl(order.getCompleteQty()).compareTo(BigDecimal.ZERO) > 0 ? order.getCompleteQty() : nvl(order.getPlanQty()); for (Long materialId : materialIds) { BigDecimal unitQty = standardMap.getOrDefault(materialId, BigDecimal.ZERO); BigDecimal standard = unitQty.multiply(outputQty).setScale(4, RoundingMode.HALF_UP); BigDecimal actual = actualMap.getOrDefault( order.getProductionOrderId() + "_" + materialId, BigDecimal.ZERO); BigDecimal diff = actual.subtract(standard); if (Boolean.TRUE.equals(onlyDiff) && diff.compareTo(BigDecimal.ZERO) == 0) { continue; } MaterialConsumptionStatVo vo = new MaterialConsumptionStatVo(); vo.setProductionOrderId(order.getProductionOrderId()); vo.setNpsNo(order.getNpsNo()); vo.setFinishedProduct(order.getFinishedProduct()); vo.setMaterialModelId(materialId); ProductModel pm = modelMap.get(materialId); if (pm != null) { vo.setMaterialModel(pm.getModel()); vo.setUnit(pm.getUnit()); Product p = productMap.get(pm.getProductId()); if (p != null) { vo.setMaterialName(p.getProductName()); } } vo.setUnitQuantity(unitQty); vo.setCompleteQty(outputQty); vo.setStandardQty(standard); vo.setActualQty(actual); vo.setDiffQty(diff); if (standard.compareTo(BigDecimal.ZERO) > 0) { vo.setDiffRate(diff.multiply(BigDecimal.valueOf(100)) .divide(standard, 2, RoundingMode.HALF_UP)); } result.add(vo); } } return result; } private boolean isRoot(MaterialConsumptionStructureVo row) { return row.getParentId() == null || row.getParentId() == 0L; } private BigDecimal cumulativeUnitQuantity(MaterialConsumptionStructureVo row, Map structureById) { BigDecimal cumulative = nvl(row.getUnitQuantity()); Long parentId = row.getParentId(); int guard = 0; while (parentId != null && parentId != 0L && guard++ < 20) { MaterialConsumptionStructureVo parent = structureById.get(parentId); if (parent == null) { break; } cumulative = cumulative.multiply(nvl(parent.getUnitQuantity())); parentId = parent.getParentId(); } return cumulative.setScale(6, RoundingMode.HALF_UP); } private Map modelIds(Set ids) { if (ids.isEmpty()) { return new HashMap<>(); } return productModelMapper.selectBatchIds(ids).stream() .collect(Collectors.toMap(ProductModel::getId, m -> m, (a, b) -> a)); } private Map products(java.util.Collection models) { List ids = models.stream().map(ProductModel::getProductId) .filter(Objects::nonNull).distinct().collect(Collectors.toList()); if (ids.isEmpty()) { return new HashMap<>(); } return productMapper.selectBatchIds(ids).stream() .collect(Collectors.toMap(Product::getId, p -> p, (a, b) -> a)); } private BigDecimal nvl(BigDecimal value) { return value == null ? BigDecimal.ZERO : value; } }