已添加6个文件
已修改25个文件
879 ■■■■■ 文件已修改
basic-server/src/main/java/com/ruoyi/basic/dto/StandardTreeTemplateBindingVo.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/java/com/ruoyi/basic/dto/TemplateBindingItemDto.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/java/com/ruoyi/basic/mapper/StandardTreeTemplateDeviceMapper.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/java/com/ruoyi/basic/pojo/StandardTemplate.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/java/com/ruoyi/basic/pojo/StandardTreeTemplateDevice.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/java/com/ruoyi/basic/service/impl/StandardMethodServiceImpl.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/java/com/ruoyi/basic/service/impl/StandardTemplateServiceImpl.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/java/com/ruoyi/basic/service/impl/StandardTreeServiceImpl.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/resources/mapper/StandardTemplateMapper.xml 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/resources/mapper/StandardTreeTemplateDeviceMapper.xml 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/controller/InsOrderRatesController.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/controller/InsReportController.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/dto/TemplateRateDto.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/mapper/InsOrderMapper.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/mapper/InsOrderRatesMapper.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/pojo/InsReport.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/service/InsOrderPlanService.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/service/InsOrderRatesService.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/service/InsReportService.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/service/impl/InsOrderPlanServiceImpl.java 365 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/service/impl/InsOrderRatesServiceImpl.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/service/impl/InsOrderServiceImpl.java 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/service/impl/InsReportServiceImpl.java 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/util/ReportSignaturePictureUtils.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/resources/mapper/InsOrderMapper.xml 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/resources/mapper/InsOrderRatesMapper.xml 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/resources/mapper/InsReportMapper.xml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/resources/static/detection-report-template-v2.docx 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/resources/static/inspection-report-template-v2.docx 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/CustomController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/Custom.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/java/com/ruoyi/basic/dto/StandardTreeTemplateBindingVo.java
@@ -1,12 +1,23 @@
package com.ruoyi.basic.dto;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class StandardTreeTemplateBindingVo {
    @ApiModelProperty("模板ID")
    private Integer templateId;
    @ApiModelProperty("模板名称")
    private String name;
    @ApiModelProperty("检验项目")
    private String number;
    @ApiModelProperty("备注")
    private String remark;
    @ApiModelProperty("排序")
    private Integer sort;
    @ApiModelProperty("设备ID集合")
    private List<Integer> deviceIds = new ArrayList<>();
}
basic-server/src/main/java/com/ruoyi/basic/dto/TemplateBindingItemDto.java
@@ -1,9 +1,17 @@
package com.ruoyi.basic.dto;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class TemplateBindingItemDto {
    @ApiModelProperty("模板ID")
    private Integer templateId;
    @ApiModelProperty("排序")
    private Integer sort;
    @ApiModelProperty("设备ID集合")
    private List<Integer> deviceIds = new ArrayList<>();
}
basic-server/src/main/java/com/ruoyi/basic/mapper/StandardTreeTemplateDeviceMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,13 @@
package com.ruoyi.basic.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.basic.pojo.StandardTreeTemplateDevice;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/** æ ‡å‡†æ¨¡æ¿é»˜è®¤è®¾å¤‡ Mapper。 */
public interface StandardTreeTemplateDeviceMapper extends BaseMapper<StandardTreeTemplateDevice> {
    List<Integer> selectExistingDeviceIds(@Param("deviceIds") List<Integer> deviceIds);
}
basic-server/src/main/java/com/ruoyi/basic/pojo/StandardTemplate.java
@@ -6,6 +6,7 @@
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
@@ -73,4 +74,10 @@
     */
    @ApiModelProperty("模板编号")
    private String number;
    /**
     * ä»·æ ¼(元)
     */
    @ApiModelProperty("ä»·æ ¼(元)")
    private BigDecimal price;
}
basic-server/src/main/java/com/ruoyi/basic/pojo/StandardTreeTemplateDevice.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,39 @@
package com.ruoyi.basic.pojo;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;
/** æ ‡å‡†ä¸‹åŽŸå§‹è®°å½•æ¨¡æ¿ä¸Žé»˜è®¤è®¾å¤‡çš„ç»‘å®šå…³ç³»ã€‚ */
@Data
@TableName("standard_tree_template_device")
public class StandardTreeTemplateDevice {
    @TableId(type = IdType.AUTO)
    private Integer id;
    private Integer standardTreeId;
    private Integer templateId;
    private Integer deviceId;
    private Integer sort;
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Integer updateUser;
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}
basic-server/src/main/java/com/ruoyi/basic/service/impl/StandardMethodServiceImpl.java
@@ -244,8 +244,8 @@
    @Override
    public StandardMethodAttachment uploadAttachment(Integer standardMethodId, MultipartFile file) {
        if (standardMethodMapper.selectById(standardMethodId) == null) throw new BaseException("标准不存在");
        if (file == null || file.isEmpty() || file.getSize() > 10 * 1024 * 1024)
            throw new BaseException("附件不能为空且不能超过10MB");
        if (file == null || file.isEmpty() || file.getSize() > 200L * 1024 * 1024)
            throw new BaseException("附件不能为空且不能超过200MB");
        String originalName = file.getOriginalFilename();
        if (originalName == null || !originalName.matches("(?i).+\\.(jpg|jpeg|png|gif|doc|docx|xls|xlsx|ppt|pptx|pdf|zip|rar)$"))
            throw new BaseException("不支持的附件格式");
@@ -391,7 +391,6 @@
    }
}
basic-server/src/main/java/com/ruoyi/basic/service/impl/StandardTemplateServiceImpl.java
@@ -50,6 +50,9 @@
                    StandardTemplate::getNumber);
            standardTemplate.setNumber(giveCode);
        }
        if (standardTemplate.getPrice() == null) {
            standardTemplate.setPrice(java.math.BigDecimal.ZERO);
        }
        if (StringUtils.isNotBlank(standardTemplate.getThing())) {
            standardTemplate.setThing(GZipUtil.compress(standardTemplate.getThing()));
        } else {
@@ -60,6 +63,9 @@
    @Override
    public int upStandardTemplate(StandardTemplate standardTemplate) {
        if (standardTemplate.getPrice() == null) {
            standardTemplate.setPrice(java.math.BigDecimal.ZERO);
        }
        if (StringUtils.isNotBlank(standardTemplate.getThing())) {
            standardTemplate.setThing(GZipUtil.compress(standardTemplate.getThing()));
        } else {
@@ -128,6 +134,9 @@
        // æŸ¥è¯¢æ—§æ¨¡æ¿
        StandardTemplate standardTemplate = baseMapper.selectById(newTemplate.getId());
        newTemplate.setThing(standardTemplate.getThing());
        if (newTemplate.getPrice() == null) {
            newTemplate.setPrice(java.math.BigDecimal.ZERO);
        }
        if (StringUtils.isBlank(newTemplate.getNumber())) {
            String giveCode = numberGenerator.generateNumberWithPrefix(5,
basic-server/src/main/java/com/ruoyi/basic/service/impl/StandardTreeServiceImpl.java
@@ -18,6 +18,7 @@
import com.ruoyi.basic.mapper.StandardProductListMapper;
import com.ruoyi.basic.mapper.StandardTreeMapper;
import com.ruoyi.basic.mapper.StandardTreeTemplateBindingMapper;
import com.ruoyi.basic.mapper.StandardTreeTemplateDeviceMapper;
import com.ruoyi.basic.mapper.StructureItemParameterMapper;
import com.ruoyi.basic.mapper.StructureTestObjectMapper;
import com.ruoyi.basic.pojo.Laboratory;
@@ -25,6 +26,7 @@
import com.ruoyi.basic.pojo.StandardTree;
import com.ruoyi.basic.pojo.StandardTemplate;
import com.ruoyi.basic.pojo.StandardTreeTemplateBinding;
import com.ruoyi.basic.pojo.StandardTreeTemplateDevice;
import com.ruoyi.basic.pojo.StructureItemParameter;
import com.ruoyi.basic.pojo.StructureTestObject;
import com.ruoyi.basic.service.StandardTreeService;
@@ -45,6 +47,7 @@
import java.util.HashSet;
import java.util.Comparator;
import java.util.Set;
import java.util.stream.Collectors;
/**
 * @author Administrator
@@ -68,6 +71,8 @@
    private LaboratoryMapper laboratoryMapper;
    private StandardTreeTemplateBindingMapper standardTreeTemplateBindingMapper;
    private StandardTreeTemplateDeviceMapper standardTreeTemplateDeviceMapper;
    private StandardTemplateService standardTemplateService;
@@ -204,6 +209,8 @@
        }
        standardTreeTemplateBindingMapper.delete(Wrappers.<StandardTreeTemplateBinding>lambdaQuery()
                .eq(StandardTreeTemplateBinding::getStandardTreeId, standardTree.getId()));
        standardTreeTemplateDeviceMapper.delete(Wrappers.<StandardTreeTemplateDevice>lambdaQuery()
                .eq(StandardTreeTemplateDevice::getStandardTreeId, standardTree.getId()));
        standardTreeMapper.deleteById(standardTree.getId());
        standardProductListMapper.delete(Wrappers.<StandardProductList>lambdaQuery()
                .eq(StandardProductList::getLaboratory, industry)
@@ -384,6 +391,15 @@
                Wrappers.<StandardTreeTemplateBinding>lambdaQuery()
                        .eq(StandardTreeTemplateBinding::getStandardTreeId, standardTreeId)
                        .orderByAsc(StandardTreeTemplateBinding::getSort, StandardTreeTemplateBinding::getId));
        Map<Integer, List<Integer>> deviceIdsByTemplate = standardTreeTemplateDeviceMapper.selectList(
                        Wrappers.<StandardTreeTemplateDevice>lambdaQuery()
                                .eq(StandardTreeTemplateDevice::getStandardTreeId, standardTreeId)
                                .orderByAsc(StandardTreeTemplateDevice::getSort,
                                        StandardTreeTemplateDevice::getId))
                .stream().collect(Collectors.groupingBy(
                        StandardTreeTemplateDevice::getTemplateId,
                        LinkedHashMap::new,
                        Collectors.mapping(StandardTreeTemplateDevice::getDeviceId, Collectors.toList())));
        List<StandardTreeTemplateBindingVo> result = new ArrayList<>();
        for (StandardTreeTemplateBinding binding : bindings) {
            StandardTemplate template = standardTemplateService.getById(binding.getTemplateId());
@@ -396,6 +412,7 @@
            vo.setNumber(template.getNumber());
            vo.setRemark(template.getRemark());
            vo.setSort(binding.getSort());
            vo.setDeviceIds(deviceIdsByTemplate.getOrDefault(template.getId(), new ArrayList<>()));
            result.add(vo);
        }
        return result;
@@ -410,6 +427,7 @@
        ensureTwoLevelStandard(dto.getStandardTreeId());
        List<TemplateBindingItemDto> templates = dto.getTemplates() == null ? new ArrayList<>() : dto.getTemplates();
        Set<Integer> templateIds = new HashSet<>();
        Set<Integer> allDeviceIds = new HashSet<>();
        List<StandardTreeTemplateBinding> bindings = new ArrayList<>();
        int index = 0;
        for (TemplateBindingItemDto item : templates) {
@@ -425,12 +443,38 @@
            binding.setTemplateId(item.getTemplateId());
            binding.setSort(item.getSort() == null ? index : item.getSort());
            bindings.add(binding);
            if (item.getDeviceIds() != null) {
                allDeviceIds.addAll(item.getDeviceIds().stream()
                        .filter(Objects::nonNull).collect(Collectors.toSet()));
            }
            index++;
        }
        if (!allDeviceIds.isEmpty()) {
            List<Integer> requestedDeviceIds = new ArrayList<>(allDeviceIds);
            if (standardTreeTemplateDeviceMapper.selectExistingDeviceIds(requestedDeviceIds).size()
                    != requestedDeviceIds.size()) {
                throw new BaseException("所选设备不存在,请刷新设备列表后重试");
            }
        }
        standardTreeTemplateDeviceMapper.delete(Wrappers.<StandardTreeTemplateDevice>lambdaQuery()
                .eq(StandardTreeTemplateDevice::getStandardTreeId, dto.getStandardTreeId()));
        standardTreeTemplateBindingMapper.delete(Wrappers.<StandardTreeTemplateBinding>lambdaQuery()
                .eq(StandardTreeTemplateBinding::getStandardTreeId, dto.getStandardTreeId()));
        for (StandardTreeTemplateBinding binding : bindings) {
        for (int bindingIndex = 0; bindingIndex < bindings.size(); bindingIndex++) {
            StandardTreeTemplateBinding binding = bindings.get(bindingIndex);
            standardTreeTemplateBindingMapper.insert(binding);
            TemplateBindingItemDto item = templates.get(bindingIndex);
            List<Integer> deviceIds = item.getDeviceIds() == null ? new ArrayList<>()
                    : item.getDeviceIds().stream().filter(Objects::nonNull).distinct()
                            .collect(Collectors.toList());
            for (int deviceIndex = 0; deviceIndex < deviceIds.size(); deviceIndex++) {
                StandardTreeTemplateDevice relation = new StandardTreeTemplateDevice();
                relation.setStandardTreeId(dto.getStandardTreeId());
                relation.setTemplateId(item.getTemplateId());
                relation.setDeviceId(deviceIds.get(deviceIndex));
                relation.setSort(deviceIndex);
                standardTreeTemplateDeviceMapper.insert(relation);
            }
        }
        return true;
    }
basic-server/src/main/resources/mapper/StandardTemplateMapper.xml
@@ -17,7 +17,7 @@
    <select id="selectStandardTemplatePageList" resultType="com.ruoyi.basic.pojo.StandardTemplate">
        select * from (
        select st.id, st.name,st.number, st.remark, u2.name create_user_name, u3.name update_user_name, st.create_time, st.update_time
        select st.id, st.name,st.number, st.price, st.remark, u2.name create_user_name, u3.name update_user_name, st.create_time, st.update_time
        from standard_template st
        left join user u2 on u2.id = st.create_user
        left join user u3 on u3.id = st.update_user
basic-server/src/main/resources/mapper/StandardTreeTemplateDeviceMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.ruoyi.basic.mapper.StandardTreeTemplateDeviceMapper">
    <select id="selectExistingDeviceIds" resultType="java.lang.Integer">
        select id from device where id in
        <foreach collection='deviceIds' item='deviceId' open='(' separator=',' close=')'>
            #{deviceId}
        </foreach>
    </select>
</mapper>
inspect-server/src/main/java/com/ruoyi/inspect/controller/InsOrderRatesController.java
@@ -2,11 +2,9 @@
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.core.domain.Result;
import com.ruoyi.inspect.dto.SampleOrderDto;
import com.ruoyi.inspect.pojo.InsOrderRates;
import com.ruoyi.inspect.service.InsOrderRatesService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@@ -51,8 +49,7 @@
    @ApiOperation(value = "查询订单费用详情")
    @GetMapping("/selectRatesDetail")
    public Result selectRatesDetail(Page page, Integer insOrderId) {
        return Result.success(insOrderRatesService.page(page, Wrappers.<InsOrderRates>lambdaQuery()
                .eq(InsOrderRates::getInsOrderId, insOrderId)));
        return Result.success(insOrderRatesService.selectTemplateRatesDetail(page, insOrderId));
    }
}
inspect-server/src/main/java/com/ruoyi/inspect/controller/InsReportController.java
@@ -10,6 +10,7 @@
import com.ruoyi.common.core.domain.Result;
import com.ruoyi.framework.exception.ErrorException;
import com.ruoyi.inspect.dto.ReportPageDto;
import com.ruoyi.inspect.service.InsOrderPlanService;
import com.ruoyi.inspect.service.InsReportService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@@ -37,6 +38,9 @@
    @Resource
    private InsReportService insReportService;
    @Resource
    private InsOrderPlanService insOrderPlanService;
    @Value("${wordUrl}")
    private String wordUrl;
@@ -150,12 +154,21 @@
        return Result.success(insReportService.downAll(ids));
    }
    @ApiOperation(value = "报告下载")
    @ApiOperation(value = "报告下载(reportType:0检测报告,1检验报告)")
    @GetMapping("/download")
    @PreAuthorize("@ss.hasPermi('business:reportPreparation')")
    @PersonalScope(permsName = "business:reportPreparation", objectName = ReportPageDto.class, paramName = "createOrderUser")
    public void download(Integer id, ReportPageDto reportPageDto, HttpServletResponse response) {
        insReportService.download(id, reportPageDto.getCreateOrderUser(), response);
    public void download(Integer id, Integer reportType, ReportPageDto reportPageDto, HttpServletResponse response) {
        insReportService.download(id, reportType, reportPageDto.getCreateOrderUser(), response);
    }
    @ApiOperation(value = "重新生成报告")
    @PostMapping("/regenerate")
//    @PreAuthorize("@ss.hasPermi('business:reportPreparation')")
    @PersonalScope(permsName = "business:reportPreparation", objectName = ReportPageDto.class, paramName = "createOrderUser")
    public Result regenerate(@RequestBody ReportPageDto reportPageDto) {
        insOrderPlanService.regenerateReport(reportPageDto.getId(), reportPageDto.getCreateOrderUser());
        return Result.success();
    }
    @ApiOperation(value = "报告批量上传")
inspect-server/src/main/java/com/ruoyi/inspect/dto/TemplateRateDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
package com.ruoyi.inspect.dto;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class TemplateRateDto {
    @ApiModelProperty("检验项目")
    private String number;
    @ApiModelProperty("模板名称")
    private String name;
    @ApiModelProperty("ä»·æ ¼(元)")
    private BigDecimal price;
}
inspect-server/src/main/java/com/ruoyi/inspect/mapper/InsOrderMapper.java
@@ -42,6 +42,10 @@
    String getStandardMethodCode(@Param("id") Integer id);
    String getStandardMethodReportText(@Param("id") Integer id);
    String getStandardMethodReportTextByCode(@Param("code") String code);
    int updateStatus(@Param("id") Integer id);
    String selLaboratoryCode(@Param("laboratory") String laboratory);
inspect-server/src/main/java/com/ruoyi/inspect/mapper/InsOrderRatesMapper.java
@@ -5,6 +5,7 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.inspect.dto.SampleOrderDto;
import com.ruoyi.inspect.dto.TemplateRateDto;
import com.ruoyi.inspect.pojo.InsOrderRates;
import org.apache.ibatis.annotations.Param;
@@ -21,5 +22,11 @@
     * @return
     */
    IPage<SampleOrderDto> selectInsOrderRates(Page page, @Param("ew") QueryWrapper<SampleOrderDto> ew);
    /**
     * æŸ¥è¯¢è®¢å•费用详情(模板价格)
     * @return
     */
    IPage<TemplateRateDto> selectTemplateRatesDetail(Page page, @Param("insOrderId") Integer insOrderId);
}
inspect-server/src/main/java/com/ruoyi/inspect/pojo/InsReport.java
@@ -41,6 +41,12 @@
    private String url;
    /**
     * ç³»ç»Ÿç”Ÿæˆçš„æ£€éªŒæŠ¥å‘Šåœ°å€ï¼›ä¸Ž url å¯¹åº”的检测报告共用同一条审批记录。
     */
    @ApiModelProperty("检验报告地址")
    private String inspectionUrl;
    /**
     * æ‰‹åŠ¨ä¸Šä¼ æŠ¥å‘Šåœ°å€
     */
    @ApiModelProperty("创建时间")
inspect-server/src/main/java/com/ruoyi/inspect/service/InsOrderPlanService.java
@@ -59,6 +59,11 @@
    int rawMaterialVerifyPlan(VerifyPlanDto verifyPlanDto);
    /**
     * æŒ‰å½“前检验数据重新生成尚未批准的系统报告。
     */
    void regenerateReport(Integer reportId, Integer createOrderUser);
    /**
     * ç”µç¼†é…ç½®. æŸ¥çœ‹æ ‡è¯†
     * @param id
     * @param laboratory
inspect-server/src/main/java/com/ruoyi/inspect/service/InsOrderRatesService.java
@@ -4,6 +4,7 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.inspect.dto.SampleOrderDto;
import com.ruoyi.inspect.dto.TemplateRateDto;
import com.ruoyi.inspect.pojo.InsOrderRates;
/**
@@ -21,5 +22,13 @@
     * @return
     */
    IPage<SampleOrderDto> selectInsOrderRates(Page page, SampleOrderDto sampleOrderDto);
    /**
     * æŸ¥è¯¢è®¢å•费用详情(模板价格)
     * @param page
     * @param insOrderId
     * @return
     */
    IPage<TemplateRateDto> selectTemplateRatesDetail(Page page, Integer insOrderId);
}
inspect-server/src/main/java/com/ruoyi/inspect/service/InsReportService.java
@@ -41,8 +41,10 @@
    String downAll(String ids);
    /** å•份报告 Word ä¸‹è½½ï¼ŒcreateOrderUser ç”¨äºŽå¤ç”¨æŠ¥å‘Šç¼–制的数据范围。 */
    void download(Integer id, Integer createOrderUser, HttpServletResponse response);
    /**
     * å•份报告 Word ä¸‹è½½ï¼ŒreportType:0检测报告,1检验报告;createOrderUser ç”¨äºŽå¤ç”¨æŠ¥å‘Šç¼–制的数据范围。
     */
    void download(Integer id, Integer reportType, Integer createOrderUser, HttpServletResponse response);
    int upAll(MultipartFile file) throws IOException;
inspect-server/src/main/java/com/ruoyi/inspect/service/impl/InsOrderPlanServiceImpl.java
@@ -46,6 +46,7 @@
import com.ruoyi.inspect.service.*;
import com.ruoyi.inspect.util.HackLoopTableRenderPolicy;
import com.ruoyi.inspect.util.PreserveReportPicturePlaceholderHandler;
import com.ruoyi.inspect.util.ReportSignaturePictureUtils;
import com.ruoyi.inspect.vo.InsOrderPlanTaskSwitchVo;
import com.ruoyi.inspect.vo.InsOrderPlanVO;
import com.ruoyi.inspect.vo.InsSampleUserVO;
@@ -178,8 +179,12 @@
        String userName = null;
        Integer userId = null;
        if (ObjectUtil.isNotEmpty(insOrderPlanDTO.getUserId())) {
            userId = SecurityUtils.getUserId().intValue();
            userName = userMapper.selectById(userId).getName();
            Integer currentUserId = SecurityUtils.getUserId().intValue();
            // ç®¡ç†å‘˜å¯ä»¥æŸ¥çœ‹å¹¶å¤æ ¸å…¨éƒ¨å¾…复核任务,普通用户仍只查看分配给自己的任务。
            if (!SecurityUtils.isAdmin(currentUserId.longValue())) {
                userId = currentUserId;
                userName = userMapper.selectById(userId).getName();
            }
            insOrderPlanDTO.setUserId(null);
        }
        Integer isCheck = insOrderPlanDTO.getIsCheck();
@@ -745,18 +750,9 @@
        } catch (Exception ignored) {
            thing = compressedThing;
        }
        JSONArray cellData = JSON.parseObject(thing).getJSONArray("data").getJSONObject(0).getJSONArray("celldata");
        JSONObject sheet = JSON.parseObject(thing).getJSONArray("data").getJSONObject(0);
        JSONArray cellData = sheet.getJSONArray("celldata");
        NavigableMap<Integer, List<String>> result = new TreeMap<>();
        Integer inspectionResultColumn = null;
        for (Object item : cellData) {
            JSONObject cell = JSON.parseObject(JSON.toJSONString(item));
            Integer column = cell.getInteger("c");
            JSONObject value = cell.getJSONObject("v");
            String displayValue = value == null ? null : value.getString("v");
            if (inspectionResultColumn == null && "检验结果".equals(StringUtils.trim(displayValue))) {
                inspectionResultColumn = column;
            }
        }
        for (Object item : cellData) {
            JSONObject cell = JSON.parseObject(JSON.toJSONString(item));
            JSONObject value = cell.getJSONObject("v");
@@ -767,16 +763,119 @@
            Integer row = cell.getInteger("r");
            Integer column = cell.getInteger("c");
            if (row != null && column != null) {
                // â€œå¯¼å‡ºå€¼â€æ ‡åœ¨æ£€æµ‹é¡¹ç›®æ ‡é¢˜å•元格时,真正导出的值是同一行“检验结果”列的单元格。
                String coordinate = row + "-" + column;
                String resultCoordinate = row + "-" + inspectionResultColumn;
                if (inspectionResultColumn != null && !Objects.equals(column, inspectionResultColumn)) {
                    coordinate = resultCoordinate;
                }
                // æ‰¹æ³¨å•元格可能横向合并,右侧结果单元格也可能纵向合并;最终读取结果区域的左上角存储坐标。
                String coordinate = getRightAdjacentCoordinate(sheet, row, column);
                result.computeIfAbsent(row, ignored -> new ArrayList<>()).add(coordinate);
            }
        }
        return result;
    }
    /**
     * è¿”回指定单元格(或其所在合并区域)右侧紧邻列的下标。
     * Luckysheet çš„合并信息通常保存在 config.merge;部分历史模板只在单元格 v.mc ä¸­ä¿ç•™ï¼Œ
     * å› æ­¤ä¸¤ç§ç»“构都要兼容。
     */
    private int getRightAdjacentColumn(JSONObject sheet, int row, int column) {
        int rightEdge = column;
        JSONObject config = sheet == null ? null : sheet.getJSONObject("config");
        JSONObject merges = config == null ? null : config.getJSONObject("merge");
        if (merges != null) {
            for (Object rawMerge : merges.values()) {
                rightEdge = extendMergedRightEdge(rightEdge, row, column, rawMerge);
            }
        }
        JSONArray cellData = sheet == null ? null : sheet.getJSONArray("celldata");
        if (cellData != null) {
            for (Object rawCell : cellData) {
                JSONObject cell = JSON.parseObject(JSON.toJSONString(rawCell));
                JSONObject value = cell == null ? null : cell.getJSONObject("v");
                JSONObject merge = value == null ? null : value.getJSONObject("mc");
                rightEdge = extendMergedRightEdge(rightEdge, row, column, merge);
            }
        }
        return rightEdge + 1;
    }
    private String getRightAdjacentCoordinate(JSONObject sheet, int row, int column) {
        int adjacentColumn = getRightAdjacentColumn(sheet, row, column);
        int[] anchor = findMergedAnchor(sheet, row, adjacentColumn);
        return anchor[0] + "-" + anchor[1];
    }
    /**
     * Luckysheet åªåœ¨åˆå¹¶åŒºåŸŸå·¦ä¸Šè§’保存实际值。若目标单元格位于合并区域内部,
     * å°†å…¶è½¬æ¢ä¸ºè¯¥åŒºåŸŸçš„左上角坐标;未合并则返回原坐标。
     */
    private int[] findMergedAnchor(JSONObject sheet, int row, int column) {
        JSONObject config = sheet == null ? null : sheet.getJSONObject("config");
        JSONObject merges = config == null ? null : config.getJSONObject("merge");
        if (merges != null) {
            for (Object rawMerge : merges.values()) {
                int[] anchor = findMergedAnchor(row, column, rawMerge);
                if (anchor != null) {
                    return anchor;
                }
            }
        }
        JSONArray cellData = sheet == null ? null : sheet.getJSONArray("celldata");
        if (cellData != null) {
            for (Object rawCell : cellData) {
                JSONObject cell = JSON.parseObject(JSON.toJSONString(rawCell));
                JSONObject value = cell == null ? null : cell.getJSONObject("v");
                int[] anchor = findMergedAnchor(row, column,
                        value == null ? null : value.getJSONObject("mc"));
                if (anchor != null) {
                    return anchor;
                }
            }
        }
        return new int[]{row, column};
    }
    private int[] findMergedAnchor(int row, int column, Object rawMerge) {
        if (rawMerge == null) {
            return null;
        }
        JSONObject merge = rawMerge instanceof JSONObject
                ? (JSONObject) rawMerge : JSON.parseObject(JSON.toJSONString(rawMerge));
        Integer startRow = merge.getInteger("r");
        Integer startColumn = merge.getInteger("c");
        Integer rowSpan = merge.getInteger("rs");
        Integer columnSpan = merge.getInteger("cs");
        if (startRow == null || startColumn == null || rowSpan == null || columnSpan == null
                || rowSpan < 1 || columnSpan < 1) {
            return null;
        }
        if (row >= startRow && row < startRow + rowSpan
                && column >= startColumn && column < startColumn + columnSpan) {
            return new int[]{startRow, startColumn};
        }
        return null;
    }
    private int extendMergedRightEdge(int currentRightEdge, int row, int column, Object rawMerge) {
        if (rawMerge == null) {
            return currentRightEdge;
        }
        JSONObject merge = rawMerge instanceof JSONObject
                ? (JSONObject) rawMerge : JSON.parseObject(JSON.toJSONString(rawMerge));
        Integer startRow = merge.getInteger("r");
        Integer startColumn = merge.getInteger("c");
        Integer rowSpan = merge.getInteger("rs");
        Integer columnSpan = merge.getInteger("cs");
        if (startRow == null || startColumn == null || rowSpan == null || columnSpan == null
                || rowSpan < 1 || columnSpan < 1) {
            return currentRightEdge;
        }
        int endRow = startRow + rowSpan - 1;
        int endColumn = startColumn + columnSpan - 1;
        if (row >= startRow && row <= endRow && column >= startColumn && column <= endColumn) {
            return Math.max(currentRightEdge, endColumn);
        }
        return currentRightEdge;
    }
    /**
@@ -794,7 +893,7 @@
    }
    /**
     * ç›´ç»‘模板订单只校验模板中标记为“导出值”的单元格,不使用检验项状态或 last_value åˆ¤å®šã€‚
     * ç›´ç»‘模板订单只校验每个“导出值”批注所在区域右侧紧邻单元格,不使用检验项状态或 last_value åˆ¤å®šã€‚
     */
    private List<String> getMissingTemplateExportValues(Integer orderId) {
        List<InsOrderStandardTemplate> snapshots = insOrderStandardTemplateService.list(
@@ -1119,13 +1218,12 @@
                .last("FOR UPDATE"));
        boolean isInspectionReport = Objects.equals(INSPECTION_REPORT, reportType);
        registerInsResults = isInspectionReport && Boolean.TRUE.equals(registerInsResults);
        // 1. åˆ¤æ–­æ˜¯å¦æœ‰é‡å¤ç¼–号, æœ‰é‡å¤ç¼–号做提醒
        // 1. å§”托编号全局唯一,提交复核时再次校验,防止并发或历史数据绕过生成校验。
        Long codeCount = insOrderMapper.selectCount(Wrappers.<InsOrder>lambdaQuery()
                .ne(InsOrder::getState, -1)
                .ne(InsOrder::getIfsInventoryId, order.getIfsInventoryId())
                .ne(InsOrder::getId, orderId)
                .eq(InsOrder::getEntrustCode, order.getEntrustCode()));
        if (codeCount > 0) {
            throw new ErrorException("当前编号有重复, è¯·å…ˆåŽ»ä¿®æ”¹é‡å¤ç¼–å·");
            throw new ErrorException("样品/委托单已存在,请确认样品编号");
        }
        // 2. åˆ¤æ–­è¯¥è®¢å•是否是第一次生产(后续报告生成只取第一次提交时间)
@@ -1532,6 +1630,10 @@
            refreshed.setRecordValues(templateRecordValuesMap.get(templateId));
            refreshed.setTemperature(templateTemperatureMap.get(templateId));
            refreshed.setHumidity(templateHumidityMap.get(templateId));
            // åˆ·æ–°æ¨¡æ¿ç»“构时必须保留检验阶段填写的业务数据,避免进入复核后回显为空。
            refreshed.setDetectionTime(oldSnapshot == null ? null : oldSnapshot.getDetectionTime());
            refreshed.setDetectionPlace(oldSnapshot == null ? null : oldSnapshot.getDetectionPlace());
            refreshed.setInsResult(oldSnapshot == null ? null : oldSnapshot.getInsResult());
            Integer templateSort = templateSortMap.get(templateId);
            refreshed.setSort(templateSort == null ? nextTemplateSort++ : templateSort);
            refreshedSnapshots.add(refreshed);
@@ -1586,12 +1688,13 @@
        InsReport existingReport = insReportMapper.selectOne(Wrappers.<InsReport>lambdaQuery()
                .eq(InsReport::getInsOrderId, orderId)
                .last("limit 1"));
        boolean directTemplateOrder = isDirectTemplateOrder(orderId);
        if (currentState != null && Objects.equals(currentState.getInsState(), 5)
                && existingReport != null && StringUtils.isNotBlank(existingReport.getUrl())) {
                && existingReport != null && StringUtils.isNotBlank(existingReport.getUrl())
                && (!directTemplateOrder || StringUtils.isNotBlank(existingReport.getInspectionUrl()))) {
            // é˜²æ­¢å¤æ ¸é¡µé¢é‡å¤ç‚¹å‡»æˆ–网络重试重复生成报告、重复发起报告审批。
            return 1;
        }
        boolean directTemplateOrder = isDirectTemplateOrder(orderId);
        if (directTemplateOrder && Objects.equals(type, 1)
                && !Objects.equals(dto.getInsResult(), 0) && !Objects.equals(dto.getInsResult(), 1)) {
            throw new ErrorException("请选择检验单结论");
@@ -1640,15 +1743,22 @@
                    .eq(InsOrder::getId, orderId)
                    .set(InsOrder::getInsState, 5));
            // æ£€éªŒäººå‘˜æäº¤å¤æ ¸æ—¶ä¸å†ç”ŸæˆæŠ¥å‘Šï¼›ä»…在全部试验室复核通过后生成检测报告。
            // æ£€éªŒäººå‘˜æäº¤å¤æ ¸æ—¶ä¸ç”ŸæˆæŠ¥å‘Šï¼›å…¨éƒ¨è¯•验室复核通过后生成报告。
            if (report == null || StringUtils.isBlank(report.getUrl())) {
                this.generateReport(orderId, DETECTION_REPORT, writeUserId);
                report = insReportMapper.selectOne(Wrappers.<InsReport>lambdaQuery()
                        .eq(InsReport::getInsOrderId, orderId)
                        .last("limit 1"));
            }
            // ç›´ç»‘模板订单同时生成检验报告,两份文件共用同一条审批记录。
            if (directTemplateOrder && (report == null || StringUtils.isBlank(report.getInspectionUrl()))) {
                this.generateReport(orderId, INSPECTION_REPORT, writeUserId);
            }
            report = insReportMapper.selectOne(Wrappers.<InsReport>lambdaQuery()
                    .eq(InsReport::getInsOrderId, orderId)
                    .last("limit 1"));
            if (report == null || StringUtils.isBlank(report.getUrl())) {
                throw new ErrorException("检测报告生成失败");
            }
            if (directTemplateOrder && StringUtils.isBlank(report.getInspectionUrl())) {
                throw new ErrorException("检验报告生成失败");
            }
            if (report.getTempUrlPdf() == null) {
                String tempUrlPdf = this.wordToPdfTemp(report.getUrl().replace("/word", wordUrl));
@@ -1798,6 +1908,101 @@
            throw new ErrorException("找不到当前检验任务的检验人,无法生成检测报告");
        }
        return inspector.getUserId();
    }
    /**
     * é‡æ–°ç”Ÿæˆå°šæœªæ‰¹å‡†çš„系统报告,并恢复已存在的编制、审核签名。
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void regenerateReport(Integer reportId, Integer createOrderUser) {
        if (reportId == null) {
            throw new ErrorException("报告不能为空");
        }
        InsReport accessibleReport = insReportMapper.selectDownloadableReport(reportId, createOrderUser);
        if (accessibleReport == null) {
            throw new ErrorException("报告不存在、尚未生成或无权操作");
        }
        // æ•°æ®åº“行锁避免同一报告被多人同时重新生成并覆盖。
        InsReport report = insReportMapper.selectOne(Wrappers.<InsReport>lambdaQuery()
                .eq(InsReport::getId, reportId)
                .last("FOR UPDATE"));
        if (report == null) {
            throw new ErrorException("报告不存在");
        }
        if (Objects.equals(report.getIsRatify(), 1)) {
            throw new ErrorException("已批准报告不能重新生成");
        }
        if (StringUtils.isNotBlank(report.getUrlS())) {
            throw new ErrorException("当前报告已手动上传,请先还原为系统报告后再重新生成");
        }
        if (report.getInsOrderId() == null) {
            throw new ErrorException("报告未关联检验单,无法重新生成");
        }
        Integer reportType = report.getReportType() == null ? DETECTION_REPORT : report.getReportType();
        Integer writeUserId = report.getWriteUserId() == null
                ? SecurityUtils.getUserId().intValue() : report.getWriteUserId();
        boolean directTemplateOrder = isDirectTemplateOrder(report.getInsOrderId());
        if (directTemplateOrder) {
            generateReport(report.getInsOrderId(), DETECTION_REPORT, writeUserId);
            generateReport(report.getInsOrderId(), INSPECTION_REPORT, writeUserId);
        } else {
            generateReport(report.getInsOrderId(), reportType, writeUserId);
        }
        InsReport regenerated = insReportMapper.selectById(reportId);
        if (regenerated == null || StringUtils.isBlank(regenerated.getUrl())) {
            throw new ErrorException("报告重新生成失败");
        }
        if (directTemplateOrder && StringUtils.isBlank(regenerated.getInspectionUrl())) {
            throw new ErrorException("检验报告重新生成失败");
        }
        String reportPath = regenerated.getUrl().replace("/word", wordUrl);
        Map<String, Object> marks = buildExistingReportMarks(report);
        if (!marks.isEmpty()) {
            insReportService.wordInsertUrl(marks, reportPath);
            if (StringUtils.isNotBlank(regenerated.getInspectionUrl())) {
                insReportService.wordInsertUrl(marks,
                        regenerated.getInspectionUrl().replace("/word", wordUrl));
            }
        }
        String tempPdfName = wordToPdfTemp(reportPath);
        if (StringUtils.isBlank(tempPdfName)) {
            throw new ErrorException("报告预览文件生成失败");
        }
        // generateReport ä¼šå†™å…¥å½“前时间;重新生成不应改变原审批记录时间。
        insReportMapper.update(null, Wrappers.<InsReport>lambdaUpdate()
                .eq(InsReport::getId, reportId)
                .set(InsReport::getWriteTime, report.getWriteTime())
                .set(InsReport::getTempUrlPdf, "/word/" + tempPdfName));
    }
    private Map<String, Object> buildExistingReportMarks(InsReport report) {
        Map<String, Object> marks = new HashMap<>();
        boolean submitted = Objects.equals(report.getState(), 1)
                || report.getIsExamine() != null
                || report.getExamineTime() != null;
        if (submitted) {
            User writer = getReportSigner(report.getWriteUserId(), "编制人");
            marks.put("writeUrl", ReportSignaturePictureUtils.create(imgUrl, writer.getSignatureUrl()));
            marks.put("writeDateUrl", Pictures.ofStream(DateImageUtil.createDateImage(report.getWriteTime())).create());
            marks.put("insUrl", ReportSignaturePictureUtils.create(imgUrl, writer.getSignatureUrl()));
        }
        if (Objects.equals(report.getIsExamine(), 1)) {
            User examiner = getReportSigner(report.getExamineUserId(), "审核人");
            marks.put("examineUrl", ReportSignaturePictureUtils.create(imgUrl, examiner.getSignatureUrl()));
            marks.put("examineDateUrl", Pictures.ofStream(DateImageUtil.createDateImage(report.getExamineTime())).create());
        }
        return marks;
    }
    private User getReportSigner(Integer userId, String roleName) {
        User user = userId == null ? null : userMapper.selectById(userId);
        if (user == null || StringUtils.isBlank(user.getSignatureUrl())) {
            throw new ErrorException("找不到" + roleName + "的签名");
        }
        return user;
    }
    /**
@@ -2331,13 +2536,18 @@
        enterFactoryReport.setLotBatchNo(ifsInventoryQuantity.getUpdateBatchNo());
        // æ£€æµ‹ä¾æ®
        Set<String> standardMethod = new HashSet<>();
        Set<String> standardMethod = new LinkedHashSet<>();
        StringBuilder standardMethod2 = new StringBuilder();
        standardMethod.add(baseMapper.getStandardMethodCode(insSample.getStandardMethodListId()));
        String reportStandardMethod = baseMapper.getStandardMethodReportText(insSample.getStandardMethodListId());
        if (StringUtils.isNotBlank(reportStandardMethod)) {
            standardMethod.add(reportStandardMethod);
        }
        for (String s : standardMethod) {
            standardMethod2.append("、").append(s);
        }
        standardMethod2.replace(0, 1, "");
        if (standardMethod2.length() > 0) {
            standardMethod2.deleteCharAt(0);
        }
        // æ ·å“ç±»åž‹
        String orderType = iSysDictTypeService.selectLabelByDict(DictDataConstants.CHECK_TYPE, insOrder.getOrderType());
@@ -3009,7 +3219,7 @@
        insReport.setReportType(reportType);
        boolean showResult = Objects.equals(reportType, INSPECTION_REPORT);
        List<Map<String, Object>> tables = new ArrayList<>();
        Set<String> standardMethod = new HashSet<>();
        Set<String> standardMethod = new LinkedHashSet<>();
        Set<String> deviceSet = new HashSet<>();
        Set<String> models = new HashSet<>();
        // æŸ¥è¯¢æ£€éªŒé¡¹æ•°é‡
@@ -3022,11 +3232,13 @@
        /*基础报告(根据绘制的原始记录模版形成)*/
        samples.forEach(a -> {
            models.add(a.getModel());
            String standardMethodCode = baseMapper.getStandardMethodCode(a.getStandardMethodListId());
            if (StrUtil.isNotBlank(a.getSpecialStandardMethod())) {
                standardMethodCode = standardMethodCode + "+" + a.getSpecialStandardMethod();
            String standardMethodCode = baseMapper.getStandardMethodReportText(a.getStandardMethodListId());
            if (StrUtil.isNotBlank(standardMethodCode)) {
                if (StrUtil.isNotBlank(a.getSpecialStandardMethod())) {
                    standardMethodCode = standardMethodCode + "+" + a.getSpecialStandardMethod();
                }
                standardMethod.add(standardMethodCode);
            }
            standardMethod.add(standardMethodCode);
            for (InsProduct b : a.getInsProduct()) {
                if (b.getInsProductResult() != null) {
                    List<JSONObject> jsonObjects = JSON.parseArray(b.getInsProductResult().getEquipValue(), JSONObject.class);
@@ -3038,9 +3250,9 @@
                    }
                }
            }
            // æ–°ç‰ˆæ£€æµ‹æŠ¥å‘Šç›´æŽ¥ä½¿ç”¨å›ºå®šå››åˆ—结果行,不再构造旧版可变列 TableRenderData。
            // æ£€éªŒæŠ¥å‘Šä»æ²¿ç”¨ä¸‹æ–¹åŽŸæœ‰çš„å¤§è¡¨æ ¼é€»è¾‘ã€‚
            if (!showResult) {
            // ç›´ç»‘模板的检测报告、检验报告都使用新版固定结果行,不再进入旧版检验项表格逻辑。
            // ç›´ç»‘模板中的承载记录没有旧检验项字段,进入旧逻辑会在格式化时产生空指针。
            if (!showResult || directTemplateOrder) {
                return;
            }
            // æ”¶æ ·æ—¥æœŸ
@@ -3372,9 +3584,14 @@
        if (firstInspectDate == null) {
            firstInspectDate = insOrder.getSendTime();
        }
        if (firstInspectDate == null) {
            firstInspectDate = submitTime;
        }
        String insTime = firstInspectDate.format(DateTimeFormatter.ofPattern("yyyyå¹´MM月dd日")) + "-"
                + submitTime.format(DateTimeFormatter.ofPattern("yyyyå¹´MM月dd日"));
        String testStartDate = firstInspectDate.format(DateTimeFormatter.ofPattern("yyyyå¹´MM月dd日"));
        String testEndDate = submitTime.format(DateTimeFormatter.ofPattern("yyyyå¹´MM月dd日"));
        String insTimeEn = monthNames[firstInspectDate.getMonthValue() - 1] + " " + firstInspectDate.format(DateTimeFormatter.ofPattern("dd, yyyy")) + "-"
                + monthNames[submitTime.getMonthValue() - 1] + " " + submitTime.format(DateTimeFormatter.ofPattern("dd, yyyy"));
@@ -3431,11 +3648,8 @@
                productionDate = insOrder.getProductionDate().format(DateTimeFormatter.ofPattern("yyyyå¹´MM月dd日"));
            }
            batchNo = defaultReportText(insOrder.getProductionBatch(), "/");
            standardMethodText = templateSnapshots.stream()
                    .map(InsOrderStandardTemplate::getRemark)
                    .filter(StringUtils::isNotBlank)
                    .distinct()
                    .collect(Collectors.joining(";"));
            standardMethodText = defaultReportText(
                    baseMapper.getStandardMethodReportTextByCode(insOrder.getSample()), insOrder.getSample());
        }
        boolean containsOrganicChlorine = detectionResultRows.stream()
                .map(row -> row.get("itemName"))
@@ -3445,8 +3659,9 @@
                ? "微库仑法,有机氯含量小于0.005%时,以未检出报出。"
                : defaultReportText(insOrder.getRemark(), "/");
        // æ£€æµ‹æŠ¥å‘Šå’Œæ£€éªŒæŠ¥å‘Šç»Ÿä¸€ä½¿ç”¨æ–°ç‰ˆæŠ¥å‘Šæ¨¡æ¿
        String templateName = "/static/detection-report-template-v2.docx";
        String templateName = showResult
                ? "/static/inspection-report-template-v2.docx"
                : "/static/detection-report-template-v2.docx";
        InputStream inputStream = this.getClass().getResourceAsStream(templateName);
        if (inputStream == null) {
            throw new RuntimeException("找不到报告模板: " + templateName);
@@ -3523,6 +3738,8 @@
                    put("batch", finalBatchNo);
                    put("phone", defaultReportText(insOrder.getPhone(), "/"));
                    put("testDate", insTime);
                    put("testStartDate", testStartDate);
                    put("testEndDate", testEndDate);
                    put("entrustNo", defaultReportText(insOrder.getEntrustCode(), "/"));
                    put("sampling", "/");
                    put("standard", finalStandardMethodText);
@@ -3533,7 +3750,7 @@
                    put("resultNote", finalResultRemark);
                }});
        try {
            String name = insReport.getCode().replace("/", "") + (showResult ? ".docx" : "-C.docx");
            String name = insReport.getCode().replace("/", "") + (showResult ? "-J.docx" : "-C.docx");
            Files.createDirectories(Paths.get(wordUrl));
            template.writeAndClose(Files.newOutputStream(Paths.get(wordUrl, name)));
            insReport.setUrl("/word/" + name);
@@ -3550,6 +3767,22 @@
        InsReport existing = insReportMapper.selectOne(Wrappers.<InsReport>lambdaQuery()
                .eq(InsReport::getInsOrderId, report.getInsOrderId())
                .last("limit 1"));
        if (Objects.equals(report.getReportType(), INSPECTION_REPORT)) {
            String inspectionUrl = report.getUrl();
            if (existing == null) {
                report.setReportType(DETECTION_REPORT);
                report.setUrl(null);
                report.setInspectionUrl(inspectionUrl);
                insReportMapper.insert(report);
            } else {
                insReportMapper.update(null, Wrappers.<InsReport>lambdaUpdate()
                        .eq(InsReport::getId, existing.getId())
                        .set(InsReport::getInspectionUrl, inspectionUrl)
                        .set(InsReport::getWriteUserId, report.getWriteUserId())
                        .set(InsReport::getWriteTime, report.getWriteTime()));
            }
            return;
        }
        if (existing == null) {
            insReportMapper.insert(report);
            return;
@@ -3591,6 +3824,7 @@
                row.put("itemName", itemName);
                row.put("unit", defaultReportText(product.getUnit(), "/"));
                row.put("result", defaultReportText(product.getLastValue(), "/"));
                row.put("inspectionConclusion", formatInspectionConclusion(product.getInsResult()));
                rows.add(row);
            }
        }
