| | |
| | | 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.Base64; |
| | | import java.util.HashMap; |
| | | import java.util.LinkedHashSet; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | |
| | | } |
| | | |
| | | /** |
| | | * 监督计划下发 |
| | | * @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; |
| | | } |
| | | |
| | | /** |
| | | * 监督计划列表 |
| | | * @param page |
| | | * @param qualitySupervise |
| | |
| | | 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 { |
| | |
| | | } |
| | | |
| | | |
| | | } |
| | | |
| | | /** |
| | | * 监督频次: 按监督时间中的月份个数计算, 如 "2026.3" -> "1次/年", "3月、12月" -> "2次/年" |
| | | */ |
| | | private static String buildSuperviseFrequency(String superviseTime) { |
| | | if (StringUtils.isBlank(superviseTime)) { |
| | | return ""; |
| | | } |
| | | int count = 0; |
| | | for (String part : 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 = 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; |
| | | } |
| | | |
| | | /************************************************ 记录 ******************************************************/ |
| | |
| | | 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("无"); |
| | | |
| | | } |
| | | // 添加批准人名称 |
| | |
| | | } |
| | | |
| | | /** |
| | | * 导出监督记录表 |
| | | * 导出监督记录表(日常监督检查记录表) |
| | | * @param superviseDetailsId |
| | | * @param response |
| | | */ |
| | |
| | | public void exportSuperviseDetailRecord(Integer superviseDetailsId, HttpServletResponse response) { |
| | | QualitySuperviseDetailsRecord recordDto = qualitySuperviseDetailsRecordMapper.selectSuperviseDetailRecord(superviseDetailsId); |
| | | |
| | | // 查询检测人员 |
| | | User tserUser = new User(); |
| | | if (StringUtils.isNotBlank(recordDto.getTestMember())) { |
| | | tserUser = userMapper.selectOne(Wrappers.<User>lambdaQuery() |
| | | .eq(User::getName, recordDto.getTestMember()) |
| | | .last("limit 1")); |
| | | } |
| | | // 监督内容勾选展示串(照纸张表排布) |
| | | recordDto.setSuperviseContentStr(buildSuperviseContentStr(recordDto.getSuperviseContent())); |
| | | |
| | | // 获取路径 |
| | | 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) : ""); |
| | | }}); |
| | | |
| | | 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项, 按纸张表横排顺序) |
| | | */ |
| | | 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 { |