Jelajahi Sumber

feat(spareType): 新增备件类型导入功能并优化设备导入逻辑

- 在CustomExcelImportUtil中新增importSpareTypeList方法支持备件类型Excel导入
- 添加SpareType实体类引用到工具类
- 修改数据库查询条件is_show字段比较方式从布尔值改为数值
- 优化SbInfoServiceImpl中设备批量导入逻辑,支持新增和更新混合操作
- 添加设备位置批量初始化打点功能,将设备均匀分布到矩形区域
- 实现备件类型导入接口和模板下载功能
- 修复备件管理导入逻辑中的新增记录处理
- 优化设备导入结果统计返回信息
dazhaxie 3 hari lalu
induk
melakukan
bcb9f8acac

+ 66 - 0
platform-dao/src/main/java/com/platform/dao/util/CustomExcelImportUtil.java

@@ -11,6 +11,7 @@ import com.platform.dao.entity.purchase.*;
 import com.platform.dao.entity.sb.SbInfo;
 import com.platform.dao.entity.sb.SbModel;
 import com.platform.dao.entity.sqarepartmanage.SparePartInfo;
+import com.platform.dao.entity.sqarepartmanage.SpareType;
 import com.platform.dao.entity.store.SpareStore;
 import com.platform.dao.enums.*;
 import com.platform.dao.vo.export.sb.ExportSbInfoVO;
@@ -2084,4 +2085,69 @@ public class CustomExcelImportUtil {
         }
         return result;
     }
+
+    /**
+     * 导入备件类型
+     * Excel列:0-编码, 1-名称, 2-位置类型(大类/子类), 3-父类别编码, 4-排序, 5-备注
+     * 数据从行索引2开始读取(第1行表头,第2行填写说明)
+     *
+     * @param inputstream Excel输入流
+     * @return 备件类型列表
+     */
+    public static List<SpareType> importSpareTypeList(InputStream inputstream)
+            throws IOException, InvalidFormatException {
+        if (inputstream == null) {
+            return Collections.emptyList();
+        }
+        Workbook book = null;
+        if (!(inputstream.markSupported())) {
+            inputstream = new PushbackInputStream(inputstream, 8);
+        }
+        if (POIFSFileSystem.hasPOIFSHeader(inputstream)) {
+            book = new HSSFWorkbook(inputstream);
+        } else if (POIXMLDocument.hasOOXMLHeader(inputstream)) {
+            book = new XSSFWorkbook(OPCPackage.open(inputstream));
+        }
+        Sheet sheet = book.getSheetAt(0);
+        int rowCounts = sheet.getLastRowNum();
+        List<SpareType> result = new ArrayList<>(rowCounts);
+        StringBuffer error = new StringBuffer();
+        LOGGER.info("处理备件类型导入,总行数:" + rowCounts);
+        for (int i = 2; i <= rowCounts; i++) {
+            SpareType record = new SpareType();
+            Row row = sheet.getRow(i);
+            if (isRowEmpty(row)) {
+                continue;
+            }
+            try {
+                record.setNo(getCellValue(row.getCell(0)).trim());
+                record.setName(getCellValue(row.getCell(1)).trim());
+                // 位置类型:大类/1→1,子类/2→2
+                String typeStr = getCellValue(row.getCell(2)).trim();
+                if ("大类".equals(typeStr) || "1".equals(typeStr)) {
+                    record.setType(1);
+                } else if ("子类".equals(typeStr) || "2".equals(typeStr)) {
+                    record.setType(2);
+                } else {
+                    throw new BusinessException("位置类型填写有误,请填写【大类】或【子类】");
+                }
+                record.setParentId(getCellValue(row.getCell(3)).trim()); // 父类别编码,由Service层解析为ID
+                String sortStr = getCellValue(row.getCell(4)).trim();
+                if (StringUtils.isNotBlank(sortStr)) {
+                    record.setSort(Integer.parseInt(sortStr));
+                } else {
+                    record.setSort(1);
+                }
+                record.setRemark(getCellValue(row.getCell(5)).trim());
+                result.add(record);
+            } catch (Exception e) {
+                error.append(i).append(",");
+            }
+        }
+        String errorStr = error.toString();
+        if (StringUtils.isNotEmpty(errorStr)) {
+            throw new BusinessException(String.format("导入异常,以下行数据有问题:%s", errorStr.substring(0, errorStr.length() - 1)));
+        }
+        return result;
+    }
 }

