| New file |
| | |
| | | package com.estate.expert.admin.stat.report.builder; |
| | | |
| | | import com.alibaba.fastjson.JSONArray; |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.deepoove.poi.XWPFTemplate; |
| | | import com.deepoove.poi.config.Configure; |
| | | import com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy; |
| | | import com.estate.expert.admin.service.UserFileService; |
| | | import com.estate.expert.admin.stat.report.render.ChengduHengtaiPictureRenderPolicy; |
| | | import org.apache.poi.xwpf.usermodel.XWPFDocument; |
| | | import org.apache.poi.xwpf.usermodel.XWPFTableRow; |
| | | import org.springframework.core.io.ClassPathResource; |
| | | |
| | | import java.io.IOException; |
| | | import java.util.ArrayList; |
| | | import java.util.Map; |
| | | import java.util.Set; |
| | | import java.util.stream.Collectors; |
| | | |
| | | /** |
| | | * 成都衡泰第三方检查报告构建器 |
| | | */ |
| | | public class ChengduHengtaiSystemReportBuilder implements ReportBuilderBase { |
| | | |
| | | private static final String PICTURE_ROW_TITLE = "问题图片"; |
| | | |
| | | private static final int EMPTY_PICTURE_ROW_HEIGHT = 240; |
| | | |
| | | private static final String LEVEL_ONE = "一级隐患"; |
| | | |
| | | private static final String LEVEL_TWO = "二级隐患"; |
| | | |
| | | private static final Set<String> HAZARD_LEVELS = Set.of( |
| | | LEVEL_ONE, LEVEL_TWO, "三级隐患", "四级隐患"); |
| | | |
| | | private static final String LEVEL_ONE_COLOR = "FF0000"; |
| | | |
| | | private static final String LEVEL_TWO_COLOR = "FF9900"; |
| | | |
| | | private static final Set<String> HAZARD_TABLE_LABELS = Set.of( |
| | | "问题部位", "隐患等级", "问题描述", "违反条款", "建议措施", PICTURE_ROW_TITLE); |
| | | |
| | | private static final String TEMPLATE_FILE = "第三方检查报告-成都衡泰.docx"; |
| | | |
| | | private final UserFileService userFileService; |
| | | |
| | | public ChengduHengtaiSystemReportBuilder(UserFileService userFileService) { |
| | | this.userFileService = userFileService; |
| | | } |
| | | |
| | | @Override |
| | | public XWPFTemplate buildReport(JSONObject data, Map<String, Integer> templateConfig) { |
| | | var modelData = buildModelData(data); |
| | | var configure = Configure.builder() |
| | | .bind("data.评估结果.问题清单", new LoopRowTableRenderPolicy()) |
| | | .addPlugin('%', new ChengduHengtaiPictureRenderPolicy(userFileService)) |
| | | .build(); |
| | | var resource = new ClassPathResource("templates/" + TEMPLATE_FILE); |
| | | try { |
| | | var template = XWPFTemplate.compile(resource.getInputStream(), configure) |
| | | .render(Map.of("data", modelData)); |
| | | compactEmptyPictureRows(template.getXWPFDocument()); |
| | | applyHazardLevelColors(template.getXWPFDocument()); |
| | | return template; |
| | | } catch (IOException e) { |
| | | throw new RuntimeException(e); |
| | | } |
| | | } |
| | | |
| | | static void compactEmptyPictureRows(XWPFDocument document) { |
| | | for (var table : document.getTables()) { |
| | | var rows = table.getRows(); |
| | | for (int index = 0; index < rows.size() - 1; index++) { |
| | | if (isPictureTitleRow(rows.get(index)) && !containsPicture(rows.get(index + 1))) { |
| | | rows.get(index + 1).setHeight(EMPTY_PICTURE_ROW_HEIGHT); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | private static boolean isPictureTitleRow(XWPFTableRow row) { |
| | | return row.getTableCells().stream() |
| | | .flatMap(cell -> cell.getParagraphs().stream()) |
| | | .map(paragraph -> paragraph.getText().trim()) |
| | | .anyMatch(PICTURE_ROW_TITLE::equals); |
| | | } |
| | | |
| | | private static boolean containsPicture(XWPFTableRow row) { |
| | | return row.getTableCells().stream() |
| | | .flatMap(cell -> cell.getParagraphs().stream()) |
| | | .flatMap(paragraph -> paragraph.getRuns().stream()) |
| | | .anyMatch(run -> !run.getEmbeddedPictures().isEmpty()); |
| | | } |
| | | |
| | | static void applyHazardLevelColors(XWPFDocument document) { |
| | | for (var table : document.getTables()) { |
| | | var rows = table.getRows(); |
| | | for (int startIndex = 0; startIndex < rows.size(); startIndex++) { |
| | | var level = findHazardLevel(rows.get(startIndex)); |
| | | if (level == null) { |
| | | continue; |
| | | } |
| | | var endIndex = findNextHazardStartIndex(rows, startIndex + 1); |
| | | var color = findHazardLevelColor(level); |
| | | if (color != null) { |
| | | colorHazardRows(rows, startIndex, endIndex, color); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | private static String findHazardLevel(XWPFTableRow row) { |
| | | for (var cell : row.getTableCells()) { |
| | | var text = cell.getText().trim(); |
| | | if (HAZARD_LEVELS.contains(text)) { |
| | | return text; |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private static String findHazardLevelColor(String level) { |
| | | if (LEVEL_ONE.equals(level)) { |
| | | return LEVEL_ONE_COLOR; |
| | | } |
| | | if (LEVEL_TWO.equals(level)) { |
| | | return LEVEL_TWO_COLOR; |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private static int findNextHazardStartIndex(java.util.List<XWPFTableRow> rows, int startIndex) { |
| | | for (int index = startIndex; index < rows.size(); index++) { |
| | | if (findHazardLevel(rows.get(index)) != null) { |
| | | return index; |
| | | } |
| | | } |
| | | return rows.size(); |
| | | } |
| | | |
| | | private static void colorHazardRows(java.util.List<XWPFTableRow> rows, int startIndex, int endIndex, String color) { |
| | | for (int rowIndex = startIndex; rowIndex < endIndex; rowIndex++) { |
| | | for (var cell : rows.get(rowIndex).getTableCells()) { |
| | | if (HAZARD_TABLE_LABELS.contains(cell.getText().trim())) { |
| | | continue; |
| | | } |
| | | cell.getParagraphs().stream() |
| | | .flatMap(paragraph -> paragraph.getRuns().stream()) |
| | | .forEach(run -> run.setColor(color)); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private JSONObject buildModelData(JSONObject data) { |
| | | var modelData = new JSONObject(); |
| | | modelData.putAll(data); |
| | | modelData.put("设备概况", buildDeviceSummary(data.getJSONArray("设备分组信息"))); |
| | | |
| | | var evalInfo = new JSONObject(); |
| | | evalInfo.putAll(data.getJSONObject("评估结果")); |
| | | var checkItems = evalInfo.getJSONArray("检查项"); |
| | | evalInfo.put("项目涉及安全子项", buildSafetySubItemText(checkItems)); |
| | | evalInfo.put("问题清单", buildHazardItems(checkItems)); |
| | | modelData.put("评估结果", evalInfo); |
| | | return modelData; |
| | | } |
| | | |
| | | private String buildDeviceSummary(JSONArray deviceGroups) { |
| | | if (deviceGroups == null) { |
| | | return ""; |
| | | } |
| | | return deviceGroups.stream() |
| | | .map(JSONObject.class::cast) |
| | | .filter(group -> group.getString("设备类型") != null && !group.getString("设备类型").isBlank()) |
| | | .map(group -> group.getString("设备数量") + "台" + group.getString("设备类型")) |
| | | .collect(Collectors.joining(";")); |
| | | } |
| | | |
| | | private String buildSafetySubItemText(JSONArray checkItems) { |
| | | if (checkItems == null || checkItems.isEmpty()) { |
| | | return ""; |
| | | } |
| | | var safetyCheckItems = checkItems.stream() |
| | | .map(JSONObject.class::cast) |
| | | .filter(item -> { |
| | | var name = item.getString("名称"); |
| | | return name != null && name.contains("安全"); |
| | | }) |
| | | .collect(Collectors.toCollection(ArrayList::new)); |
| | | if (safetyCheckItems.isEmpty()) { |
| | | safetyCheckItems.add(checkItems.getJSONObject(0)); |
| | | } |
| | | |
| | | var subItemNames = new ArrayList<String>(); |
| | | for (var checkItem : safetyCheckItems) { |
| | | collectSubItemNames(checkItem, subItemNames); |
| | | } |
| | | return String.join("、", subItemNames); |
| | | } |
| | | |
| | | private void collectSubItemNames(JSONObject checkItem, ArrayList<String> subItemNames) { |
| | | var categories = checkItem.getJSONArray("分项评估"); |
| | | if (categories == null) { |
| | | return; |
| | | } |
| | | for (var category : categories) { |
| | | var subItems = ((JSONObject) category).getJSONArray("分项"); |
| | | if (subItems == null) { |
| | | continue; |
| | | } |
| | | for (var subItem : subItems) { |
| | | var name = ((JSONObject) subItem).getString("类别"); |
| | | if (name != null && !subItemNames.contains(name)) { |
| | | subItemNames.add(name); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | private JSONArray buildHazardItems(JSONArray checkItems) { |
| | | var hazardItems = new JSONArray(); |
| | | if (checkItems == null) { |
| | | return hazardItems; |
| | | } |
| | | int sequence = 1; |
| | | for (var item : checkItems) { |
| | | var categories = ((JSONObject) item).getJSONArray("分项评估"); |
| | | if (categories == null) { |
| | | continue; |
| | | } |
| | | for (var category : categories) { |
| | | var subItems = ((JSONObject) category).getJSONArray("分项"); |
| | | if (subItems == null) { |
| | | continue; |
| | | } |
| | | for (var subItem : subItems) { |
| | | var checkInfo = (JSONObject) subItem; |
| | | var problems = checkInfo.getJSONArray("问题"); |
| | | if (problems == null) { |
| | | continue; |
| | | } |
| | | for (var problem : problems) { |
| | | var hazard = new JSONObject(); |
| | | hazard.putAll((JSONObject) problem); |
| | | hazard.put("序号", sequence++); |
| | | hazard.put("检查分项", checkInfo.getString("类别")); |
| | | hazardItems.add(hazard); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | return hazardItems; |
| | | } |
| | | } |