Explorar o código

牦牛资产档案

wwh hai 1 mes
pai
achega
3bc520f8f7

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

@@ -30,24 +30,25 @@
30 30
 | `updateYakOutboundReport` | PUT | `/` 修改 |
31 31
 | `delYakOutboundReport` | DELETE | `/{ids}` 删除 |
32 32
 | `listYakOutboundReportYearOptions` | GET | `/yearOptions` 可选年份 |
33
+| `listYakOutboundReportMonthOptions` | GET | `/monthOptions` 可选月份(1~12) |
33 34
 | `listYakOutboundReportTownOptions` | GET | `/townOptions` 乡镇下拉 |
34
-| `importYakOutboundReport` | POST | `/importData`(`multipart/form-data`:`file` + `statYear`) |
35
+| `importYakOutboundReport` | POST | `/importData`(`multipart/form-data`:`file` + `statYear` + `statMonth`) |
35 36
 | `downloadYakOutboundReportTemplate` | GET | `/importTemplate` 空白模板(`responseType: 'blob'`) |
36
-| `exportYakOutboundReport` | GET | `/export?statYear=` 按年份导出(`responseType: 'blob'`) |
37
+| `exportYakOutboundReport` | GET | `/export?statYear=&statMonth=` 按年月导出(`responseType: 'blob'`) |
37 38
 
38
-模板下载、按年导出均使用 **GET** + `responseType: 'blob'`,勿用若依全局 `this.download()`(固定 **POST**)。
39
+模板下载、按年导出均使用 **GET** + `responseType: 'blob'`,勿用若依全局 `this.download()`(固定 **POST**)。
39 40
 
40 41
 ---
41 42
 
42 43
 ## 3. 页面结构
43 44
 
44
-1. **筛选区**:所属年份(`yearOptions`)、所属乡镇(`townOptions`);**搜索**、**重置**、**新增**、**导入**、**导出**。  
45
-2. **列表区**:所属年份、所属乡镇、农牧户户数、农牧户人数、牦牛出栏数、自食情况(牛);空值展示 `—`。  
45
+1. **筛选区**:所属年份、**所属月份**、所属乡镇(均可清空);**搜索**、**重置**、**新增**、**导入**、**导出**。  
46
+2. **列表区**:所属年份、**所属月份**、所属乡镇、农牧户户数、农牧户人数、牦牛出栏数、自食情况(牛);空值展示 `—`。  
46 47
 3. **操作列**:查看、修改、删除。  
47
-4. **新增/编辑弹窗**:年份/乡镇必填;出栏及自食指标选填,`el-input-number`(`:min="0"` `:precision="0"`);备注选填。表单分四块:基本信息、农牧户情况、出栏情况、自食情况。  
48
-5. **查看弹窗**:只读展示需求 **§5.5** 六字段(年份、乡镇、农牧户户数、农牧户人数、牦牛出栏数、自食牛头数);可跳转编辑。  
49
-6. **导入弹窗**:选「填报年份」→ 上传 `.xlsx` → 展示 insert/update/fail 及 fail/warn 明细。  
50
-7. **导出弹窗**:选择「所属年份」→ `GET /export` 下载 `{statYear}年牦牛出栏数据.xlsx`。
48
+4. **新增/编辑弹窗**:年份、月份、乡镇必填;出栏及自食指标选填,`el-input-number`(`:min="0"` `:precision="0"`);备注选填;**新增时默认当前年月**。表单分四块:基本信息、农牧户情况、出栏情况、自食情况。  
49
+5. **查看弹窗**:只读展示需求 **§5.5** 七字段(年份、**月份**、乡镇、农牧户户数、农牧户人数、牦牛出栏数、自食牛头数);可跳转编辑。  
50
+6. **导入弹窗**:选「填报年份」「填报月份」(**默认当前年月**)→ 上传 `.xlsx` → 展示 insert/update/fail 及 fail/warn 明细。  
51
+7. **导出弹窗**:选「所属年份」「所属月份」(**默认当前年月**)→ `GET /export` 下载 `{年}年{月}月牦牛出栏数据.xlsx`。
51 52
 
52 53
 ---
53 54
 
@@ -56,13 +57,15 @@
56 57
 | 规则 | 实现 |
57 58
 | --- | --- |
58 59
 | 年份范围 | 由 `/yearOptions` 动态加载(当前年起向前 5 年,共 6 年) |
60
+| 月份范围 | 页面 `monthOptions` 固定 1~12;展示 `{month}月`(i18n `statMonthLabel`) |
59 61
 | 乡镇下拉 | `/townOptions` 返回 `deptId` / `deptName` |
60
-| 列表筛选 | `statYear`、`townDeptId` 精确匹配 |
62
+| 列表筛选 | `statYear`、`statMonth`、`townDeptId` 精确匹配;**进入页面不默认年月** |
63
+| 重置 | 清空年份、月份、乡镇后重新查询 |
61 64
 | 数量字段 | 选填;填写时须为 ≥ 0 的整数 |
62 65
 | 单位后缀 | 户、人、头、只、匹(见 i18n `unit*`) |
63
-| 唯一性 | 同年同乡镇重复新增由后端拦截,前端展示 `msg` |
64
-| 导入 | `FormData` 提交;全局 loading;成功后 `$alert` 展示摘要与明细 |
65
-| 导出 | 弹框选年;年份须在 `yearOptions` 内 |
66
+| 唯一性 | 同年同月同乡镇重复新增由后端拦截,前端展示 `msg` |
67
+| 导入 | `FormData` 含 `statYear`、`statMonth`;全局 loading;成功后 `$alert` 展示摘要与明细 |
68
+| 导出 | 弹框选年月;年月须在合法范围内 |
66 69
 | 删除 | `$modal.confirm` 后 `delYakOutboundReport(id)`,物理删除 |
67 70
 
68 71
 ---
@@ -72,6 +75,7 @@
72 75
 | 前端 prop | 说明 | 单位 |
73 76
 | --- | --- | --- |
74 77
 | `statYear` | 所属年份 | — |
78
+| `statMonth` | 所属月份(1~12) | — |
75 79
 | `townDeptId` | 所属乡镇 | — |
76 80
 | `farmerHouseholdCount` | 农牧户户数 | 户 |
77 81
 | `farmerPopulationCount` | 农牧户人数 | 人 |
@@ -96,16 +100,17 @@
96 100
 | **文件** | `lang/zh/dataModel.js`、`lang/bo/dataModel.js` |
97 101
 | **Mixin** | `dataModelLocaleMixin`,`dmNs = 'yakOutboundReport'`,`dmT` / `dmCommon` |
98 102
 | **注册** | 已随 `dataModel` 模块在 `lang/zh-CN.js`、`lang/bo.js` 引入 |
103
+| **月份文案** | `queryStatMonth`、`colStatMonth`、`formStatMonth`、`importStatMonth`、`exportStatMonth`、`statMonthLabel` 等 |
99 104
 
100 105
 ---
101 106
 
102 107
 ## 7. 联调说明
103 108
 
104
-1. 部署后端 `BizYakOutboundReportController` 及 `biz_yak_outbound_report` 表。  
105
-2. 菜单组件 `dataModel/yakOutboundReport/index`,权限与后端 §5 一致
106
-3. 执行 `sql/biz_yak_outbound_report.sql`(及乡镇 `sys_dept` 数据)
107
-4. 验证:年/乡镇筛选、CRUD、导入模板下载、导入结果反馈、按年导出、双语切换。  
108
-5. 导入联调:选填报年 → 上传 `牲畜数据.xlsx` 第 6 Sheet 格式文件;对照 `failMessages` / `warnMessages`。
109
+1. 部署后端 `BizYakOutboundReportController` 及 `biz_yak_outbound_report` 表(含 `stat_month`)
110
+2. 已有库自 v1.1 按年表升级时执行迁移 SQL(历史 `stat_month` 默认 **12**)
111
+3. 菜单组件 `dataModel/yakOutboundReport/index`,权限与后端 §5 一致
112
+4. 验证:年/乡镇筛选、CRUD、导入模板下载、导入结果反馈、按年导出、双语切换。  
113
+5. 导入联调:选填报年 → 上传 `牲畜数据.xlsx` 第 6 Sheet 格式文件;对照 `failMessages` / `warnMessages`。
109 114
 
110 115
 ---
111 116
 
@@ -125,3 +130,4 @@
125 130
 | 版本 | 说明 |
126 131
 | --- | --- |