+ 1 - 1
platform-dao/src/main/resources/mapper/sb/SbInfoMapper.xml

@@ -433,7 +433,7 @@ user.real_name as saveUserName
         <where>
             sb.zjm is not null
             and sb.jbdh is not null
-            and is_show = true
+            and is_show = 1
         </where>
     </select>
 

+ 1 - 1
platform-dao/src/main/resources/mapper/sb/SbPositionMapper.xml

@@ -22,7 +22,7 @@
         where 1=1
         <if test="keyword != null and keyword != ''">
             and ( position.name like concat('%',#{keyword},'%') or
-            position.no like concat('%',#{keyword},'%')
+            position.no like concat('%',#{keyword},'%') )
         </if>
         <if test="parentId != null and parentId != ''">
             and ( position.parent_id = #{parentId} or position.id = #{parentId})

+ 15 - 0
platform-rest/src/main/java/com/platform/rest/controller/sb/SbPositionController.java

@@ -5,6 +5,8 @@ import com.platform.common.util.BeanConverterUtil;
 import com.platform.common.util.R;
 import com.platform.common.validation.group.AddGroup;
 import com.platform.common.validation.group.UpdateGroup;
+import com.platform.dao.dto.sb.SbInitPointRequest;
+import com.platform.dao.dto.sb.SbInitPointRequest;
 import com.platform.dao.dto.sb.SbPositionDTO;
 import com.platform.dao.util.ExcelUtil;
 import com.platform.dao.util.TreeUtil;
@@ -159,4 +161,17 @@ public class SbPositionController {
         return new R<>(sbPositionService.getSbTreeVOList(sbPositionDTO));
     }
 
+    /**
+     * 初始化打点:将指定位置下的设备均匀分布在矩形区域内
+     *
+     * @param request 矩形坐标 + 位置ID列表
+     * @return R
+     */
+    @SysLog("初始化打点")
+    @PostMapping("/initPoints")
+    public R initPoints(@RequestBody SbInitPointRequest request) {
+        sbPositionService.initPoints(request);
+        return R.success();
+    }
+
 }

+ 38 - 0
platform-rest/src/main/java/com/platform/rest/controller/sqarepartmanage/SpareTypeController.java

@@ -36,11 +36,16 @@ import com.platform.common.bean.AbstractPageResultBean;
 import com.platform.rest.log.annotation.SysLog;
 
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.FileNotFoundException;
+import java.net.URLEncoder;
 import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import javax.servlet.http.HttpServletResponse;
+import org.springframework.util.FileCopyUtils;
+import org.springframework.web.multipart.MultipartFile;
 
 /**
  * @Description 备件类型 控制器
@@ -244,5 +249,38 @@ public class SpareTypeController {
         return new R<>(TreeUtil.buildSpareTypeAndSbModelTree(spareTypeService.getModelListAndSbModelByDTO(spareTypeDTO), ""));
     }
 
+    /**
+     * 导入备件类型
+     *
+     * @param file Excel文件
+     * @return R
+     */
+    @SysLog("导入备件类型")
+    @PostMapping("/import")
+    public R importExcelByAdd(@RequestParam("file") MultipartFile file) throws Exception {
+        String result = spareTypeService.importListByAdd(file);
+        return new R<>(result);
+    }
+
+    /**
+     * 备件类型导入模板下载
+     *
+     * @param response 输出对象
+     */
+    @SysLog("备件类型导入模板下载")
+    @GetMapping("/import/template")
+    public void downloadImportTemplate(HttpServletResponse response) throws Exception {
+        response.setCharacterEncoding("UTF-8");
+        response.setContentType("application/vnd.ms-excel;charset=utf-8");
+        response.setHeader("Content-Disposition",
+                "attachment;filename=" + URLEncoder.encode("备件类型导入模板.xlsx", "UTF-8"));
+        try (InputStream in = getClass().getClassLoader().getResourceAsStream("templates/备件类型导入模板.xlsx")) {
+            if (in == null) {
+                throw new FileNotFoundException("备件类型导入模板文件不存在");
+            }
+            FileCopyUtils.copy(in, response.getOutputStream());
+            response.flushBuffer();
+        }
+    }
 
 }

+ 8 - 0
platform-service/src/main/java/com/platform/service/sb/SbPositionService.java

@@ -1,6 +1,7 @@
 package com.platform.service.sb;
 
 import com.platform.dao.bean.MyVOPage;
+import com.platform.dao.dto.sb.SbInitPointRequest;
 import com.platform.dao.dto.sb.SbPositionDTO;
 import com.platform.dao.entity.sb.SbPosition;
 import com.platform.dao.pojo.MyCommonTree;
@@ -56,4 +57,11 @@ public interface SbPositionService extends IBaseService<SbPosition, SbPositionDT
      * @return :
      */
     SbPositionVO getById(Object id);
+
+    /**
+     * 初始化打点:将指定位置下的设备均匀分布在矩形区域内
+     *
+     * @param request 矩形坐标 + 位置ID列表
+     */
+    void initPoints(SbInitPointRequest request);
 }

+ 19 - 5
platform-service/src/main/java/com/platform/service/sb/impl/SbInfoServiceImpl.java

@@ -1799,6 +1799,7 @@ public class SbInfoServiceImpl extends BaseServiceImpl<SbInfoMapper, SbInfo, SbI
 
         int addNum = 0;
         List<String> modelError = new ArrayList<String>();
+        Set<Integer> newIndexSet = new HashSet<>();
         try {
             List<SbInfoVO> items = CustomExcelImportUtil.importListByUpdateALl(file.getInputStream());
             if (!CollectionUtils.isEmpty(items)) {
@@ -1812,10 +1813,14 @@ public class SbInfoServiceImpl extends BaseServiceImpl<SbInfoMapper, SbInfo, SbI
                     // 通过设备编号no查找已有记录,获取ID用于更新(与新增导入保持一致,不依赖Excel中的ID列)
                     SbInfoVO existingInfo = mapper.getByNo(item.getNo());
                     if (existingInfo == null) {
-                        log.warn("更新导入第{}行,设备编号【{}】在系统中不存在,跳过", item.getNo(), item.getNo());
-                        continue;
+                        log.info("更新导入第{}行,设备编号【{}】在系统中不存在,将新增", addNum + 1, item.getNo());
+                        item.setId(IdGeneratorUtils.getObjectId());
+                        item.setCreatedTime(now);
+                        item.setCreatedUserId(SecurityUtils.getUserInfo().getUserId());
+                        newIndexSet.add(addNum);
+                    } else {
+                        item.setId(existingInfo.getId());
                     }
-                    item.setId(existingInfo.getId());
 
                     // 处理所属车间:将车间名称转换为车间ID(匹配不到则留空,不报错)
                     if (StringUtils.isNotBlank(item.getPositionName())) {
@@ -1930,10 +1935,19 @@ public class SbInfoServiceImpl extends BaseServiceImpl<SbInfoMapper, SbInfo, SbI
                             item.setProducerId(newProducer.getId());
                         }
                     }
-                    mapper.updateByPrimaryKeySelective(BeanConverterUtil.copyObjectProperties(item, SbInfo.class));
+                    if (newIndexSet.contains(addNum)) {
+                        SbInfo newItem = BeanConverterUtil.copyObjectProperties(item, SbInfo.class);
+                        List<SbInfo> singleList = new ArrayList<>();
+                        singleList.add(newItem);
+                        mapper.insertListforComplex(singleList);
+                    } else {
+                        mapper.updateByPrimaryKeySelective(BeanConverterUtil.copyObjectProperties(item, SbInfo.class));
+                    }
                 }
             }
-            return "总计更新:" + (items.size());
+            int newCount = newIndexSet.size();
+            int updateCount = items.size() - newCount;
+            return "总计修改导入:" + items.size() + "条,更新" + updateCount + "条,新增" + newCount + "条";
         } catch (Exception e) {
             throw new BusinessException(e.getMessage());
         }

