liding
6 天以前 4858d6a68446a5153749eca8fae8ae099ac879a2
src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java
@@ -16,17 +16,14 @@
import com.ruoyi.common.enums.StockInUnQualifiedRecordTypeEnum;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.security.LoginUser;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.stock.dto.FinishedProductTreeDto;
import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import com.ruoyi.stock.dto.StockInRecordDto;
import com.ruoyi.stock.dto.StockInventoryDto;
import com.ruoyi.stock.dto.StockOutRecordDto;
import com.ruoyi.stock.dto.StockUninventoryDto;
import com.ruoyi.stock.dto.*;
import com.ruoyi.stock.execl.FinishedProductInventoryExportData;
import com.ruoyi.stock.execl.NonFinishedProductInventoryExportData;
import com.ruoyi.stock.execl.StockInventoryExportData;
import com.ruoyi.stock.mapper.StockInventoryMapper;
import com.ruoyi.stock.pojo.StockInRecord;
@@ -45,12 +42,7 @@
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -79,40 +71,41 @@
        return stockInventoryMapper.pagestockInventory(page, stockInventoryDto);
    }
    /**
     * 查询成品库存树。
     * 返回结构沿用基础资料产品树,叶子节点补充成品库存维度:型号、工序分类、电压。
     */
    @Override
    public List<FinishedProductTreeDto> finishedProductList(StockInventoryDto stockInventoryDto) {
        List<StockInventoryDto> inventoryList = stockInventoryMapper.selectFinishedProductInventoryList(stockInventoryDto);
        if (inventoryList.isEmpty()) {
        // 查询库存数据
        List<FinishedProductTreeDto> dataList = stockInventoryMapper.selectFinishedProductList(stockInventoryDto);
        if (dataList.isEmpty()) {
            return new ArrayList<>();
        }
        List<Product> allProducts = productMapper.selectList(null);
        Map<Long, Product> productMap = new HashMap<>();
        Map<Long, List<Product>> childrenMap = new HashMap<>();
        for (Product product : allProducts) {
            productMap.put(product.getId(), product);
            childrenMap.computeIfAbsent(product.getParentId(), key -> new ArrayList<>()).add(product);
        // 按产品ID分组,构建树形结构
        Map<Long, FinishedProductTreeDto> productMap = new LinkedHashMap<>();
        for (FinishedProductTreeDto data : dataList) {
            Long productId = data.getProductId();
            if (!productMap.containsKey(productId)) {
                // 创建产品大类节点
                FinishedProductTreeDto productNode = new FinishedProductTreeDto();
                productNode.setProductId(productId);
                productNode.setProductName(data.getProductName());
                productNode.setLabel(data.getLabel());
                productNode.setChildren(new ArrayList<>());
                productMap.put(productId, productNode);
            }
            // 添加库存叶子节点
            FinishedProductTreeDto leafNode = new FinishedProductTreeDto();
            leafNode.setStockId(data.getStockId());
            leafNode.setProductModelId(data.getProductModelId());
            leafNode.setModel(data.getModel());
            leafNode.setProcessCategory(data.getProcessCategory());
            leafNode.setVoltage(data.getVoltage());
            leafNode.setMaterialCode(data.getMaterialCode());
            leafNode.setUnit(data.getUnit());
            leafNode.setQualitity(data.getQualitity());
            productMap.get(productId).getChildren().add(leafNode);
        }
        Map<Long, List<FinishedProductTreeDto>> leafMap = buildFinishedProductLeafMap(inventoryList);
        Set<Long> visibleProductIds = collectVisibleProductIds(leafMap.keySet(), productMap);
        List<FinishedProductTreeDto> result = new ArrayList<>();
        for (Product rootProduct : allProducts) {
            if (!isFinishedRoot(rootProduct)) {
                continue;
            }
            FinishedProductTreeDto rootNode = buildProductNode(rootProduct);
            rootNode.setChildren(buildFinishedChildren(rootProduct.getId(), childrenMap, leafMap, visibleProductIds));
            if (!rootNode.getChildren().isEmpty()) {
                result.add(rootNode);
            }
        }
        return result;
        return new ArrayList<>(productMap.values());
    }
    @Override
@@ -271,6 +264,11 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean subtractStockInventory(StockInventoryDto stockInventoryDto) {
        if (stockInventoryDto.getQualitity() != null
                && stockInventoryDto.getQualitity().compareTo(BigDecimal.ZERO) == 0) {
            return true;
        }
        StockOutRecordDto stockOutRecordDto = new StockOutRecordDto();
        stockOutRecordDto.setRecordId(stockInventoryDto.getRecordId());
        stockOutRecordDto.setRecordType(stockInventoryDto.getRecordType());
@@ -278,6 +276,25 @@
        stockOutRecordDto.setProductModelId(stockInventoryDto.getProductModelId());
        stockOutRecordDto.setType("0");
        stockOutRecordService.add(stockOutRecordDto);
        //销售出库按照保存的库存id
        if (stockInventoryDto.getStockId() != null) {
            StockInventory stockInventory = stockInventoryMapper.selectById(stockInventoryDto.getStockId());
            if (ObjectUtils.isEmpty(stockInventory)) {
                throw new RuntimeException("产品库存不存在");
            }
            BigDecimal lockedQty = defaultDecimal(stockInventory.getLockedQuantity());
            if (stockInventoryDto.getQualitity().compareTo(defaultDecimal(stockInventory.getQualitity()).subtract(lockedQty)) > 0) {
                ProductModel productModel = productModelMapper.selectById(stockInventoryDto.getProductModelId());
                Product product = productMapper.selectById(productModel.getProductId());
                throw new RuntimeException(product.getProductName() + "/" + productModel.getModel() + "库存不足,无法出库");
            }
            stockInventory.setQualitity(defaultDecimal(stockInventory.getQualitity()).subtract(stockInventoryDto.getQualitity()));
            stockInventory.setVersion(stockInventory.getVersion() == null ? 1 : stockInventory.getVersion() + 1);
            stockInventory.setUpdateTime(LocalDateTime.now());
            stockInventoryMapper.updateById(stockInventory);
            return true;
        }
        if (StringUtils.isBlank(stockInventoryDto.getBatchNo()) && !usesDimensionIdentity(normalizeDimension(stockInventoryDto.getProcessCategory()), normalizeDimension(stockInventoryDto.getVoltage()))) {
            List<StockInventory> stockInventories = stockInventoryMapper.selectList(new QueryWrapper<StockInventory>().lambda()
@@ -287,7 +304,7 @@
                throw new RuntimeException("产品库存不存在");
            }
            BigDecimal remainingQty = stockInventoryDto.getQualitity();
            BigDecimal remainingQty = stockInventoryDto.getQualitity() == null ? BigDecimal.ZERO : stockInventoryDto.getQualitity();
            for (StockInventory stockInventory : stockInventories) {
                BigDecimal lockedQty = defaultDecimal(stockInventory.getLockedQuantity());
                BigDecimal availableQty = defaultDecimal(stockInventory.getQualitity()).subtract(lockedQty);
@@ -335,7 +352,7 @@
    }
    @Override
    public R importStockInventory(MultipartFile file) {
    public R importStockInventory(MultipartFile file, Integer productType) {
        try {
            List<SalesLedgerProduct> salesLedgerProducts = salesLedgerProductMapper.selectProduct();
            Map<String, SalesLedgerProduct> productMap = new HashMap<>();
@@ -344,65 +361,129 @@
                productMap.put(key, product);
            }
            ExcelUtil<StockInventoryExportData> util = new ExcelUtil<>(StockInventoryExportData.class);
            List<StockInventoryExportData> list = util.importExcel(file.getInputStream());
            List<String> unmatchedRecords = new ArrayList<>();
            int successCount = 0;
            for (StockInventoryExportData dto : list) {
                String key = dto.getProductName() + "|" + dto.getModel();
                SalesLedgerProduct matchedProduct = productMap.get(key);
            // productType: 1=成品, 0=非成品
            if (productType == 1) {
                // 成品导入(包含工序类别和电压)
                ExcelUtil<FinishedProductInventoryExportData> util = new ExcelUtil<>(FinishedProductInventoryExportData.class);
                List<FinishedProductInventoryExportData> list = util.importExcel(file.getInputStream());
                if (matchedProduct != null) {
                    if (dto.getQualifiedQuantity() != null && dto.getQualifiedQuantity().compareTo(BigDecimal.ZERO) > 0) {
                        StockInventoryDto stockInventoryDto = new StockInventoryDto();
                        stockInventoryDto.setRecordId(0L);
                        stockInventoryDto.setRecordType(StockInQualifiedRecordTypeEnum.CUSTOMIZATION_STOCK_IN.getCode());
                        stockInventoryDto.setQualitity(dto.getQualifiedQuantity());
                        stockInventoryDto.setRemark(dto.getRemark());
                        stockInventoryDto.setWarnNum(dto.getWarnNum());
                        stockInventoryDto.setBatchNo(dto.getQualifiedBatchNo());
                        stockInventoryDto.setProcessCategory(dto.getProcessCategory());
                        stockInventoryDto.setVoltage(dto.getVoltage());
                for (FinishedProductInventoryExportData dto : list) {
                    String key = dto.getProductName() + "|" + dto.getModel();
                    SalesLedgerProduct matchedProduct = productMap.get(key);
                        if (ObjectUtils.isNotEmpty(dto.getQualifiedLockedQuantity())) {
                            if (dto.getQualifiedLockedQuantity().compareTo(dto.getQualifiedQuantity()) > 0) {
                                throw new RuntimeException("合格冻结数量不能超过本次导入的合格库存数量");
                    if (matchedProduct != null) {
                        if (dto.getQualifiedQuantity() != null && dto.getQualifiedQuantity().compareTo(BigDecimal.ZERO) > 0) {
                            StockInventoryDto stockInventoryDto = new StockInventoryDto();
                            stockInventoryDto.setRecordId(0L);
                            stockInventoryDto.setRecordType(StockInQualifiedRecordTypeEnum.CUSTOMIZATION_STOCK_IN.getCode());
                            stockInventoryDto.setQualitity(dto.getQualifiedQuantity());
                            stockInventoryDto.setRemark(dto.getRemark());
                            stockInventoryDto.setWarnNum(dto.getWarnNum());
                            stockInventoryDto.setBatchNo(dto.getQualifiedBatchNo());
                            stockInventoryDto.setProcessCategory(dto.getProcessCategory());
                            stockInventoryDto.setVoltage(dto.getVoltage());
                            if (ObjectUtils.isNotEmpty(dto.getQualifiedLockedQuantity())) {
                                if (dto.getQualifiedLockedQuantity().compareTo(dto.getQualifiedQuantity()) > 0) {
                                    throw new RuntimeException("合格冻结数量不能超过本次导入的合格库存数量");
                                }
                                stockInventoryDto.setLockedQuantity(dto.getQualifiedLockedQuantity());
                            } else {
                                stockInventoryDto.setLockedQuantity(BigDecimal.ZERO);
                            }
                            stockInventoryDto.setLockedQuantity(dto.getQualifiedLockedQuantity());
                        } else {
                            stockInventoryDto.setLockedQuantity(BigDecimal.ZERO);
                            stockInventoryDto.setProductModelId(matchedProduct.getProductModelId());
                            this.addstockInventory(stockInventoryDto);
                            successCount++;
                        }
                        stockInventoryDto.setProductModelId(matchedProduct.getProductModelId());
                        this.addstockInventory(stockInventoryDto);
                        successCount++;
                    }
                        if (dto.getUnQualifiedQuantity() != null && dto.getUnQualifiedQuantity().compareTo(BigDecimal.ZERO) > 0) {
                            StockUninventoryDto stockUninventoryDto = new StockUninventoryDto();
                            stockUninventoryDto.setRecordId(0L);
                            stockUninventoryDto.setRecordType(StockInUnQualifiedRecordTypeEnum.CUSTOMIZATION_UNSTOCK_IN.getCode());
                            stockUninventoryDto.setQualitity(dto.getUnQualifiedQuantity());
                            stockUninventoryDto.setRemark(dto.getRemark());
                            stockUninventoryDto.setBatchNo(dto.getUnQualifiedBatchNo());
                    if (dto.getUnQualifiedQuantity() != null && dto.getUnQualifiedQuantity().compareTo(BigDecimal.ZERO) > 0) {
                        StockUninventoryDto stockUninventoryDto = new StockUninventoryDto();
                        stockUninventoryDto.setRecordId(0L);
                        stockUninventoryDto.setRecordType(StockInUnQualifiedRecordTypeEnum.CUSTOMIZATION_UNSTOCK_IN.getCode());
                        stockUninventoryDto.setQualitity(dto.getUnQualifiedQuantity());
                        stockUninventoryDto.setRemark(dto.getRemark());
                        stockUninventoryDto.setBatchNo(dto.getUnQualifiedBatchNo());
                        if (ObjectUtils.isNotEmpty(dto.getUnQualifiedLockedQuantity())) {
                            if (dto.getUnQualifiedLockedQuantity().compareTo(dto.getUnQualifiedQuantity()) > 0) {
                                throw new RuntimeException("不合格冻结数量不能超过本次导入的不合格库存数量");
                            if (ObjectUtils.isNotEmpty(dto.getUnQualifiedLockedQuantity())) {
                                if (dto.getUnQualifiedLockedQuantity().compareTo(dto.getUnQualifiedQuantity()) > 0) {
                                    throw new RuntimeException("不合格冻结数量不能超过本次导入的不合格库存数量");
                                }
                                stockUninventoryDto.setLockedQuantity(dto.getUnQualifiedLockedQuantity());
                            } else {
                                stockUninventoryDto.setLockedQuantity(BigDecimal.ZERO);
                            }
                            stockUninventoryDto.setLockedQuantity(dto.getUnQualifiedLockedQuantity());
                        } else {
                            stockUninventoryDto.setLockedQuantity(BigDecimal.ZERO);
                            stockUninventoryDto.setProductModelId(matchedProduct.getProductModelId());
                            stockUninventoryService.addStockUninventory(stockUninventoryDto);
                            successCount++;
                        }
                    } else {
                        String unmatchedRecord = "产品名称:" + dto.getProductName() + ",型号:" + dto.getModel() + " 未匹配到库存产品";
                        unmatchedRecords.add(unmatchedRecord);
                    }
                }
            } else {
                // 非成品导入(不包含工序类别和电压)
                ExcelUtil<NonFinishedProductInventoryExportData> util = new ExcelUtil<>(NonFinishedProductInventoryExportData.class);
                List<NonFinishedProductInventoryExportData> list = util.importExcel(file.getInputStream());
                for (NonFinishedProductInventoryExportData dto : list) {
                    String key = dto.getProductName() + "|" + dto.getModel();
                    SalesLedgerProduct matchedProduct = productMap.get(key);
                    if (matchedProduct != null) {
                        if (dto.getQualifiedQuantity() != null && dto.getQualifiedQuantity().compareTo(BigDecimal.ZERO) > 0) {
                            StockInventoryDto stockInventoryDto = new StockInventoryDto();
                            stockInventoryDto.setRecordId(0L);
                            stockInventoryDto.setRecordType(StockInQualifiedRecordTypeEnum.CUSTOMIZATION_STOCK_IN.getCode());
                            stockInventoryDto.setQualitity(dto.getQualifiedQuantity());
                            stockInventoryDto.setRemark(dto.getRemark());
                            stockInventoryDto.setWarnNum(dto.getWarnNum());
                            stockInventoryDto.setBatchNo(dto.getQualifiedBatchNo());
                            if (ObjectUtils.isNotEmpty(dto.getQualifiedLockedQuantity())) {
                                if (dto.getQualifiedLockedQuantity().compareTo(dto.getQualifiedQuantity()) > 0) {
                                    throw new RuntimeException("合格冻结数量不能超过本次导入的合格库存数量");
                                }
                                stockInventoryDto.setLockedQuantity(dto.getQualifiedLockedQuantity());
                            } else {
                                stockInventoryDto.setLockedQuantity(BigDecimal.ZERO);
                            }
                            stockInventoryDto.setProductModelId(matchedProduct.getProductModelId());
                            this.addstockInventory(stockInventoryDto);
                            successCount++;
                        }
                        stockUninventoryDto.setProductModelId(matchedProduct.getProductModelId());
                        stockUninventoryService.addStockUninventory(stockUninventoryDto);
                        successCount++;
                        if (dto.getUnQualifiedQuantity() != null && dto.getUnQualifiedQuantity().compareTo(BigDecimal.ZERO) > 0) {
                            StockUninventoryDto stockUninventoryDto = new StockUninventoryDto();
                            stockUninventoryDto.setRecordId(0L);
                            stockUninventoryDto.setRecordType(StockInUnQualifiedRecordTypeEnum.CUSTOMIZATION_UNSTOCK_IN.getCode());
                            stockUninventoryDto.setQualitity(dto.getUnQualifiedQuantity());
                            stockUninventoryDto.setRemark(dto.getRemark());
                            stockUninventoryDto.setBatchNo(dto.getUnQualifiedBatchNo());
                            if (ObjectUtils.isNotEmpty(dto.getUnQualifiedLockedQuantity())) {
                                if (dto.getUnQualifiedLockedQuantity().compareTo(dto.getUnQualifiedQuantity()) > 0) {
                                    throw new RuntimeException("不合格冻结数量不能超过本次导入的不合格库存数量");
                                }
                                stockUninventoryDto.setLockedQuantity(dto.getUnQualifiedLockedQuantity());
                            } else {
                                stockUninventoryDto.setLockedQuantity(BigDecimal.ZERO);
                            }
                            stockUninventoryDto.setProductModelId(matchedProduct.getProductModelId());
                            stockUninventoryService.addStockUninventory(stockUninventoryDto);
                            successCount++;
                        }
                    } else {
                        String unmatchedRecord = "产品名称:" + dto.getProductName() + ",型号:" + dto.getModel() + " 未匹配到库存产品";
                        unmatchedRecords.add(unmatchedRecord);
                    }
                } else {
                    String unmatchedRecord = "产品名称:" + dto.getProductName() + ",型号:" + dto.getModel() + " 未匹配到库存产品";
                    unmatchedRecords.add(unmatchedRecord);
                }
            }
@@ -423,10 +504,55 @@
    }
    @Override
    public void exportStockInventory(HttpServletResponse response, StockInventoryDto stockInventoryDto) {
    public void exportStockInventory(HttpServletResponse response, StockInventoryDto stockInventoryDto, Integer productType) {
        List<StockInventoryExportData> list = stockInventoryMapper.listStockInventoryExportData(stockInventoryDto);
        ExcelUtil<StockInventoryExportData> util = new ExcelUtil<>(StockInventoryExportData.class);
        util.exportExcel(response, list, "库存信息");
        // productType: 1=成品, 0=非成品
        if (productType == 1) {
            // 成品导出(包含工序类别和电压)
            List<FinishedProductInventoryExportData> finishedList = new ArrayList<>();
            for (StockInventoryExportData data : list) {
                FinishedProductInventoryExportData finishedData = new FinishedProductInventoryExportData();
                finishedData.setProductName(data.getProductName());
                finishedData.setModel(data.getModel());
                finishedData.setUnit(data.getUnit());
                finishedData.setMaterialCode(data.getMaterialCode());
                finishedData.setProcessCategory(data.getProcessCategory());
                finishedData.setVoltage(data.getVoltage());
                finishedData.setQualifiedBatchNo(data.getQualifiedBatchNo());
                finishedData.setUnQualifiedBatchNo(data.getUnQualifiedBatchNo());
                finishedData.setQualifiedQuantity(data.getQualifiedQuantity());
                finishedData.setUnQualifiedQuantity(data.getUnQualifiedQuantity());
                finishedData.setWarnNum(data.getWarnNum());
                finishedData.setQualifiedLockedQuantity(data.getQualifiedLockedQuantity());
                finishedData.setUnQualifiedLockedQuantity(data.getUnQualifiedLockedQuantity());
                finishedData.setRemark(data.getRemark());
                finishedList.add(finishedData);
            }
            ExcelUtil<FinishedProductInventoryExportData> util = new ExcelUtil<>(FinishedProductInventoryExportData.class);
            util.exportExcel(response, finishedList, "成品库存信息");
        } else {
            // 非成品导出(不包含工序类别和电压)
            List<NonFinishedProductInventoryExportData> nonFinishedList = new ArrayList<>();
            for (StockInventoryExportData data : list) {
                NonFinishedProductInventoryExportData nonFinishedData = new NonFinishedProductInventoryExportData();
                nonFinishedData.setProductName(data.getProductName());
                nonFinishedData.setModel(data.getModel());
                nonFinishedData.setUnit(data.getUnit());
                nonFinishedData.setMaterialCode(data.getMaterialCode());
                nonFinishedData.setQualifiedBatchNo(data.getQualifiedBatchNo());
                nonFinishedData.setUnQualifiedBatchNo(data.getUnQualifiedBatchNo());
                nonFinishedData.setQualifiedQuantity(data.getQualifiedQuantity());
                nonFinishedData.setUnQualifiedQuantity(data.getUnQualifiedQuantity());
                nonFinishedData.setWarnNum(data.getWarnNum());
                nonFinishedData.setQualifiedLockedQuantity(data.getQualifiedLockedQuantity());
                nonFinishedData.setUnQualifiedLockedQuantity(data.getUnQualifiedLockedQuantity());
                nonFinishedData.setRemark(data.getRemark());
                nonFinishedList.add(nonFinishedData);
            }
            ExcelUtil<NonFinishedProductInventoryExportData> util = new ExcelUtil<>(NonFinishedProductInventoryExportData.class);
            util.exportExcel(response, nonFinishedList, "非成品库存信息");
        }
    }
    @Override
@@ -505,89 +631,5 @@
    private BigDecimal defaultDecimal(BigDecimal value) {
        return value == null ? BigDecimal.ZERO : value;
    }
    private Map<Long, List<FinishedProductTreeDto>> buildFinishedProductLeafMap(List<StockInventoryDto> inventoryList) {
        Map<Long, List<FinishedProductTreeDto>> leafMap = new HashMap<>();
        for (StockInventoryDto inventory : inventoryList) {
            FinishedProductTreeDto leafNode = new FinishedProductTreeDto();
            leafNode.setId(inventory.getId() == null ? null : -inventory.getId());
            leafNode.setParentId(inventory.getProductId());
            leafNode.setProductId(inventory.getProductId());
            leafNode.setProductModelId(inventory.getProductModelId());
            leafNode.setProductName(inventory.getProductName());
            leafNode.setLabel(buildLeafLabel(inventory));
            leafNode.setModel(inventory.getModel());
            leafNode.setMaterialCode(inventory.getMaterialCode());
            leafNode.setProcessCategory(inventory.getProcessCategory());
            leafNode.setVoltage(inventory.getVoltage());
            leafNode.setChildren(new ArrayList<>());
            leafMap.computeIfAbsent(inventory.getProductId(), key -> new ArrayList<>()).add(leafNode);
        }
        return leafMap;
    }
    private Set<Long> collectVisibleProductIds(Set<Long> leafProductIds, Map<Long, Product> productMap) {
        Set<Long> visibleIds = new HashSet<>();
        for (Long productId : leafProductIds) {
            Long currentId = productId;
            while (currentId != null) {
                Product current = productMap.get(currentId);
                if (current == null) {
                    break;
                }
                visibleIds.add(currentId);
                currentId = current.getParentId();
            }
        }
        return visibleIds;
    }
    private List<FinishedProductTreeDto> buildFinishedChildren(Long parentId,
                                                               Map<Long, List<Product>> childrenMap,
                                                               Map<Long, List<FinishedProductTreeDto>> leafMap,
                                                               Set<Long> visibleProductIds) {
        List<FinishedProductTreeDto> children = new ArrayList<>();
        List<Product> childProducts = childrenMap.getOrDefault(parentId, new ArrayList<>());
        for (Product childProduct : childProducts) {
            if (!visibleProductIds.contains(childProduct.getId())) {
                continue;
            }
            FinishedProductTreeDto childNode = buildProductNode(childProduct);
            List<FinishedProductTreeDto> subChildren = buildFinishedChildren(childProduct.getId(), childrenMap, leafMap, visibleProductIds);
            subChildren.addAll(leafMap.getOrDefault(childProduct.getId(), new ArrayList<>()));
            childNode.setChildren(subChildren);
            children.add(childNode);
        }
        return children;
    }
    private FinishedProductTreeDto buildProductNode(Product product) {
        FinishedProductTreeDto node = new FinishedProductTreeDto();
        BeanUtils.copyProperties(product, node);
        node.setLabel(product.getProductName());
        node.setChildren(new ArrayList<>());
        return node;
    }
    private boolean isFinishedRoot(Product product) {
        return product.getParentId() == null && "成品".equals(StringUtils.trimToEmpty(product.getProductName()));
    }
    private String buildLeafLabel(StockInventoryDto inventory) {
        List<String> parts = new ArrayList<>();
        if (StringUtils.isNotBlank(inventory.getProductName())) {
            parts.add(StringUtils.trim(inventory.getProductName()));
        }
        if (StringUtils.isNotBlank(inventory.getModel())) {
            parts.add(StringUtils.trim(inventory.getModel()));
        }
        if (StringUtils.isNotBlank(inventory.getProcessCategory())) {
            parts.add(StringUtils.trim(inventory.getProcessCategory()));
        }
        if (StringUtils.isNotBlank(inventory.getVoltage())) {
            parts.add(StringUtils.trim(inventory.getVoltage()));
        }
        return String.join(" / ", parts);
    }
}