| | |
| | | import com.ruoyi.common.exception.ServiceException; |
| | | import com.ruoyi.common.utils.bean.BeanUtils; |
| | | import com.ruoyi.procurementrecord.utils.StockUtils; |
| | | import com.ruoyi.production.bean.dto.ProductStructureDto; |
| | | import com.ruoyi.production.bean.dto.ProductionProductMainDto; |
| | | import com.ruoyi.production.enums.ProductOrderStatusEnum; |
| | | import com.ruoyi.production.mapper.*; |
| | | import com.ruoyi.production.pojo.*; |
| | | import com.ruoyi.production.service.ProductionOrderPickService; |
| | | import com.ruoyi.production.service.ProductionProductMainService; |
| | | import com.ruoyi.project.system.domain.SysUser; |
| | | import com.ruoyi.project.system.mapper.SysUserMapper; |
| | |
| | | private final ProductionAccountMapper productionAccountMapper; |
| | | private final ProductionOperationTaskMapper productionOperationTaskMapper; |
| | | private final ProductionOrderMapper productionOrderMapper; |
| | | private final ProductionOrderBomMapper productionOrderBomMapper; |
| | | private final ProductionBomStructureMapper productionBomStructureMapper; |
| | | private final ProductionOrderRoutingOperationMapper productionOrderRoutingOperationMapper; |
| | | private final ProductionOrderRoutingOperationParamMapper productionOrderRoutingOperationParamMapper; |
| | | private final ProductionOrderPickMapper productionOrderPickMapper; |
| | | private final ProductionOrderPickService productionOrderPickService; |
| | | private final TechnologyRoutingOperationMapper technologyRoutingOperationMapper; |
| | | private final TechnologyOperationMapper technologyOperationMapper; |
| | | private final StockUtils stockUtils; |
| | |
| | | if (productionOrder == null) { |
| | | throw new ServiceException("生产订单不存在"); |
| | | } |
| | | // 报工前置条件:当前工序必须已经领料(任一物料净领料数量大于0)。 |
| | | // 该校验在事务内执行,绕过前端直接调用接口同样受限。 |
| | | if (!productionOrderPickService.hasEffectivePick(taskId)) { |
| | | throw new ServiceException("当前工序未领料,无法报工"); |
| | | } |
| | | TechnologyRoutingOperation technologyRoutingOperation = technologyRoutingOperationMapper.selectById(routingOperation.getTechnologyRoutingOperationId()); |
| | | TechnologyOperation technologyOperation = technologyRoutingOperation == null ? null |
| | | : technologyOperationMapper.selectById(technologyRoutingOperation.getTechnologyOperationId()); |
| | |
| | | productionProductMainMapper.insert(productionProductMain); |
| | | syncOperationParamInputValue(dto, routingOperation.getId(), productionProductMain.getId()); |
| | | |
| | | List<ProductStructureDto> productStructureDtos = resolveInputStructures( |
| | | productionOrder.getId(), routingOperation, productModel.getId()); |
| | | // 如果没有bom子节点了,那么投入就是他本身 |
| | | if (productStructureDtos.isEmpty()) { |
| | | ProductStructureDto fallbackInput = new ProductStructureDto(); |
| | | fallbackInput.setProductModelId(productModel.getId()); |
| | | fallbackInput.setUnitQuantity(BigDecimal.ONE); |
| | | productStructureDtos.add(fallbackInput); |
| | | } |
| | | for (ProductStructureDto item : productStructureDtos) { |
| | | // 当前实现按工序成品直接作为投入,后续若接入领料记录可在这里替换来源。 |
| | | ProductionProductInput productionProductInput = new ProductionProductInput(); |
| | | productionProductInput.setProductionProductMainId(productionProductMain.getId()); |
| | | productionProductInput.setProductModelId(item.getProductModelId()); |
| | | productionProductInput.setInputQuantity(item.getUnitQuantity().multiply(defaultDecimal(dto.getQuantity()))); |
| | | productionProductInput.setQuantity(productionProductInput.getInputQuantity()); |
| | | productionProductInputMapper.insert(productionProductInput); |
| | | } |
| | | // 报工投入来源于当前工序的实际领料记录,不再按 BOM 单位用量 × 报工数量推算。 |
| | | // 每条有效领料只会被首次报工归集一次,重复报工不会重复计入同一批物料。 |
| | | insertInputsFromTaskPicks(taskId, productionProductMain.getId()); |
| | | |
| | | ProductionProductOutput productionProductOutput = new ProductionProductOutput(); |
| | | productionProductOutput.setProductionProductMainId(productionProductMain.getId()); |
| | |
| | | return target; |
| | | } |
| | | |
| | | private List<ProductStructureDto> resolveInputStructures(Long productionOrderId, |
| | | ProductionOrderRoutingOperation routingOperation, |
| | | Long outputProductModelId) { |
| | | if (productionOrderId == null || routingOperation == null || routingOperation.getTechnologyOperationId() == null) { |
| | | return new ArrayList<>(); |
| | | /** |
| | | * 用当前工序尚未被报工归集的实际领料生成报工投入。 |
| | | * 投入数量取净领料数量(领料+补料-退料),并记录来源领料ID, |
| | | * 保证同一笔领料不会在多次报工中被重复计入。 |
| | | */ |
| | | private void insertInputsFromTaskPicks(Long productionOperationTaskId, Long productionProductMainId) { |
| | | if (productionOperationTaskId == null || productionProductMainId == null) { |
| | | return; |
| | | } |
| | | 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<>(); |
| | | List<ProductionOrderPick> taskPickList = productionOrderPickMapper.selectList( |
| | | Wrappers.<ProductionOrderPick>lambdaQuery() |
| | | .eq(ProductionOrderPick::getProductionOperationTaskId, productionOperationTaskId) |
| | | .orderByAsc(ProductionOrderPick::getId)); |
| | | if (taskPickList.isEmpty()) { |
| | | return; |
| | | } |
| | | List<Long> pickIdList = taskPickList.stream() |
| | | .map(ProductionOrderPick::getId) |
| | | .filter(Objects::nonNull) |
| | | .collect(Collectors.toList()); |
| | | Set<Long> consumedPickIdSet = pickIdList.isEmpty() |
| | | ? Collections.emptySet() |
| | | : productionProductInputMapper.selectList( |
| | | Wrappers.<ProductionProductInput>lambdaQuery() |
| | | .in(ProductionProductInput::getProductionOrderPickId, pickIdList)) |
| | | .stream() |
| | | .map(ProductionProductInput::getProductionOrderPickId) |
| | | .filter(Objects::nonNull) |
| | | .collect(Collectors.toSet()); |
| | | |
| | | List<ProductionBomStructure> bomNodeList = productionBomStructureMapper.selectList( |
| | | Wrappers.<ProductionBomStructure>lambdaQuery() |
| | | .eq(ProductionBomStructure::getProductionOrderBomId, orderBom.getId()) |
| | | .orderByAsc(ProductionBomStructure::getId)); |
| | | if (bomNodeList.isEmpty()) { |
| | | return new ArrayList<>(); |
| | | } |
| | | |
| | | Map<Long, ProductionBomStructure> nodeMap = bomNodeList.stream() |
| | | .filter(item -> item != null && item.getId() != null) |
| | | .collect(Collectors.toMap(ProductionBomStructure::getId, item -> item, (left, right) -> left)); |
| | | Long currentOutputModelId = routingOperation.getProductModelId() != null |
| | | ? routingOperation.getProductModelId() |
| | | : outputProductModelId; |
| | | |
| | | Map<Long, BigDecimal> unitQtyByProductModel = new LinkedHashMap<>(); |
| | | for (ProductionBomStructure node : bomNodeList) { |
| | | if (node == null || node.getParentId() == null || node.getProductModelId() == null) { |
| | | for (ProductionOrderPick pick : taskPickList) { |
| | | if (pick == null || pick.getId() == null || consumedPickIdSet.contains(pick.getId())) { |
| | | continue; |
| | | } |
| | | if (!Objects.equals(node.getTechnologyOperationId(), routingOperation.getTechnologyOperationId())) { |
| | | BigDecimal effectiveQuantity = defaultDecimal(pick.getQuantity()) |
| | | .add(defaultDecimal(pick.getFeedingQty())) |
| | | .subtract(defaultDecimal(pick.getReturnQty())); |
| | | if (effectiveQuantity.compareTo(BigDecimal.ZERO) <= 0) { |
| | | continue; |
| | | } |
| | | ProductionBomStructure parent = nodeMap.get(node.getParentId()); |
| | | if (parent == null || !Objects.equals(parent.getProductModelId(), currentOutputModelId)) { |
| | | continue; |
| | | ProductionProductInput productionProductInput = new ProductionProductInput(); |
| | | productionProductInput.setProductionProductMainId(productionProductMainId); |
| | | productionProductInput.setProductModelId(pick.getProductModelId()); |
| | | productionProductInput.setProductionOrderPickId(pick.getId()); |
| | | productionProductInput.setInputQuantity(effectiveQuantity); |
| | | productionProductInput.setQuantity(effectiveQuantity); |
| | | productionProductInputMapper.insert(productionProductInput); |
| | | } |
| | | unitQtyByProductModel.merge(node.getProductModelId(), defaultDecimal(node.getUnitQuantity()), BigDecimal::add); |
| | | } |
| | | |
| | | List<ProductStructureDto> result = new ArrayList<>(); |
| | | for (Map.Entry<Long, BigDecimal> entry : unitQtyByProductModel.entrySet()) { |
| | | if (entry.getValue().compareTo(BigDecimal.ZERO) <= 0) { |
| | | continue; |
| | | } |
| | | ProductStructureDto item = new ProductStructureDto(); |
| | | item.setProductModelId(entry.getKey()); |
| | | item.setUnitQuantity(entry.getValue()); |
| | | result.add(item); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | private Boolean removeProductMainByProductionTask(ProductionProductMain productionProductMain) { |