| | |
| | | import com.baomidou.mybatisplus.core.toolkit.Wrappers; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import com.deepoove.poi.XWPFTemplate; |
| | | import com.deepoove.poi.config.Configure; |
| | | 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.device.service.DeviceExaminePlanDetailsService; |
| | | import com.ruoyi.device.service.DeviceExaminePlanService; |
| | | import com.ruoyi.framework.exception.ErrorException; |
| | | import com.ruoyi.inspect.util.HackLoopTableRenderPolicy; |
| | | import com.ruoyi.inspect.util.UserUtils; |
| | | import com.ruoyi.system.mapper.UserMapper; |
| | | import com.ruoyi.system.service.InformationNotificationService; |
| | | import org.apache.poi.ss.usermodel.BorderStyle; |
| | | import org.apache.poi.ss.usermodel.Cell; |
| | | import org.apache.poi.ss.usermodel.CellStyle; |
| | | import org.apache.poi.ss.usermodel.Font; |
| | | import org.apache.poi.ss.usermodel.HorizontalAlignment; |
| | | import org.apache.poi.ss.usermodel.Row; |
| | | import org.apache.poi.ss.usermodel.Sheet; |
| | | import org.apache.poi.ss.usermodel.VerticalAlignment; |
| | | import org.apache.poi.ss.usermodel.Workbook; |
| | | import org.apache.poi.ss.util.CellRangeAddress; |
| | | import org.apache.poi.xssf.usermodel.XSSFWorkbook; |
| | | import org.springframework.beans.BeanUtils; |
| | | import org.springframework.beans.factory.annotation.Value; |
| | | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; |
| | |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | import java.io.File; |
| | | import java.io.IOException; |
| | | import java.io.InputStream; |
| | | import java.io.OutputStream; |
| | | import java.net.URLEncoder; |
| | | import java.time.LocalDate; |
| | |
| | | import java.util.ArrayList; |
| | | import java.util.HashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.stream.Collectors; |
| | | |
| | | /** |
| | |
| | | } |
| | | |
| | | /** |
| | | * 导出设备核查计划 |
| | | * 导出设备核查计划(Excel):有筛选条件时导出筛选结果,没有筛选时导出该年份全部 |
| | | * @param deviceExaminePlanId 设备核查计划id |
| | | * @param deviceName 设备名称筛选 |
| | | * @param deviceNumber 设备编号筛选 |
| | | * @param response 响应体 |
| | | */ |
| | | @Override |
| | | public void exportDeviceExaminePlanDetail(Integer deviceExaminePlanId, HttpServletResponse response) { |
| | | // 查询设备核查计划 |
| | | DeviceExaminePlanDto deviceExaminePlanDto = baseMapper.selectExamineExaminePlanDto(deviceExaminePlanId); |
| | | public void exportDeviceExaminePlanDetail(Integer deviceExaminePlanId, String deviceName, String deviceNumber, HttpServletResponse response) { |
| | | DeviceExaminePlan deviceExaminePlan = baseMapper.selectById(deviceExaminePlanId); |
| | | if (deviceExaminePlan == null) { |
| | | throw new ErrorException("设备核查计划不存在"); |
| | | } |
| | | DeviceExaminePlanDetails query = new DeviceExaminePlanDetails(); |
| | | query.setPlanId(deviceExaminePlanId); |
| | | query.setDeviceName(deviceName); |
| | | query.setDeviceNumber(deviceNumber); |
| | | List<DeviceExaminePlanDetailsDto> deviceExaminePlanDetailsDtoList = |
| | | deviceExaminePlanDetailsMapper.listDeviceExaminePlanDetail(QueryWrappers.queryWrappers(query)); |
| | | |
| | | // 查询设备核查计划详情 |
| | | List<DeviceExaminePlanDetails> deviceExaminePlanDetailsList = deviceExaminePlanDetailsMapper.selectList(Wrappers.<DeviceExaminePlanDetails>lambdaQuery().eq(DeviceExaminePlanDetails::getPlanId, deviceExaminePlanId) |
| | | .orderByAsc(DeviceExaminePlanDetails::getCheckTime)); |
| | | // 设置序号 |
| | | ArrayList<DeviceExaminePlanDetailsDto> deviceExaminePlanDetailsDtoList = new ArrayList<>(); |
| | | deviceExaminePlanDetailsList.forEach(deviceExamineRecordContrastDetail -> { |
| | | DeviceExaminePlanDetailsDto deviceExaminePlanDetailsDto = new DeviceExaminePlanDetailsDto(); |
| | | BeanUtils.copyProperties(deviceExamineRecordContrastDetail, deviceExaminePlanDetailsDto); |
| | | deviceExaminePlanDetailsDto.setIndex(deviceExaminePlanDetailsList.indexOf(deviceExamineRecordContrastDetail) + 1); |
| | | deviceExaminePlanDetailsDtoList.add(deviceExaminePlanDetailsDto); |
| | | String planName = StringUtils.isNotBlank(deviceExaminePlan.getPlanName()) ? deviceExaminePlan.getPlanName() : "设备核查计划"; |
| | | String[] columns = {"序号", "设备编号", "设备名称", "设备型号", "核查时间", "核查指标", |
| | | "核查方法", "结果如何判定", "核查负责人", "审核状态", "备注"}; |
| | | List<String[]> dataRows = new ArrayList<>(); |
| | | int index = 1; |
| | | for (DeviceExaminePlanDetailsDto detail : deviceExaminePlanDetailsDtoList) { |
| | | String checkTime = StringUtils.isNotBlank(detail.getCompleteDate()) ? detail.getCompleteDate() : detail.getCheckTime(); |
| | | dataRows.add(new String[]{ |
| | | String.valueOf(index++), |
| | | blankToEmpty(detail.getDeviceNumber()), |
| | | blankToEmpty(detail.getDeviceName()), |
| | | blankToEmpty(detail.getDeviceModel()), |
| | | blankToEmpty(checkTime), |
| | | blankToEmpty(detail.getCheckIndex()), |
| | | blankToEmpty(detail.getCheckMethod()), |
| | | blankToEmpty(detail.getHowResults()), |
| | | blankToEmpty(detail.getCheckChargerUser()), |
| | | recordStatusText(detail.getRecordStatus()), |
| | | blankToEmpty(detail.getRemark()) |
| | | }); |
| | | } |
| | | |
| | | // 获取路径 |
| | | InputStream inputStream = this.getClass().getResourceAsStream("/static/word/examine-plan-detail.docx"); |
| | | Configure configure = Configure.builder() |
| | | .bind("deviceExaminePlanDetailsDtoList", new HackLoopTableRenderPolicy()) |
| | | .build(); |
| | | XWPFTemplate template = XWPFTemplate.compile(inputStream, configure).render( |
| | | new HashMap<String, Object>() {{ |
| | | put("deviceExaminePlan", deviceExaminePlanDto); |
| | | put("deviceExaminePlanDetailsDtoList", deviceExaminePlanDetailsDtoList); |
| | | //获取核查人的签名地址 |
| | | put("writeUserUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceExaminePlanDto.getWriteUserId())); |
| | | //获取审查人的签名地址 |
| | | put("reviewUserUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceExaminePlanDto.getRatifyUserId())); |
| | | }}); |
| | | |
| | | response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); |
| | | response.setCharacterEncoding("UTF-8"); |
| | | try { |
| | | response.setContentType("application/msword"); |
| | | String fileName = URLEncoder.encode( |
| | | "设备核查计划", "UTF-8"); |
| | | response.setHeader("Content-disposition", |
| | | "attachment;filename=" + fileName + ".docx"); |
| | | OutputStream os = response.getOutputStream(); |
| | | template.write(os); |
| | | os.flush(); |
| | | os.close(); |
| | | inputStream.close(); |
| | | "attachment;filename=" + URLEncoder.encode(planName, "UTF-8") + ".xlsx"); |
| | | writePlanExcel(response, planName, columns, dataRows); |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | throw new RuntimeException("导出失败"); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 写计划表:标题行 + 列名 + 数据,整体只加最外边框,全部居中,无背景色 |
| | | */ |
| | | private void writePlanExcel(HttpServletResponse response, String planName, String[] columns, |
| | | List<String[]> dataRows) throws IOException { |
| | | Workbook workbook = new XSSFWorkbook(); |
| | | Map<String, CellStyle> styleCache = new HashMap<>(); |
| | | Font titleFont = buildExcelFont(workbook, true, 16); |
| | | Font headerFont = buildExcelFont(workbook, true, 12); |
| | | Font contentFont = buildExcelFont(workbook, false, 12); |
| | | |
| | | Sheet sheet = workbook.createSheet("设备核查计划"); |
| | | int lastColumn = columns.length - 1; |
| | | |
| | | Row titleRow = sheet.createRow(0); |
| | | titleRow.setHeightInPoints(30); |
| | | for (int c = 0; c <= lastColumn; c++) { |
| | | Cell cell = titleRow.createCell(c); |
| | | if (c == 0) { |
| | | cell.setCellValue(planName); |
| | | } |
| | | cell.setCellStyle(exportStyle(workbook, styleCache, titleFont, c == 0, c == lastColumn, true, false)); |
| | | } |
| | | sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, lastColumn)); |
| | | |
| | | Row headerRow = sheet.createRow(1); |
| | | headerRow.setHeightInPoints(22); |
| | | for (int c = 0; c <= lastColumn; c++) { |
| | | Cell cell = headerRow.createCell(c); |
| | | cell.setCellValue(columns[c]); |
| | | cell.setCellStyle(exportStyle(workbook, styleCache, headerFont, |
| | | c == 0, c == lastColumn, false, dataRows.isEmpty())); |
| | | } |
| | | |
| | | for (int r = 0; r < dataRows.size(); r++) { |
| | | Row row = sheet.createRow(r + 2); |
| | | boolean lastDataRow = r == dataRows.size() - 1; |
| | | for (int c = 0; c <= lastColumn; c++) { |
| | | Cell cell = row.createCell(c); |
| | | cell.setCellValue(dataRows.get(r)[c]); |
| | | cell.setCellStyle(exportStyle(workbook, styleCache, contentFont, |
| | | c == 0, c == lastColumn, false, lastDataRow)); |
| | | } |
| | | } |
| | | |
| | | for (int c = 0; c <= lastColumn; c++) { |
| | | int width = textWidth(columns[c]); |
| | | for (String[] row : dataRows) { |
| | | width = Math.max(width, textWidth(row[c])); |
| | | } |
| | | sheet.setColumnWidth(c, Math.min(255, width + 4) * 256); |
| | | } |
| | | |
| | | OutputStream os = response.getOutputStream(); |
| | | workbook.write(os); |
| | | os.flush(); |
| | | workbook.close(); |
| | | } |
| | | |
| | | private Font buildExcelFont(Workbook workbook, boolean bold, int size) { |
| | | Font font = workbook.createFont(); |
| | | font.setFontName("宋体"); |
| | | font.setBold(bold); |
| | | font.setFontHeightInPoints((short) size); |
| | | return font; |
| | | } |
| | | |
| | | /** |
| | | * 单元格样式:居中、无背景色;内部细网格线,整体最外圈粗边框 |
| | | */ |
| | | private CellStyle exportStyle(Workbook workbook, Map<String, CellStyle> styleCache, Font font, |
| | | boolean outerLeft, boolean outerRight, boolean outerTop, boolean outerBottom) { |
| | | String key = font.getBold() + "-" + font.getFontHeightInPoints() + "-" + (outerLeft ? 1 : 0) |
| | | + (outerRight ? 1 : 0) + (outerTop ? 1 : 0) + (outerBottom ? 1 : 0); |
| | | CellStyle cached = styleCache.get(key); |
| | | if (cached != null) { |
| | | return cached; |
| | | } |
| | | CellStyle style = workbook.createCellStyle(); |
| | | style.setFont(font); |
| | | style.setAlignment(HorizontalAlignment.CENTER); |
| | | style.setVerticalAlignment(VerticalAlignment.CENTER); |
| | | style.setBorderLeft(BorderStyle.THIN); |
| | | style.setBorderRight(BorderStyle.THIN); |
| | | style.setBorderTop(BorderStyle.THIN); |
| | | style.setBorderBottom(BorderStyle.THIN); |
| | | if (outerLeft) { |
| | | style.setBorderLeft(BorderStyle.MEDIUM); |
| | | } |
| | | if (outerRight) { |
| | | style.setBorderRight(BorderStyle.MEDIUM); |
| | | } |
| | | if (outerTop) { |
| | | style.setBorderTop(BorderStyle.MEDIUM); |
| | | } |
| | | if (outerBottom) { |
| | | style.setBorderBottom(BorderStyle.MEDIUM); |
| | | } |
| | | styleCache.put(key, style); |
| | | return style; |
| | | } |
| | | |
| | | /** |
| | | * 估算显示宽度(中文按两个字符宽) |
| | | */ |
| | | private int textWidth(String text) { |
| | | if (text == null) { |
| | | return 0; |
| | | } |
| | | int width = 0; |
| | | for (int i = 0; i < text.length(); i++) { |
| | | width += text.charAt(i) > 0xFF ? 2 : 1; |
| | | } |
| | | return width; |
| | | } |
| | | |
| | | /** |
| | | * 记录状态文案:0未开始、1待批准、2通过、3不通过 |
| | | */ |
| | | private String recordStatusText(Integer recordStatus) { |
| | | if (recordStatus == null) { |
| | | return "未开始"; |
| | | } |
| | | switch (recordStatus) { |
| | | case 1: |
| | | return "待批准"; |
| | | case 2: |
| | | return "通过"; |
| | | case 3: |
| | | return "不通过"; |
| | | default: |
| | | return "未开始"; |
| | | } |
| | | } |
| | | |
| | | private String blankToEmpty(String value) { |
| | | return value == null ? "" : value; |
| | | } |
| | | |
| | | @Override |
| | |
| | | } |
| | | } |
| | | return planDto; |
| | | } |
| | | |
| | | /** |
| | | * 核查计划年份列表:删除没有任何明细的空年份计划后,返回有明细的年份(倒序) |
| | | */ |
| | | @Override |
| | | public List<String> getExaminePlanYears() { |
| | | List<DeviceExaminePlan> plans = baseMapper.selectList(null); |
| | | for (DeviceExaminePlan plan : plans) { |
| | | Long detailCount = deviceExaminePlanDetailsMapper.selectCount(Wrappers.<DeviceExaminePlanDetails>lambdaQuery() |
| | | .eq(DeviceExaminePlanDetails::getPlanId, plan.getPlanId())); |
| | | if (detailCount == null || detailCount == 0) { |
| | | removeById(plan.getPlanId()); |
| | | } |
| | | } |
| | | return baseMapper.selectPlanYears(); |
| | | } |
| | | |
| | | /** |
| | |
| | | } |
| | | |
| | | /** |
| | | * 计划中尚无该设备本轮明细时自动补一条;核查时间取应核查月份 |
| | | * 计划中尚无该设备本轮明细时自动补一条;核查时间取档案下次核查日期(精确到日) |
| | | */ |
| | | private void fillExamineDetailIfAbsent(DeviceExaminePlan plan, Device device) { |
| | | LocalDate due = calcExamineDueDate(device); |
| | | if (due == null) { |
| | | return; |
| | | } |
| | | if (plan.getPlanYear() != null && String.valueOf(due.getYear()).compareTo(plan.getPlanYear()) > 0) { |
| | | if (StringUtils.isBlank(plan.getPlanYear())) { |
| | | return; |
| | | } |
| | | String checkTime = due.format(DateTimeFormatter.ofPattern("yyyy-MM")); |
| | | int planYearValue; |
| | | try { |
| | | planYearValue = Integer.parseInt(plan.getPlanYear().trim()); |
| | | } catch (NumberFormatException e) { |
| | | return; |
| | | } |
| | | int dueYear = due.getYear(); |
| | | // 未来年份的计划只登记应核查日期就在该年份的设备,避免把往年的旧日期补进未来的计划 |
| | | if (planYearValue > LocalDate.now().getYear()) { |
| | | if (dueYear != planYearValue) { |
| | | return; |
| | | } |
| | | } else if (dueYear > planYearValue) { |
| | | return; |
| | | } |
| | | String checkTime = due.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); |
| | | Long exist = deviceExaminePlanDetailsMapper.selectCount(Wrappers.<DeviceExaminePlanDetails>lambdaQuery() |
| | | .eq(DeviceExaminePlanDetails::getPlanId, plan.getPlanId()) |
| | | .eq(DeviceExaminePlanDetails::getDeviceId, device.getId()) |
| | |
| | | .eq(DeviceExamineRecord::getPlanDetailsId, planDetailsId)); |
| | | List<DeviceExamineRecordContrast> contrasts = deviceExamineRecordContrastMapper.selectList(Wrappers.<DeviceExamineRecordContrast>lambdaQuery() |
| | | .eq(DeviceExamineRecordContrast::getPlanDetailsId, planDetailsId)); |
| | | boolean hasReviewedRecord = records.stream().anyMatch(item -> item.getReviewStatus() != null |
| | | && (item.getReviewStatus() == 0 || item.getReviewStatus() == 1)); |
| | | boolean hasReviewedContrast = contrasts.stream().anyMatch(item -> item.getReviewStatus() != null |
| | | && (item.getReviewStatus() == 0 || item.getReviewStatus() == 1)); |
| | | if (hasReviewedRecord || hasReviewedContrast) { |
| | | throw new ErrorException("该计划已存在审核完成的核查记录,为保留历史记录不允许删除"); |
| | | // 审核不通过的记录允许连同明细一起删除;审核通过的记录为历史留档,不允许删除 |
| | | boolean hasPassedRecord = records.stream().anyMatch(item -> item.getReviewStatus() != null |
| | | && item.getReviewStatus() == 1); |
| | | boolean hasPassedContrast = contrasts.stream().anyMatch(item -> item.getReviewStatus() != null |
| | | && item.getReviewStatus() == 1); |
| | | if (hasPassedRecord || hasPassedContrast) { |
| | | throw new ErrorException("该计划已存在审核通过的核查记录,为保留历史记录不允许删除"); |
| | | } |
| | | if (!records.isEmpty()) { |
| | | List<Integer> recordIds = records.stream().map(DeviceExamineRecord::getRecordId).collect(Collectors.toList()); |