yuan
15 小时以前 47806d9e390ee00e1d29ad1da8c1aa908882a758
src/main/java/com/ruoyi/production/service/impl/ProductionOrderServiceImpl.java
@@ -22,6 +22,8 @@
import com.ruoyi.production.bean.vo.ProductionOrderVo;
import com.ruoyi.production.bean.vo.ProductionPlanVo;
import com.ruoyi.production.bean.vo.ProductionOrderWorkOrderDetailVo;
import com.ruoyi.production.bean.vo.ProcessRouteStatusVo;
import com.ruoyi.production.bean.vo.ProductionOrderProcessTaskVo;
import com.ruoyi.production.enums.ProductOrderStatusEnum;
import com.ruoyi.production.mapper.*;
import com.ruoyi.production.pojo.*;
@@ -44,7 +46,6 @@
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
@@ -87,6 +88,7 @@
        // 分页查询生产订单
        Page<ProductionOrderVo> result = (Page<ProductionOrderVo>) baseMapper.pageProductionOrder(page, dto);
        fillProductImages(result.getRecords());
        fillProcessRouteStatus(result.getRecords());
        return result;
    }
@@ -95,6 +97,7 @@
        // 查询生产订单列表
        List<ProductionOrderVo> records = baseMapper.listProductionOrder(dto);
        fillProductImages(records);
        fillProcessRouteStatus(records);
        return records;
    }
@@ -116,7 +119,7 @@
        // 下单入口统一补齐来源单据、计划和工艺信息,避免前端分别传多套字段。
        validateAndFillOrder(productionOrder, oldOrder);
        if (productionOrder.getNpsNo() == null || productionOrder.getNpsNo().trim().isEmpty()) {
            productionOrder.setNpsNo(generateNextOrderNo(productionOrder.getCreateTime() != null ? productionOrder.getCreateTime() : LocalDateTime.now()));
            productionOrder.setNpsNo(generateNextOrderNo());
        }
        if (productionOrder.getCompleteQuantity() == null) {
            productionOrder.setCompleteQuantity(BigDecimal.ZERO);
@@ -134,7 +137,7 @@
                || !Objects.equals(oldOrder.getProductModelId(), productionOrder.getProductModelId())
                || compareDecimal(oldOrder.getQuantity(), productionOrder.getQuantity()) != 0
                || productionOrderRoutingMapper.selectCount(Wrappers.<ProductionOrderRouting>lambdaQuery()
                        .eq(ProductionOrderRouting::getProductionOrderId, productionOrder.getId())) == 0);
                .eq(ProductionOrderRouting::getProductionOrderId, productionOrder.getId())) == 0);
        if (needSync) {
            syncProductionOrderSnapshot(productionOrder.getId());
        }
