Pārlūkot izejas kodu

合作社发展数据填报

wwh 2 nedēļas atpakaļ
vecāks
revīzija
88c25b09ef

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

@@ -0,0 +1,117 @@
1
+package com.ruoyi.web.modules.industryservice.support;
2
+
3
+import static org.junit.jupiter.api.Assertions.assertEquals;
4
+import static org.junit.jupiter.api.Assertions.assertNotNull;
5
+import static org.junit.jupiter.api.Assertions.assertTrue;
6
+
7
+import java.io.ByteArrayInputStream;
8
+import java.math.BigDecimal;
9
+import java.util.Arrays;
10
+import java.util.Collections;
11
+import org.apache.poi.ss.usermodel.Row;
12
+import org.apache.poi.ss.usermodel.Sheet;
13
+import org.apache.poi.ss.usermodel.Workbook;
14
+import org.apache.poi.ss.usermodel.WorkbookFactory;
15
+import org.junit.jupiter.api.DisplayName;
16
+import org.junit.jupiter.api.Test;
17
+import com.ruoyi.web.modules.industryservice.domain.BizCooperativeDevelopment;
18
+
19
+@DisplayName("CooperativeDevelopmentExcelExporter")
20
+class CooperativeDevelopmentExcelExporterTest
21
+{
22
+    private final CooperativeDevelopmentExcelExporter exporter = new CooperativeDevelopmentExcelExporter();
23
+
24
+    @Test
25
+    @DisplayName("导出仅数据行、不含合计")
26
+    void exportWithoutTotalRow() throws Exception
27
+    {
28
+        BizCooperativeDevelopment a = sample("雅安镇", "雅安镇荣嘎村", "合作社A", 10, 20);
29
+        BizCooperativeDevelopment b = sample("雅安镇", "雅安镇公随村", "合作社B", 5, 15);
30
+        byte[] bytes = exporter.export(2026, 7, Arrays.asList(a, b), "admin");
31
+        assertNotNull(bytes);
32
+        assertTrue(bytes.length > 1000);
33
+
34
+        try (Workbook wb = WorkbookFactory.create(new ByteArrayInputStream(bytes)))
35
+        {
36
+            Sheet sheet = CooperativeDevelopmentExcelParser.resolveSheet(wb);
37
+            int header = CooperativeDevelopmentExcelParser.detectHeaderRow(sheet);
38
+            int dataStart = header + CooperativeDevelopmentExcelParser.HEADER_SPAN;
39
+            assertEquals(dataStart + 1, sheet.getLastRowNum());
40
+            Row last = sheet.getRow(sheet.getLastRowNum());
41
+            assertNotNull(last);
42
+            assertEquals("合作社B", CooperativeDevelopmentExcelParser.cellString(last,
43
+                    CooperativeDevelopmentExcelParser.COL_COOP_NAME));
44
+            for (int r = dataStart; r <= sheet.getLastRowNum(); r++)
45
+            {
46
+                String seq = CooperativeDevelopmentExcelParser.cellString(sheet.getRow(r), 0);
47
+                assertTrue(seq == null || !seq.contains("合计"), "不应出现合计行");
48
+            }
49
+        }
50
+    }
51
+
52
+    @Test
53
+    @DisplayName("第3行及以后复用模板数据行边框样式")
54
+    void exportThirdRowKeepsBorderStyle() throws Exception
55
+    {
56
+        BizCooperativeDevelopment a = sample("雅安镇", "雅安镇荣嘎村", "合作社A", 10, 20);
57
+        BizCooperativeDevelopment b = sample("雅安镇", "雅安镇公随村", "合作社B", 5, 15);
58
+        BizCooperativeDevelopment c = sample("雅安镇", "雅安镇荣嘎村", "合作社C", 8, 12);
59
+        byte[] bytes = exporter.export(2026, 7, Arrays.asList(a, b, c), "admin");
60
+        try (Workbook wb = WorkbookFactory.create(new ByteArrayInputStream(bytes)))
61
+        {
62
+            Sheet sheet = CooperativeDevelopmentExcelParser.resolveSheet(wb);
63
+            int header = CooperativeDevelopmentExcelParser.detectHeaderRow(sheet);
64
+            int dataStart = header + CooperativeDevelopmentExcelParser.HEADER_SPAN;
65
+            Row first = sheet.getRow(dataStart);
66
+            Row third = sheet.getRow(dataStart + 2);
67
+            assertNotNull(first);
68
+            assertNotNull(third);
69
+            assertEquals("合作社C", CooperativeDevelopmentExcelParser.cellString(third,
70
+                    CooperativeDevelopmentExcelParser.COL_COOP_NAME));
71
+            org.apache.poi.ss.usermodel.BorderStyle expected = first.getCell(0).getCellStyle().getBorderBottom();
72
+            assertTrue(expected != org.apache.poi.ss.usermodel.BorderStyle.NONE);
73
+            assertEquals(expected, third.getCell(0).getCellStyle().getBorderBottom());
74
+            assertEquals(expected, third.getCell(CooperativeDevelopmentExcelParser.COL_LAST).getCellStyle()
75
+                    .getBorderBottom());
76
+        }
77
+    }
78
+
79
+    @Test
80
+    @DisplayName("无数据时不写合计行")
81
+    void exportEmptyNoTotal() throws Exception
82
+    {
83
+        byte[] bytes = exporter.export(2026, 7, Collections.emptyList(), "admin");
84
+        try (Workbook wb = WorkbookFactory.create(new ByteArrayInputStream(bytes)))
85
+        {
86
+            Sheet sheet = CooperativeDevelopmentExcelParser.resolveSheet(wb);
87
+            int header = CooperativeDevelopmentExcelParser.detectHeaderRow(sheet);
88
+            int dataStart = header + CooperativeDevelopmentExcelParser.HEADER_SPAN;
89
+            assertTrue(sheet.getLastRowNum() < dataStart);
90
+            for (int r = 0; r <= sheet.getLastRowNum(); r++)
91
+            {
92
+                Row row = sheet.getRow(r);
93
+                if (row == null)
94
+                {
95
+                    continue;
96
+                }
97
+                String seq = CooperativeDevelopmentExcelParser.cellString(row, 0);
98
+                assertTrue(seq == null || !seq.contains("合计"));
99
+            }
100
+        }
101
+    }
102
+
103
+    private static BizCooperativeDevelopment sample(String town, String village, String coop, int hh, int pop)
104
+    {
105
+        BizCooperativeDevelopment row = new BizCooperativeDevelopment();
106
+        row.setTownName(town);
107
+        row.setVillageDeptId((long) village.hashCode());
108
+        row.setVillageName(village);
109
+        row.setCooperativeName(coop);
110
+        row.setMemberHouseholdCount(hh);
111
+        row.setMemberPopulationCount(pop);
112
+        row.setRegisteredCapital(new BigDecimal("100"));
113
+        row.setYearEndTotalAssets(new BigDecimal("200"));
114
+        row.setTotalIncome(new BigDecimal("50"));
115
+        return row;
116
+    }
117
+}

