liding
6 天以前 3728fa293e1faf4bf4f9bf41105b84ddb43dc82e
inspect-server/src/main/java/com/ruoyi/inspect/service/impl/InsReportServiceImpl.java
@@ -35,6 +35,8 @@
import com.ruoyi.common.constant.InsOrderTypeConstants;
import com.ruoyi.common.constant.MenuJumpPathConstants;
import com.ruoyi.common.core.domain.Result;
import com.ruoyi.common.core.domain.entity.User;
import com.ruoyi.common.core.domain.entity.SysMenu;
import com.ruoyi.common.core.domain.entity.InformationNotification;
import com.ruoyi.common.utils.DateImageUtil;
import com.ruoyi.common.utils.QueryWrappers;
@@ -49,10 +51,12 @@
import com.ruoyi.inspect.service.InsOrderService;
import com.ruoyi.inspect.service.InsReportService;
import com.ruoyi.inspect.service.RawMaterialOrderService;
import com.ruoyi.inspect.service.SealUsageLedgerService;
import com.ruoyi.inspect.util.PreserveReportPicturePlaceholderHandler;
import com.ruoyi.inspect.util.ReportSealLayoutUtil;
import com.ruoyi.inspect.util.ReportSignaturePictureUtils;
import com.ruoyi.system.mapper.UserMapper;
import com.ruoyi.system.service.ISysMenuService;
import com.ruoyi.system.service.InformationNotificationService;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
@@ -100,17 +104,28 @@
    private static final int DETECTION_REPORT = 0;
    private static final int INSPECTION_REPORT = 1;
    private static final String RETURN_TO_INSPECTION_TASK = "INSPECTION_TASK";
    private static final String RETURN_TO_REPORT_REVIEW = "REPORT_REVIEW";
    private static final int RETURNED_STATUS = 2;
    @Resource
    private UserMapper userMapper;
    @Resource
    private InsReportMapper insReportMapper;
    @Resource
    private InsReportEditSessionMapper insReportEditSessionMapper;
    @Resource
    private InsReportReturnRecordMapper insReportReturnRecordMapper;
    @Resource
    private SealUsageLedgerService sealUsageLedgerService;
    @Resource
    private InsOrderStateMapper insOrderStateMapper;
    @Resource
    private InsProductMapper insProductMapper;
    @Resource
    private InformationNotificationService informationNotificationService;
    @Resource
    private ISysMenuService menuService;
    @Value("${wordUrl}")
    private String wordUrl;
    @Value("${file.path}")
@@ -198,6 +213,7 @@
        if (userId == null) {
            throw new ErrorException("缺少审核人");
        }
        ensureReportPreparationPermission(userId, "审核人");
        insReport.setExamineUserId(userId);//审核人
        insReport.setWriteTime(LocalDateTime.now());//提交时间
        //获取提交人的签名地址
@@ -226,8 +242,6 @@
        info.setViewStatus(false);
        info.setJumpPath(MenuJumpPathConstants.REPORT_PREPARATION);
        informationNotificationService.addInformationNotification(info);
        String primaryReportUrl = getPrimaryReportUrl(insReport);
        // 判断是否是原材料  需要替换****成供应商
        IfsInventoryQuantity one = ifsInventoryQuantityMapper.selectOne(new LambdaQueryWrapper<IfsInventoryQuantity>()
                .eq(IfsInventoryQuantity::getId, order.getIfsInventoryId()));
@@ -248,9 +262,7 @@
        }};
        writeMarksToReportFiles(insReport, writeMarks);
        // 修改临时pdf
        String tempUrlPdf = wordToPdfTemp(primaryReportUrl.replace("/word", wordUrl));
        insReport.setTempUrlPdf("/word/" + tempUrlPdf);
        refreshTemporaryPreviews(insReport);
        insReportMapper.updateById(insReport);
@@ -263,6 +275,33 @@
                .set(InsReport::getIsExamine, null));
        return 1;
    }
    /**
     * 指定审核人/批准人时,确认对方登录后能看到「报告编制」页面,
     * 避免指定后登录只剩首页、无法继续审批。
     */
    private void ensureReportPreparationPermission(Integer userId, String userLabel) {
        List<SysMenu> menus = menuService.selectMenuTreeByUserId(userId.longValue());
        if (!containsReportPreparation(menus)) {
            throw new ErrorException("所选" + userLabel + "没有「业务管理 → 报告编制」页面权限,请先在「系统管理 → 用户管理」为其分配对应角色");
        }
    }
    private boolean containsReportPreparation(List<SysMenu> menus) {
        if (CollectionUtils.isEmpty(menus)) {
            return false;
        }
        for (SysMenu menu : menus) {
            if (Long.valueOf(1129L).equals(menu.getMenuId())
                    || "reportPreparation".equals(menu.getPath())) {
                return true;
            }
            if (containsReportPreparation(menu.getChildren())) {
                return true;
            }
        }
        return false;
    }
    //审核
