gongchunyi
15 小时以前 ddc95002ef4c7593a006443690d5ae6bba693227
src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java
@@ -28,6 +28,7 @@
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
@@ -145,6 +146,9 @@
            return AjaxResult.error("未找到 [原料/半成品/成品] 工作表,或文件中没有数据");
        }
        // 去重键:产品名称 + 规格型号 + 编码(空编码按空串参与比较)
        Set<String> seenKeys = loadExistingProductKeys(sheetMap);
        int successCount = 0;
        int skipCount = 0;
        Set<String> generatedCodes = new HashSet<>();
@@ -155,45 +159,51 @@
            }
            Long topId = findOrCreateTopProduct(sheetName);
            Map<String, Long> nodeCache = new HashMap<>();
            Map<Long, Set<String>> codeCache = new HashMap<>();
            for (int i = 0; i < rows.size(); i++) {
                ProductImportRowDto row = rows.get(i);
                int rowNum = i + 2;
                //  没有名称/编码/规格的行构不成产品数据,按空行跳过
                if (hasNoProductContent(row)) {
                    continue;
                }
                String parentName = trimToNull(row.getProductParentName());
                String categoryName = trimToNull(row.getProductCategoryName());
                String productName = trimToNull(row.getProductName());
                if (categoryName == null) {
                    throw new ServiceException("第 " + rowNum + " 行 [产品大类] 不能为空");
                }
                if (productName == null) {
                    throw new ServiceException("第 " + rowNum + " 行 [产品名称] 不能为空");
                    throw new ServiceException(String.format("工作表【%s】第 %d 行 [产品名称] 不能为空(该行已填:%s)",
                            sheetName, resolveRowNum(row, i), describeRow(row)));
                }
                String model = trimToNull(row.getModel());
                if (model == null) {
                    model = "/";
                }
                String code = trimToNull(row.getProductCode());
                if (!seenKeys.add(dedupKey(productName, model, code))) {
                    skipCount++;
                    continue;
                }
                String path = String.valueOf(topId);
                Long parentNodeId = topId;
                Long currentParentId = topId;
                if (parentName != null) {
                    path = path + ">" + parentName;
                    parentNodeId = ensureChildNode(nodeCache, path, parentNodeId, parentName);
                    currentParentId = ensureChildNode(nodeCache, path, currentParentId, parentName);
                }
                path = path + ">" + categoryName;
                Long categoryId = ensureChildNode(nodeCache, path, parentNodeId, categoryName);
                if (categoryName != null) {
                    path = path + ">" + categoryName;
                    currentParentId = ensureChildNode(nodeCache, path, currentParentId, categoryName);
                }
                path = path + ">" + productName;
                Long leafId = ensureChildNode(nodeCache, path, categoryId, productName);
                Long leafId = ensureChildNode(nodeCache, path, currentParentId, productName);
                String code = trimToNull(row.getProductCode());
                if (code == null) {
                    code = generateUniqueCode(generatedCodes);
                }
                Set<String> codes = codeCache.computeIfAbsent(leafId, this::loadProductCodes);
                if (!codes.add(code)) {
                    skipCount++;
                    continue;
                }
                ProductModel productModel = new ProductModel();
                productModel.setProductId(leafId);
                productModel.setProductCode(code);
                productModel.setModel(StringUtils.isEmpty(row.getModel()) ? "/" : row.getModel().trim());
                productModel.setModel(model);
                productModel.setUnit(trimToNull(row.getUnit()));
                productModelMapper.insert(productModel);
                successCount++;
@@ -241,16 +251,89 @@
        return product.getId();
    }
    private Set<String> loadProductCodes(Long productId) {
        List<ProductModel> list = productModelMapper.selectList(new LambdaQueryWrapper<ProductModel>()
                .eq(ProductModel::getProductId, productId));
        Set<String> codes = new HashSet<>();
        for (ProductModel productModel : list) {
            if (productModel.getProductCode() != null) {
                codes.add(productModel.getProductCode().trim());
    /**
     * 预加载库中已存在的去重键(产品名称 + 规格型号 + 编码),并作为本次导入的初始集合。
     */
    private Set<String> loadExistingProductKeys(Map<String, List<ProductImportRowDto>> sheetMap) {
        Set<String> productNames = new HashSet<>();
        for (List<ProductImportRowDto> rows : sheetMap.values()) {
            if (CollectionUtils.isEmpty(rows)) {
                continue;
            }
            for (ProductImportRowDto row : rows) {
                String productName = trimToNull(row.getProductName());
                if (productName != null) {
                    productNames.add(productName);
                }
            }
        }
        return codes;
        Set<String> keys = new HashSet<>();
        if (productNames.isEmpty()) {
            return keys;
        }
        List<Product> products = productMapper.selectList(new LambdaQueryWrapper<Product>()
                .in(Product::getProductName, productNames));
        if (CollectionUtils.isEmpty(products)) {
            return keys;
        }
        Map<Long, String> nameById = new HashMap<>();
        List<Long> productIds = new ArrayList<>();
        for (Product product : products) {
            nameById.put(product.getId(), product.getProductName());
            productIds.add(product.getId());
        }
        List<ProductModel> productModels = productModelMapper.selectList(new LambdaQueryWrapper<ProductModel>()
                .in(ProductModel::getProductId, productIds));
        for (ProductModel productModel : productModels) {
            String productName = nameById.get(productModel.getProductId());
            if (productName != null) {
                keys.add(dedupKey(productName, productModel.getModel(), productModel.getProductCode()));
            }
        }
        return keys;
    }
    /**
     * 解析时会跳过空行
     */
    private static int resolveRowNum(ProductImportRowDto row, int index) {
        return row.getExcelRowNum() != null ? row.getExcelRowNum() : index + 2;
    }
    private static boolean hasNoProductContent(ProductImportRowDto row) {
        return trimToNull(row.getProductName()) == null
                && trimToNull(row.getProductCode()) == null
                && trimToNull(row.getModel()) == null;
    }
    private static String describeRow(ProductImportRowDto row) {
        StringBuilder description = new StringBuilder();
        appendFilledColumn(description, "产品父类", row.getProductParentName());
        appendFilledColumn(description, "产品大类", row.getProductCategoryName());
        appendFilledColumn(description, "产品名称", row.getProductName());
        appendFilledColumn(description, "产品编码", row.getProductCode());
        appendFilledColumn(description, "规格型号", row.getModel());
        appendFilledColumn(description, "单位", row.getUnit());
        return description.length() == 0 ? "无" : description.toString();
    }
    private static void appendFilledColumn(StringBuilder description, String columnName, String value) {
        String trimmed = trimToNull(value);
        if (trimmed == null) {
            return;
        }
        if (description.length() > 0) {
            description.append(",");
        }
        description.append(columnName).append('=').append(trimmed);
    }
    private static String dedupKey(String productName, String model, String productCode) {
        String normalizedModel = trimToNull(model) == null ? "/" : model.trim();
        String normalizedCode = productCode == null ? "" : productCode.trim();
        return productName.trim() + "\u0001" + normalizedModel + "\u0001" + normalizedCode;
    }
    private String generateUniqueCode(Set<String> generatedCodes) {