Explorar o código

牦牛出栏数据

wwh hai 1 mes
pai
achega
aa24fc91a0

+ 28 - 3
baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/support/YakOutboundReportExcelExporter.java

@@ -34,7 +34,9 @@ public class YakOutboundReportExcelExporter
34 34
 
35 35
     private static final int COL_SELF_SUBTOTAL = 14;
36 36
 
37
-    private static final Pattern YEAR_IN_TEXT = Pattern.compile("(\\d{4})年");
37
+    private static final Pattern YEAR_IN_TEXT = Pattern.compile("(\\d{4})\\s*年");
38
+
39
+    private static final Pattern SHEET_YEAR_NAME = Pattern.compile("(\\d{2,4})年出栏");
38 40
 
39 41
     public byte[] export(Integer statYear, List<SysDept> towns, Map<Long, BizYakOutboundReport> reportByTownId)
40 42
     {
@@ -72,7 +74,8 @@ public class YakOutboundReportExcelExporter
72 74
             {
73 75
                 throw new ServiceException("未识别到表头行(乡镇/户数/牦牛)");
74 76
             }
75
-            updateTitleYear(sheet, statYear);
77
+            updateTitleYear(sheet, statYear, headerRowIndex);
78
+            updateSheetName(workbook, sheet, statYear);
76 79
             int dataStartRow = headerRowIndex + 1;
77 80
             clearTemplateDataRegion(sheet, dataStartRow);
78 81
             Totals totals = new Totals();
@@ -127,9 +130,15 @@ public class YakOutboundReportExcelExporter
127 130
     }
128 131
 
129 132
     static void updateTitleYear(Sheet sheet, int statYear)
133
+    {
134
+        updateTitleYear(sheet, statYear, YakOutboundReportExcelParser.detectHeaderRow(sheet));
135
+    }
136
+
137
+    static void updateTitleYear(Sheet sheet, int statYear, int headerRowIndex)
130 138
     {
131 139
         DataFormatter formatter = new DataFormatter();
132
-        int last = Math.min(sheet.getLastRowNum(), 4);
140
+        int last = Math.max(headerRowIndex + 1, 4);
141
+        last = Math.min(last, sheet.getLastRowNum());
133 142
         for (int r = 0; r <= last; r++)
134 143
         {
135 144
             Row row = sheet.getRow(r);
@@ -163,6 +172,22 @@ public class YakOutboundReportExcelExporter
163 172
         }
164 173
     }
165 174
 
175
+    static void updateSheetName(Workbook workbook, Sheet sheet, int statYear)
176
+    {
177
+        String suffix = (statYear % 100) + "年出栏";
178
+        String current = sheet.getSheetName();
179
+        if (StringUtils.isNotEmpty(current))
180
+        {
181
+            Matcher matcher = SHEET_YEAR_NAME.matcher(current);
182
+            if (matcher.find())
183
+            {
184
+                workbook.setSheetName(workbook.getSheetIndex(sheet), matcher.replaceAll(suffix));
185
+                return;
186
+            }
187
+        }
188
+        workbook.setSheetName(workbook.getSheetIndex(sheet), suffix);
189
+    }
190
+
166 191
     private static void clearDataCells(Row row)
167 192
     {
168 193
         short lastCell = row.getLastCellNum();

+ 38 - 0
baqing-admin/src/test/java/com/ruoyi/web/modules/industryservice/support/YakOutboundReportExcelExporterTest.java

@@ -1,6 +1,7 @@
1 1
 package com.ruoyi.web.modules.industryservice.support;
2 2
 
3 3
 import static org.junit.jupiter.api.Assertions.assertEquals;
4
+import static org.junit.jupiter.api.Assertions.assertFalse;
4 5
 import static org.junit.jupiter.api.Assertions.assertNotNull;
5 6
 import static org.junit.jupiter.api.Assertions.assertTrue;
6 7
 
@@ -79,6 +80,43 @@ class YakOutboundReportExcelExporterTest
79 80
             java.util.List<ParsedRow> rows = YakOutboundReportExcelParser.parse(in);
80 81
             assertTrue(rows.stream().anyMatch(r -> Integer.valueOf(1523).equals(r.getYakOutboundCount())));
81 82
             assertTrue(rows.stream().anyMatch(r -> Integer.valueOf(488).equals(r.getSelfConsumptionCattleCount())));
83
+            try (org.apache.poi.ss.usermodel.Workbook wb = org.apache.poi.ss.usermodel.WorkbookFactory.create(
84
+                    new ByteArrayInputStream(bytes)))
85
+            {
86
+                org.apache.poi.ss.usermodel.Sheet sheet = YakOutboundReportExcelParser.resolveSheet(wb);
87
+                assertEquals("26年出栏", sheet.getSheetName());
88
+                org.apache.poi.ss.usermodel.DataFormatter fmt = new org.apache.poi.ss.usermodel.DataFormatter();
89
+                org.apache.poi.ss.usermodel.Row titleRow = sheet.getRow(1);
90
+                assertTrue(fmt.formatCellValue(titleRow.getCell(0)).contains("2026年"));
91
+                org.apache.poi.ss.usermodel.Row sectionRow = sheet.getRow(3);
92
+                assertTrue(fmt.formatCellValue(sectionRow.getCell(4)).contains("2026年"));
93
+            }
94
+        }
95
+    }
96
+
97
+    @Test
98
+    @DisplayName("导出时表头区块年份与 Sheet 名同步替换")
99
+    void exportUpdatesHeaderSectionYear() throws Exception
100
+    {
101
+        byte[] bytes = exporter.export(2024, Collections.singletonList(sampleTown()), Collections.emptyMap());
102
+        try (org.apache.poi.ss.usermodel.Workbook wb = org.apache.poi.ss.usermodel.WorkbookFactory.create(
103
+                new ByteArrayInputStream(bytes)))
104
+        {
105
+            org.apache.poi.ss.usermodel.Sheet sheet = YakOutboundReportExcelParser.resolveSheet(wb);
106
+            assertEquals("24年出栏", sheet.getSheetName());
107
+            org.apache.poi.ss.usermodel.DataFormatter fmt = new org.apache.poi.ss.usermodel.DataFormatter();
108
+            String sectionHeader = fmt.formatCellValue(sheet.getRow(3).getCell(4));
109
+            assertTrue(sectionHeader.contains("2024"), "表头「年牲畜出栏数」区块年份应为 2024,实际=" + sectionHeader);
110
+            assertFalse(sectionHeader.contains("2026"), "表头不应残留模板年份 2026");
82 111
         }
83 112
     }
