From de775139ac660c9d2e38ae54364f25e6724ca668 Mon Sep 17 00:00:00 2001
From: liding <756868258@qq.com>
Date: 星期五, 28 八月 2026 15:41:38 +0800
Subject: [PATCH] feat(device): 1.添加设备导入导出功能并优化数据库关联查询 2.计量溯源性-标准物质清单导入导出

---
 cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceServiceImpl.java                |  186 ++++++++++++++++++
 cnas-device/src/main/java/com/ruoyi/device/excel/upload/DeviceImport.java                     |   73 +++++++
 cnas-require/src/main/java/com/ruoyi/require/service/FeStandardSubstanceService.java          |    7 
 cnas-require/src/main/java/com/ruoyi/require/service/impl/FeStandardSubstanceServiceImpl.java |  137 +++++++++++++
 inspect-server/src/main/resources/mapper/InsSampleMapper.xml                                  |    1 
 cnas-device/src/main/resources/mapper/DeviceMapper.xml                                        |   18 
 cnas-device/src/main/java/com/ruoyi/device/service/DeviceService.java                         |    5 
 cnas-require/src/main/java/com/ruoyi/require/excel/FeStandardSubstanceImport.java             |   46 ++++
 inspect-server/src/main/java/com/ruoyi/inspect/service/impl/InsOrderServiceImpl.java          |   54 +++++
 cnas-device/src/main/java/com/ruoyi/device/controller/DeviceController.java                   |   29 ++
 cnas-require/src/main/java/com/ruoyi/require/controller/FeStandardSubstanceController.java    |   13 +
 11 files changed, 550 insertions(+), 19 deletions(-)