@@ -320,6 +359,7 @@
            if (userId == null) {
                throw new ErrorException("缺少批准人");
            }
            ensureReportPreparationPermission(userId, "批准人");
            if (Objects.equals(insReport.getExamineUserId(), userId)) {
                throw new ErrorException("审核人和批准人不能为同一人,请重新选择批准人");
            }
@@ -350,16 +390,13 @@
        info.setViewStatus(false);
        info.setJumpPath(MenuJumpPathConstants.REPORT_PREPARATION);
        informationNotificationService.addInformationNotification(info);
        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());
        }};
        writeMarksToReportFiles(insReport, examineMarks);
        // 修改临时pdf
        String tempUrlPdf = wordToPdfTemp(primaryReportUrl.replace("/word", wordUrl));
        insReport.setTempUrlPdf("/word/" + tempUrlPdf);
        refreshTemporaryPreviews(insReport);
        // 发送企业微信通知(通知批准人审批)
        threadPoolTaskExecutor.execute(() -> {
@@ -405,7 +442,21 @@
    @Transactional(rollbackFor = Exception.class)
    public int ratifyReport(Integer id, Integer isRatify, String ratifyTell, String sealIds,
                            Integer maSealId, Integer reportSealId, Integer seamSealId) {
        InsReport insReport = insReportMapper.selectById(id);
        if (id == null || (isRatify == null || (isRatify != 0 && isRatify != 1))) {
            throw new ErrorException("批准参数不正确");
        }
        InsReport insReport = insReportMapper.selectOne(Wrappers.<InsReport>lambdaQuery()
                .eq(InsReport::getId, id)
                .last("FOR UPDATE"));
        if (insReport == null) {
            throw new ErrorException("报告不存在");
        }
        if (Objects.equals(insReport.getIsRatify(), 1)) {
            throw new ErrorException("报告已批准,请勿重复操作");
        }
        if (!Objects.equals(insReport.getState(), 1) || !Objects.equals(insReport.getIsExamine(), 1)) {
            throw new ErrorException("报告尚未审核通过,不能批准");
        }
        if (Objects.equals(insReport.getExamineUserId(), SecurityUtils.getUserId().intValue())) {
            throw new ErrorException("审核人和批准人不能为同一人,请重新选择批准人");
        }
@@ -459,10 +510,21 @@
                }
            });
            //如果批准不通过 直接退回到提交人那边去
            insReport.setState(0);
            insReport.setIsExamine(0);
            return insReportMapper.updateById(insReport);
            // 批准不通过直接退回报告审核,由原审核人修改后重新审核、批准。
            recordReportReturn(insReport, "REPORT_APPROVAL", RETURN_TO_REPORT_REVIEW,
                    StringUtils.isBlank(ratifyTell) ? "批准不通过" : ratifyTell, insReport.getRatifyTime());
            insReport.setState(1);
            insReport.setExamineTell(ratifyTell);
            int affected = insReportMapper.updateById(insReport);
            if (affected != 1) {
                throw new ErrorException("报告退回审核失败");
            }
            insReportMapper.update(null, Wrappers.<InsReport>lambdaUpdate()
                    .eq(InsReport::getId, id)
                    .set(InsReport::getIsExamine, RETURNED_STATUS)
                    .set(InsReport::getExamineTime, insReport.getRatifyTime()));
            expireReportEditSessions(id);
            return affected;
        }
        //获取批准人的签名地址
        String signatureUrl;
@@ -512,6 +574,7 @@
        insReport.setReportSealName(reportSeal == null ? null : reportSeal.getName());
        insReport.setSeamSealId(seamSeal == null ? null : seamSeal.getId());
        insReport.setSeamSealName(seamSeal == null ? null : seamSeal.getName());
        prepareApprovalCopies(insReport);
        String primaryReportUrl = getPrimaryReportUrl(insReport);
        String finalUrl = primaryReportUrl.replace("/word", wordUrl);
