package com.ruoyi.production.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.ruoyi.production.bean.dto.ProductionBomStructureDto; import com.ruoyi.production.bean.vo.ProductionBomStructureVo; import com.ruoyi.production.mapper.ProductionBomStructureMapper; import com.ruoyi.production.mapper.ProductionOperationTaskMapper; import com.ruoyi.production.mapper.ProductionOrderBomMapper; import com.ruoyi.production.mapper.ProductionOrderMapper; import com.ruoyi.production.mapper.ProductionOrderRoutingOperationMapper; import com.ruoyi.production.pojo.ProductionBomStructure; import com.ruoyi.production.pojo.ProductionOperationTask; import com.ruoyi.production.pojo.ProductionOrder; import com.ruoyi.production.pojo.ProductionOrderBom; import com.ruoyi.production.pojo.ProductionOrderRoutingOperation; import com.ruoyi.production.service.ProductionBomStructureService; import lombok.RequiredArgsConstructor; import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.util.*; import java.util.stream.Collectors; /** *

* 生产订单BOM产品结构 服务实现类 *

* * @author 芯导软件(江苏)有限公司 * @since 2026-04-21 03:55:52 */ @Service @RequiredArgsConstructor() public class ProductionBomStructureServiceImpl extends ServiceImpl implements ProductionBomStructureService { private final ProductionBomStructureMapper productionBomStructureMapper; private final ProductionOrderBomMapper productionOrderBomMapper; private final ProductionOrderMapper productionOrderMapper; private final ProductionOrderRoutingOperationMapper productionOrderRoutingOperationMapper; private final ProductionOperationTaskMapper productionOperationTaskMapper; /** * 根据BOM查询并组装结构树。 */ @Override public List listByBomId(Long bomId) { // 按BOMID查询生产结构数据 List list = productionBomStructureMapper.listByBomId(bomId); Map map = new HashMap<>(); for (ProductionBomStructureVo node : list) { node.setChildren(new ArrayList<>()); map.put(node.getId(), node); } List tree = new ArrayList<>(); for (ProductionBomStructureVo node : list) { Long parentId = node.getParentId(); if (parentId == null || parentId == 0L) { tree.add(node); continue; } ProductionBomStructureVo parent = map.get(parentId); if (parent != null) { parent.getChildren().add(node); } } return tree; } @Override @Transactional(rollbackFor = Exception.class) public Boolean addProductionBomStructure(ProductionBomStructureDto dto) { // 新增生产BOM结构 // 读取当前订单BOM主键,并把前端树结构拍平成列表 Long orderBomId = dto.getProductionOrderBomId(); List flatDtoList = new ArrayList<>(); flattenTree(dto.getChildren(), flatDtoList); // 查询数据库已有结构,用于后续做增删改对比 List dbList = this.list(new LambdaQueryWrapper() .eq(ProductionBomStructure::getProductionOrderBomId, orderBomId)); // 收集前端仍然存在的节点ID Set frontendIds = new HashSet<>(); for (ProductionBomStructureDto item : flatDtoList) { if (item.getId() != null) { frontendIds.add(item.getId()); } } // 计算需要删除的节点(数据库有、前端已删除) Set deleteIds = new HashSet<>(); for (ProductionBomStructure dbItem : dbList) { if (!frontendIds.contains(dbItem.getId())) { deleteIds.add(dbItem.getId()); } } // 先删掉前端已经移除的节点 if (!deleteIds.isEmpty()) { this.removeByIds(deleteIds); } // 按是否有ID拆分为新增和更新,同时缓存新增节点的临时ID映射 List insertList = new ArrayList<>(); List updateList = new ArrayList<>(); Map tempEntityMap = new HashMap<>(); for (ProductionBomStructureDto item : flatDtoList) { ProductionBomStructure entity = new ProductionBomStructure(); BeanUtils.copyProperties(item, entity); entity.setProductionOrderBomId(orderBomId); if (item.getId() == null) { entity.setParentId(null); insertList.add(entity); tempEntityMap.put(item.getTempId(), entity); } else { updateList.add(entity); } } // 批量新增,拿到数据库生成的真实ID if (!insertList.isEmpty()) { this.saveBatch(insertList); } // 新增节点二次回写父ID(前端传的是临时父ID) List parentFixList = new ArrayList<>(); for (ProductionBomStructureDto item : flatDtoList) { if (item.getId() == null && item.getParentTempId() != null) { ProductionBomStructure child = tempEntityMap.get(item.getTempId()); if (child == null) { continue; } ProductionBomStructure parent = tempEntityMap.get(item.getParentTempId()); // 父节点是本次新增时,直接用新增后的真实ID;否则回退为前端传入父ID Long realParentId = parent != null ? parent.getId() : Long.valueOf(item.getParentTempId()); child.setParentId(realParentId); parentFixList.add(child); } } // 回写新增节点的父子关系 if (!parentFixList.isEmpty()) { this.updateBatchById(parentFixList); } // 批量更新已有节点 if (!updateList.isEmpty()) { this.updateBatchById(updateList); } syncDemandedQuantityAndTaskPlanQuantity(orderBomId, dto.getProductionOrderId()); return true; } private void syncDemandedQuantityAndTaskPlanQuantity(Long orderBomId, Long productionOrderId) { if (orderBomId == null) { return; } ProductionOrderBom orderBom = productionOrderBomMapper.selectById(orderBomId); if (orderBom == null) { return; } Long currentProductionOrderId = productionOrderId != null ? productionOrderId : orderBom.getProductionOrderId(); if (currentProductionOrderId == null) { return; } ProductionOrder productionOrder = productionOrderMapper.selectById(currentProductionOrderId); if (productionOrder == null) { return; } BigDecimal orderQuantity = defaultDecimal(productionOrder.getQuantity()); List structureList = this.list( Wrappers.lambdaQuery() .eq(ProductionBomStructure::getProductionOrderBomId, orderBomId) .orderByAsc(ProductionBomStructure::getId)); syncStructureDemandedQuantity(structureList, orderQuantity); syncTaskPlanQuantity( currentProductionOrderId, structureList, orderQuantity, orderBom.getProductModelId() != null ? orderBom.getProductModelId() : productionOrder.getProductModelId()); } private void syncStructureDemandedQuantity(List structureList, BigDecimal orderQuantity) { if (structureList == null || structureList.isEmpty()) { return; } List updateList = new ArrayList<>(); for (ProductionBomStructure structure : structureList) { if (structure == null || structure.getId() == null) { continue; } BigDecimal demandedQuantity = defaultDecimal(structure.getUnitQuantity()).multiply(orderQuantity); if (compareDecimal(structure.getDemandedQuantity(), demandedQuantity) == 0) { continue; } ProductionBomStructure update = new ProductionBomStructure(); update.setId(structure.getId()); update.setDemandedQuantity(demandedQuantity); updateList.add(update); structure.setDemandedQuantity(demandedQuantity); } if (!updateList.isEmpty()) { this.updateBatchById(updateList); } } private void syncTaskPlanQuantity(Long productionOrderId, List structureList, BigDecimal orderQuantity, Long rootProductModelId) { List taskList = productionOperationTaskMapper.selectList( Wrappers.lambdaQuery() .eq(ProductionOperationTask::getProductionOrderId, productionOrderId) .orderByAsc(ProductionOperationTask::getId)); if (taskList == null || taskList.isEmpty()) { return; } Set routingOperationIds = taskList.stream() .map(ProductionOperationTask::getProductionOrderRoutingOperationId) .filter(Objects::nonNull) .collect(Collectors.toSet()); if (routingOperationIds.isEmpty()) { return; } Map routingOperationMap = productionOrderRoutingOperationMapper .selectBatchIds(routingOperationIds) .stream() .filter(item -> item != null && item.getId() != null) .collect(Collectors.toMap(ProductionOrderRoutingOperation::getId, item -> item, (left, right) -> left)); Map demandedQuantityMap = buildOperationDemandedQuantityMap(structureList, rootProductModelId, orderQuantity); for (ProductionOperationTask task : taskList) { if (task == null || task.getId() == null || task.getProductionOrderRoutingOperationId() == null) { continue; } ProductionOrderRoutingOperation routingOperation = routingOperationMap.get(task.getProductionOrderRoutingOperationId()); if (routingOperation == null || routingOperation.getTechnologyRoutingOperationId() == null) { continue; } BigDecimal planQuantity = resolveTaskPlanQuantity(routingOperation, demandedQuantityMap, orderQuantity); if (compareDecimal(task.getPlanQuantity(), planQuantity) == 0) { continue; } ProductionOperationTask update = new ProductionOperationTask(); update.setId(task.getId()); update.setPlanQuantity(planQuantity); productionOperationTaskMapper.updateById(update); } } private Map buildOperationDemandedQuantityMap(List structureList, Long rootProductModelId, BigDecimal orderQuantity) { if (structureList == null || structureList.isEmpty()) { return Collections.emptyMap(); } Map structureById = structureList.stream() .filter(item -> item != null && item.getId() != null) .collect(Collectors.toMap(ProductionBomStructure::getId, item -> item, (left, right) -> left)); Map demandedQuantityMap = new HashMap<>(); for (ProductionBomStructure bomStructure : structureList) { if (bomStructure == null || bomStructure.getTechnologyOperationId() == null || bomStructure.getUnitQuantity() == null) { continue; } Long outputProductModelId = resolveOutputProductModelId(bomStructure, structureById, rootProductModelId); String key = buildOperationDemandedQuantityKey(bomStructure.getTechnologyOperationId(), outputProductModelId); demandedQuantityMap.merge(key, bomStructure.getUnitQuantity().multiply(orderQuantity), BigDecimal::add); } return demandedQuantityMap; } private BigDecimal resolveTaskPlanQuantity(ProductionOrderRoutingOperation routingOperation, Map demandedQuantityMap, BigDecimal orderQuantity) { if (routingOperation == null || demandedQuantityMap == null || demandedQuantityMap.isEmpty()) { return orderQuantity; } String key = buildOperationDemandedQuantityKey( routingOperation.getTechnologyOperationId(), routingOperation.getProductModelId()); BigDecimal planQuantity = demandedQuantityMap.get(key); return planQuantity != null ? planQuantity : orderQuantity; } private String buildOperationDemandedQuantityKey(Long operationId, Long outputProductModelId) { return String.valueOf(operationId) + "#" + String.valueOf(outputProductModelId); } private Long resolveOutputProductModelId(ProductionBomStructure bomStructure, Map structureById, Long rootProductModelId) { if (bomStructure == null) { return rootProductModelId; } Long parentId = bomStructure.getParentId(); if (parentId == null) { return rootProductModelId != null ? rootProductModelId : bomStructure.getProductModelId(); } ProductionBomStructure parent = structureById.get(parentId); if (parent != null && parent.getProductModelId() != null) { return parent.getProductModelId(); } return rootProductModelId != null ? rootProductModelId : bomStructure.getProductModelId(); } private BigDecimal defaultDecimal(BigDecimal value) { return value == null ? BigDecimal.ZERO : value; } private int compareDecimal(BigDecimal left, BigDecimal right) { return defaultDecimal(left).compareTo(defaultDecimal(right)); } /** * 将树形结构拍平成列表,便于统一保存。 */ private void flattenTree(List source, List result) { // 扁平化处理树 if (source == null) { return; } for (ProductionBomStructureDto node : source) { result.add(node); flattenTree(node.getChildren(), result); } } }