+ 97 - 1
platform-service/src/main/java/com/platform/service/sb/impl/SbPositionServiceImpl.java

@@ -7,6 +7,7 @@ import com.platform.common.util.ListUtils;
 import com.platform.common.util.MapUtils;
 import com.platform.common.util.StringUtils;
 import com.platform.dao.bean.MyVOPage;
+import com.platform.dao.dto.sb.SbInitPointRequest;
 import com.platform.dao.dto.sb.SbPositionDTO;
 import com.platform.dao.dto.sqarepartmanage.SparePartUsedDTO;
 import com.platform.dao.entity.part.PartInfo;
@@ -26,13 +27,18 @@ import com.platform.service.base.impl.BaseServiceImpl;
 import com.platform.service.sb.SbPositionService;
 import com.platform.service.util.ExecuteSql;
 import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import tk.mybatis.mapper.weekend.Weekend;
 import tk.mybatis.mapper.weekend.WeekendCriteria;
 
+import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.time.LocalDateTime;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 
 /**
  * @Description 设备位置 service 实现类
@@ -41,6 +47,7 @@ import java.util.Map;
  * @Version Copyright (c) 2019,北京乾元坤和科技有限公司 All rights reserved.
  */
 @AllArgsConstructor
+@Slf4j
 @Service("sbPositionService")
 public class SbPositionServiceImpl extends BaseServiceImpl<SbPositionMapper, SbPosition, SbPositionDTO> implements SbPositionService {
 
@@ -183,7 +190,7 @@ public class SbPositionServiceImpl extends BaseServiceImpl<SbPositionMapper, SbP
             sbPartTreeMap.put(vo.getId(), vo);
         });
         partInfoList.forEach(item -> {
-            MyCommonTree sbInfoTreeVO = sbInfoTreeMap.get(item.getSbId());
+            MyCommonTree sbInfoTreeVO = sbPartTreeMap.get(item.getSbId());
             if (sbInfoTreeVO != null) {
                 sbInfoTreeVO.add(sbPartTreeMap.get(item.getId()));
             }
@@ -228,5 +235,94 @@ public class SbPositionServiceImpl extends BaseServiceImpl<SbPositionMapper, SbP
         return mapper.getById(id);
     }
 
+    @Override
+    public void initPoints(SbInitPointRequest request) {
+        List<SbInitPointRequest.Point> points = request.getPoints();
+        List<String> positionIds = request.getPositionIds();
+        String mapName = request.getMapName();
+        Integer isShow = request.getIsShow();
+        if (points == null || points.size() != 4) {
+            throw new BusinessException("请提供矩形的四个角点坐标");
+        }
+        if (positionIds == null || positionIds.isEmpty()) {
+            throw new BusinessException("请选择至少一个设备位置");
+        }
+        if (mapName == null || mapName.isEmpty()) {
+            throw new BusinessException("请选择底图");
+        }
+        if (isShow == null) {
+            throw new BusinessException("请设置是否显示");
+        }
+
+        // 1. 从四个角点中提取矩形边界
+        double lngMin = points.stream().mapToDouble(p -> p.getLongitude().doubleValue()).min().orElse(0);
+        double lngMax = points.stream().mapToDouble(p -> p.getLongitude().doubleValue()).max().orElse(0);
+        double latMin = points.stream().mapToDouble(p -> p.getLatitude().doubleValue()).min().orElse(0);
+        double latMax = points.stream().mapToDouble(p -> p.getLatitude().doubleValue()).max().orElse(0);
+
+        // 2. 查询这些位置下的所有设备
+        Weekend<SbInfo> weekend = new Weekend<>(SbInfo.class);
+        WeekendCriteria<SbInfo, Object> criteria = weekend.weekendCriteria();
+        criteria.andIn(SbInfo::getPositionId, positionIds);
+        List<SbInfo> allDevices = sbInfoMapper.selectByExample(weekend);
+        if (allDevices.isEmpty()) {
+            throw new BusinessException("所选位置下没有设备");
+        }
+
+        // 3. 按 positionId 分组,保持插入顺序
+        Map<String, List<SbInfo>> positionDeviceMap = new LinkedHashMap<>();
+        for (String positionId : positionIds) {
+            List<SbInfo> devices = allDevices.stream()
+                    .filter(d -> positionId.equals(d.getPositionId()))
+                    .collect(Collectors.toList());
+            if (!devices.isEmpty()) {
+                positionDeviceMap.put(positionId, devices);
+            }
+        }
+
+        int positionCount = positionDeviceMap.size();
+        double bandHeight = (latMax - latMin) / positionCount;
+        log.info("初始化打点:矩形边界 lng[{}, {}] lat[{}, {}],位置数{},设备总数{}",
+                lngMin, lngMax, latMin, latMax, positionCount, allDevices.size());
+
+        // 4. 每个位置分配一个条带,条带内网格分布
+        int bandIndex = 0;
+        int totalUpdated = 0;
+        for (Map.Entry<String, List<SbInfo>> entry : positionDeviceMap.entrySet()) {
+            List<SbInfo> devices = entry.getValue();
+            int n = devices.size();
+
+            double bandTop = latMax - bandIndex * bandHeight;
+            double bandBottom = bandTop - bandHeight;
+
+            // 计算条带内网格
+            int cols = (int) Math.ceil(Math.sqrt(n));
+            int rows = (int) Math.ceil((double) n / cols);
+            double lngStep = cols > 1 ? (lngMax - lngMin) / (cols - 1) : 0;
+            double latStep = rows > 1 ? (bandTop - bandBottom) / (rows - 1) : 0;
+
+            for (int i = 0; i < n; i++) {
+                int row = i / cols;
+                int col = i % cols;
+                double lng = lngMin + col * lngStep;
+                double lat = bandTop - row * latStep;
+
+                SbInfo update = new SbInfo();
+                update.setId(devices.get(i).getId());
+                update.setZjm(BigDecimal.valueOf(lng).setScale(6, RoundingMode.HALF_UP).toPlainString());
+                update.setJbdh(BigDecimal.valueOf(lat).setScale(6, RoundingMode.HALF_UP).toPlainString());
+                update.setMapName(mapName);
+                update.setIsShow(isShow);
+                sbInfoMapper.updateByPrimaryKeySelective(update);
+                totalUpdated++;
+            }
+            log.info("位置[{}] 设备数{},网格 {}x{},条带 lat[{}, {}]",
+                    entry.getKey(), n, cols, rows,
+                    BigDecimal.valueOf(bandBottom).setScale(6, RoundingMode.HALF_UP),
+                    BigDecimal.valueOf(bandTop).setScale(6, RoundingMode.HALF_UP));
+            bandIndex++;
+        }
+        log.info("初始化打点完成,共更新{}台设备坐标", totalUpdated);
+    }
 
 }