@@ -576,7 +639,12 @@
            });
        }
        return insReportMapper.updateById(insReport);
        int affected = insReportMapper.updateById(insReport);
        if (affected != 1) {
            throw new ErrorException("报告批准状态保存失败");
        }
        sealUsageLedgerService.recordApprovedReport(insReport);
        return affected;
    }
    /**
@@ -695,6 +763,61 @@
                .collect(Collectors.toList());
    }
    /** 批准章只写入副本,保留无批准章的审核稿用于批准后退回修改。 */
    private void prepareApprovalCopies(InsReport report) {
        String primarySource = getPrimaryReportUrl(report);
        String inspectionSource = report.getInspectionUrl();
        report.setReviewUrl(primarySource);
        report.setReviewInspectionUrl(inspectionSource);
        report.setUrlS(copyReportForApproval(primarySource));
        if (StringUtils.isNotBlank(inspectionSource)) {
            report.setInspectionUrl(copyReportForApproval(inspectionSource));
        }
    }
    private String copyReportForApproval(String sourceUrl) {
        try {
            Path source = Paths.get(sourceUrl.replace("/word", wordUrl));
            if (!Files.exists(source)) {
                throw new ErrorException("待批准报告文件不存在");
            }
            String fileName = "approved-" + UUID.randomUUID() + ".docx";
            Path target = Paths.get(wordUrl, fileName);
            Files.createDirectories(target.getParent());
            Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
            return "/word/" + fileName;
        } catch (ErrorException e) {
            throw e;
        } catch (Exception e) {
            log.error("创建报告批准副本失败: {}", sourceUrl, e);
            throw new ErrorException("创建报告批准副本失败");
        }
    }
    private void refreshTemporaryPreviews(InsReport report) {
        String primaryPdf = wordToPdfTemp(getPrimaryReportUrl(report).replace("/word", wordUrl));
        if (StringUtils.isBlank(primaryPdf)) {
            throw new ErrorException("检测报告预览生成失败");
        }
        report.setTempUrlPdf("/word/" + primaryPdf);
        if (StringUtils.isNotBlank(report.getInspectionUrl())) {
            String inspectionPdf = wordToPdfTemp(report.getInspectionUrl().replace("/word", wordUrl));
            if (StringUtils.isBlank(inspectionPdf)) {
                throw new ErrorException("检验报告预览生成失败");
            }
            report.setInspectionTempUrlPdf("/word/" + inspectionPdf);
        }
    }
    @Override
    public String prepareEditedReportPreview(String absoluteDocxPath) {
        String pdfName = wordToPdfTemp(absoluteDocxPath);
        if (StringUtils.isBlank(pdfName)) {
            throw new ErrorException("报告预览生成失败");
        }
        return "/word/" + pdfName;
    }
    private void writeMarksToReportFiles(InsReport report, Map<String, Object> marks) {
        for (String reportUrl : getApprovalReportUrls(report)) {
            wordInsertUrl(marks, reportUrl.replace("/word", wordUrl));
@@ -771,7 +894,12 @@
            throw new ErrorException("报告文件不存在,请联系管理员");
        }
        try {
            String filename = file.getFileName().toString();
            // 物理文件可能是 reviewed/edited UUID;对用户始终使用业务报告编号。
            String reportNumber = StringUtils.isBlank(report.getCode()) ? "报告" : report.getCode();
            reportNumber = reportNumber.replaceAll("[\\\\/:*?\"<>|\\p{Cntrl}]", "-").trim();
            String suffix = StringUtils.isNotBlank(report.getInspectionUrl())
                    ? (Objects.equals(reportType, INSPECTION_REPORT) ? "-检验报告" : "-检测报告") : "";
            String filename = reportNumber + suffix + ".docx";
            String encodedName = URLEncoder.encode(filename, "UTF-8").replace("+", "%20");
            response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
            response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encodedName);
@@ -791,6 +919,82 @@
            log.error("下载报告失败,reportId={}", id, e);
            throw new ErrorException("报告下载失败");
        }
    }
    @Override
    public void preview(Integer id, Integer reportType, Integer createOrderUser, HttpServletResponse response) {
        int type = reportType == null ? DETECTION_REPORT : reportType;
        if (id == null || (type != DETECTION_REPORT && type != INSPECTION_REPORT)) {
            throw new ErrorException("报告或报告类型不正确");
        }
        InsReport report = insReportMapper.selectDownloadableReport(id, createOrderUser);
        if (report == null) {
            throw new ErrorException("报告不存在、尚未生成或无权查看");
        }
        String sourceUrl = type == INSPECTION_REPORT ? report.getInspectionUrl() : getPrimaryReportUrl(report);
        if (StringUtils.isBlank(sourceUrl)) {
            throw new ErrorException("报告文件尚未生成");
        }
        try {
            Path source = resolvePreviewFile(sourceUrl);
            Path pdf;
            if (Objects.equals(report.getIsRatify(), 1)) {
                // 已批准报告必须使用批准时生成的 PDF,保留 PDF 专用的 MA 章和骑缝章。
                // inspectionTempUrlPdf 可能仍指向审核阶段的临时稿,不能作为有章报告返回。
                pdf = resolvePreviewFile(sourceUrl.replaceFirst("(?i)\\.docx?$", ".pdf"));
                if (pdf == null) {
                    throw new ErrorException("有章报告 PDF 不存在,请重新批准生成");
                }
            } else {
                pdf = resolvePreviewFile(type == INSPECTION_REPORT
                        ? report.getInspectionTempUrlPdf() : report.getTempUrlPdf());
                if (source == null) throw new ErrorException("Word 报告文件不存在");
                if (pdf == null || Files.getLastModifiedTime(pdf).compareTo(Files.getLastModifiedTime(source)) < 0) {
                    // 缺少预览或 Word 更新后再转换;同一版本重复查看复用缓存,不改审批数据。
                    Path directory = Paths.get(wordUrl).toAbsolutePath().normalize().resolve("preview");
                    Files.createDirectories(directory);
                    String version = source + ":" + Files.size(source) + ":" + Files.getLastModifiedTime(source);
                    String name = UUID.nameUUIDFromBytes(version.getBytes(java.nio.charset.StandardCharsets.UTF_8)) + ".pdf";
                    pdf = directory.resolve(name);
                    if (!Files.isRegularFile(pdf)) {
                        Path temporary = Files.createTempFile(directory, "report-", ".pdf");
                        try {
                            if (wordToPdf(source.toString(), temporary.toString()) == null) {
                                throw new ErrorException("报告转 PDF 失败,请联系管理员");
                            }
                            Files.move(temporary, pdf, StandardCopyOption.REPLACE_EXISTING);
                        } finally {
                            Files.deleteIfExists(temporary);
                        }
                    }
                }
            }
            String filename = (StringUtils.isBlank(report.getCode()) ? "报告" : report.getCode())
                    .replaceAll("[\\\\/:*?\"<>|\\p{Cntrl}]", "-")
                    + (type == INSPECTION_REPORT ? "-检验报告.pdf" : "-检测报告.pdf");
            response.setContentType("application/pdf");
            response.setHeader("Content-Disposition", "inline; filename*=UTF-8''"
                    + URLEncoder.encode(filename, "UTF-8").replace("+", "%20"));
            response.setHeader("Cache-Control", "no-store");
            response.setContentLengthLong(Files.size(pdf));
            Files.copy(pdf, response.getOutputStream());
        } catch (IOException e) {
            log.error("报告预览失败,reportId={}", id, e);
            throw new ErrorException("报告预览失败,请联系管理员");
        }
    }
    /** 只读取报告目录内的文件,缺失文件交由调用方处理。 */
    private Path resolvePreviewFile(String url) throws IOException {
        if (StringUtils.isBlank(url)) return null;
        String path = url.replace('\\', '/');
        if (!path.startsWith("/word/")) throw new ErrorException("报告文件地址不正确");
        Path root = Paths.get(wordUrl).toAbsolutePath().normalize();
        Path file = root.resolve(path.substring("/word/".length())).normalize();
        if (!file.startsWith(root)) throw new ErrorException("报告文件地址不正确");
        if (!Files.isRegularFile(file)) return null;
        if (!file.toRealPath().startsWith(root.toRealPath())) throw new ErrorException("报告文件地址不正确");
        return file;
    }
    //批量上传
