gaoluyang
2026-11-03 a829dd34332bbc10c6b42ef86635ed48e34c6207
src/main/java/com/ruoyi/production/service/impl/ProductionOrderServiceImpl.java
@@ -34,7 +34,9 @@
import com.ruoyi.production.service.ProductionOrderService;
import com.ruoyi.sales.mapper.SalesLedgerMapper;
import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
import com.ruoyi.stock.mapper.StockInRecordMapper;
import com.ruoyi.stock.mapper.StockInventoryMapper;
import com.ruoyi.stock.pojo.StockInRecord;
import com.ruoyi.stock.pojo.StockInventory;
import com.ruoyi.technology.mapper.*;
import com.ruoyi.technology.pojo.*;
@@ -70,6 +72,7 @@
    private final QualityInspectFileMapper qualityInspectFileMapper;
    private final ProductionPlanMapper productionPlanMapper;
    private final StockInventoryMapper stockInventoryMapper;
    private final StockInRecordMapper stockInRecordMapper;
    private final StorageAttachmentMapper storageAttachmentMapper;
    private final StorageBlobMapper storageBlobMapper;
    private final SalesLedgerMapper salesLedgerMapper;
@@ -439,9 +442,54 @@
        productionOrderBomMapper.insert(orderBom);
        Map<Long, Long> idMap = new HashMap<>();
        BigDecimal lastProcessDemandedQuantity = orderQuantity;
        // 构建 sourceId → sourceNode 映射 和 parentId → children 映射
        Map<Long, TechnologyBomStructure> sourceById = new HashMap<>();
        Map<Long, List<TechnologyBomStructure>> childrenMap = new LinkedHashMap<>();
        for (TechnologyBomStructure source : structureList) {
            // 子节点 parentId 需要映射成新快照节点 id,才能保留原始 BOM 层级。
            if (source == null || source.getId() == null) {
                continue;
            }
            sourceById.put(source.getId(), source);
            Long parentId = source.getParentId();
            childrenMap.computeIfAbsent(parentId, k -> new ArrayList<>()).add(source);
        }
        // BFS 从根节点开始,父节点先于子节点处理
        Deque<TechnologyBomStructure> queue = new ArrayDeque<>();
        List<TechnologyBomStructure> rootNodes = childrenMap.get(null);
        if (rootNodes != null) {
            queue.addAll(rootNodes);
        }
        // parentId=0 的也视为根节点
        List<TechnologyBomStructure> zeroParentNodes = childrenMap.get(0L);
        if (zeroParentNodes != null) {
            for (TechnologyBomStructure node : zeroParentNodes) {
                if (!queue.contains(node)) {
                    queue.add(node);
                }
            }
        }
        while (!queue.isEmpty()) {
            TechnologyBomStructure source = queue.poll();
            if (source == null || source.getId() == null) {
                continue;
            }
            // 计算需求量 = 父节点需求量 × 自身 unitQuantity
            BigDecimal parentDemandedQuantity = orderQuantity;
            Long sourceParentId = source.getParentId();
            if (sourceParentId != null && sourceParentId != 0L) {
                Long parentTargetId = idMap.get(sourceParentId);
                if (parentTargetId != null) {
                    // 父节点已经插入,用父节点的 targetId 从已插入列表找
                    // 这里我们直接用存下来的父节点需求量
                    TechnologyBomStructure parentSource = sourceById.get(sourceParentId);
                    if (parentSource != null && parentSource.getDemandedQuantity() != null) {
                        parentDemandedQuantity = parentSource.getDemandedQuantity();
                    }
                }
            }
            ProductionBomStructure target = new ProductionBomStructure();
            target.setProductionOrderId(productionOrder.getId());
            target.setProductionOrderBomId(orderBom.getId());
@@ -449,11 +497,20 @@
            target.setProductModelId(source.getProductModelId());
            target.setTechnologyOperationId(source.getOperationId());
            target.setUnitQuantity(source.getUnitQuantity());
            target.setDemandedQuantity(lastProcessDemandedQuantity.multiply(source.getUnitQuantity()));
            target.setDemandedQuantity(parentDemandedQuantity.multiply(defaultDecimal(source.getUnitQuantity())));
            target.setUnit(source.getUnit());
            productionBomStructureMapper.insert(target);
            idMap.put(source.getId(), target.getId());
            lastProcessDemandedQuantity = target.getDemandedQuantity();
            // 将当前节点的 demandedQuantity 回写到 source 对象上,供子节点计算使用
            source.setDemandedQuantity(target.getDemandedQuantity());
            // 将子节点加入队列
            List<TechnologyBomStructure> children = childrenMap.get(source.getId());
            if (children != null) {
                for (TechnologyBomStructure child : children) {
                    queue.add(child);
                }
            }
        }
        return orderBom;
    }