@@ -3629,6 +3863,7 @@
                row.put("itemName", formatExportItemName(reportItemName));
                row.put("unit", defaultReportText(unit, "/"));
                row.put("result", result);
                row.put("inspectionConclusion", formatInspectionConclusion(snapshot.getInsResult()));
                rows.add(row);
            }
        }
@@ -3636,6 +3871,16 @@
            throw new ErrorException("当前订单未找到可生成报告的原始记录模板");
        }
        return rows;
    }
    private static String formatInspectionConclusion(Integer insResult) {
        if (Objects.equals(insResult, 1)) {
            return "合格";
        }
        if (Objects.equals(insResult, 0)) {
            return "不合格";
        }
        return "/";
    }
    private List<TemplateExportCell> getTemplateExportCells(String compressedThing) {
@@ -3648,9 +3893,8 @@
        } catch (Exception ignored) {
            thing = compressedThing;
        }
        JSONArray cellData = JSON.parseObject(thing).getJSONArray("data").getJSONObject(0).getJSONArray("celldata");
        Integer resultColumn = null;
        Integer itemColumn = null;
        JSONObject sheet = JSON.parseObject(thing).getJSONArray("data").getJSONObject(0);
        JSONArray cellData = sheet.getJSONArray("celldata");
        Integer unitColumn = null;
        List<int[]> exportCells = new ArrayList<>();
        Map<String, String> templateValues = new HashMap<>();
