Procházet zdrojové kódy

Merge branch 'feature/report-business-setting-footer' of jyx/dcjxb.microservice into master

金逸霄 před 1 dnem
rodič
revize
794f210674
10 změnil soubory, kde provedl 370 přidání a 8 odebrání
  1. 9 1
      abilities/exam-sprint/application/src/main/java/cn/yunzhixue/ability/center/examsprint/application/report/AchievementReportContentMapper.java
  2. 104 0
      abilities/exam-sprint/application/src/test/java/cn/yunzhixue/ability/center/examsprint/application/report/AchievementReportContentMapperTest.java
  3. 61 1
      abilities/exam-sprint/contracts/src/main/java/cn/yunzhixue/ability/center/examsprint/contracts/report/AchievementExamSprintReportPayload.java
  4. 9 0
      abilities/exam-sprint/contracts/src/main/java/cn/yunzhixue/ability/center/examsprint/contracts/report/BusinessSetting.java
  5. 42 1
      abilities/exam-sprint/contracts/src/main/java/cn/yunzhixue/ability/center/examsprint/contracts/report/OutlookExamSprintReportPayload.java
  6. 34 1
      abilities/exam-sprint/domain/src/main/java/cn/yunzhixue/ability/center/examsprint/domain/report/AchievementReportContent.java
  7. 6 2
      abilities/exam-sprint/infrastructure/src/main/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/rendering/achievement/ClasspathAchievementExamSprintReportRenderer.java
  8. 7 2
      abilities/exam-sprint/infrastructure/src/main/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/rendering/outlook/ClasspathOutlookExamSprintReportRenderer.java
  9. 52 0
      abilities/exam-sprint/infrastructure/src/test/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/rendering/achievement/ClasspathAchievementExamSprintReportRendererTest.java
  10. 46 0
      abilities/exam-sprint/infrastructure/src/test/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/rendering/outlook/ClasspathOutlookExamSprintReportRendererTest.java

+ 9 - 1
abilities/exam-sprint/application/src/main/java/cn/yunzhixue/ability/center/examsprint/application/report/AchievementReportContentMapper.java

@@ -62,7 +62,15 @@ final class AchievementReportContentMapper {
                         format(payload.testPaperAfterUnMastery()),
                         format(payload.testPaperImprovedWordCount()),
                         hitWords(payload.testPaperImprovedWords())),