@@ -611,9 +668,6 @@
                .collect(Collectors.toSet());
        if (productModelIds.size() > 1) {
            throw new ServiceException("所选生产计划必须属于同一产品规格");
        }
        if (productionPlans.stream().anyMatch(item -> item.getStatus() != null && item.getStatus() == 2)) {
            throw new ServiceException("所选生产计划已下发");
        }
        ProductionPlan firstPlan = mainPlan;
        if (productionOrder.getProductModelId() == null) {
@@ -1093,12 +1147,41 @@
            List<StockInventory> stockList = stockInventoryMapper.selectList(
                    Wrappers.<StockInventory>lambdaQuery()
                            .in(StockInventory::getProductModelId, productModelIds));
            // 各批次首次入库时间,用于先进先出排序(历史数据导入时库存行的 create_time 可能相同)
            Map<Long, Map<String, LocalDateTime>> firstInTimeMap = new HashMap<>();
            List<StockInRecord> stockInRecords = stockInRecordMapper.selectList(
                    Wrappers.<StockInRecord>lambdaQuery()
                            .in(StockInRecord::getProductModelId, productModelIds)
                            .isNotNull(StockInRecord::getBatchNo));
            for (StockInRecord stockInRecord : stockInRecords) {
                if (stockInRecord == null || stockInRecord.getProductModelId() == null
                        || stockInRecord.getBatchNo() == null || stockInRecord.getCreateTime() == null) {
                    continue;
                }
                firstInTimeMap.computeIfAbsent(stockInRecord.getProductModelId(), key -> new HashMap<>())
                        .merge(stockInRecord.getBatchNo(), stockInRecord.getCreateTime(),
                                (a, b) -> a.isBefore(b) ? a : b);
            }
            // 库存行按所属批次的首次入库时间升序,保证领料下拉框是先进先出顺序
            stockList.sort(Comparator
                    .comparing((StockInventory stockItem) -> {
                        Map<String, LocalDateTime> modelBatchTimeMap = firstInTimeMap.get(stockItem.getProductModelId());
                        return modelBatchTimeMap == null ? null : modelBatchTimeMap.get(stockItem.getBatchNo());
                    }, Comparator.nullsLast(Comparator.naturalOrder()))
                    .thenComparing(StockInventory::getCreateTime, Comparator.nullsLast(Comparator.naturalOrder()))
                    .thenComparing(stockItem -> stockItem.getId() == null ? Long.MAX_VALUE : stockItem.getId()));
            for (StockInventory stockItem : stockList) {
                if (stockItem == null || stockItem.getProductModelId() == null) {
                    continue;
                }
                Long productModelId = stockItem.getProductModelId();
                stockQuantityMap.merge(productModelId, defaultDecimal(stockItem.getQualitity()), BigDecimal::add);
                // 已无可用库存(数量扣完或被锁定)的批次不再带到领料下拉框
                BigDecimal availableQuantity = defaultDecimal(stockItem.getQualitity())
                        .subtract(defaultDecimal(stockItem.getLockedQuantity()));
                if (availableQuantity.compareTo(BigDecimal.ZERO) <= 0) {
                    continue;
                }
                String batchNo = stockItem.getBatchNo();
                if (batchNo != null && !batchNo.trim().isEmpty()) {
                    stockBatchNoMap.computeIfAbsent(productModelId, key -> new LinkedHashSet<>()).add(batchNo);
@@ -1134,7 +1217,7 @@
    @Override
    public int updateOrder(ProductionOrderDto productionOrderDto) {
        // 更新生产订单主数据
        productionOrderDto.setStatus(5);
        productionOrderDto.setStatus(ProductOrderStatusEnum.END.getCode());
        return baseMapper.updateById(productionOrderDto);
    }
}