127 132
 | 1.0 | 初版:列表/CRUD/详情/导入/模板下载/按年导出;汉藏 i18n;对齐功能需求 v1.1 |
133
+| 1.1 | 按年月填报:列表月份列与筛选;导入/导出/新增默认当前年月;列表不默认年月;`/monthOptions`;导出文件名含月份 |

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

@@ -5,8 +5,8 @@
5 5
 | 项 | 说明 |
6 6
 | --- | --- |
7 7
 | 模块名称 | 牦牛出栏数据填报 |
8
-| 目标 | 按**乡镇 × 所属年**填报、导入、维护县域农牧户牲畜**出栏及自食**统计数据,支撑养殖产业监管报表及历史年度对比 |
9
-| 关联系统 | 业务后台(增删改查、Excel 导入);组织机构 `sys_dept`(乡镇字典) |
8
+| 目标 | 按**乡镇 × 所属年**填报、导入、维护县域农牧户牲畜**出栏及自食**统计数据,**每月导入一次**当月出栏;支撑养殖产业监管报表及历史对比 |
9
+| 关联系统 | 业务后台(增删改查、Excel 导入/导出);组织机构 `sys_dept`(乡镇字典) |
10 10
 | 修订依据 | 业务模板 `牲畜数据.xlsx` 第 **6** 个 Sheet(如「2026年度农牧户牲畜出栏及自食情况统计表」,附表 5);用户填报诉求 |
11 11
 
12 12
 本文档仅描述**功能需求与业务规则**,不涉及库表字段命名、接口路径、技术栈选型及实现细节(见同目录技术方案)。
@@ -17,8 +17,9 @@
17 17
 
18 18
 ### 2.1 填报粒度
19 19
 
20
-- 一条记录表示:**某一自然年、某一乡镇**的牲畜出栏及自食汇总数据(**不按村居拆行**)。  
21
-- 同一「所属年份 + 所属乡镇」在系统中**仅允许一条记录**(物理删除后可重新填报)。
20
+- 一条记录表示:**某一自然年、某一自然月、某一乡镇**的牲畜出栏及自食汇总数据(**不按村居拆行**)。  
21
+- 同一「所属年份 + 所属月份 + 所属乡镇」在系统中**仅允许一条记录**(物理删除后可重新填报)。  
22
+- **填报频率**:每个自然月导入/维护一次当月出栏快照;同月重复导入为**覆盖更新**。
22 23
 
23 24
 ### 2.2 所属年份
24 25
 
@@ -26,7 +27,16 @@
26 27
 | --- | --- |
27 28
 | 可选范围 | **当前自然年**起向前共 **6 个自然年**(含当年),即「当年、前 1 年 … 前 5 年」 |
28 29
 | 示例 | 若统计日为 2026 年,可选:2026、2025、2024、2023、2022、2021 |
29
-| 展示 | 列表、表单、导入时均以**四位年份**展示与存储 |
30
+| 展示 | 列表、表单、导入、导出时均以**四位年份**展示与存储 |
31
+
32
+### 2.2.1 所属月份
33
+
34
+| 规则 | 说明 |
35
+| --- | --- |
36
+| 取值范围 | **1~12**(自然月) |
37
+| 填报限制 | **不可填报未来月份**(相对服务器当前年月) |
38
+| 展示 | 列表、表单、导入、导出均以「**N 月**」展示 |
39
+| 历史数据 | 升级前按年填报的数据,迁移时默认 `stat_month = 12` |
30 40
 
31 41
 ### 2.3 所属乡镇
32 42
 
@@ -47,7 +57,7 @@
47 57
 | --- | --- | --- |
48 58
 | 农牧户户数 | 户 | 纳入统计的农牧户数量 |
49 59
 | 农牧户人数 | 人 | 纳入统计的农牧户人口数 |
50
-| 牦牛出栏数 | 头 | 当牦牛出栏合计 |
60
+| 牦牛出栏数 | 头 | 当牦牛出栏合计 |
51 61
 | 黄牛/奶牛/犏牛出栏数 | 头 | 黄牛、奶牛、犏牛出栏合计 |
52 62
 | 绵羊出栏数 | 只 | 绵羊出栏合计 |
53 63
 | 山羊出栏数 | 只 | 山羊出栏合计 |
@@ -69,15 +79,16 @@
69 79
 
70 80
 | 能力 | 说明 |
71 81
 | --- | --- |
72
-| 分页列表 | 按所属年份、所属乡镇筛选;见 **§5.1** |
73
-| 新增 | 手工填报一条乡镇年度数据;见 **§5.2** |
82
+| 分页列表 | 按所属年份、**所属月份**、所属乡镇筛选;见 **§5.1** |
83
+| 新增 | 手工填报一条乡镇**年月**数据;见 **§5.2** |
74 84
 | 修改 | 编辑已有记录;见 **§5.3** |
75 85
 | 删除 | **物理删除**;见 **§5.4** |
76 86
 | 详情 | 查看指定展示字段;见 **§5.5** |
77
-| Excel 导入 | 按模板批量导入**指定填报年**下所有乡镇出栏数据;见 **§5.6** |
78
-| Excel 导出 | 按**指定所属年**导出巴青县**全部乡镇**出栏数据;见 **§5.7** |
87
+| Excel 导入 | 按模板批量导入**指定填报年**下所有乡镇出栏数据;见 **§5.6** |
88
+| Excel 导出 | 按**指定所属年**导出巴青县**全部乡镇**出栏数据;见 **§5.7** |
79 89
 | 乡镇下拉 | 从 `sys_dept` 动态加载合法乡镇;见 **§5.2** |
80 90
 | 年份下拉 | 当前年起向前 5 年;见 **§2.2** |
91
+| 月份下拉 | 1~12;见 **§2.2.1** |
81 92
 
82 93
 **本期不实现:**
83 94
 
@@ -90,13 +101,13 @@
90 101
 