diff --git a/cnas-device/src/main/java/com/ruoyi/device/controller/DeviceController.java b/cnas-device/src/main/java/com/ruoyi/device/controller/DeviceController.java
index 9a58b5b..d1d30c5 100644
--- a/cnas-device/src/main/java/com/ruoyi/device/controller/DeviceController.java
+++ b/cnas-device/src/main/java/com/ruoyi/device/controller/DeviceController.java
@@ -26,7 +26,6 @@
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 import java.util.stream.Collectors;
 
 /**
@@ -51,7 +50,7 @@
 
     @ApiOperation(value = "鏌ヨ璁惧璇︽儏")
     @GetMapping("/getDeviceById")
-    public Result getDeviceById(Integer deviceId){
+    public Result getDeviceById(Integer deviceId) {
         return Result.success(deviceService.getById(deviceId));
     }
 
@@ -59,7 +58,7 @@
     //璁惧宸ュ叿鏄庣粏
     @ApiOperation(value = "鏌ヨ璁惧璇︽儏鍒楄〃")
     @GetMapping("/selectDeviceParameter")
-    public Result selectDeviceParameter(Page page, DeviceDto itemParameter, Boolean laboratoryNameIsNull){
+    public Result selectDeviceParameter(Page page, DeviceDto itemParameter, Boolean laboratoryNameIsNull) {
         return Result.success(deviceService.selectDeviceParameter(page, itemParameter, laboratoryNameIsNull));
     }
 
@@ -137,8 +136,8 @@
 
     @ApiOperation(value = "閫氳繃椤圭洰鑾峰彇璁惧鍒楄〃")
     @GetMapping("/selectDeviceByCategory")
-    public Result selectDeviceByCategory(String inspectionItem, String inspectionItemSubclass,String laboratory) {
-        return Result.success(deviceService.selectDeviceByCategory(inspectionItem, inspectionItemSubclass,laboratory));
+    public Result selectDeviceByCategory(String inspectionItem, String inspectionItemSubclass, String laboratory) {
+        return Result.success(deviceService.selectDeviceByCategory(inspectionItem, inspectionItemSubclass, laboratory));
     }
 
     @ApiOperation(value = "閫氳繃璁惧缂栧彿鑾峰彇璁惧鍒楄〃")
@@ -150,7 +149,7 @@
 
     @ApiOperation("/鏁伴噰-鏁版嵁閲囬泦")
     @PostMapping("/dataCollection")
-    public Result<?> dataAcquisition(HttpServletRequest request,@RequestBody DeviceCollectionDto dto) {
+    public Result<?> dataAcquisition(HttpServletRequest request, @RequestBody DeviceCollectionDto dto) {
         return deviceService.dataAcquisition(request, dto);
     }
 
@@ -205,13 +204,25 @@
 
     @ApiOperation(value = "璁惧妗f瀵煎嚭")
     @GetMapping("/exportDeviceFile")
-    public void exportDeviceFile(@RequestParam Integer deviceId, HttpServletResponse response){
-        deviceService.exportDeviceFile(deviceId,response);
+    public void exportDeviceFile(@RequestParam Integer deviceId, HttpServletResponse response) {
+        deviceService.exportDeviceFile(deviceId, response);
     }
 
     @ApiOperation(value = "浠櫒璁惧涓�瑙堣〃瀵煎嚭")
     @GetMapping("/exportEquipmentDetails")
-    public void exportEquipmentDetails(HttpServletResponse response){
+    public void exportEquipmentDetails(HttpServletResponse response) {
         deviceService.exportEquipmentDetails(response);
     }
+
+    @ApiOperation(value = "涓嬭浇璁惧瀵煎叆妯℃澘")
+    @GetMapping("/importTemplate")
+    public void importTemplate(HttpServletResponse response) {
+        deviceService.downloadImportTemplate(response);
+    }
+
+    @ApiOperation(value = "瀵煎叆璁惧")
+    @PostMapping("/importDevice")
+    public Result importDevice(MultipartFile file) {
+        return Result.success(deviceService.importDevice(file));
+    }
 }
diff --git a/cnas-device/src/main/java/com/ruoyi/device/excel/upload/DeviceImport.java b/cnas-device/src/main/java/com/ruoyi/device/excel/upload/DeviceImport.java
new file mode 100644
index 0000000..86904df
--- /dev/null
+++ b/cnas-device/src/main/java/com/ruoyi/device/excel/upload/DeviceImport.java
@@ -0,0 +1,73 @@
+package com.ruoyi.device.excel.upload;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import com.alibaba.excel.annotation.write.style.ColumnWidth;
+import lombok.Data;
+
+@Data
+@ColumnWidth(22)
+public class DeviceImport {
+
+    @ExcelProperty("浠櫒鍚嶇О*")
+    private String deviceName;
+
+    @ExcelProperty("瑙勬牸鍨嬪彿*")
+    private String specificationModel;
+
+    @ExcelProperty("绠$悊缂栧彿*")
+    private String managementNumber;
+
+    @ExcelProperty("鏍″噯鏈夋晥鏈�*(yyyy-MM-dd)")
+    private String activationDate;
+
+    @ExcelProperty("鎵�灞為儴闂�*")
+    private String laboratoryName;
+
+    @ExcelProperty("褰撳墠鐘舵��*")
+    private String deviceStatus;
+
+    @ExcelProperty("鏍″噯鍛ㄦ湡(鏈�)*")
+    private String calibrationDate;
+
+    @ExcelProperty("鑻辨枃鍚嶇О")
+    private String enDeviceName;
+
+    @ExcelProperty("鐢熶骇鍘傚")
+    private String manufacturer;
+
+    @ExcelProperty("鍑哄巶缂栧彿")
+    private String factoryNo;
+
+    @ExcelProperty("璧勪骇缂栫爜")
+    private String assetCode;
+
+    @ExcelProperty("浜у湴")
+    private String origin;
+
+    @ExcelProperty("璐疆鏃ユ湡(yyyy-MM-dd)")
+    private String acquisitionDate;
+
+    @ExcelProperty("璐熻矗浜�")
+    private String equipmentManagerName;
+
+    @ExcelProperty("瀛樻斁鐐�")
+    private String storagePoint;
+
+    @ExcelProperty("鎶�鏈寚鏍�")
+    private String technicalIndicators;
+
+    @ExcelProperty("鏍″噯鏈嶅姟鏈烘瀯")
+    private String calibrationServices;
+
+    @ExcelProperty("鏈�杩戞牎鍑嗘棩鏈�(yyyy-MM-dd)")
+    private String lastCalibrationDate;
+
+    @ExcelProperty("涓嬫鏍″噯鏃ユ湡(yyyy-MM-dd)")
+    private String nextCalibrationDate;
+
+    @ExcelProperty("璁惧绫诲瀷")
+    private String largeCategory;
+
+    @ExcelProperty("鍗曚环(涓囧厓)")
+    private String unitPrice;
+}
diff --git a/cnas-device/src/main/java/com/ruoyi/device/service/DeviceService.java b/cnas-device/src/main/java/com/ruoyi/device/service/DeviceService.java
index 72073e9..a7c4845 100644
--- a/cnas-device/src/main/java/com/ruoyi/device/service/DeviceService.java
+++ b/cnas-device/src/main/java/com/ruoyi/device/service/DeviceService.java
@@ -8,6 +8,7 @@
 import com.ruoyi.device.dto.DeviceCollectionDto;
 import com.ruoyi.device.dto.DeviceDto;
 import com.ruoyi.device.pojo.Device;
+import org.springframework.web.multipart.MultipartFile;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
@@ -54,4 +55,8 @@
      * @param response
      */
     void exportEquipmentDetails(HttpServletResponse response);
+
+    void downloadImportTemplate(HttpServletResponse response);
+
+    int importDevice(MultipartFile file);
 }
diff --git a/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceServiceImpl.java b/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceServiceImpl.java
index c743869..570a0f8 100644
--- a/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceServiceImpl.java
+++ b/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceServiceImpl.java
@@ -2,6 +2,7 @@
 
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.json.JSONObject;
+import com.alibaba.excel.EasyExcel;
 import com.alibaba.fastjson2.JSON;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
@@ -15,9 +16,12 @@
 import com.ruoyi.basic.mapper.StructureItemParameterMapper;
 import com.ruoyi.basic.pojo.StructureItemParameter;
 import com.ruoyi.common.core.domain.Result;
+import com.ruoyi.common.core.domain.entity.SysDictData;
+import com.ruoyi.common.core.domain.entity.SysDept;
 import com.ruoyi.common.core.domain.entity.User;
 import com.ruoyi.common.utils.QueryWrappers;
 import com.ruoyi.device.dto.*;