+ 9 - 0
platform-service/src/main/java/com/platform/service/sqarepartmanage/SpareTypeService.java

@@ -3,6 +3,7 @@ package com.platform.service.sqarepartmanage;
 import com.platform.dao.dto.sqarepartmanage.SpareTypeDTO;
 import com.platform.dao.entity.sqarepartmanage.SpareType;
 import com.platform.service.base.IBaseService;
+import org.springframework.web.multipart.MultipartFile;
 import java.util.List;
 
 /**
@@ -28,4 +29,12 @@ public interface SpareTypeService extends IBaseService<SpareType, SpareTypeDTO>
      * @return :
      */
     public List<SpareType> getModelListAndSbModelByDTO(SpareTypeDTO model);
+
+    /**
+     * 导入备件类型
+     *
+     * @param file Excel文件
+     * @return 导入结果描述
+     */
+    String importListByAdd(MultipartFile file);
 }

+ 43 - 8
platform-service/src/main/java/com/platform/service/sqarepartmanage/impl/SparePartInfoServiceImpl.java

@@ -1333,6 +1333,7 @@ public class SparePartInfoServiceImpl extends BaseServiceImpl<SparePartInfoMappe
     public String importListByUpdate(MultipartFile file) {
         int addNum = 3;
         List<SparePartInfo> updItems = new ArrayList<>();
+        List<SparePartInfo> insertItems = new ArrayList<>();
         LocalDateTime now = LocalDateTime.now();
         try {
             List<SparePartInfoVO> items = CustomExcelImportUtil.importSparePartInfoList(now, file.getInputStream());
@@ -1347,13 +1348,40 @@ public class SparePartInfoServiceImpl extends BaseServiceImpl<SparePartInfoMappe
                     if (StringUtils.isBlank(updInfo.getId())) {
                         throw new DeniedException("第" + addNum + "行的主键不为空");
                     }
-                    // 先删除当前备件的所有bom,再新增excel导入的bom
-                    SpareStore query = new SpareStore();
-                    query.setSpareId(updInfo.getId());
-                    List<SpareStore> deletelist = spareStoreMapper.select(query);
-                    if (!CollectionUtils.isEmpty(deletelist)) {
-                        deletelist.forEach(spareStoreMapper::deleteByPrimaryKey);
+                    // 检查ID对应的记录是否存在,不存在则通过备件编码no查找,仍找不到则新增
+                    boolean isNew = false;
+                    SparePartInfo existingById = mapper.selectByPrimaryKey(updInfo.getId());
+                    if (existingById == null) {
+                        if (StringUtils.isNotBlank(updInfo.getNo())) {
+                            SparePartInfo queryNo = new SparePartInfo();
+                            queryNo.setNo(updInfo.getNo());
+                            SparePartInfo existingByNo = mapper.selectOne(queryNo);
+                            if (existingByNo != null) {
+                                updInfo.setId(existingByNo.getId());
+                            } else {
+                                isNew = true;
+                                updInfo.setId(IdGeneratorUtils.getObjectId());
+                                updInfo.setCreatedTime(now);
+                                updInfo.setCreatedUserId(SecurityUtils.getUserInfo().getUserId());
+                            }
+                        } else {
+                            isNew = true;
+                            updInfo.setId(IdGeneratorUtils.getObjectId());
+                            updInfo.setCreatedTime(now);
+                            updInfo.setCreatedUserId(SecurityUtils.getUserInfo().getUserId());
+                        }
                     }
+                    if (!isNew) {
+                        // 先删除当前备件的所有bom,再新增excel导入的bom
+                        SpareStore query = new SpareStore();
+                        query.setSpareId(updInfo.getId());
+                        List<SpareStore> deletelist = spareStoreMapper.select(query);
+                        if (!CollectionUtils.isEmpty(deletelist)) {
+                            deletelist.forEach(spareStoreMapper::deleteByPrimaryKey);
+                        }
+                    }
+                    updInfo.setUpdateTime(now);
+                    updInfo.setUpdateUserId(SecurityUtils.getUserInfo().getUserId());
                     updInfo.setStoreId(null);
                     updInfo.setLevel(null);
                     updInfo.setTypeId(null);
@@ -1423,9 +1451,16 @@ public class SparePartInfoServiceImpl extends BaseServiceImpl<SparePartInfoMappe
                     if (StringUtils.isNotBlank(updInfo.getStoreId())) {
                         packageSpareStore(updInfo, spareStoreList);
                     }
-                    updItems.add(updInfo);
+                    if (isNew) {
+                        insertItems.add(updInfo);
+                    } else {
+                        updItems.add(updInfo);
+                    }
                     addNum++;
                 }
+                if (!CollectionUtils.isEmpty(insertItems)) {
+                    mapper.insertListforComplex(insertItems);
+                }
                 if (!CollectionUtils.isEmpty(updItems)) {
                     updItems.forEach(item -> mapper.updateByPrimaryKeySelective(item));
                 }
@@ -1433,7 +1468,7 @@ public class SparePartInfoServiceImpl extends BaseServiceImpl<SparePartInfoMappe
                     spareStoreMapper.insertListforComplex(spareStoreList);
                 }
             }