@@ -260,7 +263,7 @@
        Map<String, BigDecimal> operationDemandedQuantityMap =
                buildOperationDemandedQuantityMap(orderBomStructureList, rootProductModelId);
        Map<Long, String> operationNameMap = technologyOperationMapper.selectBatchIds(
        // 遍历处理数据并组装结果
                        // 遍历处理数据并组装结果
                        routingOperations.stream()
                                .map(TechnologyRoutingOperation::getTechnologyOperationId)
                                .filter(Objects::nonNull)
@@ -461,7 +464,7 @@
    private void clearProductionSnapshot(Long productionOrderId) {
        // 清理订单已生成的工艺与BOM快照数据
        boolean hasPickRecord = productionOrderPickRecordMapper.selectCount(
        // 查询并准备业务数据
                // 查询并准备业务数据
                Wrappers.<ProductionOrderPickRecord>lambdaQuery()
                        .eq(ProductionOrderPickRecord::getProductionOrderId, productionOrderId)) > 0;
        // 参数与前置条件校验
@@ -471,7 +474,7 @@
        List<Long> taskIds = productionOperationTaskMapper.selectList(
                        Wrappers.<ProductionOperationTask>lambdaQuery()
                                .eq(ProductionOperationTask::getProductionOrderId, productionOrderId))
        // 遍历处理数据并组装结果
                // 遍历处理数据并组装结果
                .stream().map(ProductionOperationTask::getId).collect(Collectors.toList());
        if (!taskIds.isEmpty()) {
            // 已有报工记录说明订单已开工,此时不允许再重建快照。
@@ -509,10 +512,9 @@
                .orderByDesc(ProductionOrder::getId);
    }
    private String generateNextOrderNo(LocalDateTime createTime) {
    private String generateNextOrderNo() {
        // 生成下一个生产订单号
        LocalDate localDate = createTime.toLocalDate();
        String datePrefix = localDate.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
        String datePrefix = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
        String prefix = "SC" + datePrefix;
        ProductionOrder latestOrder = this.getOne(Wrappers.<ProductionOrder>lambdaQuery()
                .likeRight(ProductionOrder::getNpsNo, prefix)
@@ -814,6 +816,46 @@
        }
    }
    private void fillProcessRouteStatus(List<ProductionOrderVo> records) {
        if (records == null || records.isEmpty()) {
            return;
        }
        List<Long> orderIds = records.stream()
                .map(ProductionOrderVo::getId)
                .filter(Objects::nonNull)
                .distinct()
                .collect(Collectors.toList());
        if (orderIds.isEmpty()) {
            return;
        }
        List<ProductionOrderProcessTaskVo> tasks = productionOperationTaskMapper.listProcessStatusByOrderIds(orderIds);
        Map<Long, List<ProcessRouteStatusVo>> statusMap = new LinkedHashMap<>();
        if (tasks != null) {
            for (ProductionOrderProcessTaskVo task : tasks) {
                if (task == null || task.getProductionOrderId() == null) {
                    continue;
                }
                ProcessRouteStatusVo status = new ProcessRouteStatusVo();
                status.setName(task.getOperationName() != null && !task.getOperationName().isBlank()
                        ? task.getOperationName()
                        : "未知工序");
                BigDecimal percentage = task.getCompletionStatus() == null
                        ? BigDecimal.ZERO
                        : task.getCompletionStatus();
                if (percentage.compareTo(new BigDecimal("100")) > 0) {
                    percentage = new BigDecimal("100");
                }
                status.setPercentage(percentage);
                statusMap.computeIfAbsent(task.getProductionOrderId(), key -> new ArrayList<>()).add(status);
            }
        }
        for (ProductionOrderVo record : records) {
            record.setProcessRouteStatus(statusMap.getOrDefault(record.getId(), Collections.emptyList()));
        }
    }
    private StorageBlobVO toStorageBlobVO(StorageBlob blob) {
        // 将存储文件对象转换为VO
        StorageBlobVO vo = BeanUtil.copyProperties(blob, StorageBlobVO.class);
@@ -841,7 +883,7 @@
                new Page<ProductionOperationTaskVo>(1, -1), taskQuery);
        List<ProductionOperationTaskVo> workOrderList = workOrderPage == null || workOrderPage.getRecords() == null
                ? Collections.emptyList()
        // 遍历处理数据并组装结果
                // 遍历处理数据并组装结果
                : workOrderPage.getRecords().stream()
                .filter(Objects::nonNull)
                .sorted(Comparator.comparing(ProductionOperationTaskVo::getId, Comparator.nullsLast(Comparator.naturalOrder())))
@@ -857,7 +899,7 @@
                .collect(Collectors.toList());
        List<ProductionProductMain> reportMainList = workOrderIdList.isEmpty()
                ? Collections.emptyList()
        // 查询并准备业务数据
                // 查询并准备业务数据
                : productionProductMainMapper.selectList(
                Wrappers.<ProductionProductMain>lambdaQuery()
                        .in(ProductionProductMain::getProductionOperationTaskId, workOrderIdList)
@@ -1065,20 +1107,30 @@
            }
        }
        // 过滤出非根节点(实际领料项)
        // 排除投入品与产出品相同且比例为1的情况(自身加工,不需要领料)
        // 收集订单成品规格ID:包含生产订单成品规格与BOM根节点规格,用于排除“自己”这一物料
        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);
        // 收集所有被引用为父级的节点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 -> {
                    ProductionBomStructureVo parent = structureByIdMap.get(s.getParentId());
                    if (parent == null) {
                        return true;
                    }
                    // 投入品与产出品相同且比例为1时,不需要领料
                    boolean sameProduct = Objects.equals(s.getProductModelId(), parent.getProductModelId());
                    boolean unitRatio = BigDecimal.ONE.compareTo(defaultDecimal(s.getUnitQuantity())) == 0;
                    return !(sameProduct && unitRatio);
                })
                .filter(s -> s.getId() == null || !parentNodeIds.contains(s.getId()))
                .filter(s -> s.getProductModelId() == null || !finishedProductModelIds.contains(s.getProductModelId()))
                .collect(Collectors.toList());
        // 遍历处理数据并组装结果
