liujie
2026-09-15 5a95ec36565d7ecc1a5ecb261f26752c90dec120
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
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;
    }
}