-            return "总计修改:" + updItems.size();
+            return "总计修改导入:" + (updItems.size() + insertItems.size()) + "条,更新" + updItems.size() + "条,新增" + insertItems.size() + "条";
         } catch (Exception e) {
             throw new BusinessException(e.getMessage());
         }

+ 76 - 0
platform-service/src/main/java/com/platform/service/sqarepartmanage/impl/SpareTypeServiceImpl.java

@@ -25,13 +25,17 @@ import com.platform.service.sb.SbModelService;
 import com.platform.service.sb.SbTypeService;
 import com.platform.service.sqarepartmanage.SpareTypeService;
 import com.platform.service.util.ExecuteSql;
+import com.platform.dao.util.CustomExcelImportUtil;
 import org.springframework.stereotype.Service;
 import com.platform.service.base.impl.BaseServiceImpl;
 import org.springframework.util.CollectionUtils;
+import org.springframework.web.multipart.MultipartFile;
 import tk.mybatis.mapper.weekend.Weekend;
 import tk.mybatis.mapper.weekend.WeekendCriteria;
 import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 
+import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -43,11 +47,13 @@ import java.util.List;
  * @Version Copyright (c) 2019,北京乾元坤和科技有限公司 All rights reserved.
  */
 @AllArgsConstructor
