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<MaterialConsumptionStatVo> stat(LocalDate startDate, LocalDate endDate,
|
String npsNo, String productName, Boolean onlyDiff) {
|
List<MaterialConsumptionOrderVo> orders =
|
materialConsumptionMapper.listOrders(startDate, endDate, npsNo, productName);
|
if (orders.isEmpty()) {
|
return new ArrayList<>();
|
}
|
List<Long> orderIds = orders.stream().map(MaterialConsumptionOrderVo::getProductionOrderId)
|
.filter(Objects::nonNull).distinct().collect(Collectors.toList());
|
List<MaterialConsumptionStructureVo> structures = materialConsumptionMapper.listStructures(orderIds);
|
List<MaterialConsumptionActualVo> actuals = materialConsumptionMapper.listActualInputs(orderIds);
|
|
Map<Long, MaterialConsumptionStructureVo> structureById = structures.stream()
|
.collect(Collectors.toMap(MaterialConsumptionStructureVo::getId, s -> s, (a, b) -> a));
|
Map<String, BigDecimal> actualMap = new HashMap<>();
|
for (MaterialConsumptionActualVo actual : actuals) {
|
actualMap.merge(actual.getProductionOrderId() + "_" + actual.getProductModelId(),
|
nvl(actual.getActualQty()), BigDecimal::add);
|
}
|
|
//BOM逐层展开:累计单耗=上级累计×本级单耗,按物料合并
|
Map<Long, Map<Long, BigDecimal>> 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<Long> 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<Long, ProductModel> modelMap = modelIds(materialModelIds);
|
Map<Long, Product> productMap = products(modelMap.values());
|
|
List<MaterialConsumptionStatVo> result = new ArrayList<>();
|
for (MaterialConsumptionOrderVo order : orders) {
|
Map<Long, BigDecimal> standardMap = orderStandard.getOrDefault(order.getProductionOrderId(), new LinkedHashMap<>());
|
//报工投入了但BOM没有的物料也要展示(超标/误领)
|
Set<Long> 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<Long, MaterialConsumptionStructureVo> 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<Long, ProductModel> modelIds(Set<Long> ids) {
|
if (ids.isEmpty()) {
|
return new HashMap<>();
|
}
|
return productModelMapper.selectBatchIds(ids).stream()
|
.collect(Collectors.toMap(ProductModel::getId, m -> m, (a, b) -> a));
|
}
|
|
private Map<Long, Product> products(java.util.Collection<ProductModel> models) {
|
List<Long> 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;
|
}
|
}
|