feat(device): 1.添加设备导入导出功能并优化数据库关联查询
2.计量溯源性-标准物质清单导入导出
已添加2个文件
已修改9个文件
569 ■■■■■ 文件已修改
cnas-device/src/main/java/com/ruoyi/device/controller/DeviceController.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-device/src/main/java/com/ruoyi/device/excel/upload/DeviceImport.java 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-device/src/main/java/com/ruoyi/device/service/DeviceService.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceServiceImpl.java 186 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-device/src/main/resources/mapper/DeviceMapper.xml 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-require/src/main/java/com/ruoyi/require/controller/FeStandardSubstanceController.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-require/src/main/java/com/ruoyi/require/excel/FeStandardSubstanceImport.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-require/src/main/java/com/ruoyi/require/service/FeStandardSubstanceService.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-require/src/main/java/com/ruoyi/require/service/impl/FeStandardSubstanceServiceImpl.java 137 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/service/impl/InsOrderServiceImpl.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/resources/mapper/InsSampleMapper.xml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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 = "设备档案导出")
    @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));
    }
}
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;
}
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);
}
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解析失败,请使用下载的导入模板", 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();
    }
}
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>
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));
    }
}
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("档案编号*")
    private String fileNum;
    @ExcelProperty("存放位置*")
    private String position;
    @ExcelProperty("备注")
    private String remark;
}
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);
}
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解析失败,请使用下载的导入模板");
        }
        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, "档案编号"));
            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();
    }
}
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;
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