+@Slf4j
 @Service("spareTypeService")
 public class SpareTypeServiceImpl extends BaseServiceImpl<SpareTypeMapper, SpareType, SpareTypeDTO> implements SpareTypeService {
     private final SbTypeService sbTypeService;
     private final SbModelService sbModelService;
     private final SparePartInfoMapper sparePartInfoMapper;
+    private final SpareTypeMapper spareTypeMapper;
     @Override
     public int batchDelete(List<String> ids) {
         ids.forEach(id -> {
@@ -206,4 +212,74 @@ public class SpareTypeServiceImpl extends BaseServiceImpl<SpareTypeMapper, Spare
         return super.saveModelByDTO(model);
     }
 
+    @Override
+    public String importListByAdd(MultipartFile file) {
+        LocalDateTime now = LocalDateTime.now();
+        try {
+            List<SpareType> items = CustomExcelImportUtil.importSpareTypeList(file.getInputStream());
+            if (CollectionUtils.isEmpty(items)) {
+                return "导入数据为空";
+            }
+            List<SpareType> addList = new ArrayList<>();
+            List<SpareType> updateList = new ArrayList<>();
+            // 查询所有已有备件类型,用于父类别匹配和编码重复检查
+            List<SpareType> allTypes = spareTypeMapper.selectAll();
+            int lineNum = 2;
+            for (SpareType item : items) {
+                lineNum++;
+                // 编码不可为空
+                if (StringUtils.isBlank(item.getNo())) {
+                    throw new BusinessException("第" + lineNum + "行,编码不可为空");
+                }
+                // 名称不可为空
+                if (StringUtils.isBlank(item.getName())) {
+                    throw new BusinessException("第" + lineNum + "行,名称不可为空");
+                }
+                // 解析父类别
+                if (StringUtils.isNotBlank(item.getParentId())) {
+                    boolean parentFound = false;
+                    for (SpareType existType : allTypes) {
+                        if (item.getParentId().equals(existType.getNo())) {
+                            item.setParentId(existType.getId());
+                            parentFound = true;
+                            break;
+                        }
+                    }
+                    if (!parentFound) {
+                        throw new BusinessException("第" + lineNum + "行,父类别编码【" + item.getParentId() + "】不存在");
+                    }
+                } else {
+                    item.setParentId("");
+                }
+                // 检查编码是否已存在,已存在则覆盖更新
+                Weekend<SpareType> checkWeekend = new Weekend<>(SpareType.class);
+                checkWeekend.weekendCriteria().andEqualTo(SpareType::getNo, item.getNo());
+                List<SpareType> existing = spareTypeMapper.selectByExample(checkWeekend);
+                if (!CollectionUtils.isEmpty(existing)) {
+                    SpareType existRecord = existing.get(0);
+                    item.setId(existRecord.getId());
+                    item.setUpdateTime(now);
+                    updateList.add(item);
+                } else {
+                    item.setId(IdGeneratorUtils.getObjectId());
+                    item.setDelFlag(0);
+                    item.setCreatedTime(now);
+                    item.setUpdateTime(now);
+                    addList.add(item);
+                }
+            }
+            if (!CollectionUtils.isEmpty(addList)) {
+                mapper.insertListforComplex(addList);
+            }
+            if (!CollectionUtils.isEmpty(updateList)) {
+                for (SpareType upd : updateList) {
+                    mapper.updateByPrimaryKeySelective(upd);
+                }
+            }
+            return "总计导入:" + (addList.size() + updateList.size()) + "条,新增" + addList.size() + "条,覆盖更新" + updateList.size() + "条";
+        } catch (Exception e) {
+            throw new BusinessException(e.getMessage());
+        }
+    }
+
 }