@@ -3664,13 +3908,6 @@
            }
            String displayValue = value.get("v") == null ? null : String.valueOf(value.get("v"));
            templateValues.put(row + "-" + column, displayValue);
            if (resultColumn == null && "检验结果".equals(StringUtils.trim(displayValue))) {
                resultColumn = column;
            }
            if (itemColumn == null && ("检测项目".equals(StringUtils.trim(displayValue))
                    || "检验项目".equals(StringUtils.trim(displayValue)))) {
                itemColumn = column;
            }
            if (unitColumn == null && "单位".equals(StringUtils.trim(displayValue))) {
                unitColumn = column;
            }
@@ -3685,13 +3922,9 @@
        exportCells.sort(Comparator.comparingInt((int[] cell) -> cell[0]).thenComparingInt(cell -> cell[1]));
        List<TemplateExportCell> results = new ArrayList<>();
        for (int[] exportCell : exportCells) {
            int resultColumnForRow = resultColumn == null ? exportCell[1] : resultColumn;
            // é€šå¸¸â€œå¯¼å‡ºå€¼â€æ‰¹æ³¨é…ç½®åœ¨é¡¹ç›®åç§°å•元格;若配置在结果单元格,则回到“检测项目”列取名称。
            Integer itemColumnForRow = !Objects.equals(exportCell[1], resultColumnForRow)
                    ? exportCell[1] : itemColumn;
            TemplateExportCell result = new TemplateExportCell();
            result.resultCoordinate = exportCell[0] + "-" + resultColumnForRow;
            result.itemCoordinate = itemColumnForRow == null ? null : exportCell[0] + "-" + itemColumnForRow;
            result.resultCoordinate = getRightAdjacentCoordinate(sheet, exportCell[0], exportCell[1]);
            result.itemCoordinate = exportCell[0] + "-" + exportCell[1];
            result.unitCoordinate = unitColumn == null ? null : exportCell[0] + "-" + unitColumn;
            result.templateValues = templateValues;
            results.add(result);
@@ -5139,7 +5372,7 @@
                }
            }
            // åˆ¤æ–­æ˜¯å¦æ˜¯æ•°å­—类型
            if (sampleProductDto2.getInspectionValueType().equals("1")) {
            if ("1".equals(sampleProductDto2.getInspectionValueType())) {
                // æŠŠæ£€éªŒå†…容如果正硫化点和焦烧时间把  .  åˆ‡å‰²æˆ  :
                String lastValue = sampleProductDto2.getLastValue();
                if (sampleProductDto2.getInspectionItem().contains("正硫化点") || sampleProductDto2.getInspectionItem().contains("焦烧时间")) {
inspect-server/src/main/java/com/ruoyi/inspect/service/impl/InsOrderRatesServiceImpl.java
@@ -5,6 +5,7 @@
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.utils.QueryWrappers;
import com.ruoyi.inspect.dto.SampleOrderDto;
import com.ruoyi.inspect.dto.TemplateRateDto;
import com.ruoyi.inspect.mapper.InsOrderRatesMapper;
import com.ruoyi.inspect.pojo.InsOrderRates;
import com.ruoyi.inspect.service.InsOrderRatesService;
@@ -29,5 +30,10 @@
    public IPage<SampleOrderDto> selectInsOrderRates(Page page, SampleOrderDto sampleOrderDto) {
        return baseMapper.selectInsOrderRates(page, QueryWrappers.queryWrappers(sampleOrderDto));
    }
    @Override
    public IPage<TemplateRateDto> selectTemplateRatesDetail(Page page, Integer insOrderId) {
        return baseMapper.selectTemplateRatesDetail(page, insOrderId);
    }
}
inspect-server/src/main/java/com/ruoyi/inspect/service/impl/InsOrderServiceImpl.java
@@ -364,6 +364,15 @@
                }
            }
        });
        // ä¸‹å•时生成委托编号并校验唯一性;季度检验复用进厂检验编号,留待审核处理。
        if (!InsOrderTypeConstants.QUARTERLY_TEST.equals(insOrder.getOrderType())) {
            String entrustCode = buildEntrustCode(insOrder, Collections.emptyList());
            assertEntrustCodeUnique(entrustCode, insOrder.getId());
            insOrder.setEntrustCode(entrustCode);
            insOrderMapper.update(null, Wrappers.<InsOrder>lambdaUpdate()
                    .eq(InsOrder::getId, insOrder.getId())
                    .set(InsOrder::getEntrustCode, entrustCode));
        }
        //原材料下单或外购成品下单
        if (Objects.nonNull(insOrder.getTypeSource())&&Objects.nonNull(insOrder.getIfsInventoryId())) {
            // å§”托人就是报检人, ç”Ÿäº§å•位就是供应商单位
@@ -573,11 +582,19 @@
            product.setSort(productSort++);
            insProductMapper.insert(product);
        }
        saveTemplateSnapshots(order.getId(), selections);
        saveTemplateSnapshots(order.getId(), selections, bindingMap);
        // ä¸‹å•时生成委托编号并校验唯一性,重复则提示,避免留到审核才报错。
        String entrustCode = buildEntrustCode(order, Collections.singletonList(sample));
        assertEntrustCodeUnique(entrustCode, order.getId());
        insOrderMapper.update(null, Wrappers.<InsOrder>lambdaUpdate()
                .eq(InsOrder::getId, order.getId())
                .set(InsOrder::getEntrustCode, entrustCode));
        return order.getId();
    }
    private void saveTemplateSnapshots(Integer orderId, List<OrderTemplateSelectionDto> selections) {
    private void saveTemplateSnapshots(Integer orderId, List<OrderTemplateSelectionDto> selections,
                                       Map<Integer, StandardTreeTemplateBindingVo> bindingMap) {
        for (OrderTemplateSelectionDto selection : selections) {
            StandardTemplate template = standardTemplateService.getById(selection.getTemplateId());
            if (template == null) {
@@ -592,6 +609,18 @@
            snapshot.setThing(template.getThing());
            snapshot.setSort(selection.getSort());
            insOrderStandardTemplateMapper.insert(snapshot);
            StandardTreeTemplateBindingVo binding = bindingMap.get(selection.getTemplateId());
            List<Integer> deviceIds = binding == null || binding.getDeviceIds() == null
                    ? Collections.emptyList() : binding.getDeviceIds();
            for (int index = 0; index < deviceIds.size(); index++) {
                InsOrderTemplateDevice relation = new InsOrderTemplateDevice();
                relation.setInsOrderId(orderId);
                relation.setTemplateId(selection.getTemplateId());
                relation.setDeviceId(deviceIds.get(index));
                relation.setSort(index);
                insOrderTemplateDeviceMapper.insert(relation);
            }
        }
    }
@@ -675,30 +704,12 @@
    public int upInsOrderOfState(InsOrder insOrder) {
        insOrder.setExamineTime(LocalDateTime.now());
        if (insOrder.getState() == 1) {
            //审核通过才会生成委托编号
            //委托编号已在下单时生成并校验唯一性,审核时复用;历史/季度检验订单兜底生成。
            InsOrder order = this.getById(insOrder.getId());
            List<InsSample> insSamples = insSampleMapper.selectList(Wrappers.<InsSample>lambdaQuery()
                    .eq(InsSample::getInsOrderId, insOrder.getId())
                    .orderByAsc(InsSample::getId));
            // å§”托编号按送样日期生成;新聚单样品订单的末两位取样品编号末两位。
            // åŽ†å²/外部订单未填写送样日期时回退到当天,避免影响原有流程。
            LocalDate entrustDate = order.getSampleDate() == null ? LocalDate.now() : order.getSampleDate();
            String entrustCodePrefix = "LMSY-"
                    + entrustDate.format(DateTimeFormatter.BASIC_ISO_DATE)
                    + "-";
            String no;
            if (isDirectTemplateOrder(order, insSamples)) {
                String sampleCode = insSamples.get(0).getSampleCode();
                if (StringUtils.isBlank(sampleCode) || sampleCode.trim().length() < 2) {
                    throw new ErrorException("样品编号不能为空且不能少于2位");
                }
                sampleCode = sampleCode.trim();
                no = entrustCodePrefix + sampleCode.substring(sampleCode.length() - 2);
            } else {
                no = numberGenerator.generateNumberWithPrefix(2,
                        entrustCodePrefix,
                        InsOrder::getEntrustCode);
            }
            String no = order.getEntrustCode();
            // åˆ¤æ–­æ˜¯å¦æ˜¯å­£åº¦æ£€éªŒ, æ˜¯å­£åº¦æ£€éªŒå–消原材料季度检验下单
            if (InsOrderTypeConstants.QUARTERLY_TEST.equals(order.getOrderType())) {
                ifsInventoryQuantityMapper.update(null,  Wrappers.<IfsInventoryQuantity>lambdaUpdate()
@@ -714,6 +725,10 @@
                  no = order1.getEntrustCode();
                }
            }
            if (StringUtils.isBlank(no)) {
                no = buildEntrustCode(order, insSamples);
            }
            assertEntrustCodeUnique(no, order.getId());
            int count = 1;
            for (InsSample insSample : insSamples) {
@@ -734,6 +749,37 @@
        return insOrderMapper.updateById(insOrder);
    }
    /**
     * å§”托编号全局唯一,生成编号和后续提交前均需排除当前订单后校验。
     */
    private void assertEntrustCodeUnique(String entrustCode, Integer currentOrderId) {
        Long count = insOrderMapper.selectCount(Wrappers.<InsOrder>lambdaQuery()
                .eq(InsOrder::getEntrustCode, entrustCode)
                .ne(currentOrderId != null, InsOrder::getId, currentOrderId));
        if (count > 0) {
            throw new ErrorException("样品/委托单已存在,请确认样品编号");
        }
    }
    /**
     * ç”Ÿæˆå§”托编号:新聚直绑模板订单末两位取样品编号末两位,其余按送样日期生成序号。
     */
    private String buildEntrustCode(InsOrder order, List<InsSample> insSamples) {
        LocalDate entrustDate = order.getSampleDate() == null ? LocalDate.now() : order.getSampleDate();
        String entrustCodePrefix = "LMSY-"
                + entrustDate.format(DateTimeFormatter.BASIC_ISO_DATE)
                + "-";
        if (isDirectTemplateOrder(order, insSamples)) {
            String sampleCode = insSamples.get(0).getSampleCode();
            if (StringUtils.isBlank(sampleCode) || sampleCode.trim().length() < 2) {
                throw new ErrorException("样品编号不能为空且不能少于2位");
            }
            sampleCode = sampleCode.trim();
            return entrustCodePrefix + sampleCode.substring(sampleCode.length() - 2);
        }
        return numberGenerator.generateNumberWithPrefix(2, entrustCodePrefix, InsOrder::getEntrustCode);
    }
    @Override
    public Map<String, Object> getInsOrderAndSample(Integer id, String laboratory) {
        Map<String, Object> map = new HashMap<>();
inspect-server/src/main/java/com/ruoyi/inspect/service/impl/InsReportServiceImpl.java
@@ -46,6 +46,7 @@
import com.ruoyi.inspect.service.InsReportService;
import com.ruoyi.inspect.service.RawMaterialOrderService;
import com.ruoyi.inspect.util.PreserveReportPicturePlaceholderHandler;
import com.ruoyi.inspect.util.ReportSignaturePictureUtils;
import com.ruoyi.system.mapper.UserMapper;
import com.ruoyi.system.service.InformationNotificationService;
import lombok.extern.slf4j.Slf4j;
@@ -86,6 +87,9 @@
@Slf4j
public class InsReportServiceImpl extends ServiceImpl<InsReportMapper, InsReport>
        implements InsReportService {
    private static final int DETECTION_REPORT = 0;
    private static final int INSPECTION_REPORT = 1;
    @Resource
    private UserMapper userMapper;
    @Resource
@@ -209,30 +213,30 @@
        info.setViewStatus(false);
        info.setJumpPath(MenuJumpPathConstants.REPORT_PREPARATION);
        informationNotificationService.addInformationNotification(info);
        //系统生成报告地址
        String url = insReport.getUrl();
        //手动上传报告地址
        String urlS = insReport.getUrlS();
        String primaryReportUrl = getPrimaryReportUrl(insReport);
        // åˆ¤æ–­æ˜¯å¦æ˜¯åŽŸææ–™  éœ€è¦æ›¿æ¢****成供应商
        IfsInventoryQuantity one = ifsInventoryQuantityMapper.selectOne(new LambdaQueryWrapper<IfsInventoryQuantity>()
                .eq(IfsInventoryQuantity::getId, order.getIfsInventoryId()));
        if (one != null) {
            if (isRawMater && order.getOrderType().equals(InsOrderTypeConstants.ENTER_THE_FACTORY)) {
                changeText(new HashMap<String, String>() {{
                    put("**********", one.getSupplierName());
                }}, (StrUtil.isBlank(urlS) ? url : urlS).replace("/word", wordUrl));
                for (String reportUrl : getApprovalReportUrls(insReport)) {
                    changeText(new HashMap<String, String>() {{
                        put("**********", one.getSupplierName());
                    }}, reportUrl.replace("/word", wordUrl));
                }
            }
        }
        wordInsertUrl(new HashMap<String, Object>() {{
            put("writeUrl", Pictures.ofLocal(imgUrl + "/" + signatureUrl).create());
        Map<String, Object> writeMarks = new HashMap<String, Object>() {{
            put("writeUrl", ReportSignaturePictureUtils.create(imgUrl, signatureUrl));
            put("writeDateUrl", Pictures.ofStream(DateImageUtil.createDateImage(null)).create());
            put("insUrl", Pictures.ofLocal(imgUrl + "/" + signatureUrl).create());
        }}, (StrUtil.isBlank(urlS) ? url : urlS).replace("/word", wordUrl));
            put("insUrl", ReportSignaturePictureUtils.create(imgUrl, signatureUrl));
        }};
        writeMarksToReportFiles(insReport, writeMarks);
        // ä¿®æ”¹ä¸´æ—¶pdf
        String tempUrlPdf = wordToPdfTemp((StrUtil.isBlank(urlS) ? url : urlS).replace("/word", wordUrl));
        String tempUrlPdf = wordToPdfTemp(primaryReportUrl.replace("/word", wordUrl));
        insReport.setTempUrlPdf("/word/" + tempUrlPdf);
        insReportMapper.updateById(insReport);
@@ -330,17 +334,15 @@
        info.setViewStatus(false);
        info.setJumpPath(MenuJumpPathConstants.REPORT_PREPARATION);
        informationNotificationService.addInformationNotification(info);
        //系统生成报告地址
        String url = insReport.getUrl();
        //手动上传报告地址
        String urlS = insReport.getUrlS();
        wordInsertUrl(new HashMap<String, Object>() {{
            put("examineUrl", Pictures.ofLocal(imgUrl + "/" + signatureUrl).create());
        String primaryReportUrl = getPrimaryReportUrl(insReport);
        Map<String, Object> examineMarks = new HashMap<String, Object>() {{
            put("examineUrl", ReportSignaturePictureUtils.create(imgUrl, signatureUrl));
            put("examineDateUrl", Pictures.ofStream(DateImageUtil.createDateImage(null)).create());
        }}, (StrUtil.isBlank(urlS) ? url : urlS).replace("/word", wordUrl));
        }};
        writeMarksToReportFiles(insReport, examineMarks);
        // ä¿®æ”¹ä¸´æ—¶pdf
        String tempUrlPdf = wordToPdfTemp((StrUtil.isBlank(urlS) ? url : urlS).replace("/word", wordUrl));
        String tempUrlPdf = wordToPdfTemp(primaryReportUrl.replace("/word", wordUrl));
        insReport.setTempUrlPdf("/word/" + tempUrlPdf);
        // å‘送企业微信通知(通知批准人审批)
@@ -473,28 +475,29 @@
        if (StringUtils.isBlank(sealUrl)) {
            throw new ErrorException(laboratory + "找不到报告专用章");
        }
        //系统生成报告地址
        String url = insReport.getUrl();
        //手动上传报告地址
        String urlS = insReport.getUrlS();
        String finalUrl = (StrUtil.isBlank(urlS) ? url : urlS).replace("/word", wordUrl);
        String primaryReportUrl = getPrimaryReportUrl(insReport);
        String finalUrl = primaryReportUrl.replace("/word", wordUrl);
        wordInsertUrl(new HashMap<String, Object>() {{
            put("ratifyUrl", Pictures.ofLocal(imgUrl + "/" + signatureUrl).create());
        Map<String, Object> ratifyMarks = new HashMap<String, Object>() {{
            put("ratifyUrl", ReportSignaturePictureUtils.create(imgUrl, signatureUrl));
            put("ratifyDateUrl", Pictures.ofStream(DateImageUtil.createDateImage(null)).create());
            put("seal1", Pictures.ofLocal(imgUrl + "/" + sealUrl).create());
            put("seal2", Pictures.ofLocal(imgUrl + "/" + sealUrl).create());
        }}, finalUrl);
        }};
        writeMarksToReportFiles(insReport, ratifyMarks);
        // ä¿®æ”¹ä¸´æ—¶pdf
        insReport.setTempUrlPdf((StrUtil.isBlank(urlS) ? url : urlS).replace(".docx", ".pdf"));
        insReport.setTempUrlPdf(primaryReportUrl.replace(".docx", ".pdf"));
        InsOrder insOrder = new InsOrder();
        insOrder.setId(insOrderId);
        insOrder.setState(4);
        insOrderMapper.updateById(insOrder);
        wordToPdf(finalUrl, sealUrl, isRawMater && order.getOrderType().equals(InsOrderTypeConstants.ENTER_THE_FACTORY));
        for (String reportUrl : getApprovalReportUrls(insReport)) {
            wordToPdf(reportUrl.replace("/word", wordUrl), sealUrl,
                    isRawMater && order.getOrderType().equals(InsOrderTypeConstants.ENTER_THE_FACTORY));
        }
        // åˆ¤æ–­æ˜¯å¦ä¸ºåŽŸææ–™
        if (isRawMater) {
@@ -537,6 +540,27 @@
        return 1;
    }
    private String getPrimaryReportUrl(InsReport report) {
        String reportUrl = StringUtils.isNotBlank(report.getUrlS()) ? report.getUrlS() : report.getUrl();
        if (StringUtils.isBlank(reportUrl)) {
            throw new ErrorException("检测报告文件不存在");
        }
        return reportUrl;
    }
    private List<String> getApprovalReportUrls(InsReport report) {
        return Arrays.asList(getPrimaryReportUrl(report), report.getInspectionUrl()).stream()
                .filter(StringUtils::isNotBlank)
                .distinct()
                .collect(Collectors.toList());
    }
    private void writeMarksToReportFiles(InsReport report, Map<String, Object> marks) {
        for (String reportUrl : getApprovalReportUrls(report)) {
            wordInsertUrl(marks, reportUrl.replace("/word", wordUrl));
        }
    }
    //报告批量下载
    @Override
@@ -577,17 +601,23 @@
    }
    @Override
    public void download(Integer id, Integer createOrderUser, HttpServletResponse response) {
    public void download(Integer id, Integer reportType, Integer createOrderUser, HttpServletResponse response) {
        if (id == null) {
            throw new ErrorException("报告不能为空");
        }
        reportType = reportType == null ? DETECTION_REPORT : reportType;
        if (!Objects.equals(reportType, DETECTION_REPORT) && !Objects.equals(reportType, INSPECTION_REPORT)) {
            throw new ErrorException("报告类型不正确");
        }
        InsReport report = insReportMapper.selectDownloadableReport(id, createOrderUser);
        if (report == null) {
            throw new ErrorException("报告不存在、尚未生成或无权下载");
        }
        String reportUrl = StringUtils.isNotBlank(report.getUrlS()) ? report.getUrlS() : report.getUrl();
        String reportUrl = Objects.equals(reportType, INSPECTION_REPORT)
                ? report.getInspectionUrl() : getPrimaryReportUrl(report);
        if (StringUtils.isBlank(reportUrl) || !reportUrl.replace('\\', '/').startsWith("/word/")) {
            throw new ErrorException("报告文件不存在,请联系管理员");
            throw new ErrorException((Objects.equals(reportType, INSPECTION_REPORT) ? "检验报告" : "检测报告")
                    + "文件不存在,请联系管理员");
        }
        Path root = Paths.get(wordUrl).toAbsolutePath().normalize();
        Path file = root.resolve(reportUrl.replace('\\', '/').substring("/word/".length())).normalize();
inspect-server/src/main/java/com/ruoyi/inspect/util/ReportSignaturePictureUtils.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,41 @@
package com.ruoyi.inspect.util;
import com.deepoove.poi.data.PictureRenderData;
import com.deepoove.poi.data.Pictures;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
/**
 * æŠ¥å‘Šç­¾åå›¾ç‰‡å·¥å…·ã€‚
 */
public final class ReportSignaturePictureUtils {
    private static final int MAX_WIDTH = 90;
    private static final int MAX_HEIGHT = 45;
    private ReportSignaturePictureUtils() {
    }
    /**
     * æŒ‰åŽŸå§‹æ¯”ä¾‹å°†ç­¾åé€‚é…åˆ°æŠ¥å‘Šç­¾åå•å…ƒæ ¼ï¼Œé¿å…å›¾ç‰‡è¶Šè¿‡å•å…ƒæ ¼è¾¹ç•Œè€Œè¢«è£å‰ªã€‚
     */
    public static PictureRenderData create(String imageDirectory, String signatureUrl) {
        String signaturePath = imageDirectory + "/" + signatureUrl;
        try {
            BufferedImage image = ImageIO.read(new File(signaturePath));
            if (image != null && image.getWidth() > 0 && image.getHeight() > 0) {
                double scale = Math.min((double) MAX_WIDTH / image.getWidth(),
                        (double) MAX_HEIGHT / image.getHeight());
                int width = Math.max(1, (int) Math.round(image.getWidth() * scale));
                int height = Math.max(1, (int) Math.round(image.getHeight() * scale));
                return Pictures.ofLocal(signaturePath).size(width, height).create();
            }
        } catch (IOException ignored) {
            // å›¾ç‰‡å…ƒæ•°æ®è¯»å–失败时仍可尝试以默认尺寸写入报告。
        }
        return Pictures.ofLocal(signaturePath).size(MAX_WIDTH, MAX_HEIGHT).create();
    }
}
inspect-server/src/main/resources/mapper/InsOrderMapper.xml
@@ -281,6 +281,23 @@
        from standard_method
        where id = #{id}
    </select>
    <select id="getStandardMethodReportText" resultType="java.lang.String">
        select case
                   when name is null or trim(name) = '' then code
                   else concat(code, '《', name, '》')
               end
        from standard_method
        where id = #{id}
    </select>
    <select id="getStandardMethodReportTextByCode" resultType="java.lang.String">
        select case
                   when name is null or trim(name) = '' then code
                   else concat(code, '《', name, '》')
               end
        from standard_method
        where code = #{code}
        limit 1
    </select>
    <select id="selLaboratoryCode" resultType="java.lang.String">
        select laboratory_code
        from laboratory
inspect-server/src/main/resources/mapper/InsOrderRatesMapper.xml
@@ -11,26 +11,25 @@
        then io.sample_view
        else io.sample end                            sampleStr,
        GROUP_CONCAT(DISTINCT isa.model SEPARATOR ' ') AS sample_model,
        sum(tempior.rates) + 200 total_price
        COALESCE(sum(st.price), 0) total_price
        from ins_order io
        LEFT JOIN ins_sample isa ON isa.ins_order_id = io.id
        left join (
        select ior.id, ior.ins_order_id, ior.rates from
        ins_order_rates ior
        left join ins_product ip on ip.id = ior.ins_product_id
        left join ins_order io2 on ior.ins_order_id = io2.id
        GROUP BY CASE
        WHEN ior.man_hour_group IS NOT NULL AND ior.man_hour_group != '' THEN ior.man_hour_group
        ELSE ior.id END,
        CASE WHEN ior.man_hour_group IS NOT NULL AND ior.man_hour_group != '' THEN ior.ins_sample_id ELSE ior.id END,
        CASE WHEN ior.man_hour_group IS NOT NULL AND ior.man_hour_group != '' THEN ip.radius ELSE ior.id END,
        CASE WHEN ior.man_hour_group IS NOT NULL AND ior.man_hour_group != '' THEN ip.cable_tag ELSE ior.id END,
        CASE WHEN ior.man_hour_group IS NOT NULL AND ior.man_hour_group != '' THEN ior.ins_order_id ELSE ior.id END) tempior on tempior.ins_order_id = io.id
        where tempior.id is not null
        left join ins_order_standard_template iost on iost.ins_order_id = io.id
        left join standard_template st on st.id = iost.template_id
        where iost.id is not null
        group by io.id) a
        <if test="ew.customSqlSegment != null and ew.customSqlSegment != ''">
            ${ew.customSqlSegment}
        </if>
    </select>
    <!-- æŸ¥è¯¢è®¢å•费用详情(模板价格) -->
    <select id="selectTemplateRatesDetail" resultType="com.ruoyi.inspect.dto.TemplateRateDto">
        select iost.number, iost.name, COALESCE(st.price, 0) as price
        from ins_order_standard_template iost
        left join standard_template st on st.id = iost.template_id
        where iost.ins_order_id = #{insOrderId}
        order by iost.sort, iost.id
    </select>
</mapper>
inspect-server/src/main/resources/mapper/InsReportMapper.xml
@@ -82,7 +82,10 @@
    <!-- æŠ¥å‘ŠæŠ¥è¡¨å¯¼å‡º -->
    <select id="reportAllExport" resultType="com.ruoyi.inspect.dto.InsReportExport">
        select code,
        case when report_type = 0 then '检测报告' else '检验报告' end                         report_type_name,
        case
        when inspection_url is not null and inspection_url != '' then '检测报告、检验报告'
        when report_type = 0 then '检测报告'
        else '检验报告' end                                                               report_type_name,
        case when type_source = 0 then '成品下单' else '原材料下单' end                    type_source,
        case
        when order_type = '抽检' then '抽检'
inspect-server/src/main/resources/static/detection-report-template-v2.docx
Binary files differ
inspect-server/src/main/resources/static/inspection-report-template-v2.docx
Binary files differ
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/CustomController.java
@@ -35,7 +35,7 @@
    }
    @ApiOperation(value = "删除客户信息")
    @DeleteMapping("/delCustomById")
    public Result delCustomById(Long id) {
    public Result delCustomById(@RequestParam("id") Long id) {
        return Result.success(customService.delCustomById(id));
    }
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/Custom.java
@@ -28,6 +28,9 @@
    @ApiModelProperty(value = "单位地址")
    private String address;
    @ApiModelProperty(value = "联系人")
    private String contact;
    @ApiModelProperty(value = "工厂域")
    private String code;