diff --git a/pom.xml b/pom.xml
index ad7ebcc..906438d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -47,6 +47,17 @@
${poi-tl.version}
+
+ org.springframework.boot
+ spring-boot-starter-jdbc
+
+
+
+ com.mysql
+ mysql-connector-j
+ runtime
+
+
org.springframework.boot
spring-boot-starter-test
diff --git a/src/main/java/com/gmp/mock/template/annualReport/AnnualReportDataService.java b/src/main/java/com/gmp/mock/template/annualReport/AnnualReportDataService.java
new file mode 100644
index 0000000..be10036
--- /dev/null
+++ b/src/main/java/com/gmp/mock/template/annualReport/AnnualReportDataService.java
@@ -0,0 +1,53 @@
+package com.gmp.mock.template.annualReport;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Component;
+
+/**
+ * 从数据库查询年度投资数据。
+ */
+@Component
+public class AnnualReportDataService {
+
+ private final JdbcTemplate jdbc;
+
+ public AnnualReportDataService(JdbcTemplate jdbc) {
+ this.jdbc = jdbc;
+ }
+
+ /**
+ * 按专业条线、年份汇总项目投资金额。
+ *
+ * @param years 年份列表,如 [2024, 2025, 2026]
+ * @return Map>
+ */
+ public Map> queryInvestmentByYear(List years) {
+ if (years.isEmpty()) return Map.of();
+
+ // 构建 IN 子句
+ String placeholders = years.stream().map(y -> "?").reduce((a, b) -> a + "," + b).orElse("");
+
+ String sql = String.format("""
+ SELECT PROJECT_TYPE_CODE, REPLY_YEAR, ROUND(SUM(PROJECT_AMOUNT), 0) AS total
+ FROM pc_ip_project_version
+ WHERE REPLY_YEAR IN (%s) AND IS_DELETED = 2
+ GROUP BY PROJECT_TYPE_CODE, REPLY_YEAR
+ ORDER BY PROJECT_TYPE_CODE, REPLY_YEAR
+ """, placeholders);
+
+ Object[] params = years.toArray();
+
+ Map> result = new HashMap<>();
+ jdbc.query(sql, params, rs -> {
+ String code = rs.getString("PROJECT_TYPE_CODE");
+ int year = rs.getInt("REPLY_YEAR");
+ long total = rs.getLong("total");
+ result.computeIfAbsent(code, k -> new HashMap<>()).put(year, total);
+ });
+ return result;
+ }
+}
diff --git a/src/main/java/com/gmp/mock/template/annualReport/AnnualReportProvider.java b/src/main/java/com/gmp/mock/template/annualReport/AnnualReportProvider.java
index c3d67e8..facee4b 100644
--- a/src/main/java/com/gmp/mock/template/annualReport/AnnualReportProvider.java
+++ b/src/main/java/com/gmp/mock/template/annualReport/AnnualReportProvider.java
@@ -6,7 +6,6 @@ import java.time.format.DateTimeFormatter;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
-import java.util.concurrent.ThreadLocalRandom;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFTable;
@@ -20,35 +19,49 @@ import com.gmp.mock.template.TemplateProvider;
/**
* 年度报告模板填充器。
*
- * 模板含一个固定表格,行结构如下:
- * Row 0: 表头(序号 | 专业类别 | 合计 | yearA | yearB | yearC)
- * Row 1: 总计(汇总行)
- * Row 2~11: 各投资分类(电网基建/小型基建/技改/科技/信息化)
+ * 模板含一个固定表格,数据从数据库查询,按专业条线 + 年份汇总。
*
- * postProcess() 遍历现有行,按行名填充生成的投资数据到单元格。
+ * 编码映射:
+ * 主网基建 ← TConstruct
+ * 配网基建 ← DEngineering
+ * 小型基建 ← SConstruct
+ * 生产技改 ← Imp
+ * 营销技改 ← MarketPM
+ * 其他技改 ← OImp
+ * 信息化项目 ← Info
+ * 科技项目 ← 无对应数据,填 0
*/
@Component
public class AnnualReportProvider implements TemplateProvider {
- /** 行索引 → 生成策略 */
- private static final List ROW_CONFIGS = List.of(
- new RowConfig(1, "总计", true), // 汇总行:自动求和
- new RowConfig(2, "电网基建", true), // 分类标题行
- new RowConfig(3, "主网基建", false),
- new RowConfig(4, "配网基建", false),
- new RowConfig(5, "小型基建", true), // 分类标题行(只有一项)
- new RowConfig(6, "技改项目", true), // 分类标题行
- new RowConfig(7, "生产技改", false),
- new RowConfig(8, "营销技改", false),
- new RowConfig(9, "其他技改", false),
- new RowConfig(10, "科技项目", true), // 分类标题行(只有一项)
- new RowConfig(11, "信息化项目", true) // 分类标题行(只有一项)
- );
+ /** 行索引 → 对应 PROJECT_TYPE_CODE(null 表示标题汇总行或无数据) */
+ private static final Map ROW_TO_CODE = new LinkedHashMap<>();
+ static {
+ ROW_TO_CODE.put(3, "TConstruct"); // 主网基建
+ ROW_TO_CODE.put(4, "DEngineering"); // 配网基建
+ ROW_TO_CODE.put(5, "SConstruct"); // 小型基建
+ ROW_TO_CODE.put(7, "Imp"); // 生产技改
+ ROW_TO_CODE.put(8, "MarketPM"); // 营销技改
+ ROW_TO_CODE.put(9, "OImp"); // 其他技改
+ ROW_TO_CODE.put(10, "Tech"); // 科技项目
+ ROW_TO_CODE.put(11, "Info"); // 信息化项目
+ }
- /** 非标题行的行索引列表(需要实际填充数据) */
- private static final List DATA_ROW_INDEXES = List.of(3, 4, 7, 8, 9, 10, 11);
+ /** 分类标题行 → 对应的数据行索引列表 */
+ private static final Map> CATEGORY_CHILDREN = new LinkedHashMap<>();
+ static {
+ CATEGORY_CHILDREN.put(2, List.of(3, 4)); // 电网基建
+ CATEGORY_CHILDREN.put(5, List.of(5)); // 小型基建
+ CATEGORY_CHILDREN.put(6, List.of(7, 8, 9)); // 技改项目
+ CATEGORY_CHILDREN.put(10, List.of(10)); // 科技项目
+ CATEGORY_CHILDREN.put(11, List.of(11)); // 信息化项目
+ }
- private record RowConfig(int index, String name, boolean isHeader) {}
+ private final AnnualReportDataService dataService;
+
+ public AnnualReportProvider(AnnualReportDataService dataService) {
+ this.dataService = dataService;
+ }
@Override
public String getTemplateId() {
@@ -97,54 +110,61 @@ public class AnnualReportProvider implements TemplateProvider {
if (tables.isEmpty()) return;
XWPFTable table = tables.get(0);
- // 1. 为每个数据行生成随机投资数据
+ List years = List.of(2024, 2025, 2026);
+ Map> rawData = dataService.queryInvestmentByYear(years);
+
+ // 1. 计算每个数据行
Map rowData = new LinkedHashMap<>();
- long grandTotal = 0;
- long grandYearA = 0;
- long grandYearB = 0;
- long grandYearC = 0;
- // 分类与子项分配比例
- Map> categoryChildren = Map.of(
- 2, List.of(3, 4), // 电网基建 → 主网基建 + 配网基建
- 5, List.of(5), // 小型基建(自身就是数据行)
- 6, List.of(7, 8, 9), // 技改项目 → 生产技改 + 营销技改 + 其他技改
- 10, List.of(10), // 科技项目
- 11, List.of(11) // 信息化项目
- );
-
- for (var entry : categoryChildren.entrySet()) {
- int catRowIdx = entry.getKey();
- List childIndexes = entry.getValue();
-
- // 为子项生成随机数据
- long catTotal = 0, catA = 0, catB = 0, catC = 0;
- for (int childIdx : childIndexes) {
- long a = randomInvest();
- long b = randomInvest();
- long c = randomInvest();
- long total = a + b + c;
- rowData.put(childIdx, new long[]{total, a, b, c});
- catTotal += total; catA += a; catB += b; catC += c;
- }
- // 分类标题行 = 子项之和
- rowData.put(catRowIdx, new long[]{catTotal, catA, catB, catC});
- grandTotal += catTotal; grandYearA += catA; grandYearB += catB; grandYearC += catC;
- }
- // 总计行
- rowData.put(1, new long[]{grandTotal, grandYearA, grandYearB, grandYearC});
-
- // 2. 填充表格
- for (var entry : rowData.entrySet()) {
+ for (var entry : ROW_TO_CODE.entrySet()) {
int rowIdx = entry.getKey();
- long[] vals = entry.getValue();
- XWPFTableRow row = table.getRow(rowIdx);
- if (row == null) continue;
- setCellText(row, 2, fmtLong(vals[0])); // 合计
- setCellText(row, 3, fmtLong(vals[1])); // yearA
- setCellText(row, 4, fmtLong(vals[2])); // yearB
- setCellText(row, 5, fmtLong(vals[3])); // yearC
+ String code = entry.getValue();
+ long a = 0, b = 0, c = 0;
+ if (code != null) {
+ Map byYear = rawData.getOrDefault(code, Map.of());
+ a = byYear.getOrDefault(2024, 0L);
+ b = byYear.getOrDefault(2025, 0L);
+ c = byYear.getOrDefault(2026, 0L);
+ }
+ rowData.put(rowIdx, new long[]{a + b + c, a, b, c});
}
+
+ // 2. 计算分类汇总和总计
+ Map summaries = new LinkedHashMap<>();
+ long grandTotal = 0, grandA = 0, grandB = 0, grandC = 0;
+
+ for (var entry : CATEGORY_CHILDREN.entrySet()) {
+ int catRow = entry.getKey();
+ long catTotal = 0, catA = 0, catB = 0, catC = 0;
+ for (int child : entry.getValue()) {
+ long[] v = rowData.get(child);
+ if (v != null) {
+ catTotal += v[0]; catA += v[1]; catB += v[2]; catC += v[3];
+ }
+ }
+ summaries.put(catRow, new long[]{catTotal, catA, catB, catC});
+ grandTotal += catTotal; grandA += catA; grandB += catB; grandC += catC;
+ }
+ summaries.put(1, new long[]{grandTotal, grandA, grandB, grandC});
+
+ // 3. 填入表格
+ for (var entry : summaries.entrySet()) {
+ fillRow(table, entry.getKey(), entry.getValue());
+ }
+ for (var entry : rowData.entrySet()) {
+ // 跳过已在 summaries 中的标题行
+ if (summaries.containsKey(entry.getKey())) continue;
+ fillRow(table, entry.getKey(), entry.getValue());
+ }
+ }
+
+ private void fillRow(XWPFTable table, int rowIdx, long[] vals) {
+ XWPFTableRow row = table.getRow(rowIdx);
+ if (row == null) return;
+ setCellText(row, 2, fmtLong(vals[0])); // 合计
+ setCellText(row, 3, fmtLong(vals[1])); // yearA
+ setCellText(row, 4, fmtLong(vals[2])); // yearB
+ setCellText(row, 5, fmtLong(vals[3])); // yearC
}
private void setCellText(XWPFTableRow row, int col, String text) {
@@ -153,11 +173,6 @@ public class AnnualReportProvider implements TemplateProvider {
}
}
- private static long randomInvest() {
- // 生成 500~50000 万元之间的随机投资额
- return ThreadLocalRandom.current().nextLong(500, 50001);
- }
-
private static String fmtLong(long v) {
return String.format("%,d", v);
}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 65ea3e6..fa269f6 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -7,3 +7,12 @@ spring:
gmp:
download-origin: https://closein.cn
+
+spring:
+ datasource:
+ url: jdbc:mysql://localhost:3306/pc_ip_investapproval?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
+ username: PC_IP_INVESTAPPROVAL
+ password: Insigma123$%^
+ hikari:
+ maximum-pool-size: 5
+ minimum-idle: 1