+import com.ruoyi.device.excel.upload.DeviceImport;
 import com.ruoyi.device.mapper.CollectBridgeMapper;
 import com.ruoyi.device.mapper.DeviceMaintenanceMapper;
 import com.ruoyi.device.mapper.DeviceMapper;
@@ -31,6 +35,8 @@
 import com.ruoyi.inspect.mapper.InsSampleMapper;
 import com.ruoyi.inspect.pojo.InsProduct;
 import com.ruoyi.inspect.util.HackLoopTableRenderPolicy;
+import com.ruoyi.system.mapper.SysDictDataMapper;
+import com.ruoyi.system.mapper.SysDeptMapper;
 import com.ruoyi.system.mapper.UserMapper;
 import lombok.AllArgsConstructor;
 import org.apache.logging.log4j.util.Strings;
@@ -42,6 +48,7 @@
 import javax.servlet.http.HttpServletResponse;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.math.BigDecimal;
 import java.net.URLEncoder;
 import java.text.SimpleDateFormat;
 import java.time.LocalDate;
@@ -60,6 +67,10 @@
     private DeviceMapper deviceMapper;
 
     private UserMapper userMapper;
+
+    private SysDeptMapper sysDeptMapper;
+
+    private SysDictDataMapper sysDictDataMapper;
 
     private StructureItemParameterMapper structureItemParameterMapper;
 
@@ -495,4 +506,179 @@
             throw new RuntimeException("瀵煎嚭澶辫触");
         }
     }