91 102
 ```mermaid
92 103
 flowchart TD
93
-    A[选择所属年] --> B{录入方式}
104
+    A[选择所属年] --> B{录入方式}
94 105
     B -->|手工| C[选择乡镇并填写指标]
95 106
     B -->|导入| D[上传 Excel 模板]
96 107
     B -->|导出| X[下载全县乡镇 Excel]
97 108
     C --> E[保存]
98 109
     D --> F[解析并匹配乡镇]
99
-    F --> G{同年同乡镇是否存在}
110
+    F --> G{同年同月同乡镇是否存在}
100 111
     G -->|否| H[新增]
101 112
     G -->|是| I[更新覆盖]
102 113
     E --> J[列表查询/详情]
@@ -116,18 +127,20 @@ flowchart TD
116 127
 | 条件 | 规则 |
117 128
 | --- | --- |
118 129
 | 所属年份 | 选填;**精确**匹配;下拉范围为 **§2.2** |
130
+| 所属月份 | 选填;**精确**匹配(1~12);**列表进入时不默认选中** |
119 131
 | 所属乡镇 | 选填;**精确**匹配 `dept_id` |
120 132
 
121 133
 **列表规则**
122 134
 
123
-- 默认每页 **20** 条;排序:**所属年份倒序**,同年内按乡镇 `order_num` 升序(或乡镇名称拼音)
124
-- 支持**重置**筛选。
135
+- 默认每页 **20** 条;排序:**所属年份倒序**、**所属月份倒序**,同年内同月按乡镇 `order_num` 升序。  
136
+- 支持**重置**筛选(年份、月份、乡镇均清空)
125 137
 
126 138
 **列表展示列**
127 139
 
128 140
 | 列名 | 说明 |
129 141
 | --- | --- |
130 142
 | 所属年份 | 自然年 |
143
+| 所属月份 | 展示为「N 月」 |
131 144
 | 所属乡镇 | `town_name` |
132 145
 | 农牧户户数 | 户 |
133 146
 | 农牧户人数 | 人 |
@@ -141,10 +154,11 @@ flowchart TD
141 154
 
142 155
 | 项 | 要求 |
143 156
 | --- | --- |
144
-| 必填 | **所属年份**、**所属乡镇** |
157
+| 必填 | **所属年份**、**所属月份**、**所属乡镇** |
145 158
 | 选填 | 农牧户户数、农牧户人数、各出栏数、各自食数、备注 |
146 159
 | 乡镇 | 下拉仅含 **§2.3** 合法乡镇 |
147
-| 唯一性 | 同年同乡镇已存在记录时**禁止新增**,提示「该年份该乡镇已填报」 |
160
+| 默认值 | 打开新增弹窗时,所属年份、所属月份**默认当前年月**(可改) |
161
+| 唯一性 | 同年同月同乡镇已存在记录时**禁止新增**,提示「该年该月该乡镇已填报」 |
148 162
 | 数字 | 选填项若填写须为非负整数 |
149 163
 
150 164
 ---
@@ -153,8 +167,8 @@ flowchart TD
153 167
 
154 168
 | 项 | 要求 |
155 169
 | --- | --- |
156
-| 可改字段 | 除主键外均可改;**所属年份、所属乡镇**修改后仍须满足唯一性 |
157
-| 必填 | 所属年份、所属乡镇保持必填 |
170
+| 可改字段 | 除主键外均可改;**所属年份、所属月份、所属乡镇**修改后仍须满足唯一性 |
171
+| 必填 | 所属年份、所属月份、所属乡镇保持必填 |
158 172
 | 表单 | 展示 **§2.4** 全部可填指标(含黄牛/犏牛、绵羊、山羊、马骡、驴及自食绵羊/山羊等列表未展示字段) |
159 173
 
160 174
 ---
@@ -165,7 +179,7 @@ flowchart TD
165 179
 | --- | --- |
166 180
 | 方式 | **物理删除**(记录从库中移除,**不做逻辑删除**) |
167 181
 | 确认 | 删除前二次确认 |
168
-| 效果 | 同年同乡镇可重新新增或导入 |
182
+| 效果 | 同年同月同乡镇可重新新增或导入 |
169 183
 
170 184
 ---
171 185
 
@@ -174,10 +188,10 @@ flowchart TD
174 188
 | 项 | 要求 |
175 189
 | --- | --- |
176 190
 | 入口 | 列表「查看」 |
177
-| 展示字段 | **所属年份**、**所属乡镇**、**农牧户户数**、**农牧户人数**、**牦牛出栏数**、**自食情况(牛)**(即自食牛头数) |
191
+| 展示字段 | **所属年份**、**所属月份**、**所属乡镇**、**农牧户户数**、**农牧户人数**、**牦牛出栏数**、**自食情况(牛)**(即自食牛头数) |
178 192
 | 模式 | 只读 |
179 193
 
180
-> 列表未展示的出栏分项(黄牛/犏牛、绵羊、山羊等)通过「修改」入口维护;若业务需要详情页展示全部指标,可在实施阶段扩展,**本期以用户指定 6 项为准**
194
+> 列表未展示的出栏分项(黄牛/犏牛、绵羊、山羊等)通过「修改」入口维护;若业务需要详情页展示全部指标,可在实施阶段扩展,**本期以用户指定 7 项为准**(含月份)
181 195
 
182 196
 ---
183 197
 
@@ -186,12 +200,12 @@ flowchart TD
186 200
 | 项 | 要求 |
187 201
 | --- | --- |
188 202
 | 模板来源 | `牲畜数据.xlsx` 第 **6** 个 Sheet(年度出栏及自食统计表,如「那曲市巴青县2026年度农牧户牲畜出栏及自食情况统计表」) |
189
-| 填报年份 | 导入前用户**必须选择**「填报年份」(范围 **§2.2**);Sheet 标题中年份与所选年份不一致时,**以用户所选填报年份为准**写入 `stat_year` |
203
+| 填报年月 | 导入前用户**必须选择**「填报年份」「填报月份」(月份 **§2.2.1**);Sheet 标题中年份与所选年份不一致时,**以用户所选填报年份为准**写入 `stat_year`;文件中**不写月份**时以所选为准 |
190 204
 | 解析范围 | 从数据区首行(乡镇列有值且为合法乡镇名)起,至「合计」行或乡镇为空的行结束;**跳过**标题、表头、填表单位等说明行 |
191 205
 | 乡镇匹配 | Excel「乡镇」列与 `sys_dept.dept_name` **精确匹配**(trim 后);模板中若为「扎色镇」,宜按 **§8** 同义映射为「杂色镇」 |
192
-| 唯一键 | **所属年份 + 所属乡镇(`town_dept_id`)** |
193
-| 合并策略 | **upsert**:已存在 → 全量覆盖更新;不存在 → 新增 |
194
-| 重复导入 | 允许对同一模板多次导入;已有乡镇行均为更新 |
206
+| 唯一键 | **所属年份 + 所属月份 + 所属乡镇(`town_dept_id`)** |
207
+| 合并策略 | 按「年月 + 乡镇」**upsert**:已存在 → 全量覆盖更新;不存在 → 新增 |
208
+| 重复导入 | **允许**对同一文件在同一填报年月多次导入;第二次及以后 `updateCount` 增加、`insertCount` 为 0(乡镇均有数据时) |
195 209
 | 结果反馈 | 返回 `insertCount`、`updateCount`、失败条数及失败明细(行号、乡镇、原因) |
196 210
 | 文件限制 | `.xlsx`;大小上限与项目统一上传限制一致 |
197 211
 
@@ -223,16 +237,16 @@ flowchart TD
223 237
 
224 238
 | 项 | 要求 |
225 239
 | --- | --- |
226
-| 入口 | 列表页「按年份导出」 |
227
-| 导出年份 | 用户**必须选择**筛选条件中的「所属年份」(范围 **§2.2**);未选择时前端提示 |
228
-| 导出范围 | **巴青县全部乡镇**(**§2.3** 合法乡镇,按 `order_num` 排序),**不受**「所属乡镇」筛选限制 |
240
+| 入口 | 列表页「导出」 |
241
+| 导出年月 | 用户**必须选择**「所属年份」「所属月份」(范围见 **§2.2**、**§2.2.1**);弹窗打开时**默认当前年月** |
242
+| 导出范围 | **巴青县全部乡镇**(**§2.3** 合法乡镇,按 `order_num` 排序),**不受**列表「所属乡镇」筛选限制 |
229 243
 | 模板 | 与导入共用:`baqing-admin/src/main/resources/templates/yak_outbound_report_import_template.xlsx`(业务源文件 `牲畜数据.xlsx` 第 6 Sheet) |
230
-| 填充规则 | 表头标题、表头「年牲畜出栏数」区块及 Sheet 页签中的年份均替换为所选年份;每个乡镇一行;有填报数据的乡镇填入全部指标;未填报乡镇仅保留乡镇名称,指标为空 |
244
+| 填充规则 | 表头标题、表头「年牲畜出栏数」区块及 Sheet 页签中的年份均替换为所选年份;每个乡镇一行;有该**年月**填报数据的乡镇填入全部指标;未填报乡镇仅保留乡镇名称,指标为空 |
231 245
 | 合计行 | 数据区末行输出「合计」,对**已填报乡镇**的数值列求和;出栏小计(K 列)、自食小计(O 列)由分项加总后写入 |
232
-| 文件格式 | `.xlsx`;文件名示例:`2026年牦牛出栏数据.xlsx` |
233
-| 与导入关系 | 导出文件列结构与导入模板一致,可再次导入(同年将 upsert) |
246
+| 文件格式 | `.xlsx`;文件名示例:`2026年7月牦牛出栏数据.xlsx` |
247
+| 与导入关系 | 导出文件列结构与导入模板一致,可再次导入(同年将 upsert) |
234 248
 
235
-**与「下载模板」区别**:「下载模板」为空白模板;「按年份导出」为按库内数据填充后的全县报表。
249
+**与「下载模板」区别**:「下载模板」为空白模板;「导出」为按库内**指定年月**数据填充后的全县报表。
236 250
 
237 251
 ---
238 252
 
@@ -254,9 +268,9 @@ flowchart TD
254 268
 
255 269
 ## 7. 提示与错误约定
256 270
 
257
-- 必填项未填、年份/乡镇不合法、同年同乡镇重复新增  
271
+- 必填项未填、年份/月份/乡镇不合法、同年同月同乡镇重复新增、**未来月份**  
258 272
 - 导入:文件格式错误、乡镇无法匹配、数字非整数、行为空  
259
-- 导出:未选年份、年份不在可选范围  
273
+- 导出:未选年月、年月不在可选范围  
260 274
 - 删除成功/失败、导入部分失败(展示明细)  
261 275
 
262 276
 采用项目统一 `AjaxResult` 文案风格。
@@ -270,7 +284,7 @@ flowchart TD
270 284
 | 性能 | 列表分页;导入单次 ≤ 20 行(10 乡镇 + 合计行跳过) |
271 285
 | 审计 | 记录创建人、创建时间、更新人、更新时间 |
272 286
 | 模板差异 | 业务源文件 `牲畜数据.xlsx` 第 6 Sheet 乡镇「扎色镇」与 `sys_dept`「杂色镇」同义,导入宜做别名映射 |
273
-| 扩展 | 与「养殖产业数据」看板联动 |
287
+| 扩展 | 与「养殖产业数据」看板联动(**大屏/看板口径暂仍按年汇总,见技术方案**) |
274 288
 
275 289
 ---
276 290
 
@@ -280,11 +294,12 @@ flowchart TD
280 294
 | --- | --- |
281 295
 | 表字段与必填 | §2、§5.2 |
282 296
 | 年份向前 5 年 | §2.2 |
297
+| 月份 1~12、不可填未来月 | §2.2.1 |
283 298
 | 乡镇 sys_dept | §2.3 |
284 299
 | 增删改查 | §5.1~5.5 |
285 300
 | 物理删除 | §5.4 |
286 301
 | Excel 导入第 6 Sheet | §5.6 |
287
-| Excel 按年导出 | §5.7 |
302
+| Excel 按年导出 | §5.7 |
288 303
 | 列表/详情字段 | §5.1、§5.5 |
289 304
 
290 305
 ---
@@ -295,3 +310,4 @@ flowchart TD
295 310
 | --- | --- |
296 311
 | 1.0 | 初稿:出栏及自食指标、乡镇年度填报、Excel 导入、物理删除 |
297 312
 | 1.1 | 增加 **§5.7 按年份导出** 全县乡镇 Excel;权限 `export` |
313
+| 1.2 | **按年月填报**:增加 `stat_month`;唯一键「年+月+乡镇」;每月导入一次;列表/导入/导出支持月份;列表进入不默认年月;新增/导入/导出默认当前年月 |

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

@@ -14,13 +14,13 @@
14 14
 | **Excel** | Apache POI(与「牦牛存栏数据填报」一致) |
15 15
 | **组织** | `sys_dept` 乡镇字典;复用 `TownDeptSupport` |
16 16
 
17
-**分层**:`BizYakOutboundReportController` → `IBizYakOutboundReportService` → `BizYakOutboundReportMapper` → `domain`;Excel 由 `YakOutboundReportExcelParser`(导入解析)、`YakOutboundReportExcelExporter`(按年导出)承担。
17
+**分层**:`BizYakOutboundReportController` → `IBizYakOutboundReportService` → `BizYakOutboundReportMapper` → `domain`;Excel 由 `YakOutboundReportExcelParser`(导入解析)、`YakOutboundReportExcelExporter`(按年导出)承担。
18 18
 
19 19
 **代码位置**:`baqing-admin` → `com.ruoyi.web.modules.industryservice`(与产业数据模型同包)。
20 20
 
21 21
 **模板文件**:`baqing-admin/src/main/resources/templates/yak_outbound_report_import_template.xlsx`(导入、导出、下载模板共用)。
22 22
 
23
-**参考实现**:`BizYakHerdInventory*`、`YakHerdInventoryExcelParser`、`TownDeptSupport`(存栏填报模块,模式一致)。
23
+**参考实现**:`BizYakHerdInventory*`、`YakHerdInventoryMonthSupport`、`TownDeptSupport`(存栏填报模块,年月模式一致)。
24 24
 
25 25
 ---
26 26
 
@@ -32,6 +32,7 @@
32 32
 | --- | --- | --- | --- |
33 33
 | `id` | `bigint(20)` | Y | 主键 |
34 34
 | `stat_year` | `int(11)` | Y | 所属年份(自然年) |
35
+| `stat_month` | `int(11)` | Y | 所属月份(1~12) |
35 36
 | `town_dept_id` | `bigint(20)` | Y | 所属乡镇,`sys_dept.dept_id` |
36 37
 | `town_name` | `varchar(64)` | Y | 乡镇名称冗余 |
37 38
 | `farmer_household_count` | `int(11)` | N | 农牧户户数 |
@@ -53,11 +54,12 @@
53 54
 
54 55
 **索引与约束**
55 56
 
56
-- `UNIQUE uk_year_town (stat_year, town_dept_id)` — 年份+乡镇唯一,支撑导入 upsert。  
57
-- `KEY idx_stat_year (stat_year)`  
57
+- `UNIQUE uk_year_month_town (stat_year, stat_month, town_dept_id)` — 年月+乡镇唯一,支撑导入 upsert。  
58
+- `KEY idx_stat_year_month (stat_year, stat_month)`  
58 59
 - `KEY idx_town_dept_id (town_dept_id)`
59 60
 
60
-**DDL 文件**:`sql/biz_yak_outbound_report.sql`
61
+**DDL 文件**:`sql/biz_yak_outbound_report.sql`(新库,已含 `stat_month`)。  
62
+**已有库升级**(v1.1 按年表 `uk_year_town`):增加 `stat_month` 列(历史行默认 **12**),调整唯一键为 `uk_year_month_town`(实施阶段提供迁移 SQL,模式同存栏模块)。
61 63
 
62 64
 ### 2.2 乡镇字典(复用)
63 65
 
@@ -77,7 +79,11 @@ ORDER BY order_num;
77 79
 
78 80
 ### 2.3 可选年份
79 81
 
80
-与存栏模块一致:`YakOutboundReportYearSupport` 或复用 `YakHerdInventoryYearSupport` 逻辑,返回当前年起向前 5 年共 6 个年份。
82
+与存栏模块一致:复用 `YakHerdInventoryYearSupport.resolveYearOptions()`,返回当前年起向前 5 年共 6 个年份。
83
+
84
+### 2.4 可选月份
85
+
86
+复用 `YakHerdInventoryMonthSupport.resolveMonthOptions()` 返回 **1~12**;`validateStatPeriod(year, month)` 校验年份在 **§2.3** 范围内、月份合法且**非未来月**。出栏校验类 `YakOutboundReportValidation` 亦调用该 Support。
81 87
 
82 88
 ---
83 89
 
@@ -96,9 +102,10 @@ ORDER BY order_num;
96 102
 | 3.5 | 删除 | DELETE | `/{ids}` | `remove` |
97 103
 | 3.6 | 乡镇下拉 | GET | `/townOptions` | `list` |
98 104
 | 3.7 | 年份下拉 | GET | `/yearOptions` | `list` |
99
-| 3.8 | 导入 | POST | `/importData` | `import` |
100
-| 3.9 | 下载导入模板 | GET | `/importTemplate` | `import` |
101
-| 3.10 | 按年份导出 | GET | `/export` | `export` |
105
+| 3.8 | 月份下拉 | GET | `/monthOptions` | `list` |
106
+| 3.9 | 导入 | POST | `/importData` | `import` |
107
+| 3.10 | 下载导入模板 | GET | `/importTemplate` | `import` |
108
+| 3.11 | 按年月导出 | GET | `/export` | `export` |
102 109
 
103 110
 #### 3.1.1 列表 Query
104 111
 
@@ -106,6 +113,7 @@ ORDER BY order_num;
106 113
 | --- | --- | --- |
107 114
 | `pageNum` / `pageSize` | int | 默认 1 / 20 |
108 115
 | `statYear` | int | 所属年份,精确 |
116
+| `statMonth` | int | 所属月份(1~12),精确 |
109 117
 | `townDeptId` | long | 所属乡镇 dept_id,精确 |
110 118
 
111 119
 **列表 VO**
@@ -113,6 +121,7 @@ ORDER BY order_num;
113 121
 | 列标题 | 字段 |
114 122
 | --- | --- |
115 123
 | 所属年份 | `statYear` |
124
+| 所属月份 | `statMonth` |
116 125
 | 所属乡镇 | `townName` |
117 126
 | 农牧户户数 | `farmerHouseholdCount` |
118 127
 | 农牧户人数 | `farmerPopulationCount` |
@@ -123,7 +132,7 @@ ORDER BY order_num;
123 132
 
124 133
 返回需求 **§5.5** 指定字段(驼峰):
125 134
 
126
-`statYear`、`townName`、`farmerHouseholdCount`、`farmerPopulationCount`、`yakOutboundCount`、`selfConsumptionCattleCount`
135
+`statYear`、`statMonth`、`townName`、`farmerHouseholdCount`、`farmerPopulationCount`、`yakOutboundCount`、`selfConsumptionCattleCount`
127 136
 
128 137
 可选附带 `id`、`townDeptId` 供前端跳转修改。
129 138
 
@@ -132,17 +141,18 @@ ORDER BY order_num;
132 141
 | 字段 | 必填 | 校验 |
133 142
 | --- | --- | --- |
134 143
 | `statYear` | Y | 在 `yearOptions` 范围内 |
144
+| `statMonth` | Y | 1~12;非未来月 |
135 145
 | `townDeptId` | Y | 须在乡镇字典内 |
136 146
 | `farmerHouseholdCount` 等数量字段 | N | `>= 0` 整数 |
137 147
 | `id` | 修改必填 | — |
138 148
 
139
-**唯一性(手工新增)**:`countByYearAndTownExcludeId > 0` 则拒绝。**导入**走 upsert。
149
+**唯一性(手工新增)**:`countByYearMonthAndTownExcludeId > 0` 则拒绝,提示「该年该月该乡镇已填报」。**导入**走 upsert。
140 150
 
141 151
 #### 3.5.1 删除
142 152
 
143 153
 `DELETE FROM biz_yak_outbound_report WHERE id IN (...)`,物理删除。
144 154
 
145
-#### 3.8 导入
155
+#### 3.9 导入
146 156
 
147 157
 **请求**:`multipart/form-data`
148 158
 
@@ -150,15 +160,18 @@ ORDER BY order_num;
150 160
 | --- | --- | --- |
151 161
 | `file` | Y | `.xlsx` |
152 162
 | `statYear` | Y | 填报年份 |
163
+| `statMonth` | Y | 填报月份(1~12) |
153 164
 
154 165
 **Upsert**(`BizYakOutboundReportServiceImpl.upsertImportRow`):
155 166
 
156
-1. `selectByYearAndTown(statYear, townDeptId)`  
167
+1. `selectByYearMonthAndTown(statYear, statMonth, townDeptId)`  
157 168
 2. 无记录 → `insert`;有记录 → `update` 全字段覆盖  
158 169
 
170
+同一文件对**同一填报年月**多次导入:各乡镇行均为更新,不产生重复有效记录。
171
+
159 172
 **响应 `data`**:`insertCount`、`updateCount`、`failCount`、`failMessages`、`warnMessages`(自食大于出栏等)
160 173
 
161
-#### 3.8.1 Excel 第 6 Sheet 解析约定
174
+#### 3.9.1 Excel 第 6 Sheet 解析约定
162 175
 
163 176
 | 项 | 约定 |
164 177
 | --- | --- |
@@ -189,27 +202,28 @@ ORDER BY order_num;
189 202
 
190 203
 **导入事务**:逐乡镇独立处理,单乡镇失败不影响其余。
191 204
 
192
-#### 3.10 按年份导出
205
+#### 3.11 按年月导出
193 206
 
194
-**请求**:`GET /export?statYear=2026`
207
+**请求**:`GET /export?statYear=2026&statMonth=7`
195 208
 
196 209
 | 参数 | 必填 | 说明 |
197 210
 | --- | --- | --- |
198 211
 | `statYear` | Y | 导出年份,须在 `yearOptions` 范围内 |
212
+| `statMonth` | Y | 导出月份(1~12);须通过 `validateStatPeriod` |
199 213
 
200
-**响应**:`application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` 文件流;文件名 `{statYear}年牦牛出栏数据.xlsx`。
214
+**响应**:`application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` 文件流;文件名 `{statYear}年{statMonth}月牦牛出栏数据.xlsx`。
201 215
 
202
-**实现**:`BizYakOutboundReportServiceImpl.exportByYear` → `YakOutboundReportExcelExporter.export`:
216
+**实现**:`BizYakOutboundReportServiceImpl.exportByYearMonth` → `YakOutboundReportExcelExporter.export`:
203 217
 
204 218
 1. 加载 classpath 模板 `YakOutboundReportExcelExporter.TEMPLATE_CLASSPATH`  
205 219
 2. 解析出栏 Sheet(`YakOutboundReportExcelParser.resolveSheet`)  
206 220
 3. 替换表头中的四位年份为 `statYear`(含标题行与「年牲畜出栏数」表头区块;年份与「年」之间允许空格)  
207 221
 4. 将 Sheet 名称更新为 `{年份后两位}年出栏`(如 2024 → `24年出栏`)  
208
-5. 查询 `stat_year = statYear` 的全部记录,按 `town_dept_id` 建 Map  
222
+5. 查询 `stat_year = statYear AND stat_month = statMonth` 的全部记录,按 `town_dept_id` 建 Map  
209 223
 6. 遍历 `TownDeptSupport.listReportTowns()`,逐乡镇写行;无数据则仅写乡镇名  
210 224
 7. 末行写「合计」,对已填报乡镇数值列求和;K/O 列写入出栏/自食小计  
211 225
 
212
-**与 3.9 区别**:`/importTemplate` 返回空白模板;`/export` 返回填充后的全县报表。
226
+**与 3.10 区别**:`/importTemplate` 返回空白模板;`/export` 返回填充后的全县报表。
213 227
 
214 228
 ---
215 229
 
@@ -219,12 +233,12 @@ ORDER BY order_num;
219 233
 | --- | --- |
220 234
 | **路径** | `ruoyi-ui/src/views/dataModel/yakOutboundReport/index.vue` |
221 235
 | **API** | `ruoyi-ui/src/api/dataModel/yakOutboundReport.js` |
222
-| **列表** | 年份、乡镇筛选;列:所属年份、所属乡镇、农牧户户数、农牧户人数、牦牛出栏数、自食情况(牛) |
223
-| **详情** | 对话框只读,展示 **§5.5** 六字段 |
224
-| **表单** | 新增/修改展示全部数量字段;年份/乡镇下拉;`el-input-number` `:min="0"` `:precision="0"` |
225
-| **导入** | 先选「填报年份」→ 上传 → 展示导入结果摘要 |
236
+| **列表** | 年份、**月份**、乡镇筛选;**进入页面不默认年月**;列:所属年份、**所属月份**、所属乡镇、农牧户户数、农牧户人数、牦牛出栏数、自食情况(牛) |
237
+| **详情** | 对话框只读,展示 **§5.5** 七字段(含月份) |
238
+| **表单** | 新增/修改展示全部数量字段;年份/月份/乡镇下拉;**新增默认当前年月**;`el-input-number` `:min="0"` `:precision="0"` |
239
+| **导入** | 先选「填报年份」「填报月份」(**默认当前年月**)→ 上传 → 展示导入结果摘要 |
226 240
 | **模板下载** | `GET /importTemplate`;空白模板 |
227
-| **按年份导出** | 列表先选「所属年份」→ `GET /export`;`download('dataModel/yakOutboundReport/export', { statYear })` |
241
+| **导出** | 弹窗选年月(**默认当前年月**)→ `GET /export?statYear=&statMonth=` |
228 242
 
229 243
 ---
230 244
 
@@ -248,20 +262,24 @@ ORDER BY order_num;
248 262
 
249 263
 | 模块 | 表 | 模板 Sheet | 业务含义 |
250 264
 | --- | --- | --- | --- |
251
-| 牦牛存栏数据填报 | `biz_yak_herd_inventory` | `牲畜数据.xlsx` 第 1 Sheet(畜群结构) | 年末存栏结构 |
252
-| 牦牛出栏数据填报 | `biz_yak_outbound_report` | `牲畜数据.xlsx` 第 6 Sheet | 年度出栏及自食 |
265
+| 牦牛存栏数据填报 | `biz_yak_herd_inventory` | `牲畜数据.xlsx` 第 1 Sheet(畜群结构) | 月末存栏结构 |
266
+| 牦牛出栏数据填报 | `biz_yak_outbound_report` | `牲畜数据.xlsx` 第 6 Sheet | 月度出栏及自食 |
267
+
268
+两模块共用 `TownDeptSupport`、年份范围规则、`YakHerdInventoryMonthSupport` 年月校验、导入 upsert 模式;**本期不实现**跨表校验(如出栏数不大于存栏数)。
269
+
270
+### 6.1 与大屏 / 养殖看板(暂不改)
253 271
 
254
-两模块共用 `TownDeptSupport`、年份范围规则、导入 upsert 模式;**本期不实现**跨表校验(如出栏数不大于存栏数)。
272
+产业总览中「牦牛出栏数」「农牧户户数」等字段当前仍通过 `HomeScreenStatsMapper.sumOutboundReport*ByStatYear` **仅按 `stat_year` 汇总**,不按月份过滤。同一年存在多个月份填报时可能重复累加;**后续单独设计看板口径**,本模块按年月维护明细
255 273
 
256 274
 ---
257 275
 
258 276
 ## 7. 交付清单
259 277
 
260
-- [x] `sql/biz_yak_outbound_report.sql`  
278
+- [x] `sql/biz_yak_outbound_report.sql`(含 `stat_month`)  
261 279
 - [x] `BizYakOutboundReport` Domain / Mapper / XML / Service / Controller  
262 280
 - [x] `YakOutboundReportExcelParser` + `YakOutboundReportExcelExporter` + 导入模板 xlsx(第 6 Sheet)  
263
-- [x] 复用 `TownDeptSupport`、年份 Support  
264
-- [ ] 前端列表/表单/导入/导出  
281
+- [x] 复用 `TownDeptSupport`、`YakHerdInventoryYearSupport`、`YakHerdInventoryMonthSupport`  
282
+- [ ] 前端列表/表单/导入/导出(年月 UI,对齐存栏模块)  
265 283
 - [x] 单元测试 + 接口测试  
266 284
 - [ ] `sql/biz_sys_menu_yak_outbound_report.sql`  
267 285
 
@@ -273,3 +291,4 @@ ORDER BY order_num;
273 291
 | --- | --- |
274 292
 | 1.0 | 初稿:表结构、CRUD、第 6 Sheet 导入、物理删除、乡镇字典 |
275 293
 | 1.1 | 实现按年份导出全县乡镇 Excel;`YakOutboundReportExcelExporter` |
294
+| 1.2 | 按年月填报:`stat_month`;`uk_year_month_town`;`/monthOptions`;导入/导出/CRUD 支持月份;复用 `YakHerdInventoryMonthSupport` |

+ 37 - 31
doc/产业数据模型及服务/牦牛出栏数据填报/牦牛出栏数据填报测试用例.md

@@ -17,22 +17,24 @@
17 17
 | ZCZX-MYCNCL-UT-002 | 乡镇 | 字典过滤 | 单元测试 | JUnit5 | §2.3 | Mock dept 表 | `TownDeptSupport.listReportTowns()` | 仅 `parent_id=100` 且 `order_num>12` |
18 18
 | ZCZX-MYCNCL-UT-003 | 校验 | 必填年份乡镇 | 单元测试 | JUnit5 | §5.2 | 无 | `validate(row)` 缺 `statYear` | 抛 `ServiceException` |
19 19
 | ZCZX-MYCNCL-UT-004 | 校验 | 负数拒绝 | 单元测试 | JUnit5 | §2.4 | 无 | `yakOutboundCount=-1` | 校验失败 |
20
-| ZCZX-MYCNCL-UT-005 | 唯一性 | 同年同乡镇 | 单元测试 | JUnit5+Mockito | §5.2 | 已存在 2026+巴青乡 | `insert` 同键 | 失败「已填报」 |
21
-| ZCZX-MYCNCL-UT-006 | 新增 | 新增成功 | 单元测试 | JUnit5+Mockito | §5.2 | 无同年同乡镇 | `insert` 最小必填 | 成功 |
20
+| ZCZX-MYCNCL-UT-005 | 唯一性 | 同年同月同乡镇 | 单元测试 | JUnit5+Mockito | §5.2 | 已存在 2026-05+巴青乡 | `insert` 同键 | 失败「该年该月该乡镇已填报」 |
21
+| ZCZX-MYCNCL-UT-006 | 新增 | 新增成功 | 单元测试 | JUnit5+Mockito | §5.2 | 无同年同月同乡镇 | `insert` 最小必填 | 成功 |
22 22
 | ZCZX-MYCNCL-UT-007 | 导入解析 | 识别第 6 Sheet | 单元测试 | JUnit5 | §5.6 | `牲畜数据.xlsx` | `getSheetAt(5)` | Sheet 标题含「出栏及自食」 |
23
-| ZCZX-MYCNCL-UT-008 | 导入解析 | 识别表头行 | 单元测试 | JUnit5 | §3.8.1 | 模板 | `detectHeaderRow` | 定位含「乡镇」「户数」表头 |
23
+| ZCZX-MYCNCL-UT-008 | 导入解析 | 识别表头行 | 单元测试 | JUnit5 | §3.9.1 | 模板 | `detectHeaderRow` | 定位含「乡镇」「户数」表头 |
24 24
 | ZCZX-MYCNCL-UT-009 | 导入解析 | 乡镇名匹配 | 单元测试 | JUnit5 | §5.6 | 行「杂色镇」 | `resolveTownDeptId` | 返回对应 `dept_id` |
25 25
 | ZCZX-MYCNCL-UT-010 | 导入解析 | 扎色镇别名 | 单元测试 | JUnit5 | §8 | 行「扎色镇」 | `resolveTownDeptId` | 映射为「杂色镇」`dept_id` |
26 26
 | ZCZX-MYCNCL-UT-011 | 导入解析 | 跳过合计行 | 单元测试 | JUnit5 | §5.6 | 含「合计」行 | `parseRows` | 不写入合计 |
27 27
 | ZCZX-MYCNCL-UT-012 | 导入解析 | 列映射 | 单元测试 | JUnit5 | §5.6 | 巴青乡样本行 | 解析 | `yakOutboundCount`、`selfConsumptionCattleCount` 等与模板一致 |
28
-| ZCZX-MYCNCL-UT-013 | 导入 | upsert 新增 | 单元测试 | JUnit5+Mockito | §5.6 | DB 无 2026+本塔乡 | `importYear(2026,file)` | `insertCount>=1` |
29
-| ZCZX-MYCNCL-UT-014 | 导入 | upsert 更新 | 单元测试 | JUnit5+Mockito | §5.6 | 已有 2026 各乡镇 | 同一文件再导入 | `updateCount=10`;`insertCount=0` |
28
+| ZCZX-MYCNCL-UT-013 | 导入 | upsert 新增 | 单元测试 | JUnit5+Mockito | §5.6 | DB 无 2026-05+本塔乡 | `importData(2026,5,file)` | `insertCount>=1` |
29
+| ZCZX-MYCNCL-UT-014 | 导入 | upsert 更新 | 单元测试 | JUnit5+Mockito | §5.6 | 已有 2026-05 各乡镇 | 同一文件再导入 | `updateCount=10`;`insertCount=0` |
30 30
 | ZCZX-MYCNCL-UT-015 | 修改 | 记录不存在 | 单元测试 | JUnit5+Mockito | §5.3 | id 不存在 | `update` | 抛 `ServiceException` |
31 31
 | ZCZX-MYCNCL-UT-016 | 删除 | 物理删除 | 单元测试 | JUnit5+Mockito | §5.4 | 有效 id | `deleteByIds` | 调用 Mapper 物理删除;无 `del_flag` 更新 |
32 32
 | ZCZX-MYCNCL-UT-017 | 数据权限 | 乡镇账号过滤 | 单元测试 | JUnit5+Mockito | §6 | 用户 dept=杂色镇 | `selectList` | 仅本乡镇 |
33 33
 | ZCZX-MYCNCL-UT-018 | 警告 | 自食大于出栏 | 单元测试 | JUnit5 | §2.4 | 自食牛>牦牛出栏 | 校验/导入 | 允许保存;`warnMessages` 含提示 |
34
-| ZCZX-MYCNCL-UT-019 | 导出 | 按年填充模板 | 单元测试 | JUnit5 | §5.7 | 模板+1 乡镇样本 | `YakOutboundReportExcelExporter.export` | 返回 xlsx;解析含对应出栏数 |
34
+| ZCZX-MYCNCL-UT-019 | 导出 | 按年填充模板 | 单元测试 | JUnit5 | §5.7 | 模板+1 乡镇样本 | `YakOutboundReportExcelExporter.export` | 返回 xlsx;解析含对应出栏数 |
35 35
 | ZCZX-MYCNCL-UT-020 | 导出 | 全县乡镇行数 | 单元测试 | JUnit5 | §5.7 | 10 乡镇字典 | `export(2026, towns, map)` | 解析 10 数据行;无样例残留数据 |
36
+| ZCZX-MYCNCL-UT-021 | 月份 | 未来月拒绝 | 单元测试 | JUnit5 | §2.2.1 | 当前年 | `validateStatPeriod(2026, 未来月)` | 抛 `ServiceException` |
37
+| ZCZX-MYCNCL-UT-022 | 月份 | 月份选项 | 单元测试 | JUnit5 | §2.2.1 | 无 | `resolveMonthOptions()` | 返回 1~12 |
36 38
 
37 39
 ---
38 40
 
@@ -40,31 +42,33 @@
40 42
 
41 43
 | 用例编号 | 测试模块 | 测试项 | 测试类型 | 测试工具 | 测试目的 | 前置条件 | 测试步骤 | 预期结果 |
42 44
 | --- | --- | --- | --- | --- | --- | --- | --- | --- |
43
-| ZCZX-MYCNCL-API-001 | 列表 | 默认分页 | 接口测试 | Postman | §5.1 | 已登录;有样本 | `GET /list` | `code=200`;含所属年份、所属乡镇、农牧户户数、农牧户人数、牦牛出栏数、自食情况(牛) |
45
+| ZCZX-MYCNCL-API-001 | 列表 | 默认分页 | 接口测试 | Postman | §5.1 | 已登录;有样本 | `GET /list` | `code=200`;含所属年份、**所属月份**、所属乡镇、农牧户户数、农牧户人数、牦牛出栏数、自食情况(牛) |
44 46
 | ZCZX-MYCNCL-API-002 | 列表 | 按年份筛选 | 接口测试 | Postman | §5.1 | 有 2025、2026 数据 | `statYear=2026` | 仅 2026 |
47
+| ZCZX-MYCNCL-API-002a | 列表 | 按年月筛选 | 接口测试 | Postman | §5.1 | 同 year 多 month | `statYear=2026&statMonth=5` | 仅 2026 年 5 月 |
45 48
 | ZCZX-MYCNCL-API-003 | 列表 | 按乡镇筛选 | 接口测试 | Postman | §5.1 | 已知 dept_id | `townDeptId=` | 仅该乡镇 |
46
-| ZCZX-MYCNCL-API-004 | 列表 | 组合筛选 | 接口测试 | Postman | §5.1 | 已登录 | 年+乡镇 | 交集结果 |
47
-| ZCZX-MYCNCL-API-005 | 详情 | 指定字段 | 接口测试 | Postman | §5.5 | 有效 id | `GET /{id}` | 含年份、乡镇、农牧户户数、农牧户人数、牦牛出栏数、自食牛头数 |
48
-| ZCZX-MYCNCL-API-006 | 新增 | 必填成功 | 接口测试 | Postman | §5.2 | 无重复 | `POST` 仅 `statYear`+`townDeptId` | 成功 |
49
+| ZCZX-MYCNCL-API-004 | 列表 | 组合筛选 | 接口测试 | Postman | §5.1 | 已登录 | 年+月+乡镇 | 交集结果 |
50
+| ZCZX-MYCNCL-API-005 | 详情 | 指定字段 | 接口测试 | Postman | §5.5 | 有效 id | `GET /{id}` | 含年份、**月份**、乡镇、农牧户户数、农牧户人数、牦牛出栏数、自食牛头数 |
51
+| ZCZX-MYCNCL-API-006 | 新增 | 必填成功 | 接口测试 | Postman | §5.2 | 无重复 | `POST` 含 `statYear`+`statMonth`+`townDeptId` | 成功 |
49 52
 | ZCZX-MYCNCL-API-007 | 新增 | 缺年份 | 接口测试 | Postman | §5.2 | 已登录 | 缺 `statYear` | 失败 |
50 53
 | ZCZX-MYCNCL-API-008 | 新增 | 缺乡镇 | 接口测试 | Postman | §5.2 | 已登录 | 缺 `townDeptId` | 失败 |
51
-| ZCZX-MYCNCL-API-009 | 新增 | 重复同年乡镇 | 接口测试 | Postman | §5.2 | 已有记录 | 再 `POST` 同键 | 失败 |
54
+| ZCZX-MYCNCL-API-009 | 新增 | 重复同年乡镇 | 接口测试 | Postman | §5.2 | 已有记录 | 再 `POST` 同键 | 失败 |
52 55
 | ZCZX-MYCNCL-API-010 | 修改 | 更新出栏指标 | 接口测试 | Postman | §5.3 | 有效 id | `PUT` 改 `yakOutboundCount`、绵羊出栏等 | 成功;详情/列表为新值 |
53 56
 | ZCZX-MYCNCL-API-011 | 删除 | 物理删除 | 接口测试 | Postman | §5.4 | 有效 id | `DELETE /{id}` | 成功;库中无该记录 |
54 57
 | ZCZX-MYCNCL-API-012 | 下拉 | 年份选项 | 接口测试 | Postman | §2.2 | 已登录 | `GET /yearOptions` | 6 个年份 |
55 58
 | ZCZX-MYCNCL-API-013 | 下拉 | 乡镇选项 | 接口测试 | Postman | §2.3 | 已登录 | `GET /townOptions` | 10 个乡镇 |
56
-| ZCZX-MYCNCL-API-014 | 导入 | 全量乡镇 | 接口测试 | Postman | §5.6 | 空库 2026 | `POST /importData`+第 6 Sheet+`statYear=2026` | `insertCount=10`;`failCount=0` |
57
-| ZCZX-MYCNCL-API-015 | 导入 | 覆盖更新 | 接口测试 | Postman | §5.6 | 已有 2026 全量 | 改 Excel 一行再导入 | `updateCount>=1` |
58
-| ZCZX-MYCNCL-API-016 | 导入 | 缺填报年份 | 接口测试 | Postman | §5.6 | 已登录 | 仅上传文件 | 失败 |
59
+| ZCZX-MYCNCL-API-013a | 下拉 | 月份选项 | 接口测试 | Postman | §2.2.1 | 已登录 | `GET /monthOptions` | 1~12 |
60
+| ZCZX-MYCNCL-API-014 | 导入 | 全量乡镇 | 接口测试 | Postman | §5.6 | 空库 2026-05 | `POST /importData`+第 6 Sheet+`statYear=2026&statMonth=5` | `insertCount=10`;`failCount=0` |
61
+| ZCZX-MYCNCL-API-015 | 导入 | 覆盖更新 | 接口测试 | Postman | §5.6 | 已有 2026-05 全量 | 改 Excel 一行再导入 | `updateCount>=1` |
62
+| ZCZX-MYCNCL-API-016 | 导入 | 缺填报年月 | 接口测试 | Postman | §5.6 | 已登录 | 仅上传文件或缺 `statMonth` | 失败 |
59 63
 | ZCZX-MYCNCL-API-017 | 导入 | 非 xlsx | 接口测试 | Postman | §5.6 | 已登录 | 上传 `.csv` | 失败 |
60 64
 | ZCZX-MYCNCL-API-018 | 权限 | 无 add | 接口测试 | Postman | §6 | 仅 list 角色 | `POST /` | 403 |
61 65
 | ZCZX-MYCNCL-API-019 | 权限 | 无 import | 接口测试 | Postman | §6 | 无 import | `POST /importData` | 403 |
62
-| ZCZX-MYCNCL-API-020 | 端到端 | 导入-查-详情 | 接口测试 | Postman | §4 | 空库 | 导入 2026→list→detail | 牦牛出栏数、自食牛与 Excel 一致 |
63
-| ZCZX-MYCNCL-API-021 | 导出 | 按年导出 | 接口测试 | Postman/MockMvc | §5.7 | 有 2026 数据 | `GET /export?statYear=2026` | `200`;`Content-Type` 为 xlsx |
64
-| ZCZX-MYCNCL-API-022 | 导出 | 缺 statYear | 接口测试 | Postman | §5.7 | 已登录 | `GET /export` 无参 | 失败 |
66
+| ZCZX-MYCNCL-API-020 | 端到端 | 导入-查-详情 | 接口测试 | Postman | §4 | 空库 | 导入 2026-05→list→detail | 牦牛出栏数、自食牛与 Excel 一致 |
67
+| ZCZX-MYCNCL-API-021 | 导出 | 按年导出 | 接口测试 | Postman/MockMvc | §5.7 | 有 2026-05 数据 | `GET /export?statYear=2026&statMonth=5` | `200`;xlsx |
68
+| ZCZX-MYCNCL-API-022 | 导出 | 缺 statMonth | 接口测试 | Postman | §5.7 | 已登录 | `GET /export?statYear=2026` | 失败 |
65 69
 | ZCZX-MYCNCL-API-023 | 导出 | 非法年份 | 接口测试 | Postman | §5.7 | 已登录 | `statYear=1999` | 失败 |
66
-| ZCZX-MYCNCL-API-024 | 权限 | 无 export | 接口测试 | Postman | §6 | 无 export | `GET /export?statYear=2026` | 403 |
67
-| ZCZX-MYCNCL-API-025 | 端到端 | 导出-再导入 | 接口测试 | Postman | §5.7 | 有 2026 全量 | 导出→改一行→导入 | upsert 成功 |
70
+| ZCZX-MYCNCL-API-024 | 权限 | 无 export | 接口测试 | Postman | §6 | 无 export | `GET /export?statYear=2026&statMonth=5` | 403 |
71
+| ZCZX-MYCNCL-API-025 | 端到端 | 导出-再导入 | 接口测试 | Postman | §5.7 | 有 2026-05 全量 | 导出→改一行→导入 | upsert 成功 |
68 72
 
69 73
 ---
70 74
 
@@ -74,8 +78,8 @@
74 78
 
75 79
 | 用例编号 | 测试模块 | 测试项 | 测试类型 | 测试工具 | 测试目的 | 前置条件 | 测试步骤 | 预期结果 |
76 80
 | --- | --- | --- | --- | --- | --- | --- | --- | --- |
77
-| ZCZX-MYCNCL-UI-001 | 列表 | 默认加载 | UI 测试 | Playwright | §5.1 | 有数据 | 进入菜单 | 列:所属年份、所属乡镇、农牧户户数、农牧户人数、牦牛出栏数、自食情况(牛) |
78
-| ZCZX-MYCNCL-UI-002 | 列表 | 年乡镇筛选 | UI 测试 | Playwright | §5.1 | 有数据 | 选择后搜索 | 结果正确 |
81
+| ZCZX-MYCNCL-UI-001 | 列表 | 默认加载 | UI 测试 | Playwright | §5.1 | 有数据 | 进入菜单 | 列含所属年份、**所属月份**、乡镇…;**年月筛选为空** |
82
+| ZCZX-MYCNCL-UI-002 | 列表 | 年乡镇筛选 | UI 测试 | Playwright | §5.1 | 有数据 | 选择后搜索 | 结果正确 |
79 83
 | ZCZX-MYCNCL-UI-003 | 列表 | 重置 | UI 测试 | Playwright | §5.1 | 已筛选 | 点重置 | 条件清空 |
80 84
 | ZCZX-MYCNCL-UI-004 | 新增 | 必填校验 | UI 测试 | Playwright | §5.2 | 有 add 权限 | 不选乡镇提交 | 表单提示 |
81 85
 | ZCZX-MYCNCL-UI-005 | 新增 | 年份下拉 6 项 | UI 测试 | Playwright | §2.2 | 打开表单 | 查看年份下拉 | 当年起前 5 年 |
@@ -83,14 +87,14 @@
83 87
 | ZCZX-MYCNCL-UI-007 | 新增 | 全指标可填 | UI 测试 | Playwright | §5.3 | 打开表单 | 填写绵羊/山羊出栏等 | 可保存 |
84 88
 | ZCZX-MYCNCL-UI-008 | 修改 | 回显保存 | UI 测试 | Playwright | §5.3 | 有记录 | 修改牦牛出栏数保存 | 列表更新 |
85 89
 | ZCZX-MYCNCL-UI-009 | 删除 | 二次确认 | UI 测试 | Playwright | §5.4 | 有记录 | 删除确认 | 行消失 |
86
-| ZCZX-MYCNCL-UI-010 | 详情 | 字段只读 | UI 测试 | Playwright | §5.5 | 有完整数据 | 点查看 | 展示年份、乡镇、农牧户户数、农牧户人数、牦牛出栏数、自食情况(牛) |
87
-| ZCZX-MYCNCL-UI-011 | 导入 | 选年上传 | UI 测试 | Playwright | §5.6 | 有 import 权限 | 选 2026→上传第 6 Sheet | 成功摘要 |
88
-| ZCZX-MYCNCL-UI-012 | 导入 | 未选年份 | UI 测试 | Playwright | §5.6 | 有 import 权限 | 直接上传 | 前端拦截或后端失败 |
90
+| ZCZX-MYCNCL-UI-010 | 详情 | 字段只读 | UI 测试 | Playwright | §5.5 | 有完整数据 | 点查看 | 展示年份、**月份**、乡镇、农牧户户数、农牧户人数、牦牛出栏数、自食情况(牛) |
91
+| ZCZX-MYCNCL-UI-011 | 导入 | 选年上传 | UI 测试 | Playwright | §5.6 | 有 import 权限 | 默认当前年月→上传第 6 Sheet | 成功摘要 |
92
+| ZCZX-MYCNCL-UI-012 | 导入 | 未选月份 | UI 测试 | Playwright | §5.6 | 有 import 权限 | 缺月份提交 | 表单校验失败 |
89 93
 | ZCZX-MYCNCL-UI-013 | 权限 | 无导入按钮 | UI 测试 | Playwright | §6 | 无 import | 进入页面 | 无导入入口 |
90
-| ZCZX-MYCNCL-UI-014 | 端到端 | 导入后列表 | UI 集成 | Playwright | §4 | 空库 | 导入→筛选 2026 | 10 行乡镇数据 |
91
-| ZCZX-MYCNCL-UI-015 | 导出 | 按年份导出 | UI 测试 | Playwright | §5.7 | 有 export 权限;已选年份 | 点「按年份导出」 | 下载 `{年}年牦牛出栏数据.xlsx` |
92
-| ZCZX-MYCNCL-UI-016 | 导出 | 未选年份 | UI 测试 | Playwright | §5.7 | 有 export 权限 | 未选年份点导出 | 提示先选择所属年份 |
93
-| ZCZX-MYCNCL-UI-017 | 权限 | 无导出按钮 | UI 测试 | Playwright | §6 | 无 export | 进入页面 | 无「按年份导出入口 |
94
+| ZCZX-MYCNCL-UI-014 | 端到端 | 导入后列表 | UI 集成 | Playwright | §4 | 空库 | 导入→筛选 2026-05 | 10 行乡镇数据 |
95
+| ZCZX-MYCNCL-UI-015 | 导出 | 按年月导出 | UI 测试 | Playwright | §5.7 | 有 export 权限 | 选年月导出 | 下载 `{年}年{月}月牦牛出栏数据.xlsx` |
96
+| ZCZX-MYCNCL-UI-016 | 导出 | 未选月份 | UI 测试 | Playwright | §5.7 | 有 export 权限 | 缺月份点导出 | 表单校验失败 |
97
+| ZCZX-MYCNCL-UI-017 | 权限 | 无导出按钮 | UI 测试 | Playwright | §6 | 无 export | 进入页面 | 无导出入口 |
94 98
 
95 99
 ---
96 100
 
@@ -99,12 +103,13 @@
99 103
 | 需求章节 | 要点 | 主要用例 |
100 104
 | --- | --- | --- |
101 105
 | §2.2 年份 | 向前 5 年 | UT-001;API-012;UI-005 |
106
+| §2.2.1 月份 | 1~12、非未来月 | UT-021~022;API-013a |
102 107
 | §2.3 乡镇 | sys_dept 二级 order_num>12 | UT-002;API-013;UI-006 |
103
-| §5.1 列表 | 6 数据列+操作 | API-001~004;UI-001~003 |
108
+| §5.1 列表 | 7 数据列+操作;默认不筛年月 | API-001~004、002a;UI-001~003 |
104 109
 | §5.2~5.4 CRUD | 必填、唯一、物理删除 | UT-003~006、015~016;API-006~011 |
105
-| §5.5 详情 | 六字段 | API-005;UI-010 |
110
+| §5.5 详情 | 七字段(含月份) | API-005;UI-010 |
106 111
 | §5.6 导入 | 第 6 Sheet | UT-007~014;API-014~017;UI-011~012 |
107
-| §5.7 导出 | 按年全县乡镇 | UT-019~020;API-021~025;UI-015~017 |
112
+| §5.7 导出 | 按年全县乡镇 | UT-019~020;API-021~025;UI-015~016 |
108 113
 | §6 权限 | 按钮级 | API-018~019、024;UI-013、017 |
109 114
 | §8 扎色镇别名 | 导入映射 | UT-010 |
110 115
 
@@ -116,3 +121,4 @@
116 121
 | --- | --- |
117 122
 | 1.0 | 初版:单元 18、接口 20、UI 14;编号前缀 **ZCZX-MYCNCL** |
118 123
 | 1.1 | 补充按年份导出用例 UT-019~020、API-021~025、UI-015~017 |
124
+| 1.2 | 按年月填报:UT-005/021~022;API-002a/013a/014/021~022;UI 年月筛选与导出;唯一键改为年+月+乡镇 |