@@ -1160,38 +1364,172 @@
        return unqualifiedCount;
    }
    /**
     * 退回到检验任务
     * @param id
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean sendBackTask(Integer id) {
        InsReport report = insReportMapper.selectById(id);
    public boolean sendBackTask(Integer id, String target, String reason) {
        if (id == null || (!RETURN_TO_INSPECTION_TASK.equals(target)
                && !RETURN_TO_REPORT_REVIEW.equals(target))) {
            throw new ErrorException("退回参数不正确");
        }
        if (StringUtils.isBlank(reason)) {
            throw new ErrorException("请输入退回原因");
        }
        InsReport report = insReportMapper.selectOne(Wrappers.<InsReport>lambdaQuery()
                .eq(InsReport::getId, id)
                .last("FOR UPDATE"));
        if (report == null || report.getInsOrderId() == null) {
            throw new ErrorException("报告不存在或未关联检验单");
        }
        // 报告由订单下各试验室共同形成;退回时全部恢复为待复核,避免只随机退回一个试验室。
        insOrderStateMapper.update(null, Wrappers.<InsOrderState>lambdaUpdate()
                .eq(InsOrderState::getInsOrderId, report.getInsOrderId())
                .set(InsOrderState::getInsState, 4));
        InsSampleUser sampleUser = insSampleUserMapper.selectOne(Wrappers.<InsSampleUser>lambdaQuery()
                .eq(InsSampleUser::getInsSampleId, report.getInsOrderId())
                .orderByDesc(InsSampleUser::getId)
                .last("limit 1"));
        if (sampleUser != null) {
            insSampleUserMapper.deleteById(sampleUser.getId());
        if (!Objects.equals(report.getIsPass(), 1)) {
            throw new ErrorException("报告已退出报告流程,请勿重复退回");
        }
        // 修改报告行为未显示
        LocalDateTime returnTime = LocalDateTime.now();
        recordReportReturn(report, target, reason, returnTime);
        if (RETURN_TO_REPORT_REVIEW.equals(target)) {
            if (!Objects.equals(report.getIsRatify(), 1)) {
                throw new ErrorException("只有已批准报告可以退回到报告审核");
            }
            String reviewUrl = report.getReviewUrl();
            String reviewInspectionUrl = report.getReviewInspectionUrl();
            if (StringUtils.isBlank(reviewUrl)) {
                reviewUrl = prepareLegacyApprovedReportForReview(report,
                        StringUtils.isNotBlank(report.getUrlS()) ? report.getUrlS() : report.getUrl());
                if (StringUtils.isNotBlank(report.getInspectionUrl())) {
                    reviewInspectionUrl = prepareLegacyApprovedReportForReview(report, report.getInspectionUrl());
                }
            }
            int affected = insReportMapper.update(null, Wrappers.<InsReport>lambdaUpdate()
                    .eq(InsReport::getId, id)
                    .eq(InsReport::getIsRatify, 1)
                    .set(InsReport::getState, 1)
                    .set(InsReport::getIsExamine, RETURNED_STATUS)
                    .set(InsReport::getExamineTime, returnTime)
                    .set(InsReport::getExamineTell, reason)
                    .set(InsReport::getIsRatify, RETURNED_STATUS)
                    .set(InsReport::getRatifyTime, returnTime)
                    .set(InsReport::getRatifyTell, reason)
                    .set(InsReport::getUrlS, Objects.equals(reviewUrl, report.getUrl()) ? null : reviewUrl)
                    .set(StringUtils.isNotBlank(reviewInspectionUrl), InsReport::getInspectionUrl,
                            reviewInspectionUrl)
                    .set(InsReport::getReviewUrl, null)
                    .set(InsReport::getReviewInspectionUrl, null)
                    .set(InsReport::getTempUrlPdf, null)
                    .set(InsReport::getInspectionTempUrlPdf, null));
            if (affected != 1) {
                throw new ErrorException("报告状态已变化,请刷新后重试");
            }
            expireReportEditSessions(id);
            return true;
        }
        if (!Objects.equals(report.getState(), 1) && !Objects.equals(report.getIsRatify(), 1)) {
            throw new ErrorException("当前报告不在审核或已批准状态,不能退回检验任务");
        }
        InsOrder order = insOrderMapper.selectById(report.getInsOrderId());
        if (order == null) {
            throw new ErrorException("报告关联的检验单不存在");
        }
        boolean hadApproval = report.getIsRatify() != null;
        // 恢复订单主状态,否则检验任务查询要求 io.state=1 时仍无法找到该任务。
        insOrderMapper.update(null, Wrappers.<InsOrder>lambdaUpdate()
                .eq(InsOrder::getId, report.getInsOrderId())
                .set(InsOrder::getState, 1)
                .set(InsOrder::getInsState, 4));
        // 整份报告由订单下各试验室共同形成,退回后各检验室均回到可修改、重新提交复核的状态。
        insOrderStateMapper.update(null, Wrappers.<InsOrderState>lambdaUpdate()
                .eq(InsOrderState::getInsOrderId, report.getInsOrderId())
                .set(InsOrderState::getInsState, 4)
                .set(InsOrderState::getVerifyUser, null)
                .set(InsOrderState::getVerifyTell, reason));
        // 只撤销复核人任务,保留各检验室原检验人,避免退回后任务无人可见。
        insSampleUserMapper.delete(Wrappers.<InsSampleUser>lambdaQuery()
                .eq(InsSampleUser::getInsSampleId, report.getInsOrderId())
                .eq(InsSampleUser::getState, 1));
        // 报告暂时退出编制列表。原始数据复核通过后,现有流程会恢复报告并重新提交审核。
        insReportMapper.update(null, Wrappers.<InsReport>lambdaUpdate()
                .eq(InsReport::getId, id)
                .set(InsReport::getIsPass, 0));
                .set(InsReport::getIsPass, 0)
                .set(InsReport::getState, 0)
                .set(InsReport::getIsExamine, RETURNED_STATUS)
                .set(InsReport::getExamineTime, returnTime)
                .set(InsReport::getExamineTell, reason)
                .set(InsReport::getIsRatify, hadApproval ? RETURNED_STATUS : null)
                .set(InsReport::getRatifyTime, hadApproval ? returnTime : null)
                .set(InsReport::getRatifyTell, reason)
                .set(InsReport::getUrlS, null)
                .set(InsReport::getReviewUrl, null)
                .set(InsReport::getReviewInspectionUrl, null)
                .set(InsReport::getTempUrlPdf, null)
                .set(InsReport::getInspectionTempUrlPdf, null));
        return false;
        expireReportEditSessions(id);
        return true;
    }
    private String prepareLegacyApprovedReportForReview(InsReport report, String approvedUrl) {
        if (StringUtils.isBlank(approvedUrl)) {
            throw new ErrorException("历史批准报告文件不存在,无法退回报告审核");
        }
        try {
            Path source = Paths.get(approvedUrl.replace("/word", wordUrl));
            String fileName = "returned-" + UUID.randomUUID() + ".docx";
            Path target = Paths.get(wordUrl, fileName);
            Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
            ReportSealLayoutUtil.restoreForReview(target.toString());
            return "/word/" + fileName;
        } catch (Exception e) {
            log.error("历史批准报告恢复审核稿失败: {}", approvedUrl, e);
            throw new ErrorException("历史批准报告无法清除旧批准信息,请退回检验任务重新生成");
        }
    }
    private void recordReportReturn(InsReport report, String target, String reason,
                                    LocalDateTime returnTime) {
        recordReportReturn(report,
                Objects.equals(report.getIsRatify(), 1) ? "APPROVED" : "REPORT_REVIEW",
                target, reason, returnTime);
    }
    private void recordReportReturn(InsReport report, String fromStage, String target, String reason,
                                    LocalDateTime returnTime) {
        InsReportReturnRecord record = new InsReportReturnRecord();
        record.setReportId(report.getId());
        record.setInsOrderId(report.getInsOrderId());
        record.setFromStage(fromStage);
        record.setTargetStage(target);
        record.setReturnReason(reason.trim());
        record.setState(report.getState());
        record.setIsExamine(report.getIsExamine());
        record.setExamineTime(report.getExamineTime());
        record.setIsRatify(report.getIsRatify());
        record.setRatifyTime(report.getRatifyTime());
        record.setReportUrl(StringUtils.isNotBlank(report.getUrlS()) ? report.getUrlS() : report.getUrl());
        record.setInspectionUrl(report.getInspectionUrl());
        record.setTempUrlPdf(report.getTempUrlPdf());
        record.setInspectionTempUrlPdf(report.getInspectionTempUrlPdf());
        record.setOperatorId(SecurityUtils.getUserId().intValue());
        record.setCreateTime(returnTime);
        if (insReportReturnRecordMapper.insert(record) != 1) {
            throw new ErrorException("报告退回记录保存失败");
        }
    }
    private void expireReportEditSessions(Integer reportId) {
        insReportEditSessionMapper.update(null, Wrappers.<InsReportEditSession>lambdaUpdate()
                .eq(InsReportEditSession::getReportId, reportId)
                .in(InsReportEditSession::getStatus, "EDITING", "SAVED", "FAILED", "SAVE_REQUESTED")
                .set(InsReportEditSession::getStatus, "EXPIRED")
                .set(InsReportEditSession::getMessage, "报告已退回")
                .set(InsReportEditSession::getUpdateTime, LocalDateTime.now()));
    }
    /**