gongchunyi
2 天以前 f9b26d13f0a43f0bb5c8140a2de3ebd8d9c9c8d8
src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java
@@ -1,20 +1,41 @@
package com.ruoyi.basic.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.basic.dto.ProductDto;
import com.ruoyi.basic.dto.ProductImportRowDto;
import com.ruoyi.basic.dto.ProductModelDto;
import com.ruoyi.basic.mapper.ProductMapper;
import com.ruoyi.basic.mapper.ProductModelMapper;
import com.ruoyi.basic.pojo.Product;
import com.ruoyi.basic.pojo.ProductModel;
import com.ruoyi.basic.service.IProductModelService;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import com.ruoyi.technology.mapper.TechnologyBomMapper;
import com.ruoyi.technology.pojo.TechnologyBom;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
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;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ThreadLocalRandom;
/**
 * 【请填写功能名称】Service业务层处理
@@ -26,22 +47,62 @@
@AllArgsConstructor
public class ProductModelServiceImpl extends ServiceImpl<ProductModelMapper, ProductModel> implements IProductModelService {
    private final ProductMapper productMapper;
    private final SalesLedgerProductMapper salesLedgerProductMapper;
    private final TechnologyBomMapper technologyBomMapper;
    private ProductModelMapper productModelMapper;
    private static final List<String> IMPORT_SHEET_NAMES = Arrays.asList("原料", "半成品", "成品");
    @Override
    public int addOrEditProductModel(ProductModelDto productModelDto) {
        String model = StringUtils.trim(productModelDto.getModel());
        String productCode = StringUtils.trim(productModelDto.getProductCode());
        productModelDto.setModel(model);
        productModelDto.setProductCode(productCode);
        checkModelAndProductCodeUnique(model, productCode, productModelDto.getId());
        if (productModelDto.getId() == null) {
            ProductModel productModel = new ProductModel();
            BeanUtils.copyProperties(productModelDto,productModel);
            BeanUtils.copyProperties(productModelDto, productModel);
            return productModelMapper.insert(productModel);
        } else {
            return productModelMapper.updateById(productModelDto);
        }
    }
    private void checkModelAndProductCodeUnique(String model, String productCode, Long currentId) {
        if (StringUtils.isEmpty(model) || StringUtils.isEmpty(productCode)) {
            return;
        }
        LambdaQueryWrapper<ProductModel> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ProductModel::getModel, model)
                .eq(ProductModel::getProductCode, productCode)
                .ne(currentId != null, ProductModel::getId, currentId)
                .last("limit 1");
        ProductModel duplicateProductModel = productModelMapper.selectOne(queryWrapper);
        if (duplicateProductModel != null) {
            throw new ServiceException("对应的型号" + model + "的产品编码" + productCode + "已经存在");
        }
    }
    @Override
    public int delProductModel(Long[] ids) {
        List<SalesLedgerProduct> salesLedgerProducts = salesLedgerProductMapper.selectList(new QueryWrapper<SalesLedgerProduct>()
                .lambda().in(SalesLedgerProduct::getProductModelId, ids));
        if (salesLedgerProducts != null && salesLedgerProducts.size() > 0) {
            throw new RuntimeException("已经存在该产品的销售台账和采购台账");
        }
        // 是否存在BOM
        List<TechnologyBom> technologyBoms = technologyBomMapper.selectList(new QueryWrapper<TechnologyBom>()
                .lambda().in(TechnologyBom::getProductModelId, ids));
        if (CollectionUtils.isNotEmpty(technologyBoms)) {
            throw new RuntimeException("已经存在该产品的BOM数据");
        }
        return productModelMapper.deleteBatchIds(Arrays.asList(ids));
    }
@@ -54,6 +115,7 @@
    /**
     * 根据id查询产品规格分页查询
     *
     * @param page
     * @param productDto
     * @return
@@ -64,4 +126,207 @@
        queryWrapper.eq(ProductModel::getProductId, productDto.getId());
        return productModelMapper.selectPage(page, queryWrapper);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult importProductModel(MultipartFile file) {
        if (file == null || file.isEmpty()) {
            return AjaxResult.error("请选择要导入的文件");
        }
        Map<String, List<ProductImportRowDto>> sheetMap;
        try {
            ExcelUtil<ProductImportRowDto> excelUtil = new ExcelUtil<>(ProductImportRowDto.class);
            sheetMap = excelUtil.importExcelMultiSheet(IMPORT_SHEET_NAMES, file.getInputStream(), 0);
        } catch (Exception e) {
            log.error("解析产品导入文件异常", e);
            throw new ServiceException("产品导入文件解析失败");
        }
        if (sheetMap.isEmpty()) {
            return AjaxResult.error("未找到 [原料/半成品/成品] 工作表,或文件中没有数据");
        }
        // 去重键:产品名称 + 规格型号 + 编码(空编码按空串参与比较)
        Set<String> seenKeys = loadExistingProductKeys(sheetMap);
        int successCount = 0;
        int skipCount = 0;
        Set<String> generatedCodes = new HashSet<>();
        for (String sheetName : IMPORT_SHEET_NAMES) {
            List<ProductImportRowDto> rows = sheetMap.get(sheetName);
            if (CollectionUtils.isEmpty(rows)) {
                continue;
            }
            Long topId = findOrCreateTopProduct(sheetName);
            Map<String, Long> nodeCache = new HashMap<>();
            for (int i = 0; i < rows.size(); i++) {
                ProductImportRowDto row = rows.get(i);
                int rowNum = i + 2;
                String parentName = trimToNull(row.getProductParentName());
                String categoryName = trimToNull(row.getProductCategoryName());
                String productName = trimToNull(row.getProductName());
                if (productName == null) {
                    throw new ServiceException("第 " + rowNum + " 行 [产品名称] 不能为空");
                }
                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 currentParentId = topId;
                if (parentName != null) {
                    path = path + ">" + parentName;
                    currentParentId = ensureChildNode(nodeCache, path, currentParentId, parentName);
                }
                if (categoryName != null) {
                    path = path + ">" + categoryName;
                    currentParentId = ensureChildNode(nodeCache, path, currentParentId, categoryName);
                }
                path = path + ">" + productName;
                Long leafId = ensureChildNode(nodeCache, path, currentParentId, productName);
                if (code == null) {
                    code = generateUniqueCode(generatedCodes);
                }
                ProductModel productModel = new ProductModel();
                productModel.setProductId(leafId);
                productModel.setProductCode(code);
                productModel.setModel(model);
                productModel.setUnit(trimToNull(row.getUnit()));
                productModelMapper.insert(productModel);
                successCount++;
            }
        }
        String message = skipCount > 0
                ? String.format("成功导入 %d 条,跳过已存在 %d 条", successCount, skipCount)
                : String.format("成功导入 %d 条", successCount);
        return AjaxResult.success(message);
    }
    private Long findOrCreateTopProduct(String name) {
        Product root = productMapper.selectOne(new LambdaQueryWrapper<Product>()
                .isNull(Product::getParentId)
                .eq(Product::getProductName, name)
                .last("limit 1"));
        if (root != null) {
            return root.getId();
        }
        Product product = new Product();
        product.setProductName(name);
        productMapper.insert(product);
        return product.getId();
    }
    private Long ensureChildNode(Map<String, Long> nodeCache, String path, Long parentId, String name) {
        Long id = nodeCache.get(path);
        if (id != null) {
            return id;
        }
        Product existing = productMapper.selectOne(new LambdaQueryWrapper<Product>()
                .eq(Product::getParentId, parentId)
                .eq(Product::getProductName, name)
                .last("limit 1"));
        if (existing != null) {
            nodeCache.put(path, existing.getId());
            return existing.getId();
        }
        Product product = new Product();
        product.setParentId(parentId);
        product.setProductName(name);
        productMapper.insert(product);
        nodeCache.put(path, product.getId());
        return product.getId();
    }
    /**
     * 预加载库中已存在的去重键(产品名称 + 规格型号 + 编码),并作为本次导入的初始集合。
     */
    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);
                }
            }
        }
        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 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) {
        for (int i = 0; i < 200; i++) {
            String code = randomCode(5);
            if (generatedCodes.contains(code)) {
                continue;
            }
            Long count = productModelMapper.selectCount(new LambdaQueryWrapper<ProductModel>()
                    .eq(ProductModel::getProductCode, code));
            if (count == null || count == 0) {
                generatedCodes.add(code);
                return code;
            }
        }
        throw new ServiceException("生成唯一产品编码失败,请重试");
    }
    private static String randomCode(int length) {
        String chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < length; i++) {
            sb.append(chars.charAt(ThreadLocalRandom.current().nextInt(chars.length())));
        }
        return sb.toString();
    }
    private static String trimToNull(String value) {
        if (value == null) {
            return null;
        }
        String trimmed = value.trim();
        return trimmed.isEmpty() ? null : trimmed;
    }
}