| | |
| | | import com.ruoyi.common.constant.MenuJumpPathConstants; |
| | | import com.ruoyi.common.core.domain.entity.InformationNotification; |
| | | import com.ruoyi.common.core.domain.entity.User; |
| | | import com.ruoyi.common.utils.DateImageUtil; |
| | | import com.ruoyi.common.utils.QueryWrappers; |
| | | import com.ruoyi.common.utils.SecurityUtils; |
| | | import com.ruoyi.common.utils.WxCpUtils; |
| | |
| | | import java.time.YearMonth; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.util.ArrayList; |
| | | import java.util.regex.Matcher; |
| | | import java.util.regex.Pattern; |
| | | import java.util.Base64; |
| | | import java.util.HashMap; |
| | | import java.util.LinkedHashSet; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | |
| | | throw new ErrorException("未找到被监督员:" + superviseDetails.getSupervisee()); |
| | | } |
| | | superviseDetails.setSupervisedUserId(user.getId()); |
| | | // 格式化时间 |
| | | superviseDetails.setSuperviseTime(detailsUpload.getSuperviseTime()); |
| | | // 统一日期格式,避免 Excel 把 2026-03-15 存成 2026/3/15 后无法解析 |
| | | superviseDetails.setSuperviseTime(normalizeSuperviseTime(detailsUpload.getSuperviseTime())); |
| | | |
| | | detailsUploadList.add(superviseDetails); |
| | | } |
| | |
| | | .set(QualitySupervise::getRatifyStatus, qualitySupervise.getRatifyStatus()) |
| | | .set(QualitySupervise::getRatifyTime, LocalDateTime.now()) |
| | | ); |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | | * 监督计划下发 |
| | | * @param qualitySupervise |
| | | * @return |
| | | */ |
| | | @Override |
| | | public boolean issueQualitySupervise(QualitySupervise qualitySupervise) { |
| | | if (qualitySupervise.getSuperviseId() == null) { |
| | | throw new ErrorException("缺少监督计划id"); |
| | | } |
| | | QualitySupervise supervise = baseMapper.selectById(qualitySupervise.getSuperviseId()); |
| | | if (supervise == null) { |
| | | throw new ErrorException("未找到监督计划"); |
| | | } |
| | | if (!Integer.valueOf(1).equals(supervise.getRatifyStatus())) { |
| | | throw new ErrorException("计划未批准,不能下发"); |
| | | } |
| | | if (Integer.valueOf(1).equals(supervise.getIssueStatus())) { |
| | | throw new ErrorException("计划已下发,不能重复下发"); |
| | | } |
| | | |
| | | baseMapper.update(null, Wrappers.<QualitySupervise>lambdaUpdate() |
| | | .eq(QualitySupervise::getSuperviseId, supervise.getSuperviseId()) |
| | | .set(QualitySupervise::getIssueStatus, 1) |
| | | .set(QualitySupervise::getIssueTime, LocalDateTime.now()) |
| | | ); |
| | | |
| | | // 当前登录用户 |
| | | Integer userId = SecurityUtils.getUserId().intValue(); |
| | | User user = userMapper.selectById(userId); |
| | | |
| | | // 接收人: 计划监督员 + 所有明细的被监督人(去重) |
| | | LinkedHashSet<Integer> receiveUserIds = new LinkedHashSet<>(); |
| | | if (StringUtils.isNotBlank(supervise.getRecordUserIds())) { |
| | | for (String idStr : supervise.getRecordUserIds().split(",")) { |
| | | if (StringUtils.isNotBlank(idStr)) { |
| | | receiveUserIds.add(Integer.valueOf(idStr.trim())); |
| | | } |
| | | } |
| | | } |
| | | List<QualitySuperviseDetails> detailsList = qualitySuperviseDetailsMapper.selectList(Wrappers.<QualitySuperviseDetails>lambdaQuery() |
| | | .eq(QualitySuperviseDetails::getSuperviseId, supervise.getSuperviseId())); |
| | | for (QualitySuperviseDetails detail : detailsList) { |
| | | if (detail.getSupervisedUserId() != null) { |
| | | receiveUserIds.add(detail.getSupervisedUserId()); |
| | | } |
| | | } |
| | | |
| | | // 消息发送 |
| | | for (Integer receiveUserId : receiveUserIds) { |
| | | InformationNotification info = new InformationNotification(); |
| | | // 发送人 |
| | | info.setCreateUser(user.getName()); |
| | | info.setMessageType("6"); |
| | | info.setTheme("CNAS质量监督计划下发通知"); |
| | | info.setContent(supervise.getSuperviseName() + " 质量监督计划已下发,请按计划开展质量监督,并及时填写日常监督检查记录"); |
| | | info.setSenderId(userId); |
| | | // 接收人 |
| | | info.setConsigneeId(receiveUserId); |
| | | info.setJumpPath(MenuJumpPathConstants.QUALITY_SUPERVISE); |
| | | informationNotificationService.addInformationNotification(info); |
| | | } |
| | | |
| | | // 发送企业微信通知 |
| | | threadPoolTaskExecutor.execute(() -> { |
| | | String message = ""; |
| | | message += "CNAS质量监督计划下发通知"; |
| | | message += "\n请去过程要求-质量监督计划"; |
| | | message += "\n" + supervise.getSuperviseName() + " 质量监督计划已下发,请按计划开展质量监督,并及时填写日常监督检查记录"; |
| | | for (Integer receiveUserId : receiveUserIds) { |
| | | User receiveUser = userMapper.selectById(receiveUserId); |
| | | if (receiveUser == null) { |
| | | continue; |
| | | } |
| | | //发送企业微信消息通知 |
| | | try { |
| | | WxCpUtils.inform(receiveUser.getAccount(), message, null); |
| | | } catch (Exception e) { |
| | | throw new RuntimeException(e); |
| | | } |
| | | } |
| | | }); |
| | | |
| | | return true; |
| | | } |
| | | |
| | |
| | | for (String userIdStr : userIds) { |
| | | // 转换为int类型 |
| | | Integer userId = Integer.valueOf(userIdStr); |
| | | // 获取人员签名对象 |
| | | PictureRenderData finalUserSignatureUrl = UserUtils.getFinalUserSignatureUrl(userId); |
| | | // 将人员签名对象添加到集合中 |
| | | recordUserDataList.add(finalUserSignatureUrl); |
| | | // 获取人员签名对象(缺失时兜底透明图, 避免导出失败) |
| | | recordUserDataList.add(signatureOrTransparent(userId)); |
| | | } |
| | | } |
| | | |
| | |
| | | int index = 1; |
| | | for (QualitySuperviseDetails qualitySuperviseDetail : qualitySuperviseDetails) { |
| | | qualitySuperviseDetail.setIndex(index); |
| | | qualitySuperviseDetail.setSuperviseFrequency(buildSuperviseFrequency(qualitySuperviseDetail.getSuperviseTime())); |
| | | qualitySuperviseDetail.setSuperviseTime(toMonthText(qualitySuperviseDetail.getSuperviseTime())); |
| | | index++; |
| | | } |
| | | |
| | |
| | | new HashMap<String, Object>() {{ |
| | | put("year", qualitySupervise.getSuperviseYear()); |
| | | put("superviseDetailList", qualitySuperviseDetails); |
| | | put("writeUrl", StringUtils.isNotBlank(writeUrl) ? Pictures.ofLocal(imgUrl + "/" + writeUrl).create() : null); |
| | | put("ratifyUrl", StringUtils.isNotBlank(finalRatifyUrl) ? Pictures.ofLocal(imgUrl + "/" + finalRatifyUrl).create() : null); |
| | | put("writeDateUrl", qualitySupervise.getWriteTime() != null ? |
| | | Pictures.ofStream(DateImageUtil.createDateImage(qualitySupervise.getWriteTime())).create() : null); |
| | | put("ratifyDateUrl", qualitySupervise.getRatifyTime() != null ? |
| | | Pictures.ofStream(DateImageUtil.createDateImage(qualitySupervise.getRatifyTime())).create() : null); |
| | | put("recordUserUrl1", recordUserDataList.get(0)); |
| | | put("recordUserUrl2", recordUserDataList.get(1)); |
| | | put("writeUrl", orTransparentPicture(StringUtils.isNotBlank(writeUrl) ? Pictures.ofLocal(imgUrl + "/" + writeUrl).create() : null)); |
| | | put("ratifyUrl", orTransparentPicture(StringUtils.isNotBlank(finalRatifyUrl) ? Pictures.ofLocal(imgUrl + "/" + finalRatifyUrl).create() : null)); |
| | | put("writeDateStr", qualitySupervise.getWriteTime() != null ? |
| | | qualitySupervise.getWriteTime().format(SUPERVISE_DATE_FORMATTER) : ""); |
| | | put("ratifyDateStr", qualitySupervise.getRatifyTime() != null ? |
| | | qualitySupervise.getRatifyTime().format(SUPERVISE_DATE_FORMATTER) : ""); |
| | | put("recordUserUrl1", orTransparentPicture(recordUserDataList.get(0))); |
| | | put("recordUserUrl2", orTransparentPicture(recordUserDataList.get(1))); |
| | | }}); |
| | | |
| | | try { |
| | |
| | | |
| | | } |
| | | |
| | | /** |
| | | * 统一监督日期格式为 yyyy.M: |
| | | * 2026-03-15 / 2026/3/15 / 2026.3.15 / 2026年3月15日 -> 2026.3; |
| | | * 已经是 2026.3 或 "3月、12月" 等写法保持原样。 |
| | | */ |
| | | private static String normalizeSuperviseTime(String superviseTime) { |
| | | if (StringUtils.isBlank(superviseTime)) { |
| | | return superviseTime; |
| | | } |
| | | String text = superviseTime.trim(); |
| | | Matcher matcher = Pattern.compile("^(\\d{4})\\s*[./年-]\\s*(\\d{1,2})(?:\\s*[./月-]\\s*(\\d{1,2})\\s*日?)?$") |
| | | .matcher(text); |
| | | if (matcher.matches()) { |
| | | int month = Integer.parseInt(matcher.group(2)); |
| | | if (month >= 1 && month <= 12) { |
| | | return matcher.group(1) + "." + month; |
| | | } |
| | | } |
| | | return text; |
| | | } |
| | | |
| | | /** |
| | | * 解析监督日期对应的年月,兼容历史数据中的完整日期写法。 |
| | | */ |
| | | private static YearMonth parseSuperviseYearMonth(String superviseTime, String fallbackYear) { |
| | | if (StringUtils.isNotBlank(superviseTime)) { |
| | | String first = superviseTime.split("[、,,;;]+")[0].trim(); |
| | | Matcher fullDate = Pattern.compile("(\\d{4})\\s*[./年-]\\s*(\\d{1,2})").matcher(first); |
| | | if (fullDate.find()) { |
| | | int month = Integer.parseInt(fullDate.group(2)); |
| | | if (month >= 1 && month <= 12) { |
| | | return YearMonth.of(Integer.parseInt(fullDate.group(1)), month); |
| | | } |
| | | } |
| | | // "3月、12月" 这类没有年份的写法,使用计划年度 |
| | | Matcher monthOnly = Pattern.compile("^(\\d{1,2})").matcher(first); |
| | | if (monthOnly.find()) { |
| | | int month = Integer.parseInt(monthOnly.group(1)); |
| | | if (month >= 1 && month <= 12) { |
| | | int year = LocalDate.now().getYear(); |
| | | if (StringUtils.isNotBlank(fallbackYear)) { |
| | | Matcher yearMatcher = Pattern.compile("(\\d{4})").matcher(fallbackYear); |
| | | if (yearMatcher.find()) { |
| | | year = Integer.parseInt(yearMatcher.group(1)); |
| | | } |
| | | } |
| | | return YearMonth.of(year, month); |
| | | } |
| | | } |
| | | } |
| | | return YearMonth.now(); |
| | | } |
| | | |
| | | /** |
| | | * 监督频次: 按监督时间中的月份个数计算, 如 "2026.3" -> "1次/年", "3月、12月" -> "2次/年" |
| | | */ |
| | | private static String buildSuperviseFrequency(String superviseTime) { |
| | | if (StringUtils.isBlank(superviseTime)) { |
| | | return ""; |
| | | } |
| | | int count = 0; |
| | | for (String part : normalizeSuperviseTime(superviseTime).split("[、,,;;/\\s]+")) { |
| | | if (part.matches(".*\\d.*")) { |
| | | count++; |
| | | } |
| | | } |
| | | return count > 0 ? count + "次/年" : ""; |
| | | } |
| | | |
| | | /** |
| | | * 监督时间: 表格中只展示月份, 如 "2026.3" -> "3月", "3月、12月" -> "3月、12月" |
| | | */ |
| | | private static String toMonthText(String superviseTime) { |
| | | if (StringUtils.isBlank(superviseTime)) { |
| | | return ""; |
| | | } |
| | | StringBuilder sb = new StringBuilder(); |
| | | for (String part : superviseTime.split("[、,,;;]+")) { |
| | | String s = normalizeSuperviseTime(part.trim()); |
| | | if (s.isEmpty()) { |
| | | continue; |
| | | } |
| | | int idx = s.lastIndexOf('.'); |
| | | String month = (idx >= 0 ? s.substring(idx + 1) : s).replaceAll("\\D", ""); |
| | | if (month.isEmpty() || month.length() > 2) { |
| | | return superviseTime; |
| | | } |
| | | month = month.replaceFirst("^0+(?=\\d)", ""); |
| | | if (sb.length() > 0) { |
| | | sb.append("、"); |
| | | } |
| | | sb.append(month).append("月"); |
| | | } |
| | | return sb.length() > 0 ? sb.toString() : superviseTime; |
| | | } |
| | | |
| | | /************************************************ 记录 ******************************************************/ |
| | | |
| | | /** |
| | |
| | | if (detailsRecord == null) { |
| | | // 查询详情信息 |
| | | QualitySuperviseDetails qualitySuperviseDetails = qualitySuperviseDetailsMapper.selectById(superviseDetailsId); |
| | | QualitySupervise qualitySupervise = baseMapper.selectById(qualitySuperviseDetails.getSuperviseId()); |
| | | // 查询上一个月第一条的记录信息, 判断被监督人去另外一个 |
| | | DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy.M"); |
| | | YearMonth yearMonth = YearMonth.parse(qualitySuperviseDetails.getSuperviseTime(), formatter); |
| | | // 监督日期兼容 2026.3 / 2026-03-15 / 2026/3/15 / 2026年3月15日 等写法 |
| | | YearMonth yearMonth = parseSuperviseYearMonth(qualitySuperviseDetails.getSuperviseTime(), |
| | | qualitySupervise == null ? null : qualitySupervise.getSuperviseYear()); |
| | | YearMonth previousYearMonth = yearMonth.minusMonths(1); |
| | | |
| | | LocalDate startDate = previousYearMonth.atDay(1); |
| | |
| | | |
| | | String supervisor = null; |
| | | |
| | | if (laseRecord != null) { |
| | | // 查询计划获取另一个监督员 |
| | | QualitySupervise qualitySupervise = baseMapper.selectById(qualitySuperviseDetails.getSuperviseId()); |
| | | if (StringUtils.isNotBlank(qualitySupervise.getRecordUserIds())) { |
| | | List<String> recordUserIds = StrUtil.split(qualitySupervise.getRecordUserIds(), ','); |
| | | List<User> users = userMapper.selectList(Wrappers.<User>lambdaQuery() |
| | | .in(User::getId, recordUserIds)); |
| | | if (CollectionUtils.isNotEmpty(users) && users.size() == 2) { |
| | | // 判断是否一样, 一样获取另外一个 |
| | | if (users.get(0).getName().equals(laseRecord.getSupervisor())) { |
| | | supervisor = users.get(1).getName(); |
| | | } else { |
| | | supervisor = users.get(0).getName(); |
| | | } |
| | | |
| | | // 计划指定的监督员:首次填写时默认带出第一位;有上月记录时两位监督员交替 |
| | | if (qualitySupervise != null && StringUtils.isNotBlank(qualitySupervise.getRecordUserIds())) { |
| | | List<String> recordUserIds = StrUtil.split(qualitySupervise.getRecordUserIds(), ','); |
| | | List<User> users = userMapper.selectList(Wrappers.<User>lambdaQuery() |
| | | .in(User::getId, recordUserIds)); |
| | | if (CollectionUtils.isNotEmpty(users)) { |
| | | if (laseRecord != null && users.size() > 1 |
| | | && users.get(0).getName().equals(laseRecord.getSupervisor())) { |
| | | supervisor = users.get(1).getName(); |
| | | } else { |
| | | supervisor = users.get(0).getName(); |
| | | } |
| | | } |
| | | } |
| | |
| | | detailsRecord.setSuperviseDetailsId(superviseDetailsId); |
| | | detailsRecord.setTestMember(qualitySuperviseDetails.getSupervisee()); |
| | | detailsRecord.setSupervisor(supervisor); |
| | | |
| | | detailsRecord.setPersonnel(SecurityUtils.getLoginUser().getUser().getNickName() + "有相应检测员的上岗证"); |
| | | detailsRecord.setEnvironment("温度:()℃ 湿度:()%"); |
| | | detailsRecord.setInspectionRecord("检测人员" + SecurityUtils.getLoginUser().getUser().getNickName() + "进行检测记录,记录内容真实有效"); |
| | | detailsRecord.setExaminingReport("由" + SecurityUtils.getLoginUser().getUser().getNickName() + "出示的检测报告符合规范要求"); |
| | | detailsRecord.setSupervisionEvaluation("检测按照要求进行,判定为满意"); |
| | | detailsRecord.setHandlingAdvice("/"); |
| | | // 默认监督检测日期为当天 |
| | | detailsRecord.setTestDate(LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy.M.d"))); |
| | | detailsRecord.setProblemDesc("无"); |
| | | |
| | | } |
| | | // 添加批准人名称 |
| | |
| | | } |
| | | |
| | | /** |
| | | * 导出监督记录表 |
| | | * 被监督人员确认监督记录(程序4.2.5: 被监督对象对监督记录真实性提出确认意见) |
| | | */ |
| | | @Override |
| | | public boolean confirmSuperviseRecord(QualitySuperviseDetailsRecord input) { |
| | | if (input == null || input.getSuperviseDetailsId() == null) { |
| | | throw new ErrorException("缺少监督详细信息id"); |
| | | } |
| | | QualitySuperviseDetailsRecord record = qualitySuperviseDetailsRecordMapper.selectOne( |
| | | Wrappers.<QualitySuperviseDetailsRecord>lambdaQuery() |
| | | .eq(QualitySuperviseDetailsRecord::getSuperviseDetailsId, input.getSuperviseDetailsId()) |
| | | .last("limit 1")); |
| | | if (record == null) { |
| | | throw new ErrorException("监督员尚未提交监督记录,无法确认"); |
| | | } |
| | | Integer userId = SecurityUtils.getUserId().intValue(); |
| | | User user = userMapper.selectById(userId); |
| | | if (user == null) { |
| | | throw new ErrorException("当前用户不存在"); |
| | | } |
| | | // 只有被监督人员本人才可以确认 |
| | | boolean isSupervisee = StrUtil.split(record.getTestMember(), '、').stream() |
| | | .map(String::trim) |
| | | .anyMatch(name -> name.equals(user.getName())); |
| | | if (!isSupervisee) { |
| | | QualitySuperviseDetails detail = qualitySuperviseDetailsMapper.selectById(input.getSuperviseDetailsId()); |
| | | isSupervisee = detail != null && userId.equals(detail.getSupervisedUserId()); |
| | | } |
| | | if (!isSupervisee) { |
| | | throw new ErrorException("只有被监督人员可以确认监督记录"); |
| | | } |
| | | String opinion = StringUtils.isBlank(input.getConfirmOpinion()) |
| | | ? "对监督记录真实性表示认可。" : input.getConfirmOpinion().trim(); |
| | | qualitySuperviseDetailsRecordMapper.update(null, |
| | | Wrappers.<QualitySuperviseDetailsRecord>lambdaUpdate() |
| | | .eq(QualitySuperviseDetailsRecord::getSuperviseDetailsRecordId, |
| | | record.getSuperviseDetailsRecordId()) |
| | | .set(QualitySuperviseDetailsRecord::getConfirmOpinion, opinion)); |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | | * 导出监督记录表(日常监督检查记录表) |
| | | * @param superviseDetailsId |
| | | * @param response |
| | | */ |
| | |
| | | public void exportSuperviseDetailRecord(Integer superviseDetailsId, HttpServletResponse response) { |
| | | QualitySuperviseDetailsRecord recordDto = qualitySuperviseDetailsRecordMapper.selectSuperviseDetailRecord(superviseDetailsId); |
| | | |
| | | // 查询检测人员 |
| | | User tserUser = new User(); |
| | | // 监督内容勾选展示串(照纸张表排布) |
| | | recordDto.setSuperviseContentStr(buildSuperviseContentStr(recordDto.getSuperviseContent())); |
| | | |
| | | // 受监督人多个时每行一个 |
| | | if (StringUtils.isNotBlank(recordDto.getTestMember())) { |
| | | tserUser = userMapper.selectOne(Wrappers.<User>lambdaQuery() |
| | | .eq(User::getName, recordDto.getTestMember()) |
| | | .last("limit 1")); |
| | | recordDto.setTestMember(recordDto.getTestMember().replace("、", "\n")); |
| | | } |
| | | |
| | | // 获取路径 |
| | | InputStream inputStream = this.getClass().getResourceAsStream("/static/supervision-detail-record.docx"); |
| | | InputStream inputStream = this.getClass().getResourceAsStream("/static/daily-supervise-record.docx"); |
| | | ConfigureBuilder builder = Configure.builder(); |
| | | builder.useSpringEL(true); |
| | | User finalTserUser = tserUser; |
| | | XWPFTemplate template = XWPFTemplate.compile(inputStream, builder.build()).render( |
| | | new HashMap<String, Object>() {{ |
| | | put("supervision", recordDto); |
| | | put("testMemberUrl", UserUtils.getFinalUserSignatureUrl(finalTserUser.getId())); |
| | | put("supervisoruUrl", UserUtils.getFinalUserSignatureUrl(recordDto.getSupervisor())); |
| | | put("technicalDirectorUrl", UserUtils.getFinalUserSignatureUrl(recordDto.getRatifyUserId())); |
| | | put("technicalDirectorDateUrl", recordDto.getRatifyTime() != null ? |
| | | Pictures.ofStream(DateImageUtil.createDateImage(recordDto.getRatifyTime())).create() : null); |
| | | put("supervisorUrl", signatureOrTransparent(recordDto.getSupervisor())); |
| | | put("qualityUrl", signatureOrTransparent(recordDto.getRatifyUserId())); |
| | | put("qualityDateStr", recordDto.getRatifyTime() != null ? |
| | | recordDto.getRatifyTime().format(RECORD_DATE_FORMATTER) : ""); |
| | | // 程序4.2.5: 被监督人员对监督记录真实性的确认意见, 未确认时沿用纸张表的默认表述 |
| | | put("confirmOpinionText", StringUtils.isNotBlank(recordDto.getConfirmOpinion()) |
| | | ? recordDto.getConfirmOpinion() : "对监督记录真实性表示认可。"); |
| | | put("superviseMethodText", StringUtils.isNotBlank(recordDto.getSuperviseMethod()) |
| | | ? recordDto.getSuperviseMethod() : ""); |
| | | }}); |
| | | |
| | | try { |
| | | response.setContentType("application/msword"); |
| | | String fileName = URLEncoder.encode( |
| | | "导出监督记录表", "UTF-8"); |
| | | "日常监督检查记录表", "UTF-8"); |
| | | response.setHeader("Content-disposition", |
| | | "attachment;filename=" + fileName + ".docx"); |
| | | OutputStream os = response.getOutputStream(); |
| | |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | throw new RuntimeException("导出失败"); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 监督内容(9项, 与受控记录表 LMSY-CX29-JL02 的横排顺序、文字完全一致) |
| | | */ |
| | | private static final String[] SUPERVISE_CONTENT_ITEMS = { |
| | | "人员持证及培训", "设施及环境条件", "检测方法", |
| | | "仪器设备管理", "测量溯源性", "标准物质管理", |
| | | "样品制备和处置", "检测数据处理", "结果报告结果" |
| | | }; |
| | | |
| | | /** |
| | | * 拼接"监督内容"勾选展示串, 每3项一行 |
| | | */ |
| | | private String buildSuperviseContentStr(String superviseContent) { |
| | | String content = StringUtils.isNotBlank(superviseContent) ? superviseContent : ""; |
| | | StringBuilder sb = new StringBuilder(); |
| | | for (int i = 0; i < SUPERVISE_CONTENT_ITEMS.length; i++) { |
| | | String item = SUPERVISE_CONTENT_ITEMS[i]; |
| | | sb.append(content.contains(item) ? "☑" : "□").append(item).append(" "); |
| | | if (i % 3 == 2) { |
| | | sb.append("\n"); |
| | | } |
| | | } |
| | | return sb.toString().trim(); |
| | | } |
| | | |
| | | /** |
| | | * 全透明 1x1 PNG(已验证像素 A=0, 拉伸到原标签尺寸后不可见), 图片缺失时兜底 |
| | | */ |
| | | private static final byte[] TRANSPARENT_PNG = Base64.getDecoder().decode( |
| | | "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAANSURBVBhXY2BgYGAAAAAFAAGKM+MAAAAAAElFTkSuQmCC"); |
| | | |
| | | /** |
| | | * 图片标签值兜底: null 或本地图片读取失败(如签名文件缺失)时返回透明图片 |
| | | */ |
| | | private PictureRenderData orTransparentPicture(PictureRenderData picture) { |
| | | if (picture != null) { |
| | | try { |
| | | byte[] data = picture.readPictureData(); |
| | | if (data != null && data.length > 0) { |
| | | return picture; |
| | | } |
| | | } catch (Exception ignored) { |
| | | } |
| | | } |
| | | return Pictures.ofBytes(TRANSPARENT_PNG).create(); |
| | | } |
| | | |
| | | /** |
| | | * 导出日期文字格式(如 2026年1月9日) |
| | | */ |
| | | private static final DateTimeFormatter SUPERVISE_DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy年M月d日"); |
| | | |
| | | /** |
| | | * 记录表日期文字格式(与表内"检测日期"字段一致, 如 2026.10.14) |
| | | */ |
| | | private static final DateTimeFormatter RECORD_DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy.M.d"); |
| | | |
| | | /** |
| | | * 用户签名兜底(按ID): 人员不存在或签名未配置时接口会直接抛错, 此处返回透明图片 |
| | | */ |
| | | private PictureRenderData signatureOrTransparent(Integer userId) { |
| | | try { |
| | | return orTransparentPicture(UserUtils.getFinalUserSignatureUrl(userId)); |
| | | } catch (Exception e) { |
| | | return Pictures.ofBytes(TRANSPARENT_PNG).create(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 用户签名兜底(按姓名): 人员不存在或签名未配置时接口会直接抛错, 此处返回透明图片 |
| | | */ |
| | | private PictureRenderData signatureOrTransparent(String userName) { |
| | | try { |
| | | return orTransparentPicture(UserUtils.getFinalUserSignatureUrl(userName)); |
| | | } catch (Exception e) { |
| | | return Pictures.ofBytes(TRANSPARENT_PNG).create(); |
| | | } |
| | | } |
| | | |
| | |
| | | XWPFTemplate template = XWPFTemplate.compile(inputStream, builder.build()).render( |
| | | new HashMap<String, Object>() {{ |
| | | put("control", exportDto); |
| | | put("discovererUrl", StringUtils.isNotBlank(finalDiscovererUrl) ? Pictures.ofLocal(imgUrl + "/" + finalDiscovererUrl).create() : null); |
| | | put("responsibleUrl", StringUtils.isNotBlank(finalResponsibleUrl) ? Pictures.ofLocal(imgUrl + "/" + finalResponsibleUrl).create() : null); |
| | | put("correctiveUrl", StringUtils.isNotBlank(finalCorrectiveUrl) ? Pictures.ofLocal(imgUrl + "/" + finalCorrectiveUrl).create() : null); |
| | | put("qualityUrl", StringUtils.isNotBlank(finalQualityUrl) ? Pictures.ofLocal(imgUrl + "/" + finalQualityUrl).create() : null); |
| | | put("discovererUrl", orTransparentPicture(StringUtils.isNotBlank(finalDiscovererUrl) ? Pictures.ofLocal(imgUrl + "/" + finalDiscovererUrl).create() : null)); |
| | | put("responsibleUrl", orTransparentPicture(StringUtils.isNotBlank(finalResponsibleUrl) ? Pictures.ofLocal(imgUrl + "/" + finalResponsibleUrl).create() : null)); |
| | | put("correctiveUrl", orTransparentPicture(StringUtils.isNotBlank(finalCorrectiveUrl) ? Pictures.ofLocal(imgUrl + "/" + finalCorrectiveUrl).create() : null)); |
| | | put("qualityUrl", orTransparentPicture(StringUtils.isNotBlank(finalQualityUrl) ? Pictures.ofLocal(imgUrl + "/" + finalQualityUrl).create() : null)); |
| | | }}); |
| | | |
| | | try { |
| | |
| | | XWPFTemplate template = XWPFTemplate.compile(inputStream, builder.build()).render( |
| | | new HashMap<String, Object>() {{ |
| | | put("correct", detailsCorrectDto); |
| | | put("raiseUrl", StringUtils.isNotBlank(finalRaiseUrl) ? Pictures.ofLocal(imgUrl + "/" + finalRaiseUrl).create() : null); |
| | | put("causeUrl", StringUtils.isNotBlank(finalCauseUrl) ? Pictures.ofLocal(imgUrl + "/" + finalCauseUrl).create() : null); |
| | | put("correctUrl", StringUtils.isNotBlank(finalCorrectUrl) ? Pictures.ofLocal(imgUrl + "/" + finalCorrectUrl).create() : null); |
| | | put("validationUrl", StringUtils.isNotBlank(finalValidationUrl) ? Pictures.ofLocal(imgUrl + "/" + finalValidationUrl).create() : null); |
| | | put("raiseUrl", orTransparentPicture(StringUtils.isNotBlank(finalRaiseUrl) ? Pictures.ofLocal(imgUrl + "/" + finalRaiseUrl).create() : null)); |
| | | put("causeUrl", orTransparentPicture(StringUtils.isNotBlank(finalCauseUrl) ? Pictures.ofLocal(imgUrl + "/" + finalCauseUrl).create() : null)); |
| | | put("correctUrl", orTransparentPicture(StringUtils.isNotBlank(finalCorrectUrl) ? Pictures.ofLocal(imgUrl + "/" + finalCorrectUrl).create() : null)); |
| | | put("validationUrl", orTransparentPicture(StringUtils.isNotBlank(finalValidationUrl) ? Pictures.ofLocal(imgUrl + "/" + finalValidationUrl).create() : null)); |
| | | }}); |
| | | |
| | | try { |