@@ -1092,7 +1144,8 @@
        if (!productModelIds.isEmpty()) {
            List<StockInventory> stockList = stockInventoryMapper.selectList(
                    Wrappers.<StockInventory>lambdaQuery()
                            .in(StockInventory::getProductModelId, productModelIds));
                            .in(StockInventory::getProductModelId, productModelIds)
                            .gt(StockInventory::getQualitity, BigDecimal.ZERO));
            for (StockInventory stockItem : stockList) {
                if (stockItem == null || stockItem.getProductModelId() == null) {
                    continue;
@@ -1106,29 +1159,34 @@
            }
        }
        List<ProductionOrderPickVo> pickList = new ArrayList<>();
        Map<String, ProductionOrderPickVo> mergedPickMap = new LinkedHashMap<>();
        for (ProductionBomStructureVo structure : childStructureList) {
            if (structure == null || structure.getProductModelId() == null) {
                continue;
            }
            Long productModelId = structure.getProductModelId();
            ProductionOrderPickVo vo = new ProductionOrderPickVo();
            vo.setProductModelId(productModelId);
            vo.setOperationName(structure.getOperationName());
            vo.setTechnologyOperationId(structure.getTechnologyOperationId());
            vo.setProductName(structure.getProductName());
            vo.setModel(structure.getModel());
            vo.setDemandedQuantity(defaultDecimal(structure.getDemandedQuantity()));
            vo.setUnit(structure.getUnit());
            List<String> batchNoList = stockBatchNoMap.get(productModelId) == null
                    ? Collections.emptyList()
                    : new ArrayList<>(stockBatchNoMap.get(productModelId));
            vo.setBatchNoList(batchNoList);
            vo.setStockQuantity(stockQuantityMap.getOrDefault(productModelId, BigDecimal.ZERO));
            vo.setBom(true);
            pickList.add(vo);
            String mergeKey = String.valueOf(structure.getTechnologyOperationId()) + "#" + productModelId;
            ProductionOrderPickVo vo = mergedPickMap.get(mergeKey);
            if (vo == null) {
                vo = new ProductionOrderPickVo();
                vo.setProductModelId(productModelId);
                vo.setOperationName(structure.getOperationName());
                vo.setTechnologyOperationId(structure.getTechnologyOperationId());
                vo.setProductName(structure.getProductName());
                vo.setModel(structure.getModel());
                vo.setDemandedQuantity(BigDecimal.ZERO);
                vo.setUnit(structure.getUnit());
                List<String> batchNoList = stockBatchNoMap.get(productModelId) == null
                        ? Collections.emptyList()
                        : new ArrayList<>(stockBatchNoMap.get(productModelId));
                vo.setBatchNoList(batchNoList);
                vo.setStockQuantity(stockQuantityMap.getOrDefault(productModelId, BigDecimal.ZERO));
                vo.setBom(true);
                mergedPickMap.put(mergeKey, vo);
            }
            vo.setDemandedQuantity(defaultDecimal(vo.getDemandedQuantity()).add(defaultDecimal(structure.getDemandedQuantity())));
        }
        return pickList;
        return new ArrayList<>(mergedPickMap.values());
    }
    @Override