فهرست منبع

Merge remote-tracking branch 'origin/master'

mamingxu 1 ماه پیش
والد
کامیت
be35334f74
21فایلهای تغییر یافته به همراه890 افزوده شده و 13 حذف شده
  1. 5 0
      easier-report-biz/pom.xml
  2. 5 2
      easier-report-biz/src/main/java/com/yaoyicloud/render/AbstractRender.java
  3. 45 0
      easier-report-biz/src/main/java/com/yaoyicloud/render/AntiBriberyRender.java
  4. 195 0
      easier-report-biz/src/main/java/com/yaoyicloud/render/AttachmentSectionRender.java
  5. 3 2
      easier-report-biz/src/main/java/com/yaoyicloud/render/AuditResultRender.java
  6. 3 2
      easier-report-biz/src/main/java/com/yaoyicloud/render/BasicInfoRender.java
  7. 225 0
      easier-report-biz/src/main/java/com/yaoyicloud/render/FinancialInfoRender.java
  8. 4 3
      easier-report-biz/src/main/java/com/yaoyicloud/render/PublicRecordRender.java
  9. 4 3
      easier-report-biz/src/main/java/com/yaoyicloud/render/ServiceProviderInfoRender.java
  10. 151 0
      easier-report-biz/src/main/java/com/yaoyicloud/tools/Util.java
  11. 112 1
      easier-report-biz/src/main/proto/fxy.proto
  12. 38 0
      easier-report-biz/src/test/java/com/yaoyicloud/render/test/TestAntiBriberyRender.java
  13. 42 0
      easier-report-biz/src/test/java/com/yaoyicloud/render/test/TestAttachmentSectionRender.java
  14. 3 0
      easier-report-biz/src/test/java/com/yaoyicloud/render/test/TestAuditResultRender.java
  15. 3 0
      easier-report-biz/src/test/java/com/yaoyicloud/render/test/TestBasicInfoRender.java
  16. 46 0
      easier-report-biz/src/test/java/com/yaoyicloud/render/test/TestFinancialInfoRender.java
  17. 3 0
      easier-report-biz/src/test/java/com/yaoyicloud/render/test/TestPublicRecordRender.java
  18. 3 0
      easier-report-biz/src/test/java/com/yaoyicloud/render/test/TestServiceProviderInfoRender.java
  19. BIN
      easier-report-biz/src/test/resources/docx/antiBribery.docx
  20. BIN
      easier-report-biz/src/test/resources/docx/attachments.docx
  21. BIN
      easier-report-biz/src/test/resources/docx/finance.docx

+ 5 - 0
easier-report-biz/pom.xml

@@ -68,6 +68,11 @@
             <artifactId>flying-saucer-pdf</artifactId>
             <version>9.12.0</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.pdfbox</groupId>
+            <artifactId>pdfbox</artifactId>
+            <version>3.0.5</version>
+        </dependency>
         <!-- iText 7 核心 -->
         <!--        <dependency>-->
         <!--            <groupId>com.itextpdf</groupId>-->

+ 5 - 2
easier-report-biz/src/main/java/com/yaoyicloud/render/AbstractRender.java

