Bläddra i källkod

对接牦牛资产档案

wwh 1 månad sedan
förälder
incheckning
a9ed77b554

+ 51 - 36
baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/support/YakHerdInventoryExcelExporter.java

@@ -30,8 +30,6 @@ public class YakHerdInventoryExcelExporter
30 30
 
31 31
     private static final int COL_SEQ = 0;
32 32
 
33
-    private static final int COL_F_YAK_MIRROR = 5;
34
-
35 33
     private static final Pattern YEAR_IN_TEXT = Pattern.compile("(\\d{4})年");
36 34
 
37 35
     public byte[] export(Integer statYear, List<SysDept> towns, Map<Long, BizYakHerdInventory> inventoryByTownId)
@@ -45,8 +43,24 @@ public class YakHerdInventoryExcelExporter
45 43
             throw new ServiceException("导出年份不在可选范围内");
46 44
         }
47 45
         try (InputStream in = new ClassPathResource(TEMPLATE_CLASSPATH).getInputStream();
48
-                Workbook workbook = WorkbookFactory.create(in);
49
-                ByteArrayOutputStream out = new ByteArrayOutputStream())
46
+                Workbook workbook = WorkbookFactory.create(in))
47
+        {
48
+            return exportWorkbook(workbook, statYear, towns, inventoryByTownId);
49
+        }
50
+        catch (ServiceException ex)
51
+        {
52
+            throw ex;
53
+        }
54
+        catch (Exception ex)
55
+        {
56
+            throw new ServiceException("导出 Excel 失败:" + ex.getMessage());
57
+        }
58
+    }
59
+
60
+    byte[] exportWorkbook(Workbook workbook, Integer statYear, List<SysDept> towns,
61
+            Map<Long, BizYakHerdInventory> inventoryByTownId)
62
+    {
63
+        try (ByteArrayOutputStream out = new ByteArrayOutputStream())
50 64
         {
51 65
             Sheet sheet = workbook.getSheetAt(0);
52 66
             int headerRowIndex = YakHerdInventoryExcelParser.detectHeaderRow(sheet);
@@ -56,6 +70,7 @@ public class YakHerdInventoryExcelExporter
56 70
             }
57 71
             updateTitleYear(sheet, statYear);
58 72
             int dataStartRow = headerRowIndex + 3;
73
+            clearTemplateDataRegion(sheet, dataStartRow);
59 74
             Totals totals = new Totals();
60 75
             int rowIndex = dataStartRow;
61 76
             int seq = 1;
@@ -77,16 +92,7 @@ public class YakHerdInventoryExcelExporter
77 92
             clearDataCells(totalRow);
78 93
             setStringCell(totalRow, YakHerdInventoryExcelParser.COL_TOWN, "合计");
79 94
             totals.writeToRow(totalRow);
80
-            // 清除模板中可能残留的旧数据行
81
-            for (int i = rowIndex + 1; i <= sheet.getLastRowNum(); i++)
82
-            {
83
-                Row stale = sheet.getRow(i);
84
-                if (stale != null)
85
-                {
86
-                    clearDataCells(stale);
87
-                    setStringCell(stale, YakHerdInventoryExcelParser.COL_TOWN, null);
88
-                }
89
-            }
95
+            clearTemplateDataRegion(sheet, rowIndex + 1);
90 96
             workbook.write(out);
91 97
             return out.toByteArray();
92 98
         }
@@ -100,6 +106,21 @@ public class YakHerdInventoryExcelExporter
100 106
         }
101 107
     }
102 108
 
109
+    private static void clearTemplateDataRegion(Sheet sheet, int fromRow)
110
+    {
111
+        int lastRow = sheet.getLastRowNum();
112
+        for (int i = fromRow; i <= lastRow; i++)
113
+        {
114
+            Row row = sheet.getRow(i);
115
+            if (row == null)
116
+            {
117
+                continue;
118
+            }
119
+            clearDataCells(row);
120
+            setStringCell(row, YakHerdInventoryExcelParser.COL_TOWN, null);
121
+        }
122
+    }
123
+
103 124
     static void updateTitleYear(Sheet sheet, int statYear)