+
+    @Override
+    public void downloadImportTemplate(HttpServletResponse response) {
+        response.reset();
+        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+        response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
+        try {
+            String fileName = URLEncoder.encode("璁惧瀵煎叆妯℃澘.xlsx", "UTF-8");
+            response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
+            EasyExcel.write(response.getOutputStream(), DeviceImport.class)
+                    .sheet("璁惧瀵煎叆妯℃澘")
+                    .doWrite(Collections.emptyList());
+        } catch (Exception e) {
+            throw new RuntimeException("璁惧瀵煎叆妯℃澘涓嬭浇澶辫触", e);
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int importDevice(org.springframework.web.multipart.MultipartFile file) {
+        if (file == null || file.isEmpty()) {
+            throw new RuntimeException("璇烽�夋嫨闇�瑕佸鍏ョ殑Excel鏂囦欢");
+        }
+        String fileName = file.getOriginalFilename();
+        if (fileName == null || !fileName.toLowerCase(Locale.ROOT).endsWith(".xlsx")) {
+            throw new RuntimeException("浠呮敮鎸�.xlsx鏍煎紡鏂囦欢");
+        }
+
+        final List<DeviceImport> rows;
+        try {
+            rows = EasyExcel.read(file.getInputStream())
+                    .head(DeviceImport.class)
+                    .sheet()
+                    .doReadSync();
+        } catch (Exception e) {
+            throw new RuntimeException("Excel瑙f瀽澶辫触锛岃浣跨敤涓嬭浇鐨勫鍏ユā鏉�", e);
+        }
+        if (CollectionUtils.isEmpty(rows)) {
+            throw new RuntimeException("瀵煎叆鏂囦欢娌℃湁璁惧鏁版嵁");
+        }
+
+        Set<String> managementNumbers = new HashSet<>();
+        for (int i = 0; i < rows.size(); i++) {
+            String managementNumber = required(rows.get(i).getManagementNumber(), i + 2, "绠$悊缂栧彿");
+            if (!managementNumbers.add(managementNumber)) {
+                throw new RuntimeException("绗�" + (i + 2) + "琛岋細绠$悊缂栧彿" + managementNumber + "鍦ㄦ枃浠朵腑閲嶅");
+            }
+        }
+        Set<String> existingNumbers = baseMapper.selectList(Wrappers.<Device>lambdaQuery()
+                        .in(Device::getManagementNumber, managementNumbers)
+                        .select(Device::getManagementNumber))
+                .stream().map(Device::getManagementNumber).collect(Collectors.toSet());
+
+        Map<String, List<SysDept>> departmentMap = sysDeptMapper.selectDeptList(new SysDept()).stream()
+                .filter(item -> StringUtils.isNotBlank(item.getDeptName()))
+                .collect(Collectors.groupingBy(item -> item.getDeptName().trim()));
+        Map<String, List<User>> userMap = userMapper.selectList(Wrappers.<User>lambdaQuery()
+                        .isNotNull(User::getName).select(User::getId, User::getName))
+                .stream().collect(Collectors.groupingBy(item -> item.getName().trim()));
+        Map<String, String> statusMap = dictMap("device_status");
+        Map<String, String> typeMap = dictMap("device_type");
+
+        List<Device> devices = new ArrayList<>();
+        for (int i = 0; i < rows.size(); i++) {
+            int rowNumber = i + 2;
+            DeviceImport row = rows.get(i);
+            String managementNumber = required(row.getManagementNumber(), rowNumber, "绠$悊缂栧彿");
+            if (existingNumbers.contains(managementNumber)) {
+                throw new RuntimeException("绗�" + rowNumber + "琛岋細绠$悊缂栧彿" + managementNumber + "宸插瓨鍦�");
+            }
+
+            String laboratoryName = required(row.getLaboratoryName(), rowNumber, "鎵�灞為儴闂�");
+            List<SysDept> departments = departmentMap.get(laboratoryName);
+            if (CollectionUtils.isEmpty(departments) || departments.size() > 1) {
+                throw new RuntimeException("绗�" + rowNumber + "琛岋細鎵�灞為儴闂ㄢ��" + laboratoryName + "鈥濅笉瀛樺湪鎴栦笉鍞竴");
+            }
+
+            Device device = new Device();
+            device.setDeviceName(required(row.getDeviceName(), rowNumber, "浠櫒鍚嶇О"));
+            device.setSpecificationModel(required(row.getSpecificationModel(), rowNumber, "瑙勬牸鍨嬪彿"));
+            device.setManagementNumber(managementNumber);
+            device.setActivationDate(parseDate(row.getActivationDate(), rowNumber, "鏍″噯鏈夋晥鏈�", true));
+            device.setSubordinateDepartmentsId(departments.get(0).getDeptId().intValue());
+            device.setDeviceStatus(Integer.valueOf(dictValue(statusMap, row.getDeviceStatus(), rowNumber, "褰撳墠鐘舵��", true)));
+            device.setCalibrationDate(required(row.getCalibrationDate(), rowNumber, "鏍″噯鍛ㄦ湡(鏈�)"));
+            device.setEnDeviceName(trim(row.getEnDeviceName()));
+            device.setManufacturer(trim(row.getManufacturer()));
+            device.setFactoryNo(trim(row.getFactoryNo()));
+            device.setAssetCode(trim(row.getAssetCode()));
+            device.setOrigin(trim(row.getOrigin()));
+            device.setAcquisitionDate(parseDate(row.getAcquisitionDate(), rowNumber, "璐疆鏃ユ湡", false));
+            device.setStoragePoint(trim(row.getStoragePoint()));
+            device.setTechnicalIndicators(trim(row.getTechnicalIndicators()));
+            device.setCalibrationServices(trim(row.getCalibrationServices()));
+            device.setLastCalibrationDate(parseDate(row.getLastCalibrationDate(), rowNumber, "鏈�杩戞牎鍑嗘棩鏈�", false));
+            device.setNextCalibrationDate(parseDate(row.getNextCalibrationDate(), rowNumber, "涓嬫鏍″噯鏃ユ湡", false));
+            device.setLargeCategory(dictValue(typeMap, row.getLargeCategory(), rowNumber, "璁惧绫诲瀷", false));
+            device.setUnitPrice(parseDecimal(row.getUnitPrice(), rowNumber, "鍗曚环(涓囧厓)"));
+
+            String managerName = trim(row.getEquipmentManagerName());
+            if (StringUtils.isNotBlank(managerName)) {
+                List<User> users = userMap.get(managerName);
+                if (CollectionUtils.isEmpty(users) || users.size() > 1) {
+                    throw new RuntimeException("绗�" + rowNumber + "琛岋細璐熻矗浜衡��" + managerName + "鈥濅笉瀛樺湪鎴栦笉鍞竴");
+                }
+                device.setEquipmentManager(users.get(0).getId());
+            }
+            devices.add(device);
+        }
+        saveBatch(devices);
+        return devices.size();
+    }
+
+    private Map<String, String> dictMap(String dictType) {
+        return sysDictDataMapper.selectDictDataByType(dictType).stream()
+                .collect(Collectors.toMap(item -> item.getDictLabel().trim(), SysDictData::getDictValue, (first, second) -> first));
+    }
+
+    private String dictValue(Map<String, String> values, String input, int rowNumber, String column, boolean required) {
+        String text = trim(input);
+        if (StringUtils.isBlank(text)) {
+            if (required) {
+                throw new RuntimeException("绗�" + rowNumber + "琛岋細" + column + "涓嶈兘涓虹┖");
+            }
+            return null;
+        }
+        String value = values.get(text);
+        if (value == null && values.containsValue(text)) {
+            value = text;
+        }
+        if (value == null) {
+            throw new RuntimeException("绗�" + rowNumber + "琛岋細" + column + "鈥�" + text + "鈥濅笉鍦ㄧ郴缁熷瓧鍏镐腑");
+        }
+        return value;
+    }
+
+    private LocalDateTime parseDate(String input, int rowNumber, String column, boolean required) {
+        String text = trim(input);
+        if (StringUtils.isBlank(text)) {
+            if (required) {
+                throw new RuntimeException("绗�" + rowNumber + "琛岋細" + column + "涓嶈兘涓虹┖");
+            }
+            return null;
+        }
+        try {
+            return LocalDate.parse(text.substring(0, Math.min(text.length(), 10)).replace('/', '-'), DateTimeFormatter.ofPattern("yyyy-M-d"))
+                    .atStartOfDay();
+        } catch (Exception e) {
+            throw new RuntimeException("绗�" + rowNumber + "琛岋細" + column + "鏍煎紡搴斾负yyyy-MM-dd", e);
+        }
+    }
+
+    private BigDecimal parseDecimal(String input, int rowNumber, String column) {
+        String text = trim(input);
+        if (StringUtils.isBlank(text)) {
+            return null;
+        }
+        try {
+            return new BigDecimal(text);
+        } catch (NumberFormatException e) {
+            throw new RuntimeException("绗�" + rowNumber + "琛岋細" + column + "蹇呴』鏄暟瀛�", e);
+        }
+    }
+
+    private String required(String input, int rowNumber, String column) {
+        String value = trim(input);
+        if (StringUtils.isBlank(value)) {
+            throw new RuntimeException("绗�" + rowNumber + "琛岋細" + column + "涓嶈兘涓虹┖");
+        }
+        return value;
+    }
+
+    private String trim(String value) {
+        return value == null ? null : value.trim();
+    }
 }
diff --git a/cnas-device/src/main/resources/mapper/DeviceMapper.xml b/cnas-device/src/main/resources/mapper/DeviceMapper.xml
index 2c2436a..5460a17 100644
--- a/cnas-device/src/main/resources/mapper/DeviceMapper.xml
+++ b/cnas-device/src/main/resources/mapper/DeviceMapper.xml
@@ -98,22 +98,22 @@
         cmr.next_calibration_date as next_calibration_date_two,
         GROUP_CONCAT(distinct inspection_item SEPARATOR ';') AS insProductItem,
         u.name ,
-        l.laboratory_name
+        sd.dept_name AS laboratory_name
         FROM
         device d
         LEFT JOIN `user` u ON u.id = d.equipment_manager
-        LEFT JOIN laboratory l ON l.id = d.subordinate_departments_id
+        LEFT JOIN sys_dept sd ON sd.dept_id = d.subordinate_departments_id AND sd.del_flag = '0'
         LEFT JOIN device_metric_record cmr ON d.id = cmr.device_id
         LEFT JOIN structure_item_parameter sip ON FIND_IN_SET(sip.id, d.ins_product_ids)
         <where>
             <if test="laboratoryNameIsNull != null and laboratoryNameIsNull == true">
-                l.laboratory_name is null
+                sd.dept_name is null
             </if>
             AND cmr.type = 'calibrate' or cmr.type is null or cmr.type = ''
         </where>
         GROUP BY
         d.id,
-        u.name, l.laboratory_name
+        u.name, sd.dept_name
         ) a
         <if test="ew.customSqlSegment != null and ew.customSqlSegment != ''">
             ${ew.customSqlSegment}