+ 60 - 0
doc/产业数据模型及服务/合作社发展数据填报/合作社发展数据填报功能需求.md

@@ -0,0 +1,60 @@
1
+# 合作社发展数据填报 — 功能需求
2
+
3
+## 1. 文档说明
4
+
5
+| 项 | 说明 |
6
+| --- | --- |
7
+| 模块名称 | 合作社发展数据填报 |
8
+| 目标 | 按**村 × 合作社 × 所属年月**填报、导入、维护新型经营主体(农牧民专业合作组织)发展情况;导入按唯一键 upsert |
9
+| 关联系统 | 业务后台;`sys_dept`(乡镇、村) |
10
+| 修订依据 | 交接表 `合作社发展数据填报.xlsx`;运行模板 `cooperative_development_import_template.xlsx` |
11
+
12
+---
13
+
14
+## 2. 填报粒度
15
+
16
+- 一条记录:某一自然年、某一自然月、某一行政村下的一家合作社快照。
17
+- 保存时由所选村反推所属乡镇。
18
+- 唯一键:所属年份 + 所属月份 + 所属村 + 合作社名称。
19
+- 导入按唯一键(年+月+村+合作社名称)**存在则更新、不存在则插入**;不是增幅累加,也不按年月先删后插。
20
+
21
+年月选项、乡镇/村合法性与存栏/出栏模块一致。
22
+
23
+---
24
+
25
+## 3. 功能范围
26
+
27
+| 能力 | 说明 |
28
+| --- | --- |
29
+| 分页列表 | 筛选:年、月、乡镇、村、合作社名称;列表列见下 |
30
+| 新增 / 修改 | 只选村;新增默认本年本月;牲畜入股小计=牛+羊(保存重算) |
31
+| 详情 | 全部明细;乡镇只读 |
32
+| 删除 | 物理删除 |
33
+| Excel 导入 / 导出 / 下载模板 | 另选年月;表头 1~7 行;数据从第 8 行起 |
34
+
35
+### 列表展示列
36
+
37
+所属年份、所属月份、所属村、所属乡镇、合作社名称、法定代表人、成立时间、注册资本、截止年底总资产、总收入、亏损、牛存栏、牛出栏、操作。
38
+
39
+### Excel 要点
40
+
41
+- 列顺序:**村在前、乡镇在后**(与出栏模板相反)。
42
+- 经营范围:多选,对应空格填「1」,入库逗号分隔。
43
+- 经营模式 / 运营情况:单选,对应空格填「1」。
44
+- 导出填表人=登录用户名称;填表时间=导出当日年月。
45
+- 导出仅输出已填报数据行,**不含合计行**。
46
+
47
+---
48
+
49
+## 4. 权限
50
+
51
+`dataModel:cooperativeDevelopment:list|query|add|edit|remove|import|export`
52
+
53
+---
54
+
55
+## 5. 修订记录
56
+
57
+| 版本 | 说明 |
58
+| --- | --- |
59
+| 1.0 | 首版:村级合作社快照;CRUD + 导入导出;对齐交接模板 |
60
+| 1.1 | 导入改为按唯一键 upsert,取消按年月先删后插 |