104 125
     {
105 126
         DataFormatter formatter = new DataFormatter();
@@ -139,30 +160,20 @@ public class YakHerdInventoryExcelExporter
139 160
 
140 161
     private static void clearDataCells(Row row)
141 162
     {
142
-        int[] cols = new int[] {
143
-            COL_SEQ,
144
-            YakHerdInventoryExcelParser.COL_TOWN,
145
-            YakHerdInventoryExcelParser.COL_VILLAGE_COUNT,
146
-            YakHerdInventoryExcelParser.COL_HERDSMAN,
147
-            YakHerdInventoryExcelParser.COL_YAK_TOTAL,
148
-            COL_F_YAK_MIRROR,
149
-            YakHerdInventoryExcelParser.COL_BULL_TOTAL,
150
-            YakHerdInventoryExcelParser.COL_BREEDING_BULL,
151
-            YakHerdInventoryExcelParser.COL_BULL_CASTRATED,
152
-            YakHerdInventoryExcelParser.COL_COW_TOTAL,
153
-            YakHerdInventoryExcelParser.COL_COW_UNDER_1,
154
-            YakHerdInventoryExcelParser.COL_COW_1_3,
155
-            YakHerdInventoryExcelParser.COL_COW_3_12,
156
-            YakHerdInventoryExcelParser.COL_COW_OVER_12,
157
-            YakHerdInventoryExcelParser.COL_REMARK
158
-        };
159
-        for (int col : cols)
163
+        short lastCell = row.getLastCellNum();
164
+        if (lastCell < 0)
165
+        {
166
+            return;
167
+        }
168
+        int maxCol = Math.max(lastCell, YakHerdInventoryExcelParser.COL_REMARK + 1);
169
+        for (int col = 0; col < maxCol; col++)
160 170
         {
161 171
             Cell cell = row.getCell(col);
162
-            if (cell != null)
172
+            if (cell == null)
163 173
             {
164
-                row.removeCell(cell);
174
+                continue;
165 175
             }
176
+            cell.setBlank();
166 177
         }
167 178
     }
168 179
 
@@ -181,8 +192,10 @@ public class YakHerdInventoryExcelExporter
181 192
         setIntegerCell(row, YakHerdInventoryExcelParser.COL_VILLAGE_COUNT, inv.getVillageCount());
182 193
         setIntegerCell(row, YakHerdInventoryExcelParser.COL_HERDSMAN, inv.getHerdsmanHouseholdCount());
183 194
         setIntegerCell(row, YakHerdInventoryExcelParser.COL_YAK_TOTAL, inv.getYakTotal());
184
-        setIntegerCell(row, COL_F_YAK_MIRROR, inv.getYakTotal());
195
+        setIntegerCell(row, YakHerdInventoryExcelParser.COL_YAK_GENDER_TOTAL, inv.getYakTotal());
185 196
         setIntegerCell(row, YakHerdInventoryExcelParser.COL_BULL_TOTAL, inv.getBullTotal());
197
+        setIntegerCell(row, YakHerdInventoryExcelParser.COL_COW_HEAD, inv.getCowTotal());
198
+        setIntegerCell(row, YakHerdInventoryExcelParser.COL_BULL_SECTION_TOTAL, inv.getBullTotal());
186 199
         setIntegerCell(row, YakHerdInventoryExcelParser.COL_BREEDING_BULL, inv.getBreedingBullCount());
187 200
         setIntegerCell(row, YakHerdInventoryExcelParser.COL_BULL_CASTRATED, inv.getBullCastratedCount());
188 201
         setIntegerCell(row, YakHerdInventoryExcelParser.COL_COW_TOTAL, inv.getCowTotal());
@@ -283,8 +296,10 @@ public class YakHerdInventoryExcelExporter
283 296
             setIntegerCell(row, YakHerdInventoryExcelParser.COL_VILLAGE_COUNT, villageCount);
284 297
             setIntegerCell(row, YakHerdInventoryExcelParser.COL_HERDSMAN, herdsmanHouseholdCount);
285 298
             setIntegerCell(row, YakHerdInventoryExcelParser.COL_YAK_TOTAL, yakTotal);
286
-            setIntegerCell(row, COL_F_YAK_MIRROR, yakTotal);
299
+            setIntegerCell(row, YakHerdInventoryExcelParser.COL_YAK_GENDER_TOTAL, yakTotal);
287 300
             setIntegerCell(row, YakHerdInventoryExcelParser.COL_BULL_TOTAL, bullTotal);
301
+            setIntegerCell(row, YakHerdInventoryExcelParser.COL_COW_HEAD, cowTotal);
302
+            setIntegerCell(row, YakHerdInventoryExcelParser.COL_BULL_SECTION_TOTAL, bullTotal);
288 303
             setIntegerCell(row, YakHerdInventoryExcelParser.COL_BREEDING_BULL, breedingBullCount);
289 304
             setIntegerCell(row, YakHerdInventoryExcelParser.COL_BULL_CASTRATED, bullCastratedCount);
290 305
             setIntegerCell(row, YakHerdInventoryExcelParser.COL_COW_TOTAL, cowTotal);

+ 8 - 0
baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/support/YakHerdInventoryExcelParser.java

@@ -26,8 +26,16 @@ public final class YakHerdInventoryExcelParser
26 26
 
27 27
     static final int COL_YAK_TOTAL = 4;
28 28
 
29
+    static final int COL_YAK_GENDER_TOTAL = 5;
30
+
29 31
     static final int COL_BULL_TOTAL = 6;
30 32
 
33
+    /** 牦牛公母分类 — 母牛(头) */
34
+    static final int COL_COW_HEAD = 7;
35
+
36
+    /** 公牛 — 合计(头),与 {@link #COL_BULL_TOTAL} 同值 */
37
+    static final int COL_BULL_SECTION_TOTAL = 8;
38
+
31 39
     static final int COL_BREEDING_BULL = 9;
32 40
 
33 41
     static final int COL_BULL_CASTRATED = 10;

BIN
baqing-admin/src/main/resources/templates/yak_herd_inventory_import_template.xlsx


+ 110 - 0
baqing-admin/src/test/java/com/ruoyi/web/modules/industryservice/service/impl/BizYakHerdInventoryImportUpsertTest.java

@@ -0,0 +1,110 @@
1
+package com.ruoyi.web.modules.industryservice.service.impl;
2
+
3
+import static org.junit.jupiter.api.Assertions.assertEquals;
4
+import static org.mockito.ArgumentMatchers.any;
5
+import static org.mockito.ArgumentMatchers.anyLong;
6
+import static org.mockito.ArgumentMatchers.anyString;
7
+import static org.mockito.Mockito.times;
8
+import static org.mockito.Mockito.verify;
9
+import static org.mockito.Mockito.when;
10
+
11
+import java.io.InputStream;
12
+import java.util.concurrent.atomic.AtomicLong;
13
+import org.junit.jupiter.api.DisplayName;
14
+import org.junit.jupiter.api.Test;
15
+import org.junit.jupiter.api.extension.ExtendWith;
16
+import org.mockito.InjectMocks;
17
+import org.mockito.Mock;
18
+import org.mockito.junit.jupiter.MockitoExtension;
19
+import org.mockito.junit.jupiter.MockitoSettings;
20
+import org.mockito.quality.Strictness;
21
+import org.springframework.mock.web.MockMultipartFile;
22
+import com.ruoyi.common.core.domain.entity.SysDept;
23
+import com.ruoyi.web.modules.industryservice.domain.BizYakHerdInventory;
24
+import com.ruoyi.web.modules.industryservice.domain.vo.YakHerdInventoryImportResultVo;
25
+import com.ruoyi.web.modules.industryservice.mapper.BizYakHerdInventoryMapper;
26
+import com.ruoyi.web.modules.industryservice.support.TownDeptSupport;
27
+import com.ruoyi.web.modules.industryservice.support.YakHerdInventoryExcelExporter;
28
+
29
+@ExtendWith(MockitoExtension.class)
30
+@MockitoSettings(strictness = Strictness.LENIENT)
31
+@DisplayName("牦牛存栏导入 upsert")
32
+class BizYakHerdInventoryImportUpsertTest
33
+{
34
+    @Mock
35
+    private BizYakHerdInventoryMapper mapper;
36
+
37
+    @Mock
38
+    private TownDeptSupport townDeptSupport;
39
+
40
+    @Mock
41
+    private YakHerdInventoryExcelExporter excelExporter;
42
+
43
+    @InjectMocks
44
+    private BizYakHerdInventoryServiceImpl service;
45
+
46
+    @Test
47
+    @DisplayName("ZCZX-MYCLTB-UT-011 首次导入插入")
48
+    void firstImportInserts() throws Exception
49
+    {
50
+        mockTownResolver();
51
+        when(mapper.selectByYearAndTown(any(), anyLong())).thenReturn(null);
52
+        when(mapper.insertBizYakHerdInventory(any())).thenReturn(1);
53
+
54
+        try (InputStream in = getClass().getResourceAsStream("/excel/yak_herd_inventory_2026.xlsx"))
55
+        {
56
+            MockMultipartFile file = new MockMultipartFile("file", "牦牛存栏数据.xlsx",
57
+                    "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", in);
58
+            YakHerdInventoryImportResultVo result = service.importData(file, 2026, "tester");
59
+            assertEquals(10, result.getInsertCount());
60
+            assertEquals(0, result.getUpdateCount());
61
+            verify(mapper, times(10)).insertBizYakHerdInventory(any());
62
+        }
63
+    }
64
+
65
+    @Test
66
+    @DisplayName("ZCZX-MYCLTB-UT-012 重复导入同文件全部更新")
67
+    void secondImportUpdates() throws Exception
68
+    {
69
+        mockTownResolver();
70
+        when(mapper.selectByYearAndTown(any(), anyLong())).thenAnswer(inv -> {
71
+            BizYakHerdInventory row = new BizYakHerdInventory();
72
+            row.setId((Long) inv.getArgument(1));
73
+            row.setStatYear((Integer) inv.getArgument(0));
74
+            row.setTownDeptId((Long) inv.getArgument(1));
75
+            return row;
76
+        });
77
+        when(mapper.updateBizYakHerdInventory(any())).thenReturn(1);
78
+
79
+        try (InputStream in = getClass().getResourceAsStream("/excel/yak_herd_inventory_2026.xlsx"))
80
+        {
81
+            MockMultipartFile file = new MockMultipartFile("file", "牦牛存栏数据.xlsx",
82
+                    "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", in);
83
+            YakHerdInventoryImportResultVo result = service.importData(file, 2026, "tester");
84
+            assertEquals(0, result.getInsertCount());
85
+            assertEquals(10, result.getUpdateCount());
86
+            verify(mapper, times(10)).updateBizYakHerdInventory(any());
87
+            verify(mapper, times(0)).insertBizYakHerdInventory(any());
88
+        }
89
+    }
90
+
91
+    private void mockTownResolver()
92
+    {
93
+        AtomicLong seq = new AtomicLong(100);
94
+        when(townDeptSupport.resolveTownByName(anyString())).thenAnswer(inv -> {
95
+            SysDept d = new SysDept();
96
+            long id = seq.incrementAndGet();
97
+            d.setDeptId(id);
98
+            d.setDeptName(inv.getArgument(0));
99
+            d.setOrderNum(13);
100
+            d.setDelFlag("0");
101
+            return d;
102
+        });
103
+        when(townDeptSupport.requireReportTown(anyLong())).thenAnswer(inv -> {
104
+            SysDept d = new SysDept();
105
+            d.setDeptId(inv.getArgument(0));
106
+            d.setDeptName("乡镇");
107
+            return d;
108
+        });
109
+    }
110
+}

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

@@ -1,5 +1,6 @@
1 1
 package com.ruoyi.web.modules.industryservice.support;
2 2
 
3
+import static org.junit.jupiter.api.Assertions.assertEquals;
3 4
 import static org.junit.jupiter.api.Assertions.assertNotNull;
4 5
 import static org.junit.jupiter.api.Assertions.assertTrue;
5 6
 
@@ -17,6 +18,95 @@ class YakHerdInventoryExcelExporterTest
17 18
 {
18 19
     private final YakHerdInventoryExcelExporter exporter = new YakHerdInventoryExcelExporter();
19 20
 
21
+    @Test
22
+    @DisplayName("2026样例表无库数据时导出应清空样例存栏数")
23
+    void exportFromSampleWorkbookClearsData() throws Exception
24
+    {
25
+        try (java.io.InputStream sample = getClass().getResourceAsStream("/excel/yak_herd_inventory_2026.xlsx");
26
+                org.apache.poi.ss.usermodel.Workbook sampleWb = org.apache.poi.ss.usermodel.WorkbookFactory.create(sample))
27
+        {
28
+            org.apache.poi.ss.usermodel.Sheet sheet = sampleWb.getSheetAt(0);
29
+            int header = YakHerdInventoryExcelParser.detectHeaderRow(sheet);
30
+            int dataStart = header + 3;
31
+            org.apache.poi.ss.usermodel.DataFormatter fmt = new org.apache.poi.ss.usermodel.DataFormatter();
32
+            org.apache.poi.ss.usermodel.Row firstData = sheet.getRow(dataStart);
33
+            String yakBefore = fmt.formatCellValue(firstData.getCell(YakHerdInventoryExcelParser.COL_YAK_TOTAL));
34
+            assertTrue(com.ruoyi.common.utils.StringUtils.isNotEmpty(yakBefore), "样例表首行应有存栏数");
35
+
36
+            String[] townNames = {
37
+                "巴青乡", "江绵乡", "岗切乡", "雅安镇", "拉西镇",
38
+                "本塔乡", "杂色镇", "贡日乡", "玛如乡", "阿秀乡"
39
+            };
40
+            java.util.List<SysDept> towns = new java.util.ArrayList<>();
41
+            for (int i = 0; i < townNames.length; i++)
42
+            {
43
+                SysDept d = new SysDept();
44
+                d.setDeptId(100L + i);
45
+                d.setDeptName(townNames[i]);
46
+                d.setOrderNum(13 + i);
47
+                towns.add(d);
48
+            }
49
+            byte[] bytes = exporter.exportWorkbook(sampleWb, 2026, towns, Collections.emptyMap());
50
+            try (org.apache.poi.ss.usermodel.Workbook out = org.apache.poi.ss.usermodel.WorkbookFactory.create(
51
+                    new java.io.ByteArrayInputStream(bytes)))
52
+            {
53
+                java.util.List<ParsedRow> rows = YakHerdInventoryExcelParser.parse(
54
+                        new java.io.ByteArrayInputStream(bytes));
55
+                for (ParsedRow row : rows)
56
+                {
57
+                    assertTrue(row.getYakTotal() == null || row.getYakTotal() == 0,
58
+                            row.getTownName() + " 导出后不应含样例存栏数,实际=" + row.getYakTotal());
59
+                }
60
+            }
61
+        }
62
+    }
63
+
64
+    @Test
65
+    @DisplayName("无填报数据时全县乡镇导出均不应含存栏数")
66
+    void exportAllTownsEmptyClearsTemplateSampleData() throws Exception
67
+    {
68
+        String[] townNames = {
69
+            "巴青乡", "江绵乡", "岗切乡", "雅安镇", "拉西镇",
70
+            "本塔乡", "杂色镇", "贡日乡", "玛如乡", "阿秀乡"
71
+        };
72
+        java.util.List<SysDept> towns = new java.util.ArrayList<>();
73
+        for (int i = 0; i < townNames.length; i++)
74
+        {
75
+            SysDept town = new SysDept();
76
+            town.setDeptId(100L + i);
77
+            town.setDeptName(townNames[i]);
78
+            town.setOrderNum(13 + i);
79
+            towns.add(town);
80
+        }
81
+
82
+        byte[] bytes = exporter.export(2026, towns, Collections.emptyMap());
83
+        try (java.io.ByteArrayInputStream in = new java.io.ByteArrayInputStream(bytes))
84
+        {
85
+            java.util.List<ParsedRow> rows = YakHerdInventoryExcelParser.parse(in);
86
+            assertEquals(10, rows.size());
87
+            for (ParsedRow row : rows)
88
+            {
89
+                assertTrue(row.getYakTotal() == null || row.getYakTotal() == 0,
90
+                        row.getTownName() + " 不应有存栏数,实际=" + row.getYakTotal());
91
+            }
92
+            try (org.apache.poi.ss.usermodel.Workbook wb = org.apache.poi.ss.usermodel.WorkbookFactory.create(
93
+                    new java.io.ByteArrayInputStream(bytes)))
94
+            {
95
+                org.apache.poi.ss.usermodel.Sheet sheet = wb.getSheetAt(0);
96
+                int header = YakHerdInventoryExcelParser.detectHeaderRow(sheet);
97
+                int dataStart = header + 3;
98
+                org.apache.poi.ss.usermodel.DataFormatter fmt = new org.apache.poi.ss.usermodel.DataFormatter();
99
+                for (int i = 0; i < townNames.length; i++)
100
+                {
101
+                    org.apache.poi.ss.usermodel.Row excelRow = sheet.getRow(dataStart + i);
102
+                    String yakText = fmt.formatCellValue(excelRow.getCell(YakHerdInventoryExcelParser.COL_YAK_TOTAL));
103
+                    assertTrue(com.ruoyi.common.utils.StringUtils.isEmpty(yakText) || "0".equals(yakText.trim()),
104
+                            townNames[i] + " Excel 单元格存栏数应为空,实际=" + yakText);
105
+                }
106
+            }
107
+        }
108
+    }
109
+
20 110
     @Test
21 111
     @DisplayName("按年份导出全县乡镇 Excel")
22 112
     void exportByYear() throws Exception
@@ -53,6 +143,15 @@ class YakHerdInventoryExcelExporterTest
53 143
             java.util.List<ParsedRow> rows = YakHerdInventoryExcelParser.parse(in);
54 144
             assertTrue(rows.size() >= 1);
55 145
             assertTrue(rows.stream().anyMatch(r -> Integer.valueOf(21587).equals(r.getYakTotal())));
146
+            try (org.apache.poi.ss.usermodel.Workbook wb = org.apache.poi.ss.usermodel.WorkbookFactory.create(
147
+                    new java.io.ByteArrayInputStream(bytes)))
148
+            {
149
+                int header = YakHerdInventoryExcelParser.detectHeaderRow(wb.getSheetAt(0));
150
+                org.apache.poi.ss.usermodel.Row dataRow = wb.getSheetAt(0).getRow(header + 3);
151
+                org.apache.poi.ss.usermodel.DataFormatter fmt = new org.apache.poi.ss.usermodel.DataFormatter();
152
+                assertEquals("13430", fmt.formatCellValue(dataRow.getCell(YakHerdInventoryExcelParser.COL_COW_HEAD)));
153
+                assertEquals("13430", fmt.formatCellValue(dataRow.getCell(YakHerdInventoryExcelParser.COL_COW_TOTAL)));
154
+            }
56 155
         }
57 156
     }
58 157
 }

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

@@ -183,8 +183,10 @@ for (int y = current; y >= current - 5; y--) { ... }
183 183
 | 2 | C | `villageCount` |
184 184
 | 3 | D | `herdsmanHouseholdCount` |
185 185
 | 4 | E | `yakTotal`(存栏数) |
186
-| 5 | F | 与 E 同值(模板镜像列,导出时同步写入) |
187
-| 6 | G | `bullTotal` |
186
+| 5 | F | 与 E 同值(牦牛公母分类合计) |
187
+| 6 | G | `bullTotal`(公牛(头)) |
188
+| 7 | H | `cowTotal`(牦牛公母分类 — 母牛(头)) |
189
+| 8 | I | 与 G 同值(公牛区块合计) |
188 190
 | 9 | J | `breedingBullCount` |
189 191
 | 10 | K | `bullCastratedCount` |
190 192
 | 11 | L | `cowTotal` |