@@ -149,14 +149,14 @@
     <select id="treeDevice" resultMap="treeDeviceDto">
         select
         d.id,
-        case when l.laboratory_name is null || l.laboratory_name ='' then '鍏朵粬' else l.laboratory_name end as laboratory_name,
+        case when sd.dept_name is null || sd.dept_name ='' then '鍏朵粬' else sd.dept_name end as laboratory_name,
         case when d.storage_point is null || d.storage_point ='' then '鍏朵粬' else d.storage_point end as storage_point,
         d.device_name,
         d.management_number,
         null as value,
         d.activation_date
         from device d
-        LEFT JOIN laboratory l ON l.id = d.subordinate_departments_id
+        LEFT JOIN sys_dept sd ON sd.dept_id = d.subordinate_departments_id AND sd.del_flag = '0'
         <where>
             <if test="deviceName!=null and deviceName!=''">
                 and device_name like concat('%',#{deviceName},'%')
@@ -167,18 +167,18 @@
     <select id="selectDeviceByCode" resultType="com.ruoyi.device.dto.DeviceDto">
         SELECT d.*,
                GROUP_CONCAT(DISTINCT inspection_item SEPARATOR ';') AS ins_product_item,
-               l.laboratory_name,
+               sd.dept_name AS laboratory_name,
                u1.name                                                 equipmentManager_name,
                u2.name                                                 authorized_person_name
         FROM device d
                  LEFT JOIN structure_item_parameter sip ON FIND_IN_SET(sip.id, d.ins_product_ids)
-                 LEFT JOIN laboratory l ON d.subordinate_departments_id = l.id
+                 LEFT JOIN sys_dept sd ON d.subordinate_departments_id = sd.dept_id AND sd.del_flag = '0'
                  LEFT JOIN user u1 ON u1.id = d.equipment_manager
                  LEFT JOIN user u2 ON u2.id = d.authorized_person
         WHERE d.id = #{id}
 
         GROUP BY d.id,
-                 l.laboratory_name,
+                 sd.dept_name,
                  u1.name,
                  u2.name
     </select>
diff --git a/cnas-require/src/main/java/com/ruoyi/require/controller/FeStandardSubstanceController.java b/cnas-require/src/main/java/com/ruoyi/require/controller/FeStandardSubstanceController.java
index 912fb7b..3951970 100644
--- a/cnas-require/src/main/java/com/ruoyi/require/controller/FeStandardSubstanceController.java
+++ b/cnas-require/src/main/java/com/ruoyi/require/controller/FeStandardSubstanceController.java
@@ -8,6 +8,7 @@
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.ruoyi.common.core.domain.Result;
 import com.ruoyi.require.excel.FeStandardSubstanceExcel;
+import org.springframework.web.multipart.MultipartFile;
 import com.ruoyi.require.pojo.FeStandardSubstance;
 import com.ruoyi.require.service.FeStandardSubstanceService;
 import io.swagger.annotations.Api;
@@ -77,4 +78,16 @@
                 .sheet("sheet")
                 .doWrite(studentList);
     }