+ 55 - 0
doc/产业数据模型及服务/合作社发展数据填报/合作社发展数据填报技术方案.md

@@ -0,0 +1,55 @@
1
+# 合作社发展数据填报 — 技术方案
2
+
3
+> 依据:同目录功能需求;模板 `baqing-admin/src/main/resources/templates/cooperative_development_import_template.xlsx`。
4
+
5
+## 1. 架构
6
+
7
+Controller `/dataModel/cooperativeDevelopment` → Service → Mapper;Excel:`CooperativeDevelopmentExcelParser` / `CooperativeDevelopmentExcelExporter`;校验:`CooperativeDevelopmentValidation`(村反推乡镇)。
8
+
9
+## 2. 表 `biz_cooperative_development`
10
+
11
+唯一键 `uk_year_month_village_coop (stat_year, stat_month, village_dept_id, cooperative_name)`;含乡镇/村冗余名、入社/入股/效益/就业/存出栏/产品认证/培训、经营范围(逗号)、经营模式、运营情况。DDL:`sql/biz_cooperative_development.sql`(`DROP` 全量重建)。
12
+
13
+## 3. 接口
14
+
15
+| URI | 说明 |
16
+| --- | --- |
17
+| CRUD `/list` `/{id}` POST/PUT/DELETE | 常规;列表可筛村、合作社名称 |
18
+| `/villageTree` | 县→乡镇→村 |
19
+| `/importData` | 按唯一键 upsert(存在更新 / 不存在插入) |
20
+| `/export` | 按年月导出;可选 `townDeptId`,不传则导出全部乡镇 |
21
+
22
+### Excel 列(0-based,节选)
23
+
24
+| 列 | 字段 |
25
+| --- | --- |
26
+| 1~2 | 村、乡镇 |
27
+| 3~7 | 合作社名称、法人、成立时间、注册资本、总资产 |
28
+| 8~17 | 入社/入股 |
29
+| 18~23 | 效益 |
30
+| 24~31 | 就业与存出栏 |
31
+| 32~36 | 产品与培训 |
32
+| 37~46 | 经营范围多选标记 |
33
+| 47~52 | 经营模式单选标记 |
34
+| 53~55 | 运营情况单选标记 |
35
+
36
+数据起始行:`headerRow + 5`(表头跨 1~7 行)。
37
+
38
+## 4. 前端
39
+
40
+- 页面:`ruoyi-ui/src/views/dataModel/cooperativeDevelopment/index.vue`
41
+- API:`ruoyi-ui/src/api/dataModel/cooperativeDevelopment.js`
42
+- i18n:`lang/zh|bo/dataModel.js` → `cooperativeDevelopment`
43
+- 表单只选村(Treeselect,`disable-branch-nodes`);经营范围 checkbox → 逗号提交。
44
+
45
+## 5. 导入 / 导出
46
+
47
+- 导入:任一行失败不写库 → 成功则按 `(年,月,村,合作社名称)` 查重,存在 `update`、不存在 `insert`。
48
+- 导出:写合作社行 + 标记列;**不含合计行**。
49
+
50
+## 6. 修订记录
51
+
52
+| 版本 | 说明 |
53
+| --- | --- |
54
+| 1.0 | 首版对齐交接模板与出栏模块交互 |
55
+| 1.1 | 导入按唯一键 upsert,取消 deleteByYearMonth |

+ 16 - 0
doc/产业数据模型及服务/合作社发展数据填报/合作社发展数据填报测试用例.md

@@ -0,0 +1,16 @@
1
+# 合作社发展数据填报 — 测试用例(节选)
2
+
3
+> **接口 Base Path**:`/dataModel/cooperativeDevelopment`  
4
+> **建表**:执行 `sql/biz_cooperative_development.sql`
5
+
6
+| 编号 | 场景 | 步骤 | 期望 |
7
+| --- | --- | --- | --- |
8
+| UT-001 | 解析样例 Excel | 跑 `CooperativeDevelopmentExcelParserTest` | 2 行;范围/模式/运营标记正确 |
9
+| UT-002 | 空模板 | 解析运行模板 | 0 行 |
10
+| API-001 | 新增 | 选村+合作社名+年月保存 | 成功;乡镇自动回填 |
11
+| API-002 | 唯一键 | 同年同月同村同名再增 | 失败 |
12
+| API-003 | 导入 upsert | 同月导入含已有/新建合作社 | 已有记入 updateCount,新建记入 insertCount;不删同月其他记录 |
13
+| API-004 | 导入失败 | 乡镇/村无法识别 | 整批不写 |
14
+| API-005 | 导出 | 选已有年月导出 | 仅数据行、无合计行;填表人为当前用户 |
15
+
16
+样例文件:`baqing-admin/src/test/resources/excel/cooperative_development_sample.xlsx`