From 7b78091f0d4100bd5469ce59ca87c013245b0407 Mon Sep 17 00:00:00 2001 From: agentuser Date: Mon, 25 May 2026 15:03:13 +0800 Subject: [PATCH] feat: query annual report investment data from MySQL --- pom.xml | 11 ++ .../annualReport/AnnualReportDataService.java | 53 ++++++ .../annualReport/AnnualReportProvider.java | 159 ++++++++++-------- src/main/resources/application.yml | 9 + 4 files changed, 160 insertions(+), 72 deletions(-) create mode 100644 src/main/java/com/gmp/mock/template/annualReport/AnnualReportDataService.java 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