+
+    @ApiOperation(value = "涓嬭浇鏍囧噯鐗╄川瀵煎叆妯℃澘")
+    @GetMapping("/importTemplate")
+    public void importTemplate(HttpServletResponse response) {
+        feStandardSubstanceService.downloadImportTemplate(response);
+    }
+
+    @ApiOperation(value = "瀵煎叆鏍囧噯鐗╄川娓呭崟")
+    @PostMapping("/importStandardSubstance")
+    public Result importStandardSubstance(MultipartFile file) {
+        return Result.success(feStandardSubstanceService.importStandardSubstance(file));
+    }
 }
diff --git a/cnas-require/src/main/java/com/ruoyi/require/excel/FeStandardSubstanceImport.java b/cnas-require/src/main/java/com/ruoyi/require/excel/FeStandardSubstanceImport.java
new file mode 100644
index 0000000..1588fd8
--- /dev/null
+++ b/cnas-require/src/main/java/com/ruoyi/require/excel/FeStandardSubstanceImport.java
@@ -0,0 +1,46 @@
+package com.ruoyi.require.excel;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import com.alibaba.excel.annotation.write.style.ColumnWidth;
+import lombok.Data;
+
+@Data
+@ColumnWidth(22)
+public class FeStandardSubstanceImport {
+
+    @ExcelProperty("鏍囧噯鐗╄川鍚嶇О*")
+    private String name;
+
+    @ExcelProperty("瑙勬牸鍨嬪彿*")
+    private String model;
+
+    @ExcelProperty("鐢熶骇鍘傚*")
+    private String factoryManufacturer;
+
+    @ExcelProperty("鍑哄巶缂栧彿*")
+    private String factoryNum;
+
+    @ExcelProperty("绠$悊缂栧彿*")
+    private String manageNum;
+
+    @ExcelProperty("涓嶇‘瀹氬害*")
+    private String uncertainty;
+
+    @ExcelProperty("鏁伴噺*")
+    private String quantity;
+
+    @ExcelProperty("璐疆鏃ユ湡*(yyyy-MM-dd)")
+    private String acquisitionDate;
+
+    @ExcelProperty("鏈夋晥鏈�*(yyyy-MM-dd)")
+    private String effectiveDate;
+
+    @ExcelProperty("妗f缂栧彿*")
+    private String fileNum;
+
+    @ExcelProperty("瀛樻斁浣嶇疆*")
+    private String position;
+
+    @ExcelProperty("澶囨敞")
+    private String remark;
+}
diff --git a/cnas-require/src/main/java/com/ruoyi/require/service/FeStandardSubstanceService.java b/cnas-require/src/main/java/com/ruoyi/require/service/FeStandardSubstanceService.java
index 19b2d9a..fd3d973 100644
--- a/cnas-require/src/main/java/com/ruoyi/require/service/FeStandardSubstanceService.java
+++ b/cnas-require/src/main/java/com/ruoyi/require/service/FeStandardSubstanceService.java
@@ -4,6 +4,9 @@
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.ruoyi.require.pojo.FeStandardSubstance;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.http.HttpServletResponse;
 
 /**
  * <p>
@@ -16,4 +19,8 @@
 public interface FeStandardSubstanceService extends IService<FeStandardSubstance> {
 
     IPage<FeStandardSubstance> page(Page page, FeStandardSubstance feStandardSubstance);
+
+    void downloadImportTemplate(HttpServletResponse response);
+
+    int importStandardSubstance(MultipartFile file);
 }
diff --git a/cnas-require/src/main/java/com/ruoyi/require/service/impl/FeStandardSubstanceServiceImpl.java b/cnas-require/src/main/java/com/ruoyi/require/service/impl/FeStandardSubstanceServiceImpl.java
index 2dd1a9c..d4aac72 100644
--- a/cnas-require/src/main/java/com/ruoyi/require/service/impl/FeStandardSubstanceServiceImpl.java
+++ b/cnas-require/src/main/java/com/ruoyi/require/service/impl/FeStandardSubstanceServiceImpl.java
@@ -1,12 +1,33 @@
 package com.ruoyi.require.service.impl;
 
+import com.alibaba.excel.EasyExcel;
 import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.framework.exception.ErrorException;
+import com.ruoyi.require.excel.FeStandardSubstanceImport;
 import com.ruoyi.require.mapper.FeStandardSubstanceMapper;
 import com.ruoyi.require.pojo.FeStandardSubstance;
 import com.ruoyi.require.service.FeStandardSubstanceService;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.http.HttpServletResponse;
+import java.net.URLEncoder;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * <p>
@@ -23,4 +44,120 @@
     public IPage<FeStandardSubstance> page(Page page, FeStandardSubstance feStandardSubstance) {
         return this.baseMapper.getPage(page,feStandardSubstance);
     }
+
+    @Override
+    public void downloadImportTemplate(HttpServletResponse response) {
+        response.reset();
+        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+        response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
+        try {
+            String fileName = URLEncoder.encode("鏍囧噯鐗╄川瀵煎叆妯℃澘.xlsx", "UTF-8");
+            response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
+            EasyExcel.write(response.getOutputStream(), FeStandardSubstanceImport.class)
+                    .sheet("鏍囧噯鐗╄川瀵煎叆妯℃澘")
+                    .doWrite(Collections.emptyList());
+        } catch (Exception e) {
+            throw new RuntimeException("鏍囧噯鐗╄川瀵煎叆妯℃澘涓嬭浇澶辫触", e);
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int importStandardSubstance(MultipartFile file) {
+        if (file == null || file.isEmpty()) {
+            throw new ErrorException("璇烽�夋嫨闇�瑕佸鍏ョ殑Excel鏂囦欢");
+        }
+        String fileName = file.getOriginalFilename();
+        if (fileName == null || !fileName.toLowerCase(Locale.ROOT).endsWith(".xlsx")) {
+            throw new ErrorException("浠呮敮鎸�.xlsx鏍煎紡鏂囦欢");
+        }
+
+        final List<FeStandardSubstanceImport> rows;
+        try {
+            rows = EasyExcel.read(file.getInputStream())
+                    .head(FeStandardSubstanceImport.class)
+                    .sheet()
+                    .doReadSync();
+        } catch (Exception e) {
+            throw new ErrorException("Excel瑙f瀽澶辫触锛岃浣跨敤涓嬭浇鐨勫鍏ユā鏉�");
+        }
+        if (CollectionUtils.isEmpty(rows)) {
+            throw new ErrorException("瀵煎叆鏂囦欢娌℃湁鏍囧噯鐗╄川鏁版嵁");
+        }
+
+        Set<String> manageNumbers = new HashSet<>();
+        for (int i = 0; i < rows.size(); i++) {
+            String manageNum = required(rows.get(i).getManageNum(), i + 2, "绠$悊缂栧彿");
+            if (!manageNumbers.add(manageNum)) {
+                throw new ErrorException("绗�" + (i + 2) + "琛岋細绠$悊缂栧彿" + manageNum + "鍦ㄦ枃浠朵腑閲嶅");
+            }
+        }
+        Set<String> existingNumbers = baseMapper.selectList(Wrappers.<FeStandardSubstance>lambdaQuery()
+                        .in(FeStandardSubstance::getManageNum, manageNumbers)
+                        .select(FeStandardSubstance::getManageNum))
+                .stream().map(FeStandardSubstance::getManageNum).collect(Collectors.toSet());
+
+        List<FeStandardSubstance> substances = new ArrayList<>();
+        for (int i = 0; i < rows.size(); i++) {
+            int rowNumber = i + 2;
+            FeStandardSubstanceImport row = rows.get(i);
+            String manageNum = required(row.getManageNum(), rowNumber, "绠$悊缂栧彿");
+            if (existingNumbers.contains(manageNum)) {
+                throw new ErrorException("绗�" + rowNumber + "琛岋細绠$悊缂栧彿" + manageNum + "宸插瓨鍦�");
+            }
+
+            FeStandardSubstance substance = new FeStandardSubstance();
+            substance.setName(required(row.getName(), rowNumber, "鏍囧噯鐗╄川鍚嶇О"));
+            substance.setModel(required(row.getModel(), rowNumber, "瑙勬牸鍨嬪彿"));
+            substance.setFactoryManufacturer(required(row.getFactoryManufacturer(), rowNumber, "鐢熶骇鍘傚"));
+            substance.setFactoryNum(required(row.getFactoryNum(), rowNumber, "鍑哄巶缂栧彿"));
+            substance.setManageNum(manageNum);
+            substance.setUncertainty(required(row.getUncertainty(), rowNumber, "涓嶇‘瀹氬害"));
+            substance.setQuantity(parseQuantity(row.getQuantity(), rowNumber));
+            substance.setAcquisitionDate(parseDate(row.getAcquisitionDate(), rowNumber, "璐疆鏃ユ湡"));
+            substance.setEffectiveDate(parseDate(row.getEffectiveDate(), rowNumber, "鏈夋晥鏈�"));
+            substance.setFileNum(required(row.getFileNum(), rowNumber, "妗f缂栧彿"));
+            substance.setPosition(required(row.getPosition(), rowNumber, "瀛樻斁浣嶇疆"));
+            substance.setRemark(trim(row.getRemark()));
+            substance.setState(0);
+            substances.add(substance);
+        }
+        saveBatch(substances);
+        return substances.size();
+    }
+
+    private Long parseQuantity(String input, int rowNumber) {
+        String value = required(input, rowNumber, "鏁伴噺");
+        try {
+            long quantity = Long.parseLong(value);
+            if (quantity <= 0) {
+                throw new NumberFormatException();
+            }
+            return quantity;
+        } catch (NumberFormatException e) {
+            throw new ErrorException("绗�" + rowNumber + "琛岋細鏁伴噺蹇呴』鏄ぇ浜�0鐨勬暣鏁�");
+        }
+    }
+
+    private LocalDateTime parseDate(String input, int rowNumber, String column) {
+        String value = required(input, rowNumber, column);
+        try {
+            return LocalDate.parse(value.substring(0, Math.min(value.length(), 10)).replace('/', '-'), DateTimeFormatter.ofPattern("yyyy-M-d"))
+                    .atStartOfDay();
+        } catch (Exception e) {
+            throw new ErrorException("绗�" + rowNumber + "琛岋細" + column + "鏍煎紡搴斾负yyyy-MM-dd");
+        }
+    }
+
+    private String required(String input, int rowNumber, String column) {
+        String value = trim(input);
+        if (StringUtils.isBlank(value)) {
+            throw new ErrorException("绗�" + rowNumber + "琛岋細" + column + "涓嶈兘涓虹┖");
+        }
+        return value;
+    }
+
+    private String trim(String value) {
+        return value == null ? null : value.trim();
+    }
 }
diff --git a/inspect-server/src/main/java/com/ruoyi/inspect/service/impl/InsOrderServiceImpl.java b/inspect-server/src/main/java/com/ruoyi/inspect/service/impl/InsOrderServiceImpl.java
index 1315240..cb1f287 100644
--- a/inspect-server/src/main/java/com/ruoyi/inspect/service/impl/InsOrderServiceImpl.java
+++ b/inspect-server/src/main/java/com/ruoyi/inspect/service/impl/InsOrderServiceImpl.java
@@ -874,6 +874,19 @@
     @Override
     @Transactional(rollbackFor = Exception.class)
     public Boolean updateInsOrder(InsOrderUpdateDto insOrderUpdateDto) {
+        InsOrder currentOrder = insOrderMapper.selectById(insOrderUpdateDto.getInsOrder().getId());
+        if (currentOrder == null || !Objects.equals(currentOrder.getState(), 2)) {
+            throw new ErrorException("鍙湁閫�鍥炵殑妫�楠屽崟鍙互淇敼");
+        }
+        boolean hasSelectedProduct = CollectionUtils.isNotEmpty(insOrderUpdateDto.getSampleProduct())
+                && insOrderUpdateDto.getSampleProduct().stream()
+                .filter(sample -> CollectionUtils.isNotEmpty(sample.getInsProduct()))
+                .flatMap(sample -> sample.getInsProduct().stream())
+                .anyMatch(product -> Objects.equals(product.getState(), 1));
+        if (!hasSelectedProduct) {
+            throw new ErrorException("璇疯嚦灏戦�夋嫨涓�涓楠岄」");
+        }
+
         // 淇敼璁㈠崟
         insOrderUpdateDto.getInsOrder().setState(0);
         insOrderUpdateDto.getInsOrder().setTell("");
@@ -881,11 +894,50 @@
 
         // 淇敼妫�楠岄」
         for (SampleProductDto sampleProductDto : insOrderUpdateDto.getSampleProduct()) {
+            Long sampleCount = insSampleMapper.selectCount(Wrappers.<InsSample>lambdaQuery()
+                    .eq(InsSample::getId, sampleProductDto.getId())
+                    .eq(InsSample::getInsOrderId, insOrderUpdateDto.getInsOrder().getId()));
+            if (sampleCount == 0) {
+                throw new ErrorException("妫�楠屾牱鍝佹暟鎹紓甯�");
+            }
+
             insSampleService.update(Wrappers.<InsSample>lambdaUpdate()
                     .eq(InsSample::getId, sampleProductDto.getId())
                     .set(InsSample::getSpecialStandardMethod, sampleProductDto.getSpecialStandardMethod()));
 
-            insProductService.updateBatchById(sampleProductDto.getInsProduct());
+            List<InsProduct> products = sampleProductDto.getInsProduct();
+            if (CollectionUtils.isEmpty(products)) {
+                continue;
+            }
+
+            Set<Integer> productIds = insProductMapper.selectList(Wrappers.<InsProduct>lambdaQuery()
+                            .eq(InsProduct::getInsSampleId, sampleProductDto.getId()))
+                    .stream()
+                    .map(InsProduct::getId)
+                    .collect(Collectors.toSet());
+            if (products.stream().anyMatch(product -> product.getId() != null && !productIds.contains(product.getId()))) {
+                throw new ErrorException("妫�楠岄」鏁版嵁寮傚父");
+            }
+
+            List<Integer> removedIds = products.stream()
+                    .filter(product -> product.getId() != null && !Objects.equals(product.getState(), 1))
+                    .map(InsProduct::getId)
+                    .collect(Collectors.toList());
+            if (CollectionUtils.isNotEmpty(removedIds)) {
+                insProductService.removeByIds(removedIds);
+            }
+
+            List<InsProduct> existingProducts = products.stream()
+                    .filter(product -> product.getId() != null && Objects.equals(product.getState(), 1))
+                    .collect(Collectors.toList());
+            if (CollectionUtils.isNotEmpty(existingProducts)) {
+                insProductService.updateBatchById(existingProducts);
+            }
+
+            List<InsProduct> newProducts = products.stream()
+                    .filter(product -> product.getId() == null && Objects.equals(product.getState(), 1))
+                    .collect(Collectors.toList());
+            addInsProductMethod(sampleProductDto.getId(), newProducts);
         }
 
         return true;
diff --git a/inspect-server/src/main/resources/mapper/InsSampleMapper.xml b/inspect-server/src/main/resources/mapper/InsSampleMapper.xml
index 4698777..ac53df7 100644
--- a/inspect-server/src/main/resources/mapper/InsSampleMapper.xml
+++ b/inspect-server/src/main/resources/mapper/InsSampleMapper.xml
@@ -518,6 +518,7 @@
                ipr.equip_value,
                ip.method_s,
                ip.radius,
+               ip.structure_item_parameter_id,
                isa.special_standard_method
         from ins_sample isa
                  left join ins_product ip on isa.id = ip.ins_sample_id

--
Gitblit v1.9.3