-                businessInfo(payload.businessInfo()));
+                businessInfo(payload.businessInfo()),
+                businessSetting(payload.businessSetting()));
+    }
+
+    private static AchievementReportContent.BusinessSetting businessSetting(cn.yunzhixue.ability.center.examsprint.contracts.report.BusinessSetting businessSetting) {
+        if (businessSetting == null) {
+            return null;
+        }
+        return new AchievementReportContent.BusinessSetting(businessSetting.showContactInFooter());
     }
 
     private static AchievementReportContent.BusinessInfo businessInfo(cn.yunzhixue.ability.center.examsprint.contracts.report.BusinessInfo businessInfo) {

+ 104 - 0
abilities/exam-sprint/application/src/test/java/cn/yunzhixue/ability/center/examsprint/application/report/AchievementReportContentMapperTest.java

@@ -1,11 +1,16 @@
 package cn.yunzhixue.ability.center.examsprint.application.report;
 
 import cn.yunzhixue.ability.center.examsprint.contracts.report.AchievementExamSprintReportPayload;
+import cn.yunzhixue.ability.center.examsprint.contracts.report.BusinessInfo;
+import cn.yunzhixue.ability.center.examsprint.contracts.report.BusinessSetting;
+import cn.yunzhixue.ability.center.examsprint.contracts.report.OutlookExamSprintReportPayload;
 import cn.yunzhixue.ability.center.examsprint.domain.report.AchievementReportContent;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import org.junit.jupiter.api.Test;
 
+import java.util.List;
+
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
@@ -135,6 +140,105 @@ class AchievementReportContentMapperTest {
         assertThat(content.businessInfo().address()).isEqualTo("浙江省杭州市");
     }
 
+    /** 覆盖调用方新增 BusinessSetting 场景,当 payload 指定页脚展示联系人开关时,应透传到成果报告领域内容。 */
+    @Test
+    void mapsBusinessSettingToAchievementContent() {
+        ObjectNode payload = pascalPayload();
+        payload.set("BusinessSetting", OBJECT_MAPPER.createObjectNode()
+                .put("ShowContactInFooter", 0));
+
+        AchievementReportContent content = AchievementReportContentMapper.toDomainContent(convert(payload));
+
+        assertThat(content.businessSetting()).isNotNull();
+        assertThat(content.businessSetting().showContactInFooter()).isZero();
+    }
+
+    /** 覆盖源码兼容构造器场景,新增 BusinessSetting 后仍应保留以 BusinessInfo 结尾的构造器。 */
+    @Test
+    void preservesPayloadConstructorEndingWithBusinessInfo() {
+        AchievementExamSprintReportPayload payload = payload();
+        BusinessInfo businessInfo = new BusinessInfo("张三", "138987484", "浙江省杭州市");
+
+        AchievementExamSprintReportPayload compatiblePayload = new AchievementExamSprintReportPayload(
+                payload.studentName(),
+                payload.studentStage(),
+                payload.stageName(),
+                payload.stageVocabulary(),
+                payload.studentVocabulary(),
+                payload.studentVocabularyBefore(),
+                payload.studentUnMastedWordCount(),
+                payload.studentImproveWordCount(),
+                payload.testPaperTitle(),
+                payload.testPaperWordCount(),
+                payload.testPaperBeforUnMastery(),
+                payload.testPaperBeforMastery(),
+                payload.testPaperLatestMastery(),
+                payload.testPaperAfterUnMastery(),
+                payload.testPaperImprovedWords(),
+                payload.testPaperImprovedWordCount(),
+                payload.testPaperImproveRate(),
+                payload.paperMasteryHitRate(),
+                payload.improveStudyEfficiency(),
+                payload.studentInitialVocabMastery(),
+                payload.studentCurrentVocabMastery(),
+                payload.studentVocabMasteryImprovement(),
+                payload.testPaperBeforMasteryRate(),
+                payload.testPaperLatestMasteryRate(),
+                payload.shouldDisplaySigningGuarantee(),
+                payload.signingGuarantee(),
+                businessInfo);
+
+        assertThat(compatiblePayload.businessInfo()).isSameAs(businessInfo);
+        assertThat(compatiblePayload.businessSetting()).isNull();
+    }
+
+    /** 覆盖展望报告源码兼容构造器场景,新增 BusinessSetting 后仍应保留以 BusinessInfo 结尾的构造器。 */
+    @Test
+    void preservesOutlookPayloadConstructorEndingWithBusinessInfo() {
+        BusinessInfo businessInfo = new BusinessInfo("张三", "138987484", "浙江省杭州市");
+
+        OutlookExamSprintReportPayload payload = new OutlookExamSprintReportPayload(
+                "吴泓妤",
+                3,
+                "高考",
+                3500,
+                2347,
+                "高考英语",
+                1200,
+                List.of(new OutlookExamSprintReportPayload.StudentWordLatest(
+                        1,
+                        2,
+                        "number",
+                        3,
+                        0.8,
+                        4,
+                        5,
+                        "2026-05-13T11:00:00")),
+                2347,
+                1153,
+                207,
+                List.of(1, 2, 3),
+                "2024真题",
+                207,
+                654,
+                861,
+                false,
+                businessInfo);
+
+        assertThat(payload.businessInfo()).isSameAs(businessInfo);
+        assertThat(payload.businessSetting()).isNull();
+    }
+
+    /** 覆盖 BusinessSetting 向前兼容场景,当 JSON 包含未知字段时,应忽略未知字段并保留 ShowContactInFooter。 */
+    @Test
+    void ignoresUnknownBusinessSettingFields() throws Exception {
+        BusinessSetting businessSetting = OBJECT_MAPPER.readValue(
+                "{\"ShowContactInFooter\":0,\"UnknownField\":\"ignored\"}",
+                BusinessSetting.class);
+
+        assertThat(businessSetting.showContactInFooter()).isZero();
+    }
+
     /** 覆盖空 payload 防御场景,当 mapper 收到 null 时,应抛出包含 payload 的 NullPointerException。 */
     @Test
     void rejectsNullPayload() {

+ 61 - 1
abilities/exam-sprint/contracts/src/main/java/cn/yunzhixue/ability/center/examsprint/contracts/report/AchievementExamSprintReportPayload.java

@@ -37,7 +37,8 @@ public record AchievementExamSprintReportPayload(
         @JsonProperty("TestPaperLatestMasteryRate") @NotNull BigDecimal testPaperLatestMasteryRate,
         @JsonProperty("ShouldDisplaySigningGuarantee") Boolean shouldDisplaySigningGuarantee,
         @JsonProperty("SigningGuarantee") String signingGuarantee,
-        @JsonProperty("BusinessInfo") BusinessInfo businessInfo) {
+        @JsonProperty("BusinessInfo") BusinessInfo businessInfo,
+        @JsonProperty("BusinessSetting") BusinessSetting businessSetting) {
 
     public AchievementExamSprintReportPayload(
             String studentName,
@@ -92,6 +93,65 @@ public record AchievementExamSprintReportPayload(
                 testPaperLatestMasteryRate,
                 shouldDisplaySigningGuarantee,
                 signingGuarantee,
+                null,
+                null);
+    }
+
+    public AchievementExamSprintReportPayload(
+            String studentName,
+            BigDecimal studentStage,
+            String stageName,
+            BigDecimal stageVocabulary,
+            BigDecimal studentVocabulary,
+            BigDecimal studentVocabularyBefore,
+            BigDecimal studentUnMastedWordCount,
+            BigDecimal studentImproveWordCount,
+            String testPaperTitle,
+            BigDecimal testPaperWordCount,
+            BigDecimal testPaperBeforUnMastery,
+            BigDecimal testPaperBeforMastery,
+            BigDecimal testPaperLatestMastery,
+            BigDecimal testPaperAfterUnMastery,
+            List<JsonNode> testPaperImprovedWords,
+            BigDecimal testPaperImprovedWordCount,
+            BigDecimal testPaperImproveRate,
+            BigDecimal paperMasteryHitRate,
+            BigDecimal improveStudyEfficiency,
+            BigDecimal studentInitialVocabMastery,
+            BigDecimal studentCurrentVocabMastery,
+            BigDecimal studentVocabMasteryImprovement,
+            BigDecimal testPaperBeforMasteryRate,
+            BigDecimal testPaperLatestMasteryRate,
+            Boolean shouldDisplaySigningGuarantee,
+            String signingGuarantee,
+            BusinessInfo businessInfo) {
+        this(studentName,
+                studentStage,
+                stageName,
+                stageVocabulary,
+                studentVocabulary,
+                studentVocabularyBefore,
+                studentUnMastedWordCount,
+                studentImproveWordCount,
+                testPaperTitle,
+                testPaperWordCount,
+                testPaperBeforUnMastery,
+                testPaperBeforMastery,
+                testPaperLatestMastery,
+                testPaperAfterUnMastery,
+                testPaperImprovedWords,
+                testPaperImprovedWordCount,
+                testPaperImproveRate,
+                paperMasteryHitRate,
+                improveStudyEfficiency,
+                studentInitialVocabMastery,
+                studentCurrentVocabMastery,
+                studentVocabMasteryImprovement,
+                testPaperBeforMasteryRate,
+                testPaperLatestMasteryRate,
+                shouldDisplaySigningGuarantee,
+                signingGuarantee,
+                businessInfo,
                 null);
     }
 }

+ 9 - 0
abilities/exam-sprint/contracts/src/main/java/cn/yunzhixue/ability/center/examsprint/contracts/report/BusinessSetting.java

@@ -0,0 +1,9 @@
+package cn.yunzhixue.ability.center.examsprint.contracts.report;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public record BusinessSetting(
+        @JsonProperty("ShowContactInFooter") Integer showContactInFooter) {
+}

+ 42 - 1
abilities/exam-sprint/contracts/src/main/java/cn/yunzhixue/ability/center/examsprint/contracts/report/OutlookExamSprintReportPayload.java

@@ -29,7 +29,48 @@ public record OutlookExamSprintReportPayload(
         @JsonProperty("TestPaperMastedWordCount") @NotNull @Min(0) Integer testPaperMastedWordCount,
         @JsonProperty("TestPaperWordCount") @NotNull @Min(0) Integer testPaperWordCount,
         @JsonProperty("Complex") @NotNull Boolean complex,
-        @JsonProperty("BusinessInfo") BusinessInfo businessInfo) {
+        @JsonProperty("BusinessInfo") BusinessInfo businessInfo,
+        @JsonProperty("BusinessSetting") BusinessSetting businessSetting) {
+
+    public OutlookExamSprintReportPayload(
+            String studentName,
+            Integer studentStage,
+            String stageName,
+            Integer stageVocabulary,
+            Integer studentVocabulary,
+            String stageExaminName,
+            Integer stageImportant,
+            List<StudentWordLatest> studentWordsLatest,
+            Integer mastedWordCount,
+            Integer unMastedWordCount,
+            Integer examineStrangeWordCount,
+            List<Integer> testPaperWordIdArray,
+            String testPaperTitle,
+            Integer testPaperUnMasterWordCount,
+            Integer testPaperMastedWordCount,
+            Integer testPaperWordCount,
+            Boolean complex,
+            BusinessInfo businessInfo) {
+        this(studentName,
+                studentStage,
+                stageName,
+                stageVocabulary,
+                studentVocabulary,
+                stageExaminName,
+                stageImportant,
+                studentWordsLatest,
+                mastedWordCount,
+                unMastedWordCount,
+                examineStrangeWordCount,
+                testPaperWordIdArray,
+                testPaperTitle,
+                testPaperUnMasterWordCount,
+                testPaperMastedWordCount,
+                testPaperWordCount,
+                complex,
+                businessInfo,
+                null);
+    }
 
     public record StudentWordLatest(
             @JsonProperty("WordId") @NotNull @Min(0) Integer wordId,

+ 34 - 1
abilities/exam-sprint/domain/src/main/java/cn/yunzhixue/ability/center/examsprint/domain/report/AchievementReportContent.java

@@ -15,7 +15,8 @@ public record AchievementReportContent(
         StageVocabularySummary stageVocabularySummary,
         TestPaperVocabularySummary testPaperVocabularySummary,
         ExamUnknownWordsHitStatus examUnknownWordsHitStatus,
-        BusinessInfo businessInfo) implements ReportContent {
+        BusinessInfo businessInfo,
+        BusinessSetting businessSetting) implements ReportContent {
 
     public AchievementReportContent {
         Objects.requireNonNull(summaryMetrics, "summaryMetrics");
@@ -49,6 +50,35 @@ public record AchievementReportContent(
                 stageVocabularySummary,
                 testPaperVocabularySummary,
                 examUnknownWordsHitStatus,
+                null,
+                null);
+    }
+
+    public AchievementReportContent(
+            String studentName,
+            String reportTitle,
+            String reportSubtitle,
+            String completionTitle,
+            String completionSubtitle,
+            SummaryMetrics summaryMetrics,
+            Comparison vocabularyComparison,
+            Comparison paperKnownWordsComparison,
+            StageVocabularySummary stageVocabularySummary,
+            TestPaperVocabularySummary testPaperVocabularySummary,
+            ExamUnknownWordsHitStatus examUnknownWordsHitStatus,
+            BusinessInfo businessInfo) {
+        this(studentName,
+                reportTitle,
+                reportSubtitle,
+                completionTitle,
+                completionSubtitle,
+                summaryMetrics,
+                vocabularyComparison,
+                paperKnownWordsComparison,
+                stageVocabularySummary,
+                testPaperVocabularySummary,
+                examUnknownWordsHitStatus,
+                businessInfo,
                 null);
     }
 
@@ -105,4 +135,7 @@ public record AchievementReportContent(
 
     public record BusinessInfo(String name, String phone, String address) {
     }
+
+    public record BusinessSetting(Integer showContactInFooter) {
+    }
 }

+ 6 - 2
abilities/exam-sprint/infrastructure/src/main/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/rendering/achievement/ClasspathAchievementExamSprintReportRenderer.java

@@ -128,7 +128,7 @@ public class ClasspathAchievementExamSprintReportRenderer implements ExamSprintR
         placeholders.put("hitStatusUnknownWordHitRateText", escape(formatPercentRatio(hitStatus.unknownWordHitRateText())));
         placeholders.put("hitStatusLearningEfficiencyText", escape(withUnit(hitStatus.learningEfficiencyText(), "倍")));
         placeholders.put("hitWords", renderHitWords(hitStatus));
-        placeholders.put("reportFooterBusiness", renderFooterBusiness(reportContent.businessInfo()));
+        placeholders.put("reportFooterBusiness", renderFooterBusiness(reportContent.businessInfo(), reportContent.businessSetting()));
         return placeholders;
     }
 
@@ -207,7 +207,11 @@ public class ClasspathAchievementExamSprintReportRenderer implements ExamSprintR
         return builder.toString();
     }
 
-    private String renderFooterBusiness(AchievementReportContent.BusinessInfo businessInfo) {
+    private String renderFooterBusiness(AchievementReportContent.BusinessInfo businessInfo,
+                                        AchievementReportContent.BusinessSetting businessSetting) {
+        if (businessSetting != null && Integer.valueOf(0).equals(businessSetting.showContactInFooter())) {
+            return "";
+        }
         if (businessInfo == null) {
             return "";
         }

+ 7 - 2
abilities/exam-sprint/infrastructure/src/main/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/rendering/outlook/ClasspathOutlookExamSprintReportRenderer.java

@@ -80,7 +80,7 @@ public class ClasspathOutlookExamSprintReportRenderer implements ExamSprintRepor
                     .replace("{{headerReportType}}", "潜力展望报告")
                     .replace("{{studentName}}", escape(payloadContract.studentName()))
                     .replace("{{generatedAtText}}", escape(formatGeneratedAt(generatedAt)))
-                    .replace("{{reportFooterBusiness}}", renderFooterBusiness(payloadContract.businessInfo()))
+                    .replace("{{reportFooterBusiness}}", renderFooterBusiness(payloadContract.businessInfo(), payloadContract.businessSetting()))
                     .replace("{{syllabusMasterySection}}", renderSyllabusMasteryChart(reportPayload.syllabusMasterySection()))
                     .replace("{{pastPaperVocabularySection}}", renderPastPaperVocabularyChart(reportPayload.pastPaperVocabularySection()))
                     .replace("{{highFrequencyVocabularySection}}", renderHighFrequencyVocabularyChart(reportPayload.highFrequencyVocabularySection()))
@@ -753,7 +753,12 @@ public class ClasspathOutlookExamSprintReportRenderer implements ExamSprintRepor
         return generatedAt == null ? "" : GENERATED_AT_FORMATTER.format(generatedAt);
     }
 
-    private String renderFooterBusiness(cn.yunzhixue.ability.center.examsprint.contracts.report.BusinessInfo businessInfo) {
+    private String renderFooterBusiness(
+            cn.yunzhixue.ability.center.examsprint.contracts.report.BusinessInfo businessInfo,
+            cn.yunzhixue.ability.center.examsprint.contracts.report.BusinessSetting businessSetting) {
+        if (businessSetting != null && Integer.valueOf(0).equals(businessSetting.showContactInFooter())) {
+            return "";
+        }
         if (businessInfo == null) {
             return "";
         }

+ 52 - 0
abilities/exam-sprint/infrastructure/src/test/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/rendering/achievement/ClasspathAchievementExamSprintReportRendererTest.java

@@ -181,6 +181,40 @@ class ClasspathAchievementExamSprintReportRendererTest {
                 .contains("浙江省杭州市");
     }
 
+    @Test
+    void renderOmitsBusinessInfoFooterWhenSettingDisablesContact() throws Exception {
+        ClasspathAchievementExamSprintReportRenderer renderer = new ClasspathAchievementExamSprintReportRenderer();
+        AchievementReportContent content = withBusinessSetting(
+                withBusinessInfo(sampleContent(),
+                        new AchievementReportContent.BusinessInfo("张三", "138987484", "浙江省杭州市")),
+                new AchievementReportContent.BusinessSetting(0));
+
+        String html = renderer.render(content, Instant.parse("2026-05-13T02:08:54.657335Z"));
+
+        assertThat(html)
+                .doesNotContain("report-footer-business")
+                .doesNotContain("张三")
+                .doesNotContain("138987484")
+                .doesNotContain("浙江省杭州市");
+    }
+
+    @Test
+    void renderShowsBusinessInfoFooterWhenSettingEnablesContact() throws Exception {
+        ClasspathAchievementExamSprintReportRenderer renderer = new ClasspathAchievementExamSprintReportRenderer();
+        AchievementReportContent content = withBusinessSetting(
+                withBusinessInfo(sampleContent(),
+                        new AchievementReportContent.BusinessInfo("张三", "138987484", "浙江省杭州市")),
+                new AchievementReportContent.BusinessSetting(1));
+
+        String html = renderer.render(content, Instant.parse("2026-05-13T02:08:54.657335Z"));
+
+        assertThat(html)
+                .contains("class=\"report-footer-business\"")
+                .contains("张三")
+                .contains("Tel:138987484")
+                .contains("浙江省杭州市");
+    }
+
     @Test
     void renderOmitsBusinessInfoFooterWhenMissing() throws Exception {
         ClasspathAchievementExamSprintReportRenderer renderer = new ClasspathAchievementExamSprintReportRenderer();
@@ -549,6 +583,24 @@ class ClasspathAchievementExamSprintReportRendererTest {
                 businessInfo);
     }
 
+    private AchievementReportContent withBusinessSetting(AchievementReportContent content,
+                                                         AchievementReportContent.BusinessSetting businessSetting) {
+        return new AchievementReportContent(
+                content.studentName(),
+                content.reportTitle(),
+                content.reportSubtitle(),
+                content.completionTitle(),
+                content.completionSubtitle(),
+                content.summaryMetrics(),
+                content.vocabularyComparison(),
+                content.paperKnownWordsComparison(),
+                content.stageVocabularySummary(),
+                content.testPaperVocabularySummary(),
+                content.examUnknownWordsHitStatus(),
+                content.businessInfo(),
+                businessSetting);
+    }
+
     private AchievementReportContent withStageAndTestPaperSummaries(
             AchievementReportContent content,
             AchievementReportContent.StageVocabularySummary stageVocabularySummary,

+ 46 - 0
abilities/exam-sprint/infrastructure/src/test/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/rendering/outlook/ClasspathOutlookExamSprintReportRendererTest.java

@@ -180,6 +180,52 @@ class ClasspathOutlookExamSprintReportRendererTest {
                 .contains("浙江省杭州市");
     }
 
+    /**
+     * 覆盖展望报告页脚联系方式按配置关闭的场景,当 ShowContactInFooter=0 时不应输出机构信息。
+     */
+    @Test
+    void renderOmitsBusinessInfoFooterWhenSettingDisablesContact() throws Exception {
+        ClasspathOutlookExamSprintReportRenderer renderer = new ClasspathOutlookExamSprintReportRenderer(OBJECT_MAPPER);
+        ObjectNode payload = (ObjectNode) callerVocabularyPayload();
+        payload.set("BusinessInfo", OBJECT_MAPPER.createObjectNode()
+                .put("Name", "张三")
+                .put("Phone", "138987484")
+                .put("Address", "浙江省杭州市"));
+        payload.set("BusinessSetting", OBJECT_MAPPER.createObjectNode()
+                .put("ShowContactInFooter", 0));
+
+        String html = renderer.render(unmodeledOutlookContent(payload), Instant.parse("2026-05-13T02:08:54.657335Z"));
+
+        assertThat(html)
+                .doesNotContain("report-footer-business")
+                .doesNotContain("张三")
+                .doesNotContain("138987484")
+                .doesNotContain("浙江省杭州市");
+    }
+
+    /**
+     * 覆盖展望报告页脚联系方式按配置开启的场景,当 ShowContactInFooter=1 时应输出机构信息。
+     */
+    @Test
+    void renderShowsBusinessInfoFooterWhenSettingEnablesContact() throws Exception {
+        ClasspathOutlookExamSprintReportRenderer renderer = new ClasspathOutlookExamSprintReportRenderer(OBJECT_MAPPER);
+        ObjectNode payload = (ObjectNode) callerVocabularyPayload();
+        payload.set("BusinessInfo", OBJECT_MAPPER.createObjectNode()
+                .put("Name", "张三")
+                .put("Phone", "138987484")
+                .put("Address", "浙江省杭州市"));
+        payload.set("BusinessSetting", OBJECT_MAPPER.createObjectNode()
+                .put("ShowContactInFooter", 1));
+
+        String html = renderer.render(unmodeledOutlookContent(payload), Instant.parse("2026-05-13T02:08:54.657335Z"));
+
+        assertThat(html)
+                .contains("class=\"report-footer-business\"")
+                .contains("张三")
+                .contains("Tel:138987484")
+                .contains("浙江省杭州市");
+    }
+
     @Test
     void renderOmitsBusinessInfoFooterWhenMissing() throws Exception {
         ClasspathOutlookExamSprintReportRenderer renderer = new ClasspathOutlookExamSprintReportRenderer(OBJECT_MAPPER);