@@ -43,16 +43,19 @@ public abstract class AbstractRender {
     /**
      * Docx 渲染
      *
-     * @param info 数据
+     * @param jsonStr 数据
+     * @param addtionalMap 全局数据和动态增加的数据
      * @param templateFileContent 模板内容
      * @return 本地文件目录
      * @throws IOException
      */
-    public final String renderDocx(String jsonStr, byte[] templateFileContent, ConfigureBuilder builder, String outputPath) throws IOException {
+    public final String renderDocx(String jsonStr, Map<String, Object> addtionalMap, byte[] templateFileContent,
+        ConfigureBuilder builder, String outputPath) throws IOException {
 
         // 注: 报告模板的模板变量按照json序列化的结果命名
         ObjectMapper objectMapper = new ObjectMapper();
         Map<String, Object> data = objectMapper.readValue(jsonStr, new TypeReference<Map<String, Object>>() {});
+        data.putAll(addtionalMap);
 
         Configure config = builder.build();
         XWPFTemplate template =

+ 45 - 0
easier-report-biz/src/main/java/com/yaoyicloud/render/AntiBriberyRender.java

@@ -0,0 +1,45 @@
+package com.yaoyicloud.render;
+
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.Map;
+import java.util.UUID;
+
+import com.deepoove.poi.config.Configure;
+import com.deepoove.poi.config.ConfigureBuilder;
+import com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy;
+import com.google.protobuf.util.JsonFormat;
+import com.yaoyicloud.message.FxyProtos.AntiBribery;
+
+/**
+ * AntiBribery渲染器
+ *
+ */
+public final class AntiBriberyRender extends AbstractRender {
+
+    public AntiBriberyRender(String cwd) {
+        super(cwd);
+    }
+
+    /**
+     * Docx 渲染
+     *
+     * @param info 数据
+     * @param templateFileContent 模板内容
+     * @return 本地文件目录
+     * @throws IOException
+     */
+    public String renderDocx(AntiBribery info, Map<String, Object> addtionalMap, byte[] templateFileContent) throws IOException {
+        // 不需要定制展示逻辑的时候,使用protobuf的转json方法
+        String jsonStr = JsonFormat.printer().print(info);
+
+        // 注: 报告模板的模板变量按照json序列化的结果命名
+        // 注: 目前的实现假设:一个session对应一个cwd目录
+        ConfigureBuilder builder = Configure.builder();
+        builder.bind("questionnaireItems", new LoopRowTableRenderPolicy());
+        this.docxResultPath = this.renderDocx(jsonStr, addtionalMap, templateFileContent, builder,
+            Paths.get(cwd, UUID.randomUUID().toString() + ".docx").toString());
+        return this.docxResultPath;
+    }
+
+}

+ 195 - 0
easier-report-biz/src/main/java/com/yaoyicloud/render/AttachmentSectionRender.java

@@ -0,0 +1,195 @@
+package com.yaoyicloud.render;
+
+import java.net.URL;
+
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import javax.imageio.ImageIO;
+
+import org.apache.pdfbox.Loader;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.rendering.PDFRenderer;
+import org.apache.poi.xwpf.usermodel.XWPFRun;
+
+import com.deepoove.poi.XWPFTemplate;
+import com.deepoove.poi.config.Configure;
+import com.deepoove.poi.config.ConfigureBuilder;
+import com.deepoove.poi.data.ByteArrayPictureRenderData;
+import com.deepoove.poi.data.HyperlinkTextRenderData;
+import com.deepoove.poi.data.PictureType;
+import com.deepoove.poi.data.style.PictureStyle;
+import com.deepoove.poi.exception.RenderException;
+import com.deepoove.poi.policy.PictureRenderPolicy;
+import com.deepoove.poi.policy.TextRenderPolicy;
+import com.deepoove.poi.template.ElementTemplate;
+import com.deepoove.poi.template.run.RunTemplate;
+import com.deepoove.poi.xwpf.BodyContainer;
+import com.deepoove.poi.xwpf.BodyContainerFactory;
+import com.google.protobuf.Descriptors;
+import com.google.protobuf.util.JsonFormat;
+import com.yaoyicloud.message.FxyProtos.AttachmentSection;
+import cn.hutool.core.lang.Pair;
+import cn.hutool.core.util.StrUtil;
+
+/**
+ * AttachmentSection渲染器
+ *
+ */
+public final class AttachmentSectionRender extends AbstractRender {
+
+    public AttachmentSectionRender(String cwd) {
+        super(cwd);
+    }
+
+    /**
+     * Docx 渲染
+     *
+     * @param info 数据
+     * @param templateFileContent 模板内容
+     * @return 本地文件目录
+     * @throws IOException
+     */
+    public String renderDocx(AttachmentSection info, Map<String, Object> addtionalMap, byte[] templateFileContent)
+        throws IOException {
+
+        // 不需要定制展示逻辑的时候,使用protobuf的转json方法
+        String jsonStr = JsonFormat.printer().print(info);
+
+        // 定制缺省文字,POI-TL模板变量只支持if,不支持else
+        Map<String, Object> newAddtionMap = new HashMap<>();
+        Descriptors.Descriptor descriptor = info.getDescriptorForType();
+        for (Descriptors.FieldDescriptor field : descriptor.getFields()) {
+            if (field.isRepeated()) {
+                List<?> li = (List<?>) info.getField(field);
+                if (li.size() == 0) {
+                    newAddtionMap.put(field.getName() + "AltText", "未上传");
+                }
+            }
+        }
+        newAddtionMap.putAll(addtionalMap);
+
+        // 注: 报告模板的模板变量按照json序列化的结果命名
+        // 注: 目前的实现假设:一个session对应一个cwd目录
+        ConfigureBuilder builder = Configure.builder();
+
+        // 新增特殊处理变量
+        builder.addPlugin('^', new AttachmentRenderPolicy());
+
+        this.docxResultPath = this.renderDocx(jsonStr, newAddtionMap, templateFileContent, builder,
+            Paths.get(cwd, UUID.randomUUID().toString() + ".docx").toString());
+        return this.docxResultPath;
+    }
+
+    /**
+     * 这些render policy类都应当是共享的 重要设计假设: data的类型cast都可以建立在json通用反序列化后的基本类型基础上。
+     */
+    public class AttachmentRenderPolicy extends PictureRenderPolicy {
+
+        public AttachmentRenderPolicy() {}
+
+        @Override
+        public void render(ElementTemplate eleTemplate, Object data, XWPFTemplate template) {
+            @SuppressWarnings("unchecked")
+            Map<String, Object> mpData = (Map<String, Object>) data;
+            String filename = mpData.getOrDefault("fileName", "").toString().trim();
+            String url = mpData.getOrDefault("fileUri", "").toString();
+            float targetWidth = 456.5f;
+
+            if (StrUtil.isBlank(filename)) {
+                // uri render when no filename
+                TextRenderPolicy.Helper.renderTextRun(((RunTemplate) eleTemplate).getRun(),
+                    new HyperlinkTextRenderData(url, url));
+            } else if (filename.endsWith(".pdf")) {
+                // pdf render, replace data with bytestream
+                PDDocument document = null;
+                try {
+                    document = Loader.loadPDF(new URL(url).openStream().readAllBytes());
+                    PDFRenderer renderer = new PDFRenderer(document);
+
+                    XWPFRun run = ((RunTemplate) eleTemplate).getRun();
+                    BodyContainer bodyContainer = BodyContainerFactory.getBodyContainer(run);
+                    // 每页一张图片
+                    for (int i = 0; i < document.getNumberOfPages(); i++) {
+                        // pdf 转 jpeg
+                        BufferedImage image = renderer.renderImageWithDPI(i, 150);
+                        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+                        ImageIO.write(image, "jpg", stream);
+
+                        // 准备docx元素
+                        if (i == 0) {
+                            run.setText("", 0);
+                        } else {
+                            run = bodyContainer.insertNewParagraph(run).createRun();
+                        }
+
+                        // 准备POI-TL数据
+                        ByteArrayPictureRenderData picData =
+                            new ByteArrayPictureRenderData(stream.toByteArray(), PictureType.JPEG);
+                        Pair<Integer, Integer> targetSize =
+                            getTargetSize(image.getWidth(), image.getHeight(), targetWidth);
+                        PictureStyle style = new PictureStyle();
+                        style.setWidth(targetSize.getKey());
+                        style.setHeight(targetSize.getValue());
+                        picData.setPictureStyle(style);
+                        PictureRenderPolicy.Helper.renderPicture(run, picData);
+                    }
+                } catch (Exception e) {
+                    throw new RenderException(
+                        "AttachmentRenderPolicy for " + eleTemplate + " error: " + e.getMessage(), e);
+                } finally {
+                    if (document != null) {
+                        try {
+                            document.close();
+                        } catch (IOException e) {
+                            e.printStackTrace();
+                        }
+                    }
+                }
+            } else {
+                BufferedImage image = null;
+                ByteArrayOutputStream stream = null;
+                try {
+                    image = ImageIO.read(new URL(url));
+                    stream = new ByteArrayOutputStream();
+                    ImageIO.write(image, filename.substring(filename.lastIndexOf('.') + 1, filename.length()), stream);
+                } catch (IOException e) {
+                    throw new RenderException(
+                        "AttachmentRenderPolicy for " + eleTemplate + " error: " + e.getMessage(), e);
+                }
+
+                ByteArrayPictureRenderData picData =
+                    new ByteArrayPictureRenderData(stream.toByteArray(), PictureType.suggestFileType(filename));
+                Pair<Integer, Integer> targetSize = getTargetSize(image.getWidth(), image.getHeight(), targetWidth);
+                PictureStyle style = new PictureStyle();
+                style.setWidth(targetSize.getKey());
+                style.setHeight(targetSize.getValue());
+                picData.setPictureStyle(style);
+                super.render(eleTemplate, picData, template);
+            }
+        }
+
+        /**
+         * 这个方法也应该独立出去共享
+         *
+         * @param originWidth
+         * @param originHeight
+         * @param targetWidth
+         * @return
+         */
+        private static Pair<Integer, Integer> getTargetSize(int originWidth, int originHeight, float targetWidth) {
+            float targetHeight = (originHeight * targetWidth) / originWidth;
+            if (targetHeight > 645) {
+                targetHeight = 645;
+                targetWidth = (originWidth * targetHeight) / originHeight;
+            }
+            return Pair.of(Math.round(targetWidth), Math.round(targetHeight));
+        }
+    }
+}

+ 3 - 2
easier-report-biz/src/main/java/com/yaoyicloud/render/AuditResultRender.java

@@ -2,6 +2,7 @@ package com.yaoyicloud.render;
 
 import java.io.IOException;
 import java.nio.file.Paths;
+import java.util.Map;
 import java.util.UUID;
 
 import com.deepoove.poi.config.Configure;
@@ -37,7 +38,7 @@ public final class AuditResultRender extends AbstractRender {
      * @return 本地文件目录
      * @throws IOException
      */
-    public String renderDocx(AuditResult info, byte[] templateFileContent) throws IOException {
+    public String renderDocx(AuditResult info, Map<String, Object> addtionalMap, byte[] templateFileContent) throws IOException {
         ObjectMapper mapper = new ObjectMapper();
         SimpleModule module = new SimpleModule();
         module.addSerializer(new AuditResultSerializer(AuditResult.class));
@@ -51,7 +52,7 @@ public final class AuditResultRender extends AbstractRender {
         // 注: 目前的实现假设:一个session对应一个cwd目录
         ConfigureBuilder builder = Configure.builder();
         builder.bind("checkItemScores", new LoopRowTableRenderPolicy());
-        this.docxResultPath = this.renderDocx(jsonStr, templateFileContent, builder,
+        this.docxResultPath = this.renderDocx(jsonStr, addtionalMap, templateFileContent, builder,
             Paths.get(cwd, UUID.randomUUID().toString() + ".docx").toString());
         return this.docxResultPath;
     }

+ 3 - 2
easier-report-biz/src/main/java/com/yaoyicloud/render/BasicInfoRender.java

@@ -2,6 +2,7 @@ package com.yaoyicloud.render;
 
 import java.io.IOException;
 import java.nio.file.Paths;
+import java.util.Map;
 import java.util.UUID;
 
 import com.deepoove.poi.config.Configure;
@@ -28,7 +29,7 @@ public final class BasicInfoRender extends AbstractRender {
      * @return 本地文件目录
      * @throws IOException
      */
-    public String renderDocx(BasicInfo info, byte[] templateFileContent) throws IOException {
+    public String renderDocx(BasicInfo info, Map<String, Object> addtionalMap, byte[] templateFileContent) throws IOException {
         // 不需要定制展示逻辑的时候,使用protobuf的转json方法
         String jsonStr = JsonFormat.printer().print(info);
 
@@ -36,7 +37,7 @@ public final class BasicInfoRender extends AbstractRender {
         // 注: 目前的实现假设:一个session对应一个cwd目录
         ConfigureBuilder builder = Configure.builder();
         builder.bind("basicInfoChecks", new LoopRowTableRenderPolicy());
-        this.docxResultPath = this.renderDocx(jsonStr, templateFileContent, builder,
+        this.docxResultPath = this.renderDocx(jsonStr, addtionalMap, templateFileContent, builder,
             Paths.get(cwd, UUID.randomUUID().toString() + ".docx").toString());
         return this.docxResultPath;
     }

+ 225 - 0
easier-report-biz/src/main/java/com/yaoyicloud/render/FinancialInfoRender.java

@@ -0,0 +1,225 @@
+package com.yaoyicloud.render;
+
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import org.apache.poi.xwpf.usermodel.XWPFParagraph;
+import org.apache.poi.xwpf.usermodel.XWPFRun;
+import org.apache.poi.xwpf.usermodel.XWPFTable;
+import org.apache.poi.xwpf.usermodel.XWPFTableCell;
+import org.apache.poi.xwpf.usermodel.XWPFTableRow;
+import com.deepoove.poi.XWPFTemplate;
+import com.deepoove.poi.config.Configure;
+import com.deepoove.poi.config.ConfigureBuilder;
+import com.deepoove.poi.exception.RenderException;
+import com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy;
+import com.deepoove.poi.policy.RenderPolicy;
+import com.deepoove.poi.template.ElementTemplate;
+import com.deepoove.poi.template.run.RunTemplate;
+import com.deepoove.poi.util.TableTools;
+import com.google.protobuf.util.JsonFormat;
+import com.yaoyicloud.message.FxyProtos.FinancialInfo;
+import com.yaoyicloud.tools.Util;
+
+/**
+ * FinancialInfo渲染器
+ *
+ */
+public final class FinancialInfoRender extends AbstractRender {
+
+    public FinancialInfoRender(String cwd) {
+        super(cwd);
+    }
+
+    /**
+     * Docx 渲染
+     *
+     * @param info 数据
+     * @param templateFileContent 模板内容
+     * @return 本地文件目录
+     * @throws IOException
+     */
+    public String renderDocx(FinancialInfo info, Map<String, Object> addtionalMap, byte[] templateFileContent) throws IOException {
+
+        // 不需要定制展示逻辑的时候,使用protobuf的转json方法
+        String jsonStr = JsonFormat.printer().print(info);
+
+        // 注: 报告模板的模板变量按照json序列化的结果命名
+        // 注: 目前的实现假设:一个session对应一个cwd目录
+        ConfigureBuilder builder = Configure.builder();
+        builder.bind("indicators", new LoopRowIncludeStatisticsTableRenderPolicy("values"));
+        builder.bind("financialDataSeq", new LoopColumnStaticTableRenderPolicy("[", "]", false, true, 2));
+        builder.bind("financialCheckDetails", new LoopRowCutAndMergeFirstColTableRenderPolicy());
+        // 注意使用了SpringEL之后,每个模板变量都要设置值,不然会报错
+        builder.useSpringEL();
+
+        this.docxResultPath = this.renderDocx(jsonStr, addtionalMap, templateFileContent, builder,
+            Paths.get(cwd, UUID.randomUUID().toString() + ".docx").toString());
+        return this.docxResultPath;
+    }
+
+    /**
+     * 这些render policy类都应当是共享的 重要设计假设: data的类型cast都可以建立在json通用反序列化后的基本类型基础上。
+     */
+    public class LoopColumnStaticTableRenderPolicy implements RenderPolicy {
+
+        private String prefix;
+        private String suffix;
+        private boolean onSameLine;
+        private boolean reverse;
+        private int valColIndex;
+
+        public LoopColumnStaticTableRenderPolicy() {
+            this(false);
+        }
+
+        public LoopColumnStaticTableRenderPolicy(boolean onSameLine) {
+            this("[", "]", onSameLine);
+        }
+
+        public LoopColumnStaticTableRenderPolicy(String prefix, String suffix) {
+            this(prefix, suffix, false);
+        }
+
+        public LoopColumnStaticTableRenderPolicy(String prefix, String suffix, boolean onSameLine) {
+            this(prefix, suffix, onSameLine, false);
+        }
+
+        public LoopColumnStaticTableRenderPolicy(String prefix, String suffix, boolean onSameLine, boolean reverse) {
+            this(prefix, suffix, onSameLine, false, 1);
+        }
+
+        public LoopColumnStaticTableRenderPolicy(String prefix, String suffix, boolean onSameLine, boolean reverse,
+            int valRowIndex) {
+            this.prefix = prefix;
+            this.suffix = suffix;
+            this.onSameLine = onSameLine;
+            this.reverse = reverse;
+            this.valColIndex = valRowIndex;
+        }
+
+        @Override
+        public void render(ElementTemplate eleTemplate, Object data, XWPFTemplate template) {
+            RunTemplate runTemplate = (RunTemplate) eleTemplate;
+            XWPFRun run = runTemplate.getRun();
+            try {
+                if (!TableTools.isInsideTable(run)) {
+                    throw new IllegalStateException(
+                        "The template tag " + runTemplate.getSource() + " must be inside a table");
+                }
+                XWPFTableCell tagCell = (XWPFTableCell) ((XWPFParagraph) run.getParent()).getBody();
+                XWPFTable table = tagCell.getTableRow().getTable();
+                run.setText("", 0);
+
+                int templateColIndex = getTemplateColIndex(tagCell);
+                // 模版变量列总是写在左边
+                int minIndex = templateColIndex;
+                int maxIndex = table.getRows().get(valColIndex).getTableCells().size() - 1;
+                int currIndex = reverse ? maxIndex : minIndex;
+                int indexDelta = reverse ? -1 : 1;
+
+                // 目前expression当作数据Map的key来对待,将来可以当作POI-TL变量一致化的处理
+                Map<Integer, String> idx2Expression = new HashMap<>();
+                for (int i = 0; i < table.getRows().size(); i++) {
+                    XWPFTableCell cell = table.getRows().get(i).getCell(templateColIndex);
+                    String text = cell.getText().trim();
+                    if (text.startsWith(prefix) && text.endsWith(suffix)) {
+                        idx2Expression.put(i, text.substring(1, text.length() - 1));
+                        cell.setText("");
+                    }
+                }
+
+                int rowSize = table.getRows().size();
+                @SuppressWarnings("unchecked")
+                List<Map<String, Object>> mpData = (List<Map<String, Object>>) data;
+
+                for (Map<String, Object> realData : mpData) {
+                    for (int i = 0; i < rowSize; i++) {
+                        if (!idx2Expression.containsKey(i)) {
+                            continue;
+                        }
+                        XWPFTableRow row = table.getRow(i);
+                        XWPFTableCell valueCell = row.getCell(currIndex);
+                        String valStr = realData.getOrDefault(idx2Expression.get(i), "-").toString();
+                        valueCell.setText(valStr);
+                    }
+                    currIndex += indexDelta;
+                }
+
+            } catch (Exception e) {
+                throw new RenderException("HackLoopTable for " + eleTemplate + "error: " + e.getMessage(), e);
+            }
+        }
+
+        private int getTemplateColIndex(XWPFTableCell tagCell) {
+            return onSameLine ? Util.getColIndexOfFirstRow(tagCell) : (Util.getColIndexOfFirstRow(tagCell) + 1);
+        }
+    }
+
+    public class LoopRowCutAndMergeFirstColTableRenderPolicy extends LoopRowTableRenderPolicy {
+
+        @Override
+        public void render(ElementTemplate eleTemplate, Object data, XWPFTemplate template) {
+            RunTemplate runTemplate = (RunTemplate) eleTemplate;
+            XWPFRun run = runTemplate.getRun();
+            XWPFTable table = null;
+            try {
+                if (!TableTools.isInsideTable(run)) {
+                    throw new IllegalStateException(
+                        "The template tag " + runTemplate.getSource() + " must be inside a table");
+                }
+                // Reserve the first two rows
+                XWPFTableCell tagCell = (XWPFTableCell) ((XWPFParagraph) run.getParent()).getBody();
+                table = tagCell.getTableRow().getTable();
+                for (int i = table.getNumberOfRows() - 1; i > 1; i--) {
+                    table.removeRow(i);
+                }
+            } catch (Exception e) {
+                throw new RenderException(
+                    "LoopRowCutAndMergeFirstColTable for " + eleTemplate + " error: " + e.getMessage(), e);
+            }
+
+            // in case data not sorted by rank
+            @SuppressWarnings("unchecked")
+            List<Map<String, Object>> mpData = (List<Map<String, Object>>) data;
+            mpData.sort((a, b) -> Integer.valueOf(a.getOrDefault("rank", "0").toString())
+                - Integer.valueOf(b.getOrDefault("rank", "0").toString()));
+
+            super.render(eleTemplate, data, template);
+
+            try {
+                // merge the first column
+                Util.mergeFirstNColSimple(table, 1, 0);
+            } catch (Exception e) {
+                throw new RenderException(
+                    "LoopRowCutAndMergeFirstColTable for " + eleTemplate + " error: " + e.getMessage(), e);
+            }
+        }
+    }
+
+    public class LoopRowIncludeStatisticsTableRenderPolicy extends LoopRowTableRenderPolicy {
+
+        private String valueTag;
+
+        public LoopRowIncludeStatisticsTableRenderPolicy(String valueTag) {
+            this.valueTag = valueTag;
+        }
+
+        @Override
+        public void render(ElementTemplate eleTemplate, Object data, XWPFTemplate template) {
+            @SuppressWarnings("unchecked")
+            List<Map<String, Object>> mpData = (List<Map<String, Object>>) data;
+            for (Map<String, Object> row : mpData) {
+                @SuppressWarnings("unchecked")
+                List<String> values = (List<String>) row.get(valueTag);
+                row.put("avg", values.stream().mapToLong(Long::valueOf).average().orElse(Double.NaN));
+            }
+
+            super.render(eleTemplate, data, template);
+        }
+    }
+}

+ 4 - 3
easier-report-biz/src/main/java/com/yaoyicloud/render/PublicRecordRender.java

@@ -3,6 +3,7 @@ package com.yaoyicloud.render;
 import java.io.IOException;
 import java.nio.file.Paths;
 import java.util.List;
+import java.util.Map;
 import java.util.UUID;
 import java.util.stream.Collectors;
 
@@ -21,7 +22,7 @@ import com.google.protobuf.util.JsonFormat;
 import com.yaoyicloud.message.FxyProtos.PublicRecord;
 
 /**
- * ServiceProviderInfo渲染器
+ * PublicRecord渲染器
  *
  */
 public final class PublicRecordRender extends AbstractRender {
@@ -38,7 +39,7 @@ public final class PublicRecordRender extends AbstractRender {
      * @return 本地文件目录
      * @throws IOException
      */
-    public String renderDocx(PublicRecord info, byte[] templateFileContent) throws IOException {
+    public String renderDocx(PublicRecord info, Map<String, Object> addtionalMap, byte[] templateFileContent) throws IOException {
         // TODO: let mapper be package static
         ObjectMapper mapper = new ObjectMapper();
         SimpleModule module = new SimpleModule();
@@ -56,7 +57,7 @@ public final class PublicRecordRender extends AbstractRender {
         builder.bind("taxPenalties", new LoopRowTableRenderPolicy());
         builder.bind("severeViolations", new LoopRowTableRenderPolicy());
         this.docxResultPath =
-            this.renderDocx(jsonStr, templateFileContent, builder,
+            this.renderDocx(jsonStr, addtionalMap, templateFileContent, builder,
                 Paths.get(cwd, UUID.randomUUID().toString() + ".docx").toString());
         return this.docxResultPath;
     }

+ 4 - 3
easier-report-biz/src/main/java/com/yaoyicloud/render/ServiceProviderInfoRender.java

@@ -2,6 +2,7 @@ package com.yaoyicloud.render;
 
 import java.io.IOException;
 import java.nio.file.Paths;
+import java.util.Map;
 import java.util.UUID;
 
 import com.deepoove.poi.config.Configure;
@@ -33,7 +34,8 @@ public final class ServiceProviderInfoRender extends AbstractRender {
      * @return 本地文件目录
      * @throws IOException
      */
-    public String renderDocx(ServiceProviderInfo info, byte[] templateFileContent) throws IOException {
+    public String renderDocx(ServiceProviderInfo info, Map<String, Object> addtionalMap, byte[] templateFileContent)
+        throws IOException {
         ObjectMapper mapper = new ObjectMapper();
         SimpleModule module = new SimpleModule();
         module.addSerializer(new ServiceProviderInfoSerializer(ServiceProviderInfo.class));
@@ -44,7 +46,7 @@ public final class ServiceProviderInfoRender extends AbstractRender {
         // 注: 报告模板的模板变量按照json序列化的结果命名
         // 注: 目前的实现假设:一个session对应一个cwd目录
         this.docxResultPath =
-            this.renderDocx(jsonStr, templateFileContent, Configure.builder(),
+            this.renderDocx(jsonStr, addtionalMap, templateFileContent, Configure.builder(),
                 Paths.get(cwd, UUID.randomUUID().toString() + ".docx").toString());
         return this.docxResultPath;
     }
@@ -72,7 +74,6 @@ public final class ServiceProviderInfoRender extends AbstractRender {
             jgen.writeStringField("name", StrUtil.isBlank(value.getName()) ? "-" : value.getName());
             jgen.writeStringField("type", StrUtil.isBlank(value.getType()) ? "-" : value.getType());
             jgen.writeStringField("reportDate", value.getReportDate());
-            jgen.writeStringField("tenantName", value.getTenantName());
             jgen.writeEndObject();
         }
     }

+ 151 - 0
easier-report-biz/src/main/java/com/yaoyicloud/tools/Util.java

@@ -0,0 +1,151 @@
+package com.yaoyicloud.tools;
+
+import org.apache.poi.xwpf.usermodel.XWPFParagraph;
+import org.apache.poi.xwpf.usermodel.XWPFRun;
+import org.apache.poi.xwpf.usermodel.XWPFTable;
+import org.apache.poi.xwpf.usermodel.XWPFTableCell;
+import org.apache.poi.xwpf.usermodel.XWPFTableRow;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTDecimalNumber;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTc;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcPr;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.STVerticalJc;
+
+import com.deepoove.poi.util.TableTools;
+
+public final class Util {
+
+    /**
+     * 合并第n列
+     *
+     * @param table 表格对象
+     * @param sizeMap 任务汇总map
+     * @param count 合并前n列
+     */
+    public static void mergeFirstNCol(XWPFTable table, int n, int keySeq) {
+        if (table.getNumberOfRows() < 1) {
+            return;
+        }
+        int currentStart = 1;
+        for (int i = 1; i < table.getNumberOfRows() - 1; i++) { // 最后一行是合计
+            XWPFParagraph par = getParagraphForCell(table.getRow(i).getCell(keySeq));
+            String key = par.getRuns().get(0).getText(0);
+            for (int j = 0; j < n; j++) {
+                if (key.equals("")) {
+                    TableTools.mergeCellsVertically(table, j, currentStart, i);
+                } else {
+                    currentStart = i;
+                }
+                // 设置居中
+                verticalCentering(table.getRow(i).getCell(j));
+            }
+        }
+        int i = 0;
+        while (table.getRow(table.getNumberOfRows() - 1).getCell(i) != null) {
+            verticalCentering(table.getRow(table.getNumberOfRows() - 1).getCell(i));
+            i++;
+        }
+    }
+
+    public static void mergeFirstNColSimple(XWPFTable table, int n, int keySeq) {
+        if (table.getNumberOfRows() < 1) {
+            return;
+        }
+        int currentStart = 1;
+        String lastKey = "impossible_to_collide";
+        for (int i = 1; i < table.getNumberOfRows(); i++) {
+            XWPFParagraph par = getParagraphForCell(table.getRow(i).getCell(keySeq));
+            String key = par.getRuns().get(0).getText(0);
+            for (int j = 0; j < n; j++) {
+                if (lastKey.equals(key)) {
+                    // 清除被合并单元格内容
+                    getParagraphForCell(table.getRow(i).getCell(j)).getRuns().get(0).setText("");
+                    TableTools.mergeCellsVertically(table, j, currentStart, i);
+                } else {
+                    currentStart = i;
+                }
+                // 设置居中
+                verticalCentering(table.getRow(i).getCell(j));
+            }
+            lastKey = key;
+        }
+    }
+
+    /**
+     * 垂直居中
+     *
+     * @param cell 单元格
+     */
+    public static void verticalCentering(XWPFTableCell cell) {
+        CTTc cttc = cell.getCTTc();
+        CTTcPr ctPr = cttc.addNewTcPr();
+        ctPr.addNewVAlign().setVal(STVerticalJc.CENTER);
+        // cttc.getPList().get(0).addNewPPr().addNewJc().setVal(STJc.CENTER);
+    }
+
+    /**
+     *
+     * @param cell
+     * @param val
+     */
+    public static void setText(XWPFTableCell cell, String val) {
+        cell.setText(val);
+        verticalCentering(cell);
+    }
+
+    /**
+     * 合计染色
+     *
+     * @param cell
+     */
+    public static XWPFTableCell colorSummary(XWPFTableCell cell, String value) {
+        XWPFParagraph par = getParagraphForCell(cell);
+        XWPFRun run = par.createRun();
+        run.setColor("8FAADC");
+        run.setText(value);
+        verticalCentering(cell);
+        return cell;
+    }
+
+    /**
+     *
+     * @param cell
+     * @param value
+     */
+    public static void replaceLastRowText(XWPFTableCell cell, String value) {
+        if (cell.getParagraph(cell.getCTTc().getPArray(0)) != null
+            && cell.getParagraph(cell.getCTTc().getPArray(0)).getRuns().size() > 0
+            && cell.getParagraph(cell.getCTTc().getPArray(0)).getRuns().get(0) != null) {
+            cell.getParagraph(cell.getCTTc().getPArray(0)).getRuns().get(0).setText(value, 0);
+        } else {
+            cell.setText(value);
+        }
+    }
+
+    private static XWPFParagraph getParagraphForCell(XWPFTableCell cell) {
+        CTTc ctTc = cell.getCTTc();
+        CTP ctP = (ctTc.sizeOfPArray() == 0) ? ctTc.addNewP() : ctTc.getPArray(0);
+        return new XWPFParagraph(ctP, cell);
+    }
+
+    public static int getColIndexOfFirstRow(XWPFTableCell cell) {
+        XWPFTableRow tableRow = cell.getTableRow();
+        int orginalCol = 0;
+        for (int i = 0; i < tableRow.getTableCells().size(); i++) {
+            XWPFTableCell current = tableRow.getCell(i);
+            int intValue = 1;
+            CTTcPr tcPr = current.getCTTc().getTcPr();
+            if (null != tcPr) {
+                CTDecimalNumber gridSpan = tcPr.getGridSpan();
+                if (null != gridSpan) {
+                    intValue = gridSpan.getVal().intValue();
+                }
+            }
+            orginalCol += intValue;
+            if (current.getCTTc() == cell.getCTTc()) {
+                return orginalCol - intValue;
+            }
+        }
+        return -1;
+    }
+}

+ 112 - 1
easier-report-biz/src/main/proto/fxy.proto

@@ -7,7 +7,6 @@ message ServiceProviderInfo {
     optional string name = 1; // ${服务商信息:服务商名称}
     optional string type = 2; // "基金会"
     optional string reportDate = 3; // ${服务商信息:报告日期}
-    optional string tenantName = 4; // ${服务商信息:租户名称}
 }
 
 message CheckItemDetail {
@@ -18,6 +17,8 @@ message CheckItemDetail {
     optional int64 score = 5;      //
     optional string reviewResult = 6; // 复核结果
     optional int32 reviewScore = 7; // 复核评分
+    optional string category = 8;
+    optional int32 rank = 9;  // 展示排序
 }
 
 message CheckItemScore {
@@ -82,6 +83,7 @@ message Attachment {
     optional string fileUri = 2;
 }
 
+/* 通用基础信息 */
 message BasicInfo {
     optional string entName = 1;
     optional string establishmentDate = 2; // 成立登记日期(格式:yyyy-MM-dd)
@@ -213,3 +215,112 @@ message PublicRecord {
     repeated AdministrativeSeriousIllegal severeViolations = 5; // 严重违法记录(3.5)
     optional CheckSummary publicRecordSummary = 6; // 公共记录评分及建议(3.6)
 }
+
+message FinancialData {
+    optional int32 year = 1; // 年份(如2022、2023、2024)
+    optional string donationIncome = 2; // 捐赠收入(单位:元)
+    optional string publicExpense = 3; // 公益事业支出(单位:元)
+    optional string totalAssets = 4; // 总资产(单位:元) 资产合计
+    optional string netAssets = 5; // 净资产(单位:元)
+    optional string totalIncome = 6; // 总收入(单位:元) 收入合计
+    optional string investmentIncome = 7; // 投资收益(单位:元)
+    optional string governmentGrants = 8; // 政府补助收入(单位:元)
+    optional string serviceIncome = 9; // 服务收入(单位:元)
+    optional string totalExpense = 10; // 总支出(单位:元)
+    optional string salaryExpense = 11; // 工资福利支出(单位:元)
+    optional string adminExpense = 12; // 行政办公支出(单位:元)
+    optional string activityCost = 13; // 业务活动成本(单位:元)
+    optional string managementExpense = 14; // 管理费用(单位:元)
+    optional string fundraisingExpense = 15; // 筹资费用(单位:元)
+    optional string lastYearFundBalance = 16; // 上年基金余额(单位:元)
+
+    optional string flowAssets = 17; // 流动资产
+    optional string flowLiabilities = 18; // 流动负债
+    optional string flowCapital = 19; // 营运资本
+    optional string fixedAsset = 20; // 固定资产
+    optional string inventory = 21; // 存货
+    optional string receivables = 22; // 应收账款
+    optional string liabTotal = 23; // 总负债
+    optional string las3yTotAmtLiaEquMap = 24; // 所有者权益
+    optional string operatingIncome = 25; // 营业收入
+    optional string mainBusInc = 26; // 主营业务收入
+    optional string mainBusProfit = 27; // 营业利润
+    optional string netProfit = 28; // 净利润
+    optional string las3yTotProfMap = 29; // 利润总额
+    optional string interestExpense = 30; // 利息支出
+
+    //捐赠项目成本
+    optional string donationProjectCost= 31;
+    //净资产合计
+    optional string totalNetAssets = 32;
+    //费用合计
+    optional string totalCost = 33;
+}
+
+message FinancialIndicator {
+    optional string category = 1; // 分类(如"运营能力"、"发展能力")
+    optional string indicatorName = 2; // 指标名称(如"公益支出比例")财务指标
+    optional string formula = 3; // 计算公式(如"公益支出/上年基金余额")
+
+    repeated string values = 4;  //按年取值
+}
+
+message FinancialInfo {
+    repeated FinancialData financialDataSeq = 1; // 重要财务数据(4.1)
+    repeated string years = 4;   //按年取值
+    optional string remark = 5; // 备注
+    repeated FinancialIndicator indicators = 9; // 财务指标(4.2)
+    repeated CheckItemDetail financialCheckDetails = 10;
+    optional CheckSummary financialSummary = 11; // 财务信息评分及建议(4.3)
+    repeated Attachment financialFiles = 12; // 没有财务解析时的临时方案
+
+    // 平台报告新加字段
+    optional string operatingRevenue = 13; // 最近一年营业收入
+    optional string neProfit = 14; // 最近一年净利润
+}
+
+message QuestionnaireItem {
+    optional int32 id = 1;
+    optional string question = 2;
+    optional string answer = 3;
+}
+
+message AntiBribery {
+    repeated QuestionnaireItem questionnaireItems = 1; // 反贿赂反腐败诚信保证问卷
+    optional CheckSummary antiBriberySummary = 2; // 反贿赂反腐败诚信保证评分及建议
+}
+
+/* 包含所有可能的附件 */
+message AttachmentSection {
+    optional string selfDeclLink = 1;
+    repeated string fillDeclLinks = 2;
+
+    repeated Attachment businessLicenseImages = 3; // 营业执照图片路径
+
+    repeated Attachment bankCertificateImages = 4; // 银行开户证明图片路径
+
+    repeated Attachment isoCertificationImages = 5; // ISO认证证书图片路径
+
+    repeated Attachment otherCertifications = 6; // 其他资质文件路径
+
+    repeated Attachment securityLevelCertifications = 7; // 信息安全等级保护证明路径
+
+    repeated Attachment organizationalStructureImages = 8; // 组织架构图路径
+
+    repeated Attachment financialInfoLinks = 9; // 财务信息在线链接
+
+    repeated Attachment taxDeclarationImages = 10; // 增值税申报表路径
+
+    repeated Attachment taxPaymentCertificateImages = 11; // 完税凭证图片路径
+
+    repeated Attachment socialSecurityDetailsLinks = 12; // 社保缴纳证明路径
+
+    repeated Attachment nsxydjwj = 13; // 纳税信用等级相关支持性文件
+
+    repeated Attachment interestConflicts = 14; // 利益冲突
+
+    repeated Attachment disanfangcangzhaos = 15; // 第三方参照
+
+    repeated Attachment overallGuaranteeStatements = 16; // 总体保证声明
+
+}

+ 38 - 0
easier-report-biz/src/test/java/com/yaoyicloud/render/test/TestAntiBriberyRender.java

@@ -0,0 +1,38 @@
+package com.yaoyicloud.render.test;
+
+import static org.junit.Assert.assertTrue;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Map;
+
+import org.junit.Test;
+
+import com.yaoyicloud.message.FxyProtos.AntiBribery;
+import com.yaoyicloud.message.FxyProtos.CheckSummary;
+import com.yaoyicloud.message.FxyProtos.QuestionnaireItem;
+import com.yaoyicloud.render.AntiBriberyRender;
+
+public class TestAntiBriberyRender {
+
+    @Test
+    public void testRenderDocx() throws IOException {
+
+        byte[] content = Files
+            .readAllBytes(Paths.get(getClass().getClassLoader().getResource("docx/antiBribery.docx").getFile()));
+        AntiBriberyRender render = new AntiBriberyRender("../temp/");
+        String retPath = render.renderDocx(
+            AntiBribery.newBuilder()
+                .setAntiBriberySummary(CheckSummary.newBuilder().setRiskSummary("high risk").setSuggestion("accept it"))
+                .addQuestionnaireItems(
+                    QuestionnaireItem.newBuilder().setId(1).setQuestion("question1").setAnswer("answer1"))
+                .addQuestionnaireItems(
+                    QuestionnaireItem.newBuilder().setId(1).setQuestion("question2").setAnswer("answer2"))
+                .build(),
+            Map.of(),
+            content);
+
+        assertTrue(retPath.length() > 0);
+    }
+
+}

+ 42 - 0
easier-report-biz/src/test/java/com/yaoyicloud/render/test/TestAttachmentSectionRender.java

@@ -0,0 +1,42 @@
+package com.yaoyicloud.render.test;
+
+import static org.junit.Assert.assertTrue;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Map;
+
+import org.junit.Test;
+
+import com.yaoyicloud.message.FxyProtos.Attachment;
+import com.yaoyicloud.message.FxyProtos.AttachmentSection;
+import com.yaoyicloud.render.AttachmentSectionRender;
+
+public class TestAttachmentSectionRender {
+
+    @Test
+    public void testRenderDocx() throws IOException {
+
+        byte[] content = Files
+            .readAllBytes(Paths.get(getClass().getClassLoader().getResource("docx/attachments.docx").getFile()));
+        AttachmentSectionRender render = new AttachmentSectionRender("../temp/");
+        String retPath = render.renderDocx(
+            AttachmentSection.newBuilder()
+                .setSelfDeclLink("https://abc.com/111")
+                .addBusinessLicenseImages(Attachment.newBuilder().setFileName("").setFileUri("https://bcd.com"))
+                .addBusinessLicenseImages(Attachment.newBuilder().setFileName("yyc_past_logo.70f2a91c.png")
+                    .setFileUri("https://pre.yaoeasier.com/assets/yyc_past_logo.70f2a91c.png"))
+                .addBusinessLicenseImages(Attachment.newBuilder().setFileName(".pdf")
+                    .setFileUri(
+                        "https://yyc3-1321096020.cos.ap-beijing.myqcloud.com/declaration/20250522/1dfcf990ed6442abbf816d302298667b.pdf"
+                            + "?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20250523T052431Z&X-Amz-SignedHeaders=host&X-Amz-Expires=86400"
+                            + "&X-Amz-Credential=AKIDRilauFg4fDfE9B6tJoNxTvHSdovqXBfw%2F20250523%2Fap-beijing%2Fs3%2Faws4_request&X-Amz-Signature"
+                            + "=45ca610048b38dd4fa54bdd7b0b926b5e40864fda1b27556da51a8665526a848"))
+                .build(),
+            Map.of(),
+            content);
+
+        assertTrue(retPath.length() > 0);
+    }
+
+}

+ 3 - 0
easier-report-biz/src/test/java/com/yaoyicloud/render/test/TestAuditResultRender.java

@@ -4,6 +4,8 @@ import static org.junit.Assert.assertTrue;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Paths;
+import java.util.Map;
+
 import org.junit.Test;
 
 import com.yaoyicloud.message.FxyProtos.AuditResult;
@@ -27,6 +29,7 @@ public class TestAuditResultRender {
                 .addCheckItemScores(
                     CheckItemScore.newBuilder().setCategory("cate2").setItemName("name2").setScore(2L))
                 .build(),
+            Map.of(),
             content);
 
         assertTrue(retPath.length() > 0);

+ 3 - 0
easier-report-biz/src/test/java/com/yaoyicloud/render/test/TestBasicInfoRender.java

@@ -4,6 +4,8 @@ import static org.junit.Assert.assertTrue;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Paths;
+import java.util.Map;
+
 import org.junit.Test;
 
 import com.yaoyicloud.message.FxyProtos.BasicInfo;
@@ -27,6 +29,7 @@ public class TestBasicInfoRender {
                 .addBasicInfoChecks(
                     CheckItemDetail.newBuilder().setName("item2").setScore(2L))
                 .build(),
+            Map.of(),
             content);
 
         assertTrue(retPath.length() > 0);

+ 46 - 0
easier-report-biz/src/test/java/com/yaoyicloud/render/test/TestFinancialInfoRender.java

@@ -0,0 +1,46 @@
+package com.yaoyicloud.render.test;
+
+import static org.junit.Assert.assertTrue;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Map;
+
+import org.junit.Test;
+
+import com.yaoyicloud.message.FxyProtos.CheckItemDetail;
+import com.yaoyicloud.message.FxyProtos.CheckSummary;
+import com.yaoyicloud.message.FxyProtos.FinancialData;
+import com.yaoyicloud.message.FxyProtos.FinancialIndicator;
+import com.yaoyicloud.message.FxyProtos.FinancialInfo;
+import com.yaoyicloud.render.FinancialInfoRender;
+
+public class TestFinancialInfoRender {
+
+    @Test
+    public void testRenderDocx() throws IOException {
+
+        byte[] content = Files
+            .readAllBytes(Paths.get(getClass().getClassLoader().getResource("docx/finance.docx").getFile()));
+        FinancialInfoRender render = new FinancialInfoRender("../temp/");
+        String retPath = render.renderDocx(
+            FinancialInfo.newBuilder().setNeProfit("20").addYears("2024").addYears("2023").addYears("2022")
+                .setOperatingRevenue("999")
+                .setFinancialSummary(CheckSummary.newBuilder().setRiskSummary("high risk").setSuggestion("accept it"))
+                .addIndicators(
+                    FinancialIndicator.newBuilder().setCategory("cate1").setIndicatorName("name1").addValues("13")
+                        .addValues("14").addValues("15").setFormula("magic"))
+                .addFinancialDataSeq(FinancialData.newBuilder().setFixedAsset("1000").build())
+                .addFinancialCheckDetails(CheckItemDetail.newBuilder().setCategory("cate1").setName("n2")
+                    .setResult("r1").setScore(0).setReviewResult("rr1").setReviewScore(0).setRank(1))
+                .addFinancialCheckDetails(CheckItemDetail.newBuilder().setCategory("cate2").setName("n3")
+                    .setResult("r1").setScore(0).setReviewResult("rr1").setReviewScore(0).setRank(2))
+                .addFinancialCheckDetails(CheckItemDetail.newBuilder().setCategory("cate1").setName("n1")
+                    .setResult("r1").setScore(0).setReviewResult("rr1").setReviewScore(0).setRank(0))
+                .build(),
+            Map.of(),
+            content);
+
+        assertTrue(retPath.length() > 0);
+    }
+}

+ 3 - 0
easier-report-biz/src/test/java/com/yaoyicloud/render/test/TestPublicRecordRender.java

@@ -4,6 +4,8 @@ import static org.junit.Assert.assertTrue;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Paths;
+import java.util.Map;
+
 import org.junit.Test;
 
 import com.yaoyicloud.message.FxyProtos.CheckSummary;
@@ -25,6 +27,7 @@ public class TestPublicRecordRender {
                 .addDishonestPersons(DishonestPersonsInfo.newBuilder().setCourt("fake cout"))
                 .addDishonestPersons(DishonestPersonsInfo.newBuilder().setAmount("fake amount"))
                 .build(),
+            Map.of(),
             content);
 
         assertTrue(retPath.length() > 0);

+ 3 - 0
easier-report-biz/src/test/java/com/yaoyicloud/render/test/TestServiceProviderInfoRender.java

@@ -4,6 +4,8 @@ import static org.junit.Assert.assertTrue;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Paths;
+import java.util.Map;
+
 import org.junit.Test;
 
 import com.yaoyicloud.message.FxyProtos.ServiceProviderInfo;
@@ -20,6 +22,7 @@ public class TestServiceProviderInfoRender {
         ServiceProviderInfoRender render = new ServiceProviderInfoRender("../temp/");
         String retPath = render.renderDocx(
             ServiceProviderInfo.newBuilder().setName("宇宙公司").setType("服务商").build(),
+            Map.of(),
             content);
 
         assertTrue(retPath.length() > 0);

BIN
easier-report-biz/src/test/resources/docx/antiBribery.docx


BIN
easier-report-biz/src/test/resources/docx/attachments.docx


BIN
easier-report-biz/src/test/resources/docx/finance.docx