gongchunyi
2 天以前 52e93e45d7c989483693f53ded1b4483891fb055
feat: 物料类型与存货类别的新增、删除和修改,物料规格的导入数据
已添加3个文件
已修改9个文件
460 ■■■■■ 文件已修改
src/main/java/com/ruoyi/production/controller/ProductMaterialController.java 56 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductMaterialSkuController.java 32 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/dto/ProductMaterialConfigDto.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/dto/ProductMaterialGroupDto.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/enums/MaterialConfigTypeEnum.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductMaterialSkuImportDto.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/ProductMaterialConfigService.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/ProductMaterialService.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/ProductMaterialSkuService.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductMaterialConfigServiceImpl.java 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductMaterialServiceImpl.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductMaterialSkuServiceImpl.java 105 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductMaterialController.java
@@ -3,20 +3,17 @@
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.production.dto.ProductMaterialConfigDto;
import com.ruoyi.production.dto.ProductMaterialGroupDto;
import com.ruoyi.production.pojo.ProductMaterial;
import com.ruoyi.production.pojo.ProductMaterialConfig;
import com.ruoyi.production.service.ProductMaterialConfigService;
import com.ruoyi.production.service.ProductMaterialService;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
 * <br>
@@ -34,6 +31,9 @@
    @Autowired
    private ProductMaterialService productMaterialService;
    @Autowired
    private ProductMaterialConfigService productMaterialConfigService;
    @GetMapping("/loadData")
    @ApiOperation("拉取物料编码数据")
    @Log(title = "拉取物料编码数据", businessType = BusinessType.INSERT)
@@ -46,7 +46,7 @@
    @ApiOperation("物料数据")
    @Log(title = "物料数据", businessType = BusinessType.OTHER)
    public AjaxResult productMaterialList(String materialName) {
        Map<String, List<ProductMaterial>> productMaterialMap = productMaterialService.ProductMaterialList(materialName);
        List<ProductMaterialGroupDto> productMaterialMap = productMaterialService.ProductMaterialList(materialName);
        return AjaxResult.success(productMaterialMap);
    }
@@ -75,5 +75,43 @@
    }
    @GetMapping("/materialTypeList")
    @ApiOperation("物料类型数据集合")
    @Log(title = "物料类型数据集合", businessType = BusinessType.OTHER)
    public AjaxResult materialTypeList() {
        List<ProductMaterialConfig> list = productMaterialConfigService.materialTypeList();
        return AjaxResult.success(list);
    }
    @GetMapping("/inventoryCategoryList")
    @ApiOperation("'存货类别数据集合")
    @Log(title = "'存货类别数据集合", businessType = BusinessType.OTHER)
    public AjaxResult inventoryCategoryList() {
        List<ProductMaterialConfig> list = productMaterialConfigService.inventoryCategoryList();
        return AjaxResult.success(list);
    }
    @PostMapping("/config/add")
    @ApiOperation("新增物料配置")
    @Log(title = "新增物料配置", businessType = BusinessType.INSERT)
    public AjaxResult addProductMaterialConfig(@RequestBody ProductMaterialConfigDto config) {
        productMaterialConfigService.addProductMaterialConfig(config);
        return AjaxResult.success();
    }
    @PutMapping("/config/update")
    @ApiOperation("修改物料配置")
    @Log(title = "修改物料配置", businessType = BusinessType.UPDATE)
    public AjaxResult updateProductMaterialConfig(@RequestBody ProductMaterialConfigDto config) {
        productMaterialConfigService.updateProductMaterialConfig(config);
        return AjaxResult.success();
    }
    @DeleteMapping("/config/delete")
    @ApiOperation("删除物料配置")
    @Log(title = "删除物料配置", businessType = BusinessType.DELETE)
    public AjaxResult deleteProductMaterialConfig(@RequestBody List<Integer> ids) {
        productMaterialConfigService.deleteProductMaterialConfig(ids);
        return AjaxResult.success();
    }
}
src/main/java/com/ruoyi/production/controller/ProductMaterialSkuController.java
@@ -1,22 +1,20 @@
package com.ruoyi.production.controller;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.production.dto.ProductMaterialSkuDto;
import com.ruoyi.production.pojo.ProductMaterialSku;
import com.ruoyi.production.pojo.ProductMaterialSkuImportDto;
import com.ruoyi.production.service.ProductMaterialSkuService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
@@ -30,6 +28,7 @@
 */
@RestController
@RequestMapping("/productMaterialSku")
@Api(tags = "物料规格管理接口")
public class ProductMaterialSkuController {
    @Autowired
@@ -66,4 +65,21 @@
        productMaterialSkuService.deleteProductMaterialSku(ids);
        return AjaxResult.success();
    }
    @PostMapping("/downloadTemplate")
    @Log(title = "下载物料规格导入模板", businessType = BusinessType.EXPORT)
    @ApiOperation("下载物料规格导入模板")
    public void importTemplate(HttpServletResponse response) {
        ExcelUtil<ProductMaterialSkuImportDto> excelUtil = new ExcelUtil<>(ProductMaterialSkuImportDto.class);
        excelUtil.importTemplateExcel(response, "下载物料规格导入模板");
    }
    @PostMapping("/import")
    @ApiOperation("物料规格数据导入")
    @Log(title = "物料规格数据导入", businessType = BusinessType.IMPORT)
    public AjaxResult importProdData(@RequestParam("file") MultipartFile file, @RequestParam("materialId") Long materialId) {
        productMaterialSkuService.importProdData(file, materialId);
        return AjaxResult.success("导入成功");
    }
}
src/main/java/com/ruoyi/production/dto/ProductMaterialConfigDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
package com.ruoyi.production.dto;
import com.baomidou.mybatisplus.annotation.TableField;
import com.ruoyi.production.pojo.ProductMaterialConfig;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
 * <br>
 * ç‰©æ–™ä¿¡æ¯è¡¨é…ç½®è¡¨Dto
 * </br>
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/03/12 14:07
 */
@Data
public class ProductMaterialConfigDto extends ProductMaterialConfig {
    @ApiModelProperty("配置类型")
    @TableField(exist = false)
    private Integer type;
}
src/main/java/com/ruoyi/production/dto/ProductMaterialGroupDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,30 @@
package com.ruoyi.production.dto;
import com.ruoyi.production.pojo.ProductMaterial;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
/**
 * <br>
 * ç‰©æ–™é…ç½®åˆ†ç»„ DTO
 * </br>
 *
 * @author deslrey
 * @since 2026/03/12 13:43
 */
@Data
@ApiModel(value = "ProductMaterialGroupDto", description = "物料配置分组数据")
public class ProductMaterialGroupDto {
    @ApiModelProperty("配置ID")
    private Integer configId;
    @ApiModelProperty("配置名称")
    private String configName;
    @ApiModelProperty("物料列表")
    private List<ProductMaterial> materialList;
}
src/main/java/com/ruoyi/production/enums/MaterialConfigTypeEnum.java
@@ -13,13 +13,50 @@
    /**
     * ç‰©æ–™ç±»åž‹
     * å¯¹åº”数据库 config_type = MATERIAL_TYPE
     */
    MATERIAL_TYPE,
    MATERIAL_TYPE(1, "物料类型"),
    /**
     * å­˜è´§ç±»åˆ«
     * å¯¹åº”数据库 config_type = INVENTORY_CAT
     */
    INVENTORY_CAT
    INVENTORY_CAT(2, "存货类别");
    private final Integer type;
    private final String desc;
    MaterialConfigTypeEnum(Integer type, String desc) {
        this.type = type;
        this.desc = desc;
    }
    public Integer getType() {
        return type;
    }
    public String getDesc() {
        return desc;
    }
    /**
     * æ ¹æ® type èŽ·å–æžšä¸¾
     */
    public static MaterialConfigTypeEnum getByType(Integer type) {
        for (MaterialConfigTypeEnum value : values()) {
            if (value.type.equals(type)) {
                return value;
            }
        }
        return null;
    }
    /**
     * æ ¹æ® type èŽ·å–æ•°æ®åº“å­˜å‚¨å€¼
     */
    public static String getConfigType(Integer type) {
        MaterialConfigTypeEnum e = getByType(type);
        if (e == null) {
            throw new IllegalArgumentException("配置类型错误");
        }
        return e.name();
    }
}
src/main/java/com/ruoyi/production/pojo/ProductMaterialSkuImportDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,35 @@
package com.ruoyi.production.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.time.LocalDateTime;
/**
 * <br>
 * ç‰©æ–™è§„格导入
 * </br>
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/03/12 13:08
 */
@Data
@ApiModel(value = "ProductMaterialSkuImportDto", description = "物料规格表导入")
public class ProductMaterialSkuImportDto {
    @ApiModelProperty("规格型号")
    @Excel(name = "规格型号")
    private String specification;
    @ApiModelProperty("供应方式(自制,外购)")
    @Excel(name = "供应方式")
    private String supplyType;
}
src/main/java/com/ruoyi/production/service/ProductMaterialConfigService.java
@@ -1,7 +1,10 @@
package com.ruoyi.production.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.production.dto.ProductMaterialConfigDto;
import com.ruoyi.production.pojo.ProductMaterialConfig;
import java.util.List;
/**
 * <br>
@@ -13,4 +16,13 @@
 * @since 2026/03/11 16:58
 */
public interface ProductMaterialConfigService extends IService<ProductMaterialConfig> {
    List<ProductMaterialConfig> materialTypeList();
    List<ProductMaterialConfig> inventoryCategoryList();
    void addProductMaterialConfig(ProductMaterialConfigDto config);
    void updateProductMaterialConfig(ProductMaterialConfigDto config);
    void deleteProductMaterialConfig(List<Integer> ids);
}
src/main/java/com/ruoyi/production/service/ProductMaterialService.java
@@ -1,10 +1,10 @@
package com.ruoyi.production.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.production.dto.ProductMaterialGroupDto;
import com.ruoyi.production.pojo.ProductMaterial;
import java.util.List;
import java.util.Map;
/**
 * <br>
@@ -21,7 +21,7 @@
    void syncProductMaterialJob();
    Map<String, List<ProductMaterial>> ProductMaterialList(String materialName);
    List<ProductMaterialGroupDto> ProductMaterialList(String materialName);
    void addProductMaterial(ProductMaterial productMaterial);
src/main/java/com/ruoyi/production/service/ProductMaterialSkuService.java
@@ -3,6 +3,7 @@
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.production.dto.ProductMaterialSkuDto;
import com.ruoyi.production.pojo.ProductMaterialSku;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
@@ -23,4 +24,6 @@
    void updateProductMaterialSku(ProductMaterialSku productMaterialSku);
    void deleteProductMaterialSku(List<Long> ids);
    void importProdData(MultipartFile file, Long materialId);
}
src/main/java/com/ruoyi/production/service/impl/ProductMaterialConfigServiceImpl.java
@@ -1,11 +1,19 @@
package com.ruoyi.production.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.production.dto.ProductMaterialConfigDto;
import com.ruoyi.production.enums.MaterialConfigTypeEnum;
import com.ruoyi.production.mapper.ProductMaterialConfigMapper;
import com.ruoyi.production.pojo.ProductMaterialConfig;
import com.ruoyi.production.service.ProductMaterialConfigService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
 * <br>
@@ -20,5 +28,93 @@
@Service
public class ProductMaterialConfigServiceImpl extends ServiceImpl<ProductMaterialConfigMapper, ProductMaterialConfig> implements ProductMaterialConfigService {
    @Override
    public List<ProductMaterialConfig> materialTypeList() {
        return getByType(MaterialConfigTypeEnum.MATERIAL_TYPE);
    }
    @Override
    public List<ProductMaterialConfig> inventoryCategoryList() {
        return getByType(MaterialConfigTypeEnum.INVENTORY_CAT);
    }
    private List<ProductMaterialConfig> getByType(MaterialConfigTypeEnum type) {
        return list(new LambdaQueryWrapper<ProductMaterialConfig>().eq(ProductMaterialConfig::getConfigType, type.name()));
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addProductMaterialConfig(ProductMaterialConfigDto config) {
        config.setConfigType(MaterialConfigTypeEnum.getConfigType(config.getType()));
        validateConfig(config, false);
        if (existsConfig(config.getConfigType(), config.getConfigName(), null)) {
            throw new ServiceException("配置名称已存在");
        }
        if (!this.save(config)) {
            throw new ServiceException("新增配置失败");
        }
        log.info("新增物料配置成功 type={}, name={}", config.getConfigType(), config.getConfigName());
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateProductMaterialConfig(ProductMaterialConfigDto config) {
        config.setConfigType(MaterialConfigTypeEnum.getConfigType(config.getType()));
        validateConfig(config, true);
        ProductMaterialConfig exist = this.getById(config.getId());
        if (exist == null) {
            throw new ServiceException("配置不存在");
        }
        if (existsConfig(config.getConfigType(), config.getConfigName(), config.getId())) {
            throw new ServiceException("配置名称已存在");
        }
        if (!this.updateById(config)) {
            throw new ServiceException("修改配置失败");
        }
        log.info("修改物料配置成功 id={}", config.getId());
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteProductMaterialConfig(List<Integer> ids) {
        if (ids == null || ids.isEmpty()) {
            throw new ServiceException("请选择至少一条数据");
        }
        if (!this.removeByIds(ids)) {
            throw new ServiceException("删除配置失败");
        }
        log.info("删除物料配置成功 ids={}", ids);
    }
    private void validateConfig(ProductMaterialConfig config, boolean requireId) {
        if (config == null) {
            throw new ServiceException("参数不能为空");
        }
        if (requireId && config.getId() == null) {
            throw new ServiceException("主键ID不能为空");
        }
        if (StringUtils.isEmpty(config.getConfigType())) {
            throw new ServiceException("配置类型不能为空");
        }
        try {
            MaterialConfigTypeEnum.valueOf(config.getConfigType());
        } catch (IllegalArgumentException e) {
            throw new ServiceException("配置类型不合法");
        }
        if (StringUtils.isEmpty(config.getConfigName())) {
            throw new ServiceException("配置名称不能为空");
        }
    }
    private boolean existsConfig(String configType, String configName, Integer excludeId) {
        if (StringUtils.isEmpty(configName) || StringUtils.isEmpty(configType)) {
            return false;
        }
        LambdaQueryWrapper<ProductMaterialConfig> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(ProductMaterialConfig::getConfigType, configType).eq(ProductMaterialConfig::getConfigName, configName);
        if (excludeId != null) {
            wrapper.ne(ProductMaterialConfig::getId, excludeId);
        }
        return this.count(wrapper) > 0;
    }
}
src/main/java/com/ruoyi/production/service/impl/ProductMaterialServiceImpl.java
@@ -9,6 +9,7 @@
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.http.HttpUtils;
import com.ruoyi.framework.config.AliDingConfig;
import com.ruoyi.production.dto.ProductMaterialGroupDto;
import com.ruoyi.production.enums.MaterialConfigTypeEnum;
import com.ruoyi.production.mapper.ProductMaterialMapper;
import com.ruoyi.production.pojo.ProductMaterial;
@@ -337,13 +338,12 @@
    }
    @Override
    public Map<String, List<ProductMaterial>> ProductMaterialList(String materialName) {
    public List<ProductMaterialGroupDto> ProductMaterialList(String materialName) {
        List<ProductMaterialConfig> materialConfigList =
                productMaterialConfigService.list(new LambdaQueryWrapper<ProductMaterialConfig>()
                        .eq(ProductMaterialConfig::getConfigType, MaterialConfigTypeEnum.MATERIAL_TYPE.name()));
        List<ProductMaterialConfig> materialConfigList = productMaterialConfigService.list(new LambdaQueryWrapper<ProductMaterialConfig>()
                .eq(ProductMaterialConfig::getConfigType, MaterialConfigTypeEnum.MATERIAL_TYPE.name()));
        Map<String, List<ProductMaterial>> productMaterialMap = new HashMap<>();
        List<ProductMaterialGroupDto> productMaterialMap = new ArrayList<>();
        if (materialConfigList == null || materialConfigList.isEmpty()) {
            return productMaterialMap;
        }
@@ -352,14 +352,16 @@
            wrapper.eq(ProductMaterial::getMaterialTypeId, materialConfig.getId())
                    .select(ProductMaterial::getId, ProductMaterial::getMaterialName)
                    .like(materialName != null && !materialName.isEmpty(), ProductMaterial::getMaterialName, materialName);
            List<ProductMaterial> productMaterialList = list(wrapper);
            if (productMaterialList != null && !productMaterialList.isEmpty()) {
                productMaterialMap.put(materialConfig.getConfigName(), productMaterialList);
                ProductMaterialGroupDto dto = new ProductMaterialGroupDto();
                dto.setConfigId(materialConfig.getId());
                dto.setConfigName(materialConfig.getConfigName());
                dto.setMaterialList(productMaterialList);
                productMaterialMap.add(dto);
            }
        }
        return productMaterialMap;
    }
src/main/java/com/ruoyi/production/service/impl/ProductMaterialSkuServiceImpl.java
@@ -4,20 +4,28 @@
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.production.dto.ProductMaterialSkuDto;
import com.ruoyi.production.mapper.ProductMaterialMapper;
import com.ruoyi.production.mapper.ProductMaterialSkuMapper;
import com.ruoyi.production.pojo.ProductMaterial;
import com.ruoyi.production.pojo.ProductMaterialSku;
import com.ruoyi.production.pojo.ProductMaterialSkuImportDto;
import com.ruoyi.production.service.ProductMaterialSkuService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
 * <br>
@@ -163,4 +171,101 @@
        return this.count(queryWrapper) > 0;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void importProdData(MultipartFile file, Long materialId) {
        if (materialId == null) {
            throw new ServiceException("物料ID不能为空");
        }
        if (file == null || file.isEmpty()) {
            throw new ServiceException("导入文件不能为空");
        }
        ProductMaterial material = productMaterialMapper.selectById(materialId);
        if (material == null) {
            throw new ServiceException("物料不存在");
        }
        ExcelUtil<ProductMaterialSkuImportDto> excelUtil = new ExcelUtil<>(ProductMaterialSkuImportDto.class);
        List<ProductMaterialSkuImportDto> importList;
        try {
            importList = excelUtil.importExcel(file.getInputStream());
        } catch (Exception e) {
            log.error("导入物料规格Excel解析失败", e);
            throw new ServiceException("Excel解析失败");
        }
        if (importList == null || importList.isEmpty()) {
            throw new ServiceException("Excel没有数据");
        }
        Map<String, ProductMaterialSkuImportDto> specMap = new LinkedHashMap<>();
        for (ProductMaterialSkuImportDto dto : importList) {
            if (dto == null || StringUtils.isEmpty(dto.getSpecification())) {
                continue;
            }
            String specification = dto.getSpecification().trim();
            if (specification.isEmpty()) {
                continue;
            }
            specMap.putIfAbsent(specification, dto);
        }
        if (specMap.isEmpty()) {
            throw new ServiceException("Excel没有有效的规格数据");
        }
        Set<String> specifications = specMap.keySet();
        List<ProductMaterialSku> existList = this.list(new LambdaQueryWrapper<ProductMaterialSku>()
                .eq(ProductMaterialSku::getMaterialId, materialId)
                .in(ProductMaterialSku::getSpecification, specifications));
        Map<String, ProductMaterialSku> existMap = existList.stream()
                .collect(Collectors.toMap(ProductMaterialSku::getSpecification, sku -> sku, (a, b) -> a));
        LocalDateTime now = LocalDateTime.now();
        List<ProductMaterialSku> saveList = new ArrayList<>();
        List<ProductMaterialSku> updateList = new ArrayList<>();
        for (Map.Entry<String, ProductMaterialSkuImportDto> entry : specMap.entrySet()) {
            String specification = entry.getKey();
            ProductMaterialSkuImportDto dto = entry.getValue();
            String supplyType = StringUtils.isNotEmpty(dto.getSupplyType()) ? dto.getSupplyType().trim() : null;
            ProductMaterialSku exist = existMap.get(specification);
            if (exist == null) {
                ProductMaterialSku sku = new ProductMaterialSku();
                sku.setMaterialId(materialId);
                sku.setSpecification(specification);
                sku.setSupplyType(supplyType);
                sku.setCreateTime(now);
                sku.setUpdateTime(now);
                saveList.add(sku);
            } else {
                boolean needUpdate = false;
                if (supplyType != null && !supplyType.equals(exist.getSupplyType())) {
                    exist.setSupplyType(supplyType);
                    needUpdate = true;
                }
                if (needUpdate) {
                    exist.setUpdateTime(now);
                    updateList.add(exist);
                }
            }
        }
        if (saveList.isEmpty() && updateList.isEmpty()) {
            throw new ServiceException("Excel与现有数据一致,无需导入");
        }
        if (!saveList.isEmpty()) {
            this.saveBatch(saveList);
        }
        if (!updateList.isEmpty()) {
            this.updateBatchById(updateList);
        }
        log.info("物料规格导入完成 materialId={}, æ–°å¢ž{}条,更新{}条", materialId, saveList.size(), updateList.size());
    }
}