113
+
114
+    private static SysDept sampleTown()
115
+    {
116
+        SysDept town = new SysDept();
117
+        town.setDeptId(101L);
118
+        town.setDeptName("巴青乡");
119
+        town.setOrderNum(13);
120
+        return town;
121
+    }
84 122
 }

+ 1 - 1
doc/产业数据模型及服务/牦牛出栏数据填报/牦牛出栏数据填报功能需求.md

@@ -227,7 +227,7 @@ flowchart TD
227 227
 | 导出年份 | 用户**必须选择**筛选条件中的「所属年份」(范围 **§2.2**);未选择时前端提示 |
228 228
 | 导出范围 | **巴青县全部乡镇**(**§2.3** 合法乡镇,按 `order_num` 排序),**不受**「所属乡镇」筛选限制 |
229 229
 | 模板 | 与导入共用:`baqing-admin/src/main/resources/templates/yak_outbound_report_import_template.xlsx`(业务源文件 `牲畜数据.xlsx` 第 6 Sheet) |
230
-| 填充规则 | 表头标题中的年份替换为所选年份;每个乡镇一行;有填报数据的乡镇填入全部指标;未填报乡镇仅保留乡镇名称,指标为空 |
230
+| 填充规则 | 表头标题、表头「年牲畜出栏数」区块及 Sheet 页签中的年份替换为所选年份;每个乡镇一行;有填报数据的乡镇填入全部指标;未填报乡镇仅保留乡镇名称,指标为空 |
231 231
 | 合计行 | 数据区末行输出「合计」,对**已填报乡镇**的数值列求和;出栏小计(K 列)、自食小计(O 列)由分项加总后写入 |
232 232
 | 文件格式 | `.xlsx`;文件名示例:`2026年牦牛出栏数据.xlsx` |
233 233
 | 与导入关系 | 导出文件列结构与导入模板一致,可再次导入(同年将 upsert) |

+ 5 - 4
doc/产业数据模型及服务/牦牛出栏数据填报/牦牛出栏数据填报技术方案.md

@@ -203,10 +203,11 @@ ORDER BY order_num;
203 203
 
204 204
 1. 加载 classpath 模板 `YakOutboundReportExcelExporter.TEMPLATE_CLASSPATH`  
205 205
 2. 解析出栏 Sheet(`YakOutboundReportExcelParser.resolveSheet`)  
206
-3. 替换表头中的四位年份为 `statYear`  
207
-4. 查询 `stat_year = statYear` 的全部记录,按 `town_dept_id` 建 Map  
208
-5. 遍历 `TownDeptSupport.listReportTowns()`,逐乡镇写行;无数据则仅写乡镇名  
209
-6. 末行写「合计」,对已填报乡镇数值列求和;K/O 列写入出栏/自食小计  
206
+3. 替换表头中的四位年份为 `statYear`(含标题行与「年牲畜出栏数」表头区块;年份与「年」之间允许空格)  
207
+4. 将 Sheet 名称更新为 `{年份后两位}年出栏`(如 2024 → `24年出栏`)  
208
+5. 查询 `stat_year = statYear` 的全部记录,按 `town_dept_id` 建 Map  
209
+6. 遍历 `TownDeptSupport.listReportTowns()`,逐乡镇写行;无数据则仅写乡镇名  
210
+7. 末行写「合计」,对已填报乡镇数值列求和;K/O 列写入出栏/自食小计  
210 211
 
211 212
 **与 3.9 区别**:`/importTemplate` 返回空白模板;`/export` 返回填充后的全县报表。
212 213