| | |
| | | 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.basic.mapper.ProductModelMapper; |
| | | import com.ruoyi.common.exception.ServiceException; |
| | | import com.ruoyi.production.bean.dto.ProductionBomStructureDto; |
| | | import com.ruoyi.production.bean.vo.ProductionBomStructureVo; |
| | |
| | | import com.ruoyi.technology.pojo.TechnologyOperationParam; |
| | | import com.ruoyi.technology.pojo.TechnologyParam; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.beans.BeanUtils; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | |
| | |
| | | private final ProductionOrderRoutingOperationParamMapper productionOrderRoutingOperationParamMapper; |
| | | private final ProductionOperationTaskMapper productionOperationTaskMapper; |
| | | private final ProductionProductMainMapper productionProductMainMapper; |
| | | private final ProductModelMapper productModelMapper; |
| | | private final TechnologyOperationMapper technologyOperationMapper; |
| | | private final TechnologyOperationParamMapper technologyOperationParamMapper; |
| | | private final TechnologyParamMapper technologyParamMapper; |
| | | |
| | | /** |
| | | * 根据BOM查询并组装结构树。 |
| | | * 按订单BOM查询扁平物料明细。 |
| | | */ |
| | | @Override |
| | | public List<ProductionBomStructureVo> listByBomId(Long bomId) { |
| | | // 按BOMID查询生产结构数据 |
| | | List<ProductionBomStructureVo> list = productionBomStructureMapper.listByBomId(bomId); |
| | | Map<Long, ProductionBomStructureVo> map = new HashMap<>(); |
| | | for (ProductionBomStructureVo node : list) { |
| | | node.setChildren(new ArrayList<>()); |
| | | map.put(node.getId(), node); |
| | | if (bomId == null) { |
| | | return new ArrayList<>(); |
| | | } |
| | | |
| | | List<ProductionBomStructureVo> 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; |
| | | return productionBomStructureMapper.listByBomId(bomId); |
| | | } |
| | | |
| | | /** |
| | | * 按生产订单查询订单BOM快照的扁平物料明细。 |
| | | */ |
| | | @Override |
| | | public List<ProductionBomStructureVo> listByOrderId(Long productionOrderId) { |
| | | if (productionOrderId == null) { |
| | | return new ArrayList<>(); |
| | | } |
| | | ProductionOrderBom orderBom = productionOrderBomMapper.selectOne( |
| | | Wrappers.<ProductionOrderBom>lambdaQuery() |
| | | .eq(ProductionOrderBom::getProductionOrderId, productionOrderId) |
| | | .orderByDesc(ProductionOrderBom::getId) |
| | | .last("limit 1")); |
| | | if (orderBom == null || orderBom.getId() == null) { |
| | | return new ArrayList<>(); |
| | | } |
| | | return listByBomId(orderBom.getId()); |
| | | } |
| | | |
| | | /** |
| | | * 全量同步订单BOM快照的扁平物料明细,并按其中的工序重建订单工艺路线快照与工单。 |
| | | */ |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Boolean addProductionBomStructure(ProductionBomStructureDto dto) { |
| | | // 新增生产BOM结构 |
| | | // 读取当前订单BOM主键,并把前端树结构拍平成列表 |
| | | Long orderBomId = dto.getProductionOrderBomId(); |
| | | List<ProductionBomStructureDto> flatDtoList = new ArrayList<>(); |
| | | flattenTree(dto.getChildren(), flatDtoList); |
| | | if (orderBomId == null) { |
| | | throw new ServiceException("订单BOM ID不能为空"); |
| | | } |
| | | ProductionOrderBom orderBom = productionOrderBomMapper.selectById(orderBomId); |
| | | if (orderBom == null) { |
| | | throw new ServiceException("订单BOM不存在"); |
| | | } |
| | | Long productionOrderId = dto.getProductionOrderId() != null |
| | | ? dto.getProductionOrderId() |
| | | : orderBom.getProductionOrderId(); |
| | | |
| | | // 查询数据库已有结构,用于后续做增删改对比 |
| | | List<ProductionBomStructureDto> detailList = dto.getDetailList() == null |
| | | ? Collections.emptyList() |
| | | : dto.getDetailList(); |
| | | |
| | | List<ProductionBomStructure> dbList = this.list(new LambdaQueryWrapper<ProductionBomStructure>() |
| | | .eq(ProductionBomStructure::getProductionOrderBomId, orderBomId)); |
| | | Set<Long> dbIds = dbList.stream().map(ProductionBomStructure::getId).collect(Collectors.toSet()); |
| | | |
| | | // 收集前端仍然存在的节点ID |
| | | Set<Long> frontendIds = new HashSet<>(); |
| | | for (ProductionBomStructureDto item : flatDtoList) { |
| | | if (item.getId() != null) { |
| | | frontendIds.add(item.getId()); |
| | | } |
| | | } |
| | | |
| | | // 计算需要删除的节点(数据库有、前端已删除) |
| | | Set<Long> deleteIds = new HashSet<>(); |
| | | for (ProductionBomStructure dbItem : dbList) { |
| | | if (!frontendIds.contains(dbItem.getId())) { |
| | | deleteIds.add(dbItem.getId()); |
| | | } |
| | | } |
| | | // 先删掉前端已经移除的节点 |
| | | if (!deleteIds.isEmpty()) { |
| | | this.removeByIds(deleteIds); |
| | | } |
| | | |
| | | // 按是否有ID拆分为新增和更新,同时缓存新增节点的临时ID映射 |
| | | Set<Long> submittedIds = new HashSet<>(); |
| | | Set<String> relationKeys = new HashSet<>(); |
| | | List<ProductionBomStructure> insertList = new ArrayList<>(); |
| | | List<ProductionBomStructure> updateList = new ArrayList<>(); |
| | | Map<String, ProductionBomStructure> tempEntityMap = new HashMap<>(); |
| | | |
| | | for (ProductionBomStructureDto item : flatDtoList) { |
| | | for (ProductionBomStructureDto item : detailList) { |
| | | Long productModelId = item.getProductModelId(); |
| | | Long technologyOperationId = item.getTechnologyOperationId(); |
| | | if (productModelId == null) { |
| | | throw new ServiceException("请选择物料规格"); |
| | | } |
| | | if (productModelMapper.selectById(productModelId) == null) { |
| | | throw new ServiceException("物料规格不存在"); |
| | | } |
| | | if (technologyOperationId == null) { |
| | | throw new ServiceException("请选择消耗工序"); |
| | | } |
| | | if (technologyOperationMapper.selectById(technologyOperationId) == null) { |
| | | throw new ServiceException("消耗工序不存在"); |
| | | } |
| | | if (productModelId.equals(orderBom.getProductModelId())) { |
| | | throw new ServiceException("成品规格不能作为自身的物料"); |
| | | } |
| | | if (!relationKeys.add(productModelId + "#" + technologyOperationId)) { |
| | | throw new ServiceException("同一物料规格在同一工序下不能重复配置"); |
| | | } |
| | | |
| | | ProductionBomStructure entity = new ProductionBomStructure(); |
| | | BeanUtils.copyProperties(item, entity); |
| | | entity.setProductionOrderBomId(orderBomId); |
| | | entity.setProductionOrderId(productionOrderId); |
| | | entity.setProductModelId(productModelId); |
| | | entity.setTechnologyOperationId(technologyOperationId); |
| | | // 扁平快照不再使用层级与数量语义 |
| | | entity.setParentId(null); |
| | | entity.setUnitQuantity(null); |
| | | entity.setDemandedQuantity(null); |
| | | entity.setUnit(null); |
| | | |
| | | if (item.getId() == null) { |
| | | entity.setParentId(null); |
| | | insertList.add(entity); |
| | | tempEntityMap.put(item.getTempId(), entity); |
| | | } else { |
| | | if (!dbIds.contains(item.getId())) { |
| | | throw new ServiceException("订单BOM物料明细不存在或不属于当前订单BOM"); |
| | | } |
| | | entity.setId(item.getId()); |
| | | submittedIds.add(item.getId()); |
| | | updateList.add(entity); |
| | | } |
| | | } |
| | | |
| | | // 批量新增,拿到数据库生成的真实ID |
| | | Set<Long> deleteIds = new HashSet<>(dbIds); |
| | | deleteIds.removeAll(submittedIds); |
| | | if (!deleteIds.isEmpty()) { |
| | | this.removeByIds(deleteIds); |
| | | } |
| | | if (!updateList.isEmpty()) { |
| | | this.updateBatchById(updateList); |
| | | } |
| | | if (!insertList.isEmpty()) { |
| | | this.saveBatch(insertList); |
| | | } |
| | | |
| | | // 新增节点二次回写父ID(前端传的是临时父ID) |
| | | List<ProductionBomStructure> 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()); |
| | | syncRoutingAndTaskByStructure(orderBomId, productionOrderId); |
| | | return true; |
| | | } |
| | | |
| | | private void syncDemandedQuantityAndTaskPlanQuantity(Long orderBomId, Long productionOrderId) { |
| | | /** |
| | | * 订单BOM物料变化后,按其工序集合重建订单工艺路线工序快照与工单计划数量。 |
| | | */ |
| | | private void syncRoutingAndTaskByStructure(Long orderBomId, Long productionOrderId) { |
| | | if (orderBomId == null) { |
| | | return; |
| | | } |
| | |
| | | return; |
| | | } |
| | | |
| | | BigDecimal orderQuantity = defaultDecimal(productionOrder.getQuantity()); |
| | | List<ProductionBomStructure> structureList = this.list( |
| | | Wrappers.<ProductionBomStructure>lambdaQuery() |
| | | .eq(ProductionBomStructure::getProductionOrderBomId, orderBomId) |
| | | .orderByAsc(ProductionBomStructure::getId)); |
| | | //同步需求数量 |
| | | syncStructureDemandedQuantity(structureList, orderQuantity); |
| | | Long rootProductModelId = orderBom.getProductModelId() != null ? orderBom.getProductModelId() : productionOrder.getProductModelId(); |
| | | //同步生产工艺路线 |
| | | Long rootProductModelId = orderBom.getProductModelId() != null |
| | | ? orderBom.getProductModelId() |
| | | : productionOrder.getProductModelId(); |
| | | // 同步生产工艺路线快照 |
| | | syncRoutingOperationsByBom(currentProductionOrderId, productionOrder, orderBom, structureList, rootProductModelId); |
| | | //同步工单 |
| | | syncTaskPlanQuantity( |
| | | currentProductionOrderId, |
| | | structureList, |
| | | orderQuantity, |
| | | rootProductModelId); |
| | | // 同步工单计划数量:扁平BOM无数量配置,计划数量统一取订单数量 |
| | | syncTaskPlanQuantity(currentProductionOrderId, TaskPlanQuantityUtil.resolveTaskPlanQuantity(productionOrder)); |
| | | } |
| | | |
| | | private void syncStructureDemandedQuantity(List<ProductionBomStructure> structureList, BigDecimal orderQuantity) { |
| | | if (structureList == null || structureList.isEmpty()) { |
| | | return; |
| | | } |
| | | List<ProductionBomStructure> updateList = new ArrayList<>(); |
| | | BigDecimal lastProcessDemandedQuantity = orderQuantity; |
| | | for (ProductionBomStructure structure : structureList) { |
| | | if (structure == null || structure.getId() == null) { |
| | | continue; |
| | | } |
| | | |
| | | BigDecimal demandedQuantity = lastProcessDemandedQuantity.multiply(defaultDecimal(structure.getUnitQuantity())); |
| | | // if (compareDecimal(structure.getDemandedQuantity(), demandedQuantity) == 0) { |
| | | // continue; |
| | | // } |
| | | ProductionBomStructure update = new ProductionBomStructure(); |
| | | update.setId(structure.getId()); |
| | | update.setDemandedQuantity(demandedQuantity); |
| | | updateList.add(update); |
| | | structure.setDemandedQuantity(demandedQuantity); |
| | | lastProcessDemandedQuantity = demandedQuantity; |
| | | } |
| | | if (!updateList.isEmpty()) { |
| | | this.updateBatchById(updateList); |
| | | } |
| | | } |
| | | |
| | | private void syncTaskPlanQuantity(Long productionOrderId, |
| | | List<ProductionBomStructure> structureList, |
| | | BigDecimal orderQuantity, |
| | | Long rootProductModelId) { |
| | | private void syncTaskPlanQuantity(Long productionOrderId, BigDecimal planQuantity) { |
| | | List<ProductionOperationTask> taskList = productionOperationTaskMapper.selectList( |
| | | Wrappers.<ProductionOperationTask>lambdaQuery() |
| | | .eq(ProductionOperationTask::getProductionOrderId, productionOrderId) |
| | |
| | | if (taskList == null || taskList.isEmpty()) { |
| | | return; |
| | | } |
| | | Set<Long> routingOperationIds = taskList.stream() |
| | | .map(ProductionOperationTask::getProductionOrderRoutingOperationId) |
| | | .filter(Objects::nonNull) |
| | | .collect(Collectors.toSet()); |
| | | if (routingOperationIds.isEmpty()) { |
| | | return; |
| | | } |
| | | Map<Long, ProductionOrderRoutingOperation> routingOperationMap = productionOrderRoutingOperationMapper |
| | | .selectBatchIds(routingOperationIds) |
| | | .stream() |
| | | .filter(item -> item != null && item.getId() != null) |
| | | .collect(Collectors.toMap(ProductionOrderRoutingOperation::getId, item -> item, (left, right) -> left)); |
| | | // Keep task plan quantities aligned with the same order BOM snapshot demand used during snapshot creation. |
| | | Map<String, BigDecimal> demandedQuantityMap = TaskPlanQuantityUtil.buildOperationDemandedQuantityMap(structureList, rootProductModelId); |
| | | for (ProductionOperationTask task : taskList) { |
| | | if (task == null || task.getId() == null || task.getProductionOrderRoutingOperationId() == null) { |
| | | if (task == null || task.getId() == null) { |
| | | continue; |
| | | } |
| | | ProductionOrderRoutingOperation routingOperation = routingOperationMap.get(task.getProductionOrderRoutingOperationId()); |
| | | if (routingOperation == null) { |
| | | continue; |
| | | } |
| | | BigDecimal planQuantity = resolveTaskPlanQuantity( |
| | | routingOperation, |
| | | demandedQuantityMap, |
| | | orderQuantity, |
| | | rootProductModelId); |
| | | if (compareDecimal(task.getPlanQuantity(), planQuantity) == 0) { |
| | | continue; |
| | | } |
| | |
| | | return orderRouting; |
| | | } |
| | | |
| | | /** |
| | | * 扁平BOM按工序去重生成期望的订单工序列表, |
| | | * 顺序取物料明细中工序首次出现的顺序,产出规格固定为订单成品规格。 |
| | | */ |
| | | private List<ProductionOrderRoutingOperation> buildDesiredRoutingOperationList(List<ProductionBomStructure> structureList, |
| | | Long rootProductModelId) { |
| | | if (structureList == null || structureList.isEmpty()) { |
| | | return Collections.emptyList(); |
| | | } |
| | | Map<Long, ProductionBomStructure> structureById = structureList.stream() |
| | | .filter(item -> item != null && item.getId() != null) |
| | | .collect(Collectors.toMap(ProductionBomStructure::getId, item -> item, (left, right) -> left)); |
| | | |
| | | // 构建父-子映射关系 |
| | | Map<Long, List<ProductionBomStructure>> treeMap = buildParentChildMap(structureList); |
| | | |
| | | // 使用后序遍历构建操作列表(先子后父,确保工艺路线顺序正确) |
| | | // 使用深度作为排序依据的辅助结构 |
| | | Map<String, ProductionBomStructure> operationMap = new LinkedHashMap<>(); |
| | | Map<String, Integer> depthMap = new HashMap<>(); |
| | | buildOperationListPostOrderWithDepth(null, treeMap, operationMap, depthMap, structureById, rootProductModelId, 1); |
| | | |
| | | // 按深度排序,深度大的排前面 |
| | | List<Map.Entry<String, ProductionBomStructure>> sortedEntries = new ArrayList<>(operationMap.entrySet()); |
| | | sortedEntries.sort((a, b) -> { |
| | | int depthCompare = Integer.compare( |
| | | depthMap.getOrDefault(b.getKey(), 0), |
| | | depthMap.getOrDefault(a.getKey(), 0)); |
| | | if (depthCompare != 0) { |
| | | return depthCompare; |
| | | Set<Long> uniqueOperationIdList = new LinkedHashSet<>(); |
| | | for (ProductionBomStructure structure : structureList) { |
| | | if (structure == null || structure.getTechnologyOperationId() == null) { |
| | | continue; |
| | | } |
| | | return 0; |
| | | }); |
| | | uniqueOperationIdList.add(structure.getTechnologyOperationId()); |
| | | } |
| | | |
| | | List<ProductionOrderRoutingOperation> desiredOperationList = new ArrayList<>(); |
| | | List<ProductionOrderRoutingOperation> desiredOperationList = new ArrayList<>(uniqueOperationIdList.size()); |
| | | int dragSort = 1; |
| | | for (Map.Entry<String, ProductionBomStructure> entry : sortedEntries) { |
| | | ProductionBomStructure bomStructure = entry.getValue(); |
| | | Long outputProductModelId = resolveOutputProductModelId(resolveOperationOutputNode(bomStructure, structureById), rootProductModelId); |
| | | TechnologyOperation technologyOperation = getTechnologyOperation(bomStructure.getTechnologyOperationId()); |
| | | for (Long operationId : uniqueOperationIdList) { |
| | | TechnologyOperation technologyOperation = getTechnologyOperation(operationId); |
| | | ProductionOrderRoutingOperation routingOperation = new ProductionOrderRoutingOperation(); |
| | | routingOperation.setProductModelId(outputProductModelId); |
| | | routingOperation.setTechnologyOperationId(bomStructure.getTechnologyOperationId()); |
| | | routingOperation.setProductModelId(rootProductModelId); |
| | | routingOperation.setTechnologyOperationId(operationId); |
| | | routingOperation.setOperationName(technologyOperation == null ? null : technologyOperation.getName()); |
| | | routingOperation.setIsQuality(technologyOperation == null ? null : technologyOperation.getIsQuality()); |
| | | routingOperation.setIsProduction(technologyOperation == null ? null : technologyOperation.getIsProduction()); |
| | |
| | | desiredOperationList.add(routingOperation); |
| | | } |
| | | return desiredOperationList; |
| | | } |
| | | |
| | | private void buildOperationListPostOrderWithDepth(Long parentId, |
| | | Map<Long, List<ProductionBomStructure>> treeMap, |
| | | Map<String, ProductionBomStructure> operationMap, |
| | | Map<String, Integer> depthMap, |
| | | Map<Long, ProductionBomStructure> structureById, |
| | | Long rootProductModelId, |
| | | int currentDepth) { |
| | | List<ProductionBomStructure> children = treeMap.get(parentId); |
| | | if (children == null || children.isEmpty()) { |
| | | return; |
| | | } |
| | | for (ProductionBomStructure child : children) { |
| | | // 先递归处理子节点 |
| | | buildOperationListPostOrderWithDepth(child.getId(), treeMap, operationMap, depthMap, structureById, rootProductModelId, currentDepth + 1); |
| | | |
| | | // 再处理当前节点 |
| | | if (child.getTechnologyOperationId() != null) { |
| | | Long outputProductModelId = resolveOutputProductModelId(resolveOperationOutputNode(child, structureById), rootProductModelId); |
| | | String key = buildBomOperationDedupKey(child, outputProductModelId); |
| | | // 保留深度最大的操作 |
| | | Integer existingDepth = depthMap.get(key); |
| | | if (existingDepth == null || currentDepth > existingDepth) { |
| | | operationMap.put(key, child); |
| | | depthMap.put(key, currentDepth); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | private Map<Long, List<ProductionBomStructure>> buildParentChildMap(List<ProductionBomStructure> structureList) { |
| | | Map<Long, List<ProductionBomStructure>> treeMap = new LinkedHashMap<>(); |
| | | Map<Long, ProductionBomStructure> structureById = new HashMap<>(); |
| | | |
| | | // 构建父-子映射和ID映射 |
| | | for (ProductionBomStructure structure : structureList) { |
| | | if (structure == null) continue; |
| | | Long parentId = structure.getParentId(); |
| | | treeMap.computeIfAbsent(parentId, k -> new ArrayList<>()).add(structure); |
| | | if (structure.getId() != null) { |
| | | structureById.put(structure.getId(), structure); |
| | | } |
| | | } |
| | | |
| | | // 计算每个节点的深度(从根节点到当前节点的距离,根节点深度为1) |
| | | Map<Long, Integer> depthMap = new HashMap<>(); |
| | | for (ProductionBomStructure structure : structureList) { |
| | | if (structure == null || structure.getId() == null) continue; |
| | | computeDepthFromRoot(structure.getId(), structureById, depthMap); |
| | | } |
| | | |
| | | // 对每个父节点下的子节点按深度倒序排序(最深层的优先) |
| | | for (Map.Entry<Long, List<ProductionBomStructure>> entry : treeMap.entrySet()) { |
| | | List<ProductionBomStructure> children = entry.getValue(); |
| | | children.sort((a, b) -> { |
| | | // 优先按深度排序,深度大的排前面(最深层优先) |
| | | int depthCompare = Integer.compare( |
| | | depthMap.getOrDefault(b.getId(), 0), |
| | | depthMap.getOrDefault(a.getId(), 0)); |
| | | if (depthCompare != 0) { |
| | | return depthCompare; |
| | | } |
| | | // 深度相同时按ID排序保证稳定性 |
| | | return Long.compare(a.getId(), b.getId()); |
| | | }); |
| | | } |
| | | |
| | | return treeMap; |
| | | } |
| | | |
| | | /** |
| | | * 计算节点深度(从根节点到当前节点的距离) |
| | | * 根节点深度为1,每向下一层深度加1 |
| | | */ |
| | | private int computeDepthFromRoot(Long nodeId, Map<Long, ProductionBomStructure> structureById, Map<Long, Integer> depthMap) { |
| | | if (depthMap.containsKey(nodeId)) { |
| | | return depthMap.get(nodeId); |
| | | } |
| | | |
| | | ProductionBomStructure structure = structureById.get(nodeId); |
| | | if (structure == null) { |
| | | depthMap.put(nodeId, 1); |
| | | return 1; |
| | | } |
| | | |
| | | Long parentId = structure.getParentId(); |
| | | if (parentId == null || parentId == 0L) { |
| | | // 根节点深度为1 |
| | | depthMap.put(nodeId, 1); |
| | | return 1; |
| | | } |
| | | |
| | | // 子节点深度 = 父节点深度 + 1 |
| | | int parentDepth = computeDepthFromRoot(parentId, structureById, depthMap); |
| | | int depth = parentDepth + 1; |
| | | depthMap.put(nodeId, depth); |
| | | return depth; |
| | | } |
| | | |
| | | private void buildOperationListPostOrder(Long parentId, |
| | | Map<Long, List<ProductionBomStructure>> treeMap, |
| | | Map<String, ProductionBomStructure> uniqueOperationMap, |
| | | Map<Long, ProductionBomStructure> structureById, |
| | | Long rootProductModelId) { |
| | | List<ProductionBomStructure> children = treeMap.get(parentId); |
| | | if (children == null || children.isEmpty()) { |
| | | return; |
| | | } |
| | | for (ProductionBomStructure child : children) { |
| | | // 先递归处理子节点 |
| | | buildOperationListPostOrder(child.getId(), treeMap, uniqueOperationMap, structureById, rootProductModelId); |
| | | |
| | | // 再处理当前节点 |
| | | if (child.getTechnologyOperationId() != null) { |
| | | Long outputProductModelId = resolveOutputProductModelId(resolveOperationOutputNode(child, structureById), rootProductModelId); |
| | | String key = buildBomOperationDedupKey(child, outputProductModelId); |
| | | // 去重时保留深度最大的操作(后序遍历先遇到深层节点,所以直接覆盖即可) |
| | | uniqueOperationMap.put(key, child); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private Map<String, Deque<ProductionOrderRoutingOperation>> buildExistingRoutingOperationBucketMap(List<ProductionOrderRoutingOperation> existingOperationList) { |
| | |
| | | } |
| | | } |
| | | |
| | | private Map<String, BigDecimal> buildOperationDemandedQuantityMap(List<ProductionBomStructure> structureList, |
| | | Long rootProductModelId) { |
| | | if (structureList == null || structureList.isEmpty()) { |
| | | return Collections.emptyMap(); |
| | | } |
| | | Map<Long, ProductionBomStructure> structureById = structureList.stream() |
| | | .filter(item -> item != null && item.getId() != null) |
| | | .collect(Collectors.toMap(ProductionBomStructure::getId, item -> item, (left, right) -> left)); |
| | | Map<String, BigDecimal> demandedQuantityMap = new HashMap<>(); |
| | | Set<String> mergedOutputNodeKeySet = new HashSet<>(); |
| | | for (ProductionBomStructure bomStructure : structureList) { |
| | | if (bomStructure == null || bomStructure.getTechnologyOperationId() == null) { |
| | | continue; |
| | | } |
| | | // Resolve the output node first, then read the output node demand for the task plan quantity. |
| | | ProductionBomStructure outputNode = resolveOperationOutputNode(bomStructure, structureById); |
| | | Long outputProductModelId = resolveOutputProductModelId(outputNode, rootProductModelId); |
| | | if (outputProductModelId == null) { |
| | | continue; |
| | | } |
| | | String mergedOutputNodeKey = buildOperationOutputNodeKey( |
| | | bomStructure.getTechnologyOperationId(), |
| | | outputNode == null ? null : outputNode.getId(), |
| | | outputProductModelId); |
| | | if (!mergedOutputNodeKeySet.add(mergedOutputNodeKey)) { |
| | | continue; |
| | | } |
| | | // Multiple input rows can point to the same output node, so only count that output demand once. |
| | | String key = buildOperationDemandedQuantityKey(bomStructure.getTechnologyOperationId(), outputProductModelId); |
| | | demandedQuantityMap.merge(key, defaultDecimal(outputNode == null ? null : outputNode.getDemandedQuantity()), BigDecimal::add); |
| | | } |
| | | return demandedQuantityMap; |
| | | } |
| | | |
| | | private BigDecimal resolveTaskPlanQuantity(ProductionOrderRoutingOperation routingOperation, |
| | | Map<String, BigDecimal> demandedQuantityMap, |
| | | BigDecimal orderQuantity, |
| | | Long rootProductModelId) { |
| | | if (routingOperation == null || demandedQuantityMap == null || demandedQuantityMap.isEmpty()) { |
| | | return orderQuantity; |
| | | } |
| | | Long outputProductModelId = routingOperation.getProductModelId() != null |
| | | ? routingOperation.getProductModelId() |
| | | : rootProductModelId; |
| | | String key = buildOperationDemandedQuantityKey( |
| | | routingOperation.getTechnologyOperationId(), |
| | | outputProductModelId); |
| | | 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 String buildRoutingOperationBucketKey(Long operationId, Long outputProductModelId) { |
| | | return String.valueOf(operationId) + "#" + String.valueOf(outputProductModelId); |
| | | } |
| | | |
| | | private String buildBomOperationDedupKey(ProductionBomStructure bomStructure, Long outputProductModelId) { |
| | | Long operationId = bomStructure == null ? null : bomStructure.getTechnologyOperationId(); |
| | | Long parentId = bomStructure == null ? null : bomStructure.getParentId(); |
| | | return operationId + "#" + outputProductModelId + "#" + parentId; |
| | | } |
| | | |
| | | private String buildOperationOutputNodeKey(Long operationId, Long outputNodeId, Long outputProductModelId) { |
| | | return String.valueOf(operationId) + "#" + String.valueOf(outputNodeId) + "#" + String.valueOf(outputProductModelId); |
| | | } |
| | | |
| | | private ProductionBomStructure resolveOperationOutputNode(ProductionBomStructure bomStructure, |
| | | Map<Long, ProductionBomStructure> structureById) { |
| | | if (bomStructure == null) { |
| | | return null; |
| | | } |
| | | // The root node is the first output node; other rows use their direct parent as the current operation output. |
| | | if (bomStructure.getParentId() == null) { |
| | | return bomStructure; |
| | | } |
| | | ProductionBomStructure parent = structureById.get(bomStructure.getParentId()); |
| | | return parent != null ? parent : bomStructure; |
| | | } |
| | | |
| | | private Long resolveOutputProductModelId(ProductionBomStructure outputNode, |
| | | Long rootProductModelId) { |
| | | if (outputNode == null) { |
| | | return rootProductModelId; |
| | | } |
| | | return outputNode.getProductModelId() != null ? outputNode.getProductModelId() : rootProductModelId; |
| | | } |
| | | |
| | | private TechnologyOperation getTechnologyOperation(Long technologyOperationId) { |
| | |
| | | |
| | | private int compareDecimal(BigDecimal left, BigDecimal right) { |
| | | return defaultDecimal(left).compareTo(defaultDecimal(right)); |
| | | } |
| | | |
| | | /** |
| | | * 将树形结构拍平成列表,便于统一保存。 |
| | | */ |
| | | private void flattenTree(List<ProductionBomStructureDto> source, List<ProductionBomStructureDto> result) { |
| | | // 扁平化处理树 |
| | | if (source == null) { |
| | | return; |
| | | } |
| | | for (ProductionBomStructureDto node : source) { |
| | | result.add(node); |
| | | flattenTree(node.getChildren(), result); |
| | | } |
| | | } |
| | | |
| | | } |