| | |
| | | productionOrderBomMapper.insert(orderBom); |
| | | |
| | | Map<Long, Long> idMap = new HashMap<>(); |
| | | Map<Long, Boolean> operationPickMap = loadOperationPickMap(structureList.stream() |
| | | .map(TechnologyBomStructure::getOperationId) |
| | | .collect(Collectors.toList())); |
| | | for (TechnologyBomStructure source : structureList) { |
| | | // 子节点 parentId 需要映射成新快照节点 id,才能保留原始 BOM 层级。 |
| | | ProductionBomStructure target = new ProductionBomStructure(); |
| | |
| | | target.setParentId(source.getParentId() == null ? null : idMap.get(source.getParentId())); |
| | | target.setProductModelId(source.getProductModelId()); |
| | | target.setTechnologyOperationId(source.getOperationId()); |
| | | target.setIsPick(operationPickMap.getOrDefault(source.getOperationId(), Boolean.FALSE)); |
| | | target.setUnitQuantity(source.getUnitQuantity()); |
| | | target.setDemandedQuantity(defaultDecimal(orderQuantity) |
| | | .multiply(defaultDecimal(source.getUnitQuantity()))); |
| | |
| | | idMap.put(source.getId(), target.getId()); |
| | | } |
| | | return orderBom; |
| | | } |
| | | |
| | | /** |
| | | * 批量读取工序的“是否领料”标记,避免BOM快照逐行查询工序表。 |
| | | */ |
| | | private Map<Long, Boolean> loadOperationPickMap(List<Long> technologyOperationIds) { |
| | | if (technologyOperationIds == null || technologyOperationIds.isEmpty()) { |
| | | return Collections.emptyMap(); |
| | | } |
| | | Set<Long> operationIdSet = technologyOperationIds.stream() |
| | | .filter(Objects::nonNull) |
| | | .collect(Collectors.toSet()); |
| | | if (operationIdSet.isEmpty()) { |
| | | return Collections.emptyMap(); |
| | | } |
| | | return technologyOperationMapper.selectBatchIds(operationIdSet).stream() |
| | | .filter(item -> item != null && item.getId() != null) |
| | | .collect(Collectors.toMap(TechnologyOperation::getId, |
| | | item -> Boolean.TRUE.equals(item.getIsPick()), |
| | | (left, right) -> left)); |
| | | } |
| | | |
| | | private void clearProductionSnapshot(Long productionOrderId) { |
| | |
| | | } |
| | | } |
| | | |
| | | // 收集订单成品规格ID:包含生产订单成品规格与BOM根节点规格,用于排除“自己”这一物料 |
| | | // production_bom_structure.is_pick 是生产计划下发时的工序领料标记快照; |
| | | // 领料台账读取时以工序主数据的最新标记为准,保证工序上的“是否领料”调整后无需重新下发即可生效; |
| | | // 工序已被删除时保留快照值,避免历史订单的领料范围丢失。 |
| | | Map<Long, Boolean> operationPickMap = loadOperationPickMap(bomStructureList.stream() |
| | | .filter(Objects::nonNull) |
| | | .map(ProductionBomStructureVo::getTechnologyOperationId) |
| | | .collect(Collectors.toList())); |
| | | for (ProductionBomStructureVo structure : bomStructureList) { |
| | | if (structure == null || structure.getTechnologyOperationId() == null) { |
| | | continue; |
| | | } |
| | | Boolean latestPick = operationPickMap.get(structure.getTechnologyOperationId()); |
| | | if (latestPick != null) { |
| | | structure.setIsPick(latestPick); |
| | | } |
| | | } |
| | | |
| | | // 收集订单成品规格ID:生产订单成品规格 + 订单BOM快照的根节点规格,用于排除“自己”这一物料。 |
| | | // 注意:BOM结构里领料物料本身也可能是 parent_id 为空的独立节点,这里不能再把所有根节点都当成成品排除, |
| | | // 否则“领料”工序下的物料会被整体过滤掉。 |
| | | Set<Long> finishedProductModelIds = new HashSet<>(); |
| | | if (productionOrder != null && productionOrder.getProductModelId() != null) { |
| | | finishedProductModelIds.add(productionOrder.getProductModelId()); |
| | | } |
| | | bomStructureList.stream() |
| | | .filter(s -> s != null && (s.getParentId() == null || s.getParentId() == 0)) |
| | | .map(ProductionBomStructureVo::getProductModelId) |
| | | .filter(Objects::nonNull) |
| | | .forEach(finishedProductModelIds::add); |
| | | if (orderBom.getProductModelId() != null) { |
| | | finishedProductModelIds.add(orderBom.getProductModelId()); |
| | | } |
| | | |
| | | // 收集所有被引用为父级的节点ID:BOM从最后一道工序往里配置前一道工序, |
| | | // 中间节点是上一道工序的产出(自制),只有每条分支最里层的叶子节点才是真正需要领用的物料。 |
| | | // 中间节点是上一道工序的产出(自制),旧口径只有每条分支最里层的叶子节点才是真正需要领用的物料。 |
| | | Set<Long> parentNodeIds = bomStructureList.stream() |
| | | .filter(s -> s != null && s.getParentId() != null && s.getParentId() != 0) |
| | | .map(ProductionBomStructureVo::getParentId) |
| | | .filter(Objects::nonNull) |
| | | .collect(Collectors.toSet()); |
| | | |
| | | // 过滤出叶子节点(实际领料项):非根节点、非中间节点,并排除与订单成品同规格的物料(自制成品无需领料) |
| | | List<ProductionBomStructureVo> childStructureList = bomStructureList.stream() |
| | | .filter(s -> s != null && s.getParentId() != null && s.getParentId() != 0) |
| | | .filter(s -> s.getId() == null || !parentNodeIds.contains(s.getId())) |
| | | // 领料范围以工序的“是否领料”标记为准:只有标记了领料的工序,其消耗的物料才进入领料台账。 |
| | | // 注意:领料物料在BOM结构中可能是独立节点(parent_id 为空),所以这里不限制层级,只看工序标记; |
| | | // 只排除与订单成品同规格的物料(成品本身无需领料)。 |
| | | List<ProductionBomStructureVo> markedPickStructureList = bomStructureList.stream() |
| | | .filter(s -> s != null && Boolean.TRUE.equals(s.getIsPick())) |
| | | .filter(s -> s.getProductModelId() == null || !finishedProductModelIds.contains(s.getProductModelId())) |
| | | .collect(Collectors.toList()); |
| | | |
| | | // 实际领料项:优先使用工序领料标记; |
| | | // 兼容历史数据:当该订单BOM结构里没有任何工序标记领料时,回退为原来的“叶子节点领料”规则, |
| | | // 避免尚未启用“是否领料”标记的客户打开领料台账时数据突然为空。 |
| | | List<ProductionBomStructureVo> childStructureList = markedPickStructureList; |
| | | if (childStructureList.isEmpty()) { |
| | | childStructureList = bomStructureList.stream() |
| | | .filter(s -> s != null && s.getParentId() != null && s.getParentId() != 0) |
| | | .filter(s -> s.getId() == null || !parentNodeIds.contains(s.getId())) |
| | | .filter(s -> s.getProductModelId() == null || !finishedProductModelIds.contains(s.getProductModelId())) |
| | | .collect(Collectors.toList()); |
| | | } |
| | | |
| | | // 遍历处理数据并组装结果 |
| | | List<Long> productModelIds = childStructureList.stream() |
| | | .map(ProductionBomStructureVo::getProductModelId) |