wwh пре 2 недеља
родитељ
комит
e0e26a5404

+ 5 - 4
doc/产业数据模型及服务/养殖产业数据/养殖产业数据功能需求.md

@@ -130,7 +130,7 @@
130 130
 | 总览出栏、农牧户 | `biz_yak_outbound_report` | 同上最近填报年月;字段 `yak_outbound_count`、`farmer_household_count` |
131 131
 | 公/母牛数量 | `biz_yak_herd_inventory` | 最近填报年月全县 `bull_total`、`cow_total` |
132 132
 | 存栏趋势 | `biz_yak_herd_inventory` | 距当前 **12** 个自然月;按月全县公/母合计;缺月 **0** |
133
-| 年龄结构 | `biz_yak_herd_inventory` | 最近填报年月母牛四龄档合计 |
133
+| 年龄结构 | `biz_yak_herd_inventory` | 最近填报年月母牛两龄档合计(0~1.5 / 1.5+) |
134 134
 
135 135
 ---
136 136
 
@@ -142,7 +142,7 @@
142 142
 | --- | --- |
143 143
 | 产业总览 | 六项指标卡片展示,见 **§5.2** |
144 144
 | 牦牛存栏变动 | 当前公、母牛数量 + 按月存栏趋势图,见 **§5.3** |
145
-| 牦牛年龄结构 | 母牛档年龄占比图,见 **§5.4** |
145
+| 牦牛年龄结构 | 母牛档年龄占比图,见 **§5.4** |
146 146
 | 草场类型占比 | 可用/使用中数量 + 退化等级数量与占比,见 **§5.5** |
147 147
 | 刷新 | 重新拉取并计算全页指标,见 **§5.1** |
148 148
 
@@ -266,8 +266,8 @@ flowchart LR
266 266
 | --- | --- |
267 267
 | 图表类型 | 饼图(或环形图,与原型一致) |
268 268
 | 统计对象 | 存栏填报**最近年月**全县母牛分龄合计 |
269
-| 分档 | 四档(与大屏一致):**1岁以下**、**1~3岁**、**3~12岁**、**12岁以上** |
270
-| 编码 | `COW_U1` / `COW_1_3` / `COW_3_12` / `COW_O12` |
269
+| 分档 | 两档(与大屏一致):**0~1.5岁幼畜**、**1.5岁以上成畜** |
270
+| 编码 | `COW_YOUNG` / `COW_ADULT` |
271 271
 | 展示 | 每档展示**头数**与**占比(%)**;各档占比之和为 **100%**(四舍五入 ±1% 可接受) |
272 272
 
273 273
 ### 5.5 草场类型占比
@@ -380,5 +380,6 @@ flowchart LR
380 380
 
381 381
 | 版本 | 日期 | 说明 |
382 382
 | --- | --- | --- |
383
+| 1.3 | 2026-07-17 | 年龄结构改为母牛两档(0~1.5 / 1.5+),与存栏填报、大屏对齐 |
383 384
 | 1.2 | 2026-07-04 | 存栏变动/年龄/总览三项改填报表;滚动 12 月;母牛四档;与大屏对齐 |
384 385
 | 1.1 | (既有) | 总览存栏/出栏/农牧户改填报合计 |

+ 5 - 4
doc/产业数据模型及服务/养殖产业数据/养殖产业数据技术方案.md

@@ -249,12 +249,12 @@ return success(dashboardService.loadDashboard(allowed, forceRefresh));
249 249
 
250 250
 | 字段 | 类型 | 说明 |
251 251
 | --- | --- | --- |
252
-| `bandCode` | string | `COW_U1` / `COW_1_3` / `COW_3_12` / `COW_O12` |
253
-| `bandLabel` | string | `1岁以下` / `1~3岁` / `3~12岁` / `12岁以上` |
252
+| `bandCode` | string | `COW_YOUNG` / `COW_ADULT` |
253
+| `bandLabel` | string | `0~1.5岁幼畜` / `1.5岁以上成畜` |
254 254
 | `count` | int | 头数 |
255 255
 | `ratio` | decimal | 占比 0~100 数值,保留 1 位小数 |
256 256
 
257
-档固定返回(count 为 0 也返回);与大屏年龄结构一致。
257
+档固定返回(count 为 0 也返回);与大屏年龄结构一致;数据源为存栏填报 `cow_young_count` / `cow_adult_count`
258 258
 
259 259
 #### 3.1.6 `grassland`
260 260
 
@@ -370,7 +370,7 @@ VALUES ('养殖产业数据', {parentId}, 10, 'breedingIndustry', 'livestockIndu
370 370
 | --- | --- |
371 371
 | §5.2 产业总览 | `overview` + **§2.2** SQL |
372 372
 | §5.3 存栏变动 | `inventoryChange` + 填报表滚动 12 月(与大屏一致) |
373
-| §5.4 年龄结构 | `ageStructure` 母牛档(填报最近年月) |
373
+| §5.4 年龄结构 | `ageStructure` 母牛档(填报最近年月) |
374 374
 | §5.5 草场占比 | `grassland` |
375 375
 | §5.1 刷新 | **3.1** `forceRefresh` |
376 376
 | §2.9 数据权限 | **§2.6** `BreedingIndustryPastureScope`(仅预警等档案类指标) |
@@ -382,5 +382,6 @@ VALUES ('养殖产业数据', {parentId}, 10, 'breedingIndustry', 'livestockIndu
382 382
 
383 383
 | 版本 | 日期 | 说明 |
384 384
 | --- | --- | --- |
385
+| 1.3 | 2026-07-17 | 年龄结构两档 `COW_YOUNG`/`COW_ADULT` |
385 386
 | 1.2 | 2026-07-04 | 存栏变动/年龄/趋势改 `biz_yak_herd_inventory` 填报表;滚动 12 月;与大屏字段对齐 |
386 387
 | 1.1 | (既有) | 总览三项改填报合计 |

+ 3 - 3
doc/产业数据模型及服务/养殖产业数据/养殖产业数据测试用例.md

@@ -66,7 +66,7 @@
66 66
 | ZCZX-YZCYSJ-UT-013 | 存栏变动 | 公母来自最近填报 | 单元测试 | JUnit5 | §5.3.1、§2.11 | HERD-L | `loadDashboard` | `bullCount`/`cowCount` 与 HERD-L 全县合计一致;`unknownGenderCount=0` |
67 67
 | ZCZX-YZCYSJ-UT-028 | 产业总览 | 填报三项最近年月 | 单元测试 | JUnit5 | §5.2、§2.11 | Mock 填报合计 | `loadDashboard` | `inventoryTotal`/`annualOutbound`/`pastureCount` 与 `HomeScreenStatsMapper` 最近年月合计一致 |
68 68
 | ZCZX-YZCYSJ-UT-014 | 性别 | trim 后匹配 | 单元测试 | JUnit5 | 技术§4 | `gender=' 母 '` | `normalizeGender` | 计母牛(档案类,预警用) |
69
-| ZCZX-YZCYSJ-UT-015 | 年龄结构 | 四档编码 | 单元测试 | JUnit5 | §5.4 | Mock 母牛四龄填报 | `toAgeStructure` | `bandCode` 为 COW_U1/COW_1_3/COW_3_12/COW_O12 |
69
+| ZCZX-YZCYSJ-UT-015 | 年龄结构 | 两档编码 | 单元测试 | JUnit5 | §5.4 | Mock 母牛两龄填报 | `toAgeStructure` | `bandCode` 为 COW_YOUNG/COW_ADULT |
70 70
 | ZCZX-YZCYSJ-UT-016 | 年龄分档 | 占比与零分母 | 单元测试 | JUnit5 | §5.4 | 母牛合计 0 | `buildCowAgeStructureFromHerdReport` | 四档 count=0、ratio=0 仍返回 |
71 71
 | ZCZX-YZCYSJ-UT-017 | 年龄分档 | 占比之和 | 单元测试 | JUnit5 | §5.4 | 有母牛数据 | sum(ratio) | 99.9~100.1 |
72 72
 | ZCZX-YZCYSJ-UT-018 | 存栏趋势 | 滚动 12 月长度 | 单元测试 | JUnit5 | §5.3.2、§2.11 | 5 月环境 | `buildRolling12MonthBullCowTrend` | 固定 **12** 条;锚点含 statYear+month |
@@ -94,7 +94,7 @@
94 94
 | ZCZX-YZCYSJ-API-004 | 存栏变动 | 公母与未标注 | 接口测试 | Postman | §5.3.1 | HERD-L | 读 `inventoryChange` | `unknownGenderCount=0`;公/母与 HERD-L 一致 |
95 95
 | ZCZX-YZCYSJ-API-005 | 存栏变动 | 滚动 12 月趋势 | 接口测试 | Postman | §5.3.2 | 统计日 5 月 | `monthlyTrend` | 长度=**12**;含 `statYear`/`month`/`bullCount`/`cowCount`;末项 2026-05 |
96 96
 | ZCZX-YZCYSJ-API-006 | 存栏变动 | 最近月与 header | 接口测试 | Postman | §5.3.1、§2.11 | HERD-L | 比 `monthlyTrend` 最后一项与 header | 公/母分别相等 |
97
-| ZCZX-YZCYSJ-API-007 | 年龄结构 | 固定四档 | 接口测试 | Postman | §5.4 | 已登录 | `ageStructure` | 长度=**4**;`bandCode` 为 COW_U1/COW_1_3/COW_3_12/COW_O12;count=0 也返回 |
97
+| ZCZX-YZCYSJ-API-007 | 年龄结构 | 固定两档 | 接口测试 | Postman | §5.4 | 已登录 | `ageStructure` | 长度=**2**;`bandCode` 为 COW_YOUNG/COW_ADULT;count=0 也返回 |
98 98
 | ZCZX-YZCYSJ-API-008 | 年龄结构 | 占比之和容忍 | 接口测试 | Postman | §5.4 | 存栏>0 | sum(ratio) | 99.9~100.1(四舍五入容忍) |
99 99
 | ZCZX-YZCYSJ-API-009 | 草场占比 | 可用/使用中 | 接口测试 | Postman | §5.5.1 | 已登录 | `grassland.availableCount/inUseCount` | 与 UT-025~026 一致 |
100 100
 | ZCZX-YZCYSJ-API-010 | 草场占比 | 退化五档与合计 | 接口测试 | Postman | §5.5.2 | 已登录 | `degradationStats` | 长度=5;sum(count)=`grasslandCount` |
@@ -126,7 +126,7 @@
126 126
 | ZCZX-YZCYSJ-UI-003 | 存栏变动 | 公母文案 | UI | Playwright+Chrome | §5.3.1 | 有 HERD-L | 查看存栏区文案 | 显示公牛、母牛数量;**无**「未标注性别」 |
127 127
 | ZCZX-YZCYSJ-UI-004 | 存栏变动 | 双折线 12 月 | UI | Playwright+Chrome | §5.3.2、前端§4 | 5 月环境 | 检查 ECharts 折线 | **12** 个刻度;横轴仅「N月」;悬停显「YYYY年N月」 |
128 128
 | ZCZX-YZCYSJ-UI-005 | 存栏变动 | 最近月与 header | UI | Playwright+Chrome | §5.3.1 | 有 HERD-L | 比对折线最后一点与上方公/母数 | 分别一致 |
129
-| ZCZX-YZCYSJ-UI-006 | 年龄结构 | 档饼图与占比 | UI | Playwright+Chrome | §5.4 | 有数据 | 查看年龄结构图例/标签 | 含 1岁以下、1~3岁、3~12岁、12岁以上;显示 % |
129
+| ZCZX-YZCYSJ-UI-006 | 年龄结构 | 档饼图与占比 | UI | Playwright+Chrome | §5.4 | 有数据 | 查看年龄结构图例/标签 | 含 0~1.5岁幼畜、1.5岁以上成畜;显示 % |
130 130
 | ZCZX-YZCYSJ-UI-007 | 草场占比 | 可用/使用中与退化 | UI | Playwright+Chrome | §5.5 | 有 G | 查看草场区 | 展示可用、使用中个数;退化列表/图五档;列表占比与图一致 |
131 131
 | ZCZX-YZCYSJ-UI-009 | 刷新 | 成功更新全页 | UI | Playwright+Chrome | §5.1 | 已打开页 | 点击「刷新」 | 按钮 loading/disabled;完成后四块数据更新;可选轻提示「数据已更新」 |
132 132
 | ZCZX-YZCYSJ-UI-010 | 刷新 | 防重复点击 | UI | Playwright+Chrome | §5.1、前端§4 | 已打开页 | 刷新未完成时连点 2 次 | 仅 1 次有效请求或第二次按钮不可用 |

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

@@ -10,7 +10,7 @@
10 10
 | --- | --- |
11 11
 | **Vue 路径** | `ruoyi-ui/src/views/dataModel/yakHerdInventory/index.vue` |
12 12
 | **组件名** | `YakHerdInventory` |
13
-| **菜单配置** | 组件路径:`dataModel/yakHerdInventory/index`(「产业数据模型及服务」目录下) |
13
+| **菜单配置** | 组件路径:`dataModel/yakHerdInventory/index` |
14 14
 | **权限前缀** | `dataModel:yakHerdInventory:list\|query\|add\|edit\|remove\|import\|export` |
15 15
 
16 16
 ---
@@ -29,25 +29,26 @@
29 29
 | `addYakHerdInventory` | POST | `/` 新增 |
30 30
 | `updateYakHerdInventory` | PUT | `/` 修改 |
31 31
 | `delYakHerdInventory` | DELETE | `/{ids}` 删除 |
32
-| `listYakHerdInventoryYearOptions` | GET | `/yearOptions` 可选年份 |
33
-| `listYakHerdInventoryMonthOptions` | GET | `/monthOptions` 可选月份(1~12) |
34
-| `listYakHerdInventoryTownOptions` | GET | `/townOptions` 乡镇下拉 |
35
-| `importYakHerdInventory` | POST | `/importData`(`multipart/form-data`:`file` + `statYear` + `statMonth`) |
36
-| `exportYakHerdInventory` | GET | `/export`(`statYear` + `statMonth`,`responseType: blob`) |
32
+| `listYakHerdInventoryYearOptions` | GET | `/yearOptions` |
33
+| `listYakHerdInventoryMonthOptions` | GET | `/monthOptions` |
34
+| `listYakHerdInventoryTownOptions` | GET | `/townOptions` |
35
+| `listYakHerdInventoryVillageTree` | GET | `/villageTree` 县→乡镇→村 |
36
+| `importYakHerdInventory` | POST | `/importData`(`file` + `statYear` + `statMonth`) |
37
+| `exportYakHerdInventory` | GET | `/export`(`statYear` + `statMonth`,`blob`) |
37 38
 
38
-模板下载使用 `downloadYakHerdInventoryTemplate()`(**GET** + `responseType: 'blob'`),勿用若依全局 `this.download()`(固定 **POST**,仅适用于导出类接口)。
39
+模板下载`downloadYakHerdInventoryTemplate()`(**GET** + `blob`),勿用若依全局 `this.download()`(固定 POST)。
39 40
 
40 41
 ---
41 42
 
42 43
 ## 3. 页面结构
43 44
 
44
-1. **筛选区**:所属年份、**所属月份**、所属乡镇(均可清空);**搜索**、**重置**、**新增**、**导入**、**导出**
45
-2. **列表区**:所属年份、**所属月份**、乡镇、村居数、牧户数、存栏数、公牛/母牛存栏数;空值展示 `—`。  
45
+1. **筛选区**:所属年份、所属月份、所属乡镇、所属村(Treeselect);搜索、重置、新增、导入、导出
46
+2. **列表区**:所属年份、所属月份、所属乡镇、所属村、村居数、牧户数、存栏数、公牛/母牛存栏数;空值展示 `—`。  
46 47
 3. **操作列**:查看、修改、删除。  
47
-4. **新增/编辑弹窗**:年份、月份、乡镇必填;存栏指标必填;**新增时默认当前年月**
48
-5. **查看弹窗**:全字段只读(含预留村居、审计信息);可跳转编辑
49
-6. **导入弹窗**:选填报年份」「填报份」**默认当前年月**)→ 上传 `.xlsx` → 展示 insert/update/fail 及 fail/warn 明细
50
-7. **导出弹窗**:选所属年份」「所属份」**默认当前年月**)→ 下载 `{年}年{月}月牦牛存栏数据.xlsx`。
48
+4. **新增/编辑弹窗**:年份、月份、所属村(必填);明细录入字段(`INPUT_COUNT_FIELDS`);**不展示**派生合计;新增默认当前年月
49
+5. **查看弹窗**:全字段只读(含派生合计、审计信息)
50
+6. **导入弹窗**:选填报年月(默认当前)→ 上传 `.xlsx` → 展示 insert/fail/warn。  
51
+7. **导出弹窗**:选所属年月(默认当前)→ 下载 `{年}年{月}月牦牛存栏数据.xlsx`。
51 52
 
52 53
 ---
53 54
 
@@ -55,16 +56,18 @@
55 56
 
56 57
 | 规则 | 实现 |
57 58
 | --- | --- |
58
-| 年份范围 | 由 `/yearOptions` 动态加载(当前年起向前 5 年) |
59
-| 月份范围 | 页面 `monthOptions` 固定 1~12;展示 `{month}月`(i18n `statMonthLabel`) |
60
-| 乡镇下拉 | `/townOptions` 返回 `deptId` / `deptName` |
61
-| 列表筛选 | `statYear`、`statMonth`、`townDeptId` 精确匹配;**进入页面不默认年月** |
62
-| 重置 | 清空年份、月份、乡镇后重新查询 |
63
-| 存栏数字 | 必填;须为大于等于 0 的整数 |
64
-| 唯一性 | 同年同月同乡镇重复由后端拦截,前端展示 `msg` |
65
-| 导入 | `FormData` 含 `statYear`、`statMonth`;成功后 `$alert` 展示摘要与明细 |
66
-| 删除 | `$modal.confirm` 后 `delYakHerdInventory(id)` |
67
-| 预留村居 | 详情展示 `villageDeptId` / `villageName`;表单本期不编辑 |
59
+| 年份 / 月份 | `/yearOptions`、`/monthOptions`;展示 `{month}月` |
60
+| 村树 | `/villageTree`;`vue-treeselect`:`disable-branch-nodes`、`append-to-body` |
61
+| 列表筛选 | `statYear`、`statMonth`、`townDeptId`、`villageDeptId`;进入页不默认年月 |
62
+| 提交体 | 传 `villageDeptId` 与明细;不传乡镇(后端反推) |
63
+| 派生字段 | 仅详情 `DERIVED_COUNT_FIELDS` 展示 |
64
+| 唯一性 | 同年同月同村重复由后端拦截 |
65
+| 导入 | `FormData` 含年月;成功后 `$alert` 摘要 |
66
+| 删除 | `$modal.confirm` 后删除 |
67
+
68
+**录入字段**(`INPUT_COUNT_FIELDS`):`villageCount`、`herdsmanHouseholdCount`、`breedingBullCount`、`bullCastratedCount`、`cowYoungCount`、`cowAdultCount`、`milkingCowCount`、`fertileCowCount`、`sheepTotal`、`milkingSheepCount`、`insuredYakCount`。
69
+
70
+**派生字段**(`DERIVED_COUNT_FIELDS`):`bullTotal`、`cowTotal`、`dairyTotal`、`yakTotal`、`grandTotal`、`yakSheepTotal`。
68 71
 
69 72
 ---
70 73
 
@@ -74,18 +77,15 @@
74 77
 | --- | --- |
75 78
 | **语言包** | `dataModel.yakHerdInventory`、`dataModel.common` |
76 79
 | **文件** | `lang/zh/dataModel.js`、`lang/bo/dataModel.js` |
77
-| **Mixin** | `dataModelLocaleMixin`,`dmNs = 'yakHerdInventory'`,`dmT` / `dmCommon` |
78
-| **月份文案** | `queryStatMonth`、`colStatMonth`、`formStatMonth`、`importStatMonth`、`exportStatMonth`、`statMonthLabel` 等 |
80
+| **Mixin** | `dataModelLocaleMixin`,`dmNs = 'yakHerdInventory'` |
79 81
 
80 82
 ---
81 83
 
82 84
 ## 6. 联调说明
83 85
 
84
-1. 部署后端 `BizYakHerdInventoryController` 及 `biz_yak_herd_inventory` 表(含 `stat_month`)。  
85
-2. 已有库执行 `sql/biz_yak_herd_inventory_stat_month.sql`。  
86
-3. 菜单组件 `dataModel/yakHerdInventory/index`,权限与后端 §5 一致。  
87
-4. 验证:年月/乡镇筛选、CRUD、导入导出、双语切换。  
88
-5. 导入联调:选填报年月 → 上传 `2026年.xlsx` 格式文件。
86
+1. 部署后端并执行 `sql/biz_yak_herd_inventory.sql`(或村唯一键迁移脚本)。  
87
+2. 菜单组件 `dataModel/yakHerdInventory/index`。  
88
+3. 验证:村树选村、CRUD、导入全量覆盖、导出合计行、详情含派生字段、双语。  
89 89
 
90 90
 ---
91 91
 
@@ -104,5 +104,6 @@
104 104
 
105 105
 | 版本 | 说明 |
106 106
 | --- | --- |
107
-| 1.0 | 初版:列表/CRUD/详情/导入/模板下载;汉藏 i18n;对齐功能需求 v1.3 |
108
-| 1.1 | 按年月填报:列表月份列与筛选;导入/导出/新增默认当前年月;列表不默认年月;导出已实现 |
107
+| 1.0 | 初版:列表/CRUD/详情/导入;汉藏 i18n |
108
+| 1.1 | 按年月填报:列表月份筛选;导入/导出 |
109
+| 1.2 | 按村填报:Treeselect 村树;列表乡镇+村;表单仅明细;详情含派生合计;字段对齐新模板 |

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

@@ -5,9 +5,9 @@
5 5
 | 项 | 说明 |
6 6
 | --- | --- |
7 7
 | 模块名称 | 牦牛存栏数据填报 |
8
-| 目标 | 按**乡镇 × 所属年月**填报、导入、维护县域牦牛存栏结构统计数据,**每月导入一次**当月存栏;支撑养殖产业看板、监管报表及历史对比 |
9
-| 关联系统 | 业务后台(增删改查、Excel 导入/导出);组织机构 `sys_dept`(乡镇字典) |
10
-| 修订依据 | 业务模板 `2026年.xlsx`(农业农村和科技水利局-业务办-畜群结构);用户填报诉求 |
8
+| 目标 | 按** × 所属年月**填报、导入、维护县域牦牛存栏畜群结构统计数据,**每月导入一次**当月存栏;支撑养殖产业看板、监管报表及历史对比 |
9
+| 关联系统 | 业务后台(增删改查、Excel 导入/导出);组织机构 `sys_dept`(乡镇、村) |
10
+| 修订依据 | 业务模板 `牦牛存栏数据填报.xlsx` / `yak_herd_inventory_import_template.xlsx`;按村填报与交接表字段对齐 |
11 11
 
12 12
 本文档仅描述**功能需求与业务规则**,不涉及库表字段命名、接口路径、技术栈选型及实现细节(见同目录技术方案)。
13 13
 
@@ -17,16 +17,16 @@
17 17
 
18 18
 ### 2.1 填报粒度
19 19
 
20
-- 一条记录表示:**某一自然年、某一自然月、某一乡镇**的牦牛存栏汇总数据(**不按村居拆行**)
21
-- **所属村居**(`village_name`、`village_dept_id`)为预留字段,**非必填**,本期可空;后续若按村居下钻再扩展
22
-- 同一「所属年份 + 所属月份 + 所属乡镇」在系统中**仅允许一条记录**(物理删除后可重新填报)。  
23
-- **填报频率**:每个自然月导入/维护一次当月存栏快照;同月重复导入为**覆盖更新**。
20
+- 一条记录表示:**某一自然年、某一自然月、某一行政村**的牦牛存栏汇总数据
21
+- 保存时根据所选村反推**所属乡镇**并冗余存储乡镇名称
22
+- 同一「所属年份 + 所属月份 + 所属」在系统中**仅允许一条记录**(物理删除后可重新填报)。  
23
+- **填报频率**:每个自然月导入/维护一次当月存栏快照;同月重复导入为**该年月全量覆盖**(先删后插)
24 24
 
25 25
 ### 2.2 所属年份
26 26
 
27 27
 | 规则 | 说明 |
28 28
 | --- | --- |
29
-| 可选范围 | **当前自然年**起向前共 **6 个自然年**(含当年),即「当年、前 1 年 … 前 5 年」 |
29
+| 可选范围 | **当前自然年**起向前共 **6 个自然年**(含当年) |
30 30
 | 示例 | 若统计日为 2026 年,可选:2026、2025、2024、2023、2022、2021 |
31 31
 | 展示 | 列表、表单、导入、导出时均以**四位年份**展示与存储 |
32 32
 
@@ -37,7 +37,6 @@
37 37
 | 取值范围 | **1~12**(自然月) |
38 38
 | 填报限制 | **不可填报未来月份**(相对服务器当前年月) |
39 39
 | 展示 | 列表、表单、导入、导出均以「**N 月**」展示 |
40
-| 历史数据 | 升级前按年填报的数据,迁移时默认 `stat_month = 12` |
41 40
 
42 41
 ### 2.3 所属乡镇
43 42
 
@@ -45,39 +44,52 @@
45 44
 | --- | --- |
46 45
 | 数据来源 | `sys_dept` 组织树**二级节点** |
47 46
 | 纳入条件 | `parent_id` 为县级根节点(与现有数据一致时为 `100`),且 `order_num > 12`,且 `del_flag = '0'`、`status = '0'` |
48
-| 展示名 | 使用 `sys_dept.dept_name`(如「巴青乡」「杂色镇」等) |
49
-| 必填 | 新增、修改、导入时**必须**能解析到有效乡镇 |
47
+| 展示名 | 使用 `sys_dept.dept_name` |
48
+| 填报方式 | **表单不单独选择乡镇**;由所属村反推写入 |
50 49
 
51 50
 本期乡镇名单与 `sql/sys_dept_import_town.sql` 一致(10 个乡镇/镇)。
52 51
 
53
-### 2.4 所属村居(预留)
52
+### 2.4 所属村
54 53
 
55 54
 | 规则 | 说明 |
56 55
 | --- | --- |
57
-| `village_dept_id` | 预留;关联 `sys_dept.dept_id`(预期为所选乡镇下三级村居节点),**非必填** |
58
-| `village_name` | 预留;村居名称冗余展示,**非必填** |
59
-| 本期 | 表单、导入均不强制填写;Excel 导入不解析该字段 |
60
-| 校验 | 若填写 `village_dept_id`,宜校验其 `parent_id` 与当前 `town_dept_id` 一致(实施阶段可选) |
56
+| 数据来源 | `sys_dept` 组织树**三级节点**(父级为 **§2.3** 合法乡镇) |
57
+| 必填 | 新增、修改、导入时**必须**能解析到有效村 |
58
+| 展示 | 列表展示所属乡镇、所属村;表单以**县→乡镇→村**树选择村(仅叶子可选) |
59
+| 校验 | 村必须属于合法乡镇;保存时回写 `town_dept_id` / `town_name` / `village_name` |
61 60
 
62 61
 ### 2.5 存栏指标含义
63 62
 
64 63
 各存栏数字段均为**非负整数**(头),允许为空(未填报);若填写则 **≥ 0**。
65 64
 
65
+**手工录入明细**(表单可编辑):
66
+
66 67
 | 业务字段 | 说明 |
67 68
 | --- | --- |
68
-| 村居数 | 该乡镇下纳入统计的行政村(居)数量 |
69
+| 村居数 | 纳入统计的村居数量模板列「村) |
69 70
 | 牧户数 | 从事牧业生产的牧户数量 |
70
-| 牦牛存栏数 | 牦牛总存栏(头) |
71
-| 公牛存栏数 | 公牛合计(头) |
72
-| 母牛存栏数 | 母牛合计(头) |
73 71
 | 种公牛存栏数 | 种用公牛(头) |
74 72
 | 公牛/阉畜存栏数 | 除种公牛外的公牛及阉畜(头) |
75
-| 1 岁以下母牛存栏数 | 月龄 < 12 个月的母牛 |
76
-| 1~3 岁母牛存栏数 | 月龄/年龄在 1 岁(含)至 3 岁(不含或按县级口径,见 **§8**) |
77
-| 3~12 岁母牛存栏数 | 年龄在 3 岁(含)至 12 岁(不含或按县级口径) |
78
-| 12 岁以上母牛存栏数 | 年龄 ≥ 12 岁的母牛 |
73
+| 0~1.5 岁幼畜存栏数 | 母牛幼畜(不含 1.5 岁) |
74
+| 1.5 岁以上成畜存栏数 | 母牛成畜(含 1.5 岁) |
75
+| 产奶母牛存栏数 | 产奶母牛(头) |
76
+| 能繁母牛存栏数 | 能繁母牛(头) |
77
+| 羊总存栏数 | 羊总数(头) |
78
+| 产奶羊存栏数 | 产奶羊(头) |
79
+| 保险牦牛存栏数 | 参保牦牛(头) |
80
+
81
+**系统计算字段**(保存时自动计算;新增/编辑表单**不展示**;详情与列表相关列展示):
82
+
83
+| 业务字段 | 计算规则 |
84
+| --- | --- |
85
+| 公牛合计 | 种公牛 + 公牛/阉畜 |
86
+| 母牛合计 | 0~1.5 岁幼畜 + 1.5 岁以上成畜 |
87
+| 奶牛合计 | 产奶母牛 + 能繁母牛 |
88
+| 牦牛总存栏 | 公牛合计 + 母牛合计 + 保险牦牛 |
89
+| 总合计 | 牦牛总存栏 + 奶牛合计 + 羊总存栏 |
90
+| 总量(牦牛+羊) | 公牛合计 + 母牛合计 + 保险牦牛 + 羊总存栏 |
79 91
 
80
-**交叉校验(建议,非阻断)**:母牛分龄之和宜等于「母牛存栏数」;「种公牛 + 公牛/阉畜」宜等于「公牛存栏数」。不一致时允许保存,列表/导入结果可提示警告(见技术方案)。
92
+导入时若 Excel 中合计列与按明细重算结果不一致,**以系统重算为准**落库,并可返回**非阻断警告**
81 93
 
82 94
 ---
83 95
 
@@ -87,19 +99,18 @@
87 99
 
88 100
 | 能力 | 说明 |
89 101
 | --- | --- |
90
-| 分页列表 | 按所属年份、**所属月份**、所属乡镇筛选;见 **§5.1** |
91
-| 新增 | 手工填报一条乡镇**年月**数据;见 **§5.2** |
102
+| 分页列表 | 按所属年份、所属月份、所属乡镇、所属村筛选;见 **§5.1** |
103
+| 新增 | 手工填报一条**年月**数据;见 **§5.2** |
92 104
 | 修改 | 编辑已有记录;见 **§5.3** |
93 105
 | 删除 | 物理删除;见 **§5.4** |
94
-| 详情 | 查看全部字段;见 **§5.5** |
95
-| Excel 导入 | 按模板批量导入**指定填报年月**下所有乡镇数据;见 **§5.6** |
96
-| Excel 导出 | 按**指定所属年月**导出巴青县**全部乡镇**存栏数据;见 **§5.7** |
97
-| 乡镇下拉 | 从 `sys_dept` 动态加载合法乡镇;见 **§5.2** |
106
+| 详情 | 查看全部字段(含计算字段);见 **§5.5** |
107
+| Excel 导入 | 按模板批量导入**指定填报年月**下各村数据(全量覆盖);见 **§5.6** |
108
+| Excel 导出 | 按**指定所属年月**导出该月已填报村数据;见 **§5.7** |
109
+| 村树下拉 | 县→乡镇→村树;见 **§5.2** |
98 110
 
99 111
 **本期不实现:**
100 112
 
101
-- 按村居维度的多行填报、审批流、版本对比、自动从生物资产台账汇总  
102
-- 所属村居下钻维护(字段预留)
113
+- 审批流、版本对比、自动从生物资产台账汇总  
103 114
 
104 115
 ---
105 116
 
@@ -108,17 +119,14 @@
108 119
 ```mermaid
109 120
 flowchart TD
110 121
     A[选择所属年月] --> B{录入方式}
111
-    B -->|手工| C[选择乡镇并填写指标]
122
+    B -->|手工| C[选择所属村并填写明细指标]
112 123
     B -->|导入| D[上传 Excel 模板]
113
-    B -->|导出| X[下载全县乡镇 Excel]
114
-    C --> E[保存]
115
-    D --> F[解析并匹配乡镇]
116
-    F --> G{同年同月同乡镇是否存在}
117
-    G -->|否| H[新增]
118
-    G -->|是| I[更新覆盖]
124
+    B -->|导出| X[下载该月已填报 Excel]
125
+    C --> E[保存并自动计算合计]
126
+    D --> F[解析并匹配乡镇+村]
127
+    F --> G[删除该年月旧数据后批量新增]
119 128
     E --> J[列表查询/详情]
120
-    H --> J
121
-    I --> J
129
+    G --> J
122 130
     X --> J
123 131
 ```
124 132
 
@@ -134,12 +142,13 @@ flowchart TD
134 142
 | --- | --- |
135 143
 | 所属年份 | 选填;**精确**匹配;下拉范围为 **§2.2** |
136 144
 | 所属月份 | 选填;**精确**匹配(1~12);**列表进入时不默认选中** |
137
-| 所属乡镇 | 选填;**精确**匹配 `dept_id` 或乡镇名称(列表列展示为「乡镇」) |
145
+| 所属乡镇 | 选填;精确匹配 `town_dept_id` |
146
+| 所属村 | 选填;树选择 `village_dept_id` |
138 147
 
139 148
 **列表规则**
140 149
 
141
-- 默认每页 **20** 条;排序:**所属年份倒序**、**所属月份倒序**,同年内同月按乡镇 `order_num` 升序。  
142
-- 支持**重置**筛选(年份、月份、乡镇均清空,展示全部数据)
150
+- 默认每页 **20** 条;排序:**所属年份倒序**、**所属月份倒序**,同年同月按乡镇、村排序。  
151
+- 支持**重置**筛选。
143 152
 
144 153
 **列表展示列**
145 154
 
@@ -147,10 +156,11 @@ flowchart TD
147 156
 | --- | --- |
148 157
 | 所属年份 | `stat_year` |
149 158
 | 所属月份 | `stat_month`(展示为「N 月」) |
150
-| 乡镇 | `town_name`(所属乡镇) |
159
+| 所属乡镇 | `town_name` |
160
+| 所属村 | `village_name` |
151 161
 | 村居数 | `village_count` |
152 162
 | 牧户数 | `herdsman_household_count` |
153
-| 存栏数 | `yak_total`(牦牛总存栏,头) |
163
+| 存栏数 | `yak_total`(牦牛总存栏) |
154 164
 | 公牛存栏数 | `bull_total` |
155 165
 | 母牛存栏数 | `cow_total` |
156 166
 | 操作 | 查看、修改、删除 |
@@ -161,11 +171,12 @@ flowchart TD
161 171
 
162 172
 | 项 | 要求 |
163 173
 | --- | --- |
164
-| 必填 | **所属年份**、**所属月份**、**所属乡镇** |
165
-| 选填 | 所属村居(`village_dept_id`、`village_name`,预留)、村居数、牧户数及全部存栏数字段 |
166
-| 乡镇 | 下拉仅含 **§2.3** 合法乡镇;选定后展示 `dept_name` |
174
+| 必填 | **所属年份**、**所属月份**、**所属** |
175
+| 选填 | 村居数、牧户数及 **§2.5** 全部手工明细字段、备注 |
176
+| 村 | 树形仅可选村叶子;选定后系统反推乡镇 |
167 177
 | 默认值 | 打开新增弹窗时,所属年份、所属月份**默认当前年月**(可改) |
168
-| 唯一性 | 同年同月同乡镇已存在有效记录时**禁止新增**,提示「该年该月该乡镇已填报」 |
178
+| 唯一性 | 同年同月同村已存在有效记录时**禁止新增**,提示「该年该月该村已填报」 |
179
+| 计算字段 | 表单**不展示**;保存时服务端按 **§2.5** 重算 |
169 180
 | 数字 | 选填项若填写须为非负整数 |
170 181
 
171 182
 ---
@@ -174,9 +185,9 @@ flowchart TD
174 185
 
175 186
 | 项 | 要求 |
176 187
 | --- | --- |
177
-| 可改字段 | 除主键外均可改;**所属年份、所属月份、所属乡镇**修改后仍须满足唯一性 |
178
-| 必填 | 所属年份、所属月份、所属乡镇保持必填 |
179
-| 权限 | 具备修改权限 |
188
+| 可改字段 | 除主键外均可改;**所属年份、所属月份、所属**修改后仍须满足唯一性 |
189
+| 必填 | 所属年份、所属月份、所属保持必填 |
190
+| 计算字段 | 保存时重新计算 |
180 191
 
181 192
 ---
182 193
 
@@ -184,9 +195,9 @@ flowchart TD
184 195
 
185 196
 | 项 | 要求 |
186 197
 | --- | --- |
187
-| 方式 | **物理删除**(`DELETE FROM biz_yak_herd_inventory`) |
198
+| 方式 | **物理删除** |
188 199
 | 确认 | 删除前二次确认 |
189
-| 效果 | 记录从库中移除;同年同月同乡镇可重新新增或导入 |
200
+| 效果 | 记录从库中移除;同年同月同可重新新增或导入 |
190 201
 
191 202
 ---
192 203
 
@@ -195,7 +206,7 @@ flowchart TD
195 206
 | 项 | 要求 |
196 207
 | --- | --- |
197 208
 | 入口 | 列表「查看」 |
198
-| 内容 | 展示**全部字段**(列表未展示的项均包含):所属年份、**所属月份**、乡镇、`village_dept_id`、`village_name`、村居数、牧户数、存栏数、公牛/母牛存栏数、种公牛、公牛/阉畜、母牛分龄(1 岁以下、1~3 岁、3~12 岁、12 岁以上)、备注及创建/更新审计信息 |
209
+| 内容 | 展示**全部字段**:所属年月、乡镇、村、全部明细与**计算合计**、备注及创建/更新审计信息 |
199 210
 | 模式 | 只读 |
200 211
 
201 212
 ---
@@ -204,35 +215,33 @@ flowchart TD
204 215
 
205 216
 | 项 | 要求 |
206 217
 | --- | --- |
207
-| 模板来源 | `2026年.xlsx` **第一个 Sheet**(表名以文件为准,通常为畜群结构统计表) |
208
-| 填报年月 | 导入前用户**必须选择**「填报年份」「填报月份」(月份 **§2.2.1**);文件中**不写年月**时以所选为准 |
209
-| 解析范围 | 从数据区首行(乡镇列有值)起,至乡镇为空的行结束;**跳过**表头、标题、填表单位等说明行 |
210
-| 乡镇匹配 | Excel「乡镇」列文本与 `sys_dept.dept_name` **精确匹配**(trim 后);仅接受 **§2.3** 范围内部门 |
211
-| 唯一键 | **所属年份 + 所属月份 + 所属乡镇(`town_dept_id`)**;同一键在库中仅保留一条有效业务数据 |
212
-| 合并策略 | 按「年月 + 乡镇」**upsert**:该键**已存在** → **全量覆盖更新**;**不存在** → **新增** |
213
-| 重复导入 | **允许**对同一文件在同一填报年月多次导入;第二次及以后 `updateCount` 增加、`insertCount` 为 0(乡镇均有数据时) |
214
-| 结果反馈 | 返回 `insertCount`、`updateCount`、失败条数及失败明细(行号、乡镇、原因) |
218
+| 模板来源 | `yak_herd_inventory_import_template.xlsx`(与交接表畜群结构列一致) |
219
+| 填报年月 | 导入前用户**必须选择**「填报年份」「填报月份」;文件中不写年月时以所选为准 |
220
+| 解析范围 | 识别含「序号」「乡镇」的表头行;数据从 `headerRow + 3` 起;乡镇为空或「合计」行终止 |
221
+| 乡镇/村匹配 | Excel「乡镇」「村」列与 `sys_dept.dept_name` **精确匹配**(trim 后);村须属于该乡镇 |
222
+| 唯一键 | **所属年份 + 所属月份 + 所属村(`village_dept_id`)** |
223
+| 合并策略 | **该年月全量覆盖**:校验全部解析行成功后,`deleteByYearMonth` 再批量 `insert` |
224
+| 结果反馈 | 返回 `insertCount`、失败条数及失败明细;合计不一致时返回 `warnMessages` |
215 225
 | 文件限制 | `.xlsx`;大小上限与项目统一上传限制一致 |
216
-| 列映射 | 与模板列顺序一致,见技术方案 **§3.8** |
226
+| 列映射 | 见技术方案 **§3.8.1** |
217 227
 
218
-**模板列(与 `2026年.xlsx` 第一个 Sheet 对齐)**
228
+**模板主要列(与导入模板对齐)**
219 229
 
220
-| 列序 | 表头(约) | 映射字段 |
230
+| 列序 | 表头(约) | 映射 |
221 231
 | --- | --- | --- |
222 232
 | A | 序号 | 忽略 |
223
-| B | 乡镇 | 所属乡镇 |
224
-| C | 村居数 | 村居数 |
225
-| D | 牧户数 | 牧户数 |
226
-| E | 总合计(头)/ 牦牛合计 | 牦牛存栏数 |
227
-| F~ | 公牛相关分列 | 公牛存栏数、种公牛、公牛/阉畜等(以模板合并表头为准,实施时按单元格列号固化) |
228
-| — | 母牛合计 | 母牛存栏数 |
229
-| — | 1 岁以下母牛 | 1 岁以下母牛存栏数 |
230
-| — | 1~3 岁母牛 | 1~3 岁母牛存栏数 |
231
-| — | 3~12 岁母牛 | 3~12 岁母牛存栏数 |
232
-| — | 12 岁以上母牛 | 12 岁以上母牛存栏数 |
233
-| — | 备注 | `remark` |
234
-
235
-实施阶段须对照真实模板冻结列号(技术方案给出默认列映射)。
233
+| B | 乡镇 | 匹配乡镇 |
234
+| C | 村 | 匹配村 |
235
+| D | 村居 | 村居数 |
236
+| E | 牧户 | 牧户数 |
237
+| F | 总合计(头) | Excel 合计对照(警告用) |
238
+| G~I | 牦牛公母分类合计/公牛/母牛 | Excel 合计对照 |
239
+| J | 保险 | 保险牦牛 |
240
+| K~M | 公牛合计/种公牛/阉畜 | 种公牛、阉畜为录入;合计对照 |
241
+| N~P | 母牛合计/幼畜/成畜 | 幼畜、成畜为录入 |
242
+| Q~S | 奶牛合计/产奶/能繁 | 产奶、能繁为录入 |
243
+| T~U | 羊总数/产奶羊 | 羊、产奶羊 |
244
+| V | 总量(牦牛+羊) | Excel 合计对照 |
236 245
 
237 246
 ---
238 247
 
@@ -241,15 +250,16 @@ flowchart TD
241 250
 | 项 | 要求 |
242 251
 | --- | --- |
243 252
 | 入口 | 列表页「导出」 |
244
-| 导出年月 | 用户**必须选择**「所属年份」「所属月份」(范围见 **§2.2**、**§2.2.1**);弹窗打开时**默认当前年月** |
245
-| 导出范围 | **巴青县全部乡镇**(**§2.3** 合法乡镇,按 `order_num` 排序),**不受**列表「所属乡镇」筛选限制 |
246
-| 模板 | 与导入共用:`baqing-admin/src/main/resources/templates/yak_herd_inventory_import_template.xlsx`(业务源文件 `2026年.xlsx` 第一个 Sheet) |
247
-| 填充规则 | 表头标题中的年份替换为所选年份;每个乡镇一行;有该**年月**填报数据的乡镇填入全部指标;未填报乡镇仅保留乡镇名称,指标为空 |
248
-| 合计行 | 数据区末行输出「合计」,对**已填报乡镇**的数值列求和 |
253
+| 导出年月 | 用户**必须选择**「所属年份」「所属月份」;弹窗打开时**默认当前年月** |
254
+| 导出范围 | 该年月库内**已填报的村记录**(按列表查询顺序),**不是**强制补全全县所有乡镇空行 |
255
+| 模板 | 与导入共用:`yak_herd_inventory_import_template.xlsx` |
256
+| 填充规则 | 标题中的年、月替换为所选年月;填表人为当前登录用户;**填表时间**为导出当日的当前年月 |
257
+| 合计行 | 数据区末行「合计」:合并**序号+乡镇+村**三列;自**村居**起各数值列对数据行**按列求和**并写入 |
258
+| 空行 | 合计行之后不保留模板多余空行 |
249 259
 | 文件格式 | `.xlsx`;文件名示例:`2026年7月牦牛存栏数据.xlsx` |
250
-| 与导入关系 | 导出文件列结构与导入模板一致,可再次导入(同年月将 upsert) |
260
+| 与导入关系 | 列结构与导入模板一致,可再次导入(同年月将全量覆盖) |
251 261
 
252
-**与「下载模板」区别**:「下载模板」为空白模板;「导出」为按库内**指定年月**数据填充后的全县报表。
262
+**与「下载模板」区别**:「下载模板」为空白/样例模板;「导出」为按库内**指定年月**数据填充后的报表。
253 263
 
254 264
 ---
255 265
 
@@ -271,10 +281,10 @@ flowchart TD
271 281
 
272 282
 ## 7. 提示与错误约定
273 283
 
274
-- 必填项未填、年份/月份/乡镇不合法、同年同月同乡镇重复、**未来月份**  
275
-- 导入:文件格式错误、乡镇无法匹配、数字非整数、行为空  
284
+- 必填项未填、年份/月份/村不合法、同年同月同村重复、**未来月份**  
285
+- 导入:文件格式错误、乡镇/村无法匹配、数字非整数、无有效数据
276 286
 - 导出:未选年月、年月不在可选范围  
277
-- 删除成功/失败、导入部分失败(展示明细)  
287
+- 删除成功/失败、导入失败(展示明细)  
278 288
 
279 289
 采用项目统一 `AjaxResult` 文案风格,**不约定** HTTP 状态码细节。
280 290
 
@@ -284,10 +294,10 @@ flowchart TD
284 294
 
285 295
 | 类别 | 建议 |
286 296
 | --- | --- |
287
-| 性能 | 列表分页;导入单次 ≤ 50 行(10 乡镇 × 多年份分批) |
297
+| 性能 | 列表分页;导入单次以全县村行数为上限 |
288 298
 | 审计 | 记录创建人、创建时间、更新人、更新时间 |
289
-| 口径 | 母牛分龄边界若有县级红头文件,以业务口径覆盖本文档示例说明 |
290
-| 扩展 | 所属村居(`village_dept_id`)下钻、与「养殖产业数据」看板联动 |
299
+| 口径 | 母牛年龄两档边界以业务模板为准(0~1.5 / 1.5+) |
300
+| 扩展 | 与「养殖产业数据」看板、大屏年龄结构(两档)联动 |
291 301
 
292 302
 ---
293 303
 
@@ -298,12 +308,11 @@ flowchart TD
298 308
 | 表字段与必填 | §2、§5.2 |
299 309
 | 年份向前 5 年 | §2.2 |
300 310
 | 月份 1~12、不可填未来月 | §2.2.1 |
301
-| 乡镇 sys_dept | §2.3 |
311
+| 乡镇/村 sys_dept | §2.3、§2.4 |
302 312
 | 增删改查 | §5.1~5.5 |
303
-| Excel 导入 | §5.6 |
304
-| Excel 导出 | §5.7 |
305
-| 列表/详情字段 | §5.1 列表 7 列;§5.5 详情全字段 |
306
-| 预留村居 `village_dept_id` | §2.4、§5.2、§5.5 |
313
+| Excel 导入全量覆盖 | §5.6 |
314
+| Excel 导出与合计行 | §5.7 |
315
+| 计算字段 | §2.5、§5.2、§5.5 |
307 316
 
308 317
 ---
309 318
 
@@ -316,4 +325,5 @@ flowchart TD
316 325
 | 1.2 | 增加 **§5.7 按年份导出** 全县乡镇 Excel;权限 `export` |
317 326
 | 1.3 | 明确导入 upsert:唯一键「年份+乡镇」;重复导入更新 |
318 327
 | 1.4 | 取消逻辑删除:表无 `del_flag`,删除为物理删除;唯一约束 `uk_year_town` |
319
-| 1.5 | **按年月填报**:增加 `stat_month`;唯一键「年+月+乡镇」;每月导入一次;列表/导入/导出支持月份;大屏年度汇总取 **12 月**快照 |
328
+| 1.5 | **按年月填报**:增加 `stat_month`;唯一键「年+月+乡镇」;每月导入一次 |
329
+| 1.6 | **按村填报**:唯一键「年+月+村」;表单只选村;字段对齐新模板(幼畜/成畜、奶牛、羊、保险等);导入年月全量覆盖;导出合计按列求和;计算字段仅详情展示 |

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

@@ -1,6 +1,6 @@
1 1
 # 牦牛存栏数据填报 — 技术方案
2 2
 
3
-> 依据:同目录 `牦牛存栏数据填报功能需求.md`;模板 `E:\luhao\工作交接\巴青项目\农牧数据\农业农村和科技水利局-业务办\畜群结构\2026年.xlsx`(第一个 Sheet)
3
+> 依据:同目录 `牦牛存栏数据填报功能需求.md`;模板 `baqing-admin/src/main/resources/templates/yak_herd_inventory_import_template.xlsx`
4 4
 
5 5
 ---
6 6
 
@@ -10,13 +10,13 @@
10 10
 | --- | --- |
11 11
 | **后端** | RuoYi **v3.9.2**(springboot2):JDK 8、Spring Boot 2.x、Spring MVC、MyBatis、Druid |
12 12
 | **数据库** | MySQL **5.7.39**,InnoDB,`utf8mb4` |
13
-| **前端** | 若依 Vue2 + Element UI |
14
-| **Excel** | Apache POI 或项目已有 EasyExcel(与若依导入规范一致) |
15
-| **组织** | `sys_dept` 乡镇字典;`SysDeptMapper` 查询二级节点 |
13
+| **前端** | 若依 Vue2 + Element UI + `@riophae/vue-treeselect` |
14
+| **Excel** | Apache POI |
15
+| **组织** | `sys_dept` 乡镇/村字典;`TownDeptSupport` |
16 16
 
17
-**分层**:`BizYakHerdInventoryController` → `IBizYakHerdInventoryService` → `BizYakHerdInventoryMapper` → `domain`;Excel 由 `YakHerdInventoryExcelParser`(导入解析)、`YakHerdInventoryExcelExporter`(按年导出)承担。
17
+**分层**:`BizYakHerdInventoryController` → `IBizYakHerdInventoryService` → `BizYakHerdInventoryMapper` → `domain`;Excel 由 `YakHerdInventoryExcelParser`(导入)、`YakHerdInventoryExcelExporter`(导出)承担;校验与派生计算由 `YakHerdInventoryValidation`
18 18
 
19
-**代码位置**:`baqing-admin` → `com.ruoyi.web.modules.industryservice`(与产业数据模型同包)
19
+**代码位置**:`baqing-admin` → `com.ruoyi.web.modules.industryservice`。
20 20
 
21 21
 **模板文件**:`baqing-admin/src/main/resources/templates/yak_herd_inventory_import_template.xlsx`(导入、导出、下载模板共用)。
22 22
 
@@ -29,69 +29,73 @@
29 29
 | 字段 | 类型 | 非空 | 说明 |
30 30
 | --- | --- | --- | --- |
31 31
 | `id` | `bigint(20)` | Y | 主键 |
32
-| `stat_year` | `int(11)` | Y | 所属年份(自然年) |
32
+| `stat_year` | `int(11)` | Y | 所属年份 |
33 33
 | `stat_month` | `int(11)` | Y | 所属月份(1~12) |
34
-| `town_dept_id` | `bigint(20)` | Y | 所属乡镇,`sys_dept.dept_id` |
35
-| `town_name` | `varchar(64)` | Y | 乡镇名称冗余,便于列表展示 |
36
-| `village_dept_id` | `bigint(20)` | N | **预留**所属村居,`sys_dept.dept_id`(乡镇下三级节点) |
37
-| `village_name` | `varchar(128)` | N | **预留**所属村居名称冗余 |
34
+| `town_dept_id` | `bigint(20)` | Y | 所属乡镇,`sys_dept.dept_id`(由村反推) |
35
+| `town_name` | `varchar(64)` | Y | 乡镇名称冗余 |
36
+| `village_dept_id` | `bigint(20)` | Y | 所属村,`sys_dept.dept_id` |
37
+| `village_name` | `varchar(128)` | Y | 村名称冗余 |
38 38
 | `village_count` | `int(11)` | N | 村居数 |
39 39
 | `herdsman_household_count` | `int(11)` | N | 牧户数 |
40
-| `yak_total` | `int(11)` | N | 牦牛存栏数 |
41
-| `bull_total` | `int(11)` | N | 公牛存栏数 |
42
-| `cow_total` | `int(11)` | N | 母牛存栏数 |
43
-| `breeding_bull_count` | `int(11)` | N | 种公牛存栏数 |
44
-| `bull_castrated_count` | `int(11)` | N | 公牛/阉畜存栏数 |
45
-| `cow_under_1_count` | `int(11)` | N | 1 岁以下母牛存栏数 |
46
-| `cow_1_3_count` | `int(11)` | N | 1~3 岁母牛存栏数 |
47
-| `cow_3_12_count` | `int(11)` | N | 3~12 岁母牛存栏数 |
48
-| `cow_over_12_count` | `int(11)` | N | 12 岁以上母牛存栏数 |
40
+| `breeding_bull_count` | `int(11)` | N | 种公牛 |
41
+| `bull_castrated_count` | `int(11)` | N | 公牛/阉畜 |
42
+| `bull_total` | `int(11)` | N | 公牛合计(派生) |
43
+| `cow_young_count` | `int(11)` | N | 0~1.5 岁幼畜(不含 1.5 岁) |
44
+| `cow_adult_count` | `int(11)` | N | 1.5 岁以上成畜(含 1.5 岁) |
45
+| `cow_total` | `int(11)` | N | 母牛合计(派生) |
46
+| `milking_cow_count` | `int(11)` | N | 产奶母牛 |
47
+| `fertile_cow_count` | `int(11)` | N | 能繁母牛 |
48
+| `dairy_total` | `int(11)` | N | 奶牛合计(派生) |
49
+| `sheep_total` | `int(11)` | N | 羊总数 |
50
+| `milking_sheep_count` | `int(11)` | N | 产奶羊 |
51
+| `insured_yak_count` | `int(11)` | N | 保险牦牛 |
52
+| `yak_total` | `int(11)` | N | 牦牛总存栏(派生) |
53
+| `grand_total` | `int(11)` | N | 总合计(派生) |
54
+| `yak_sheep_total` | `int(11)` | N | 总量牦牛+羊(派生) |
49 55
 | `create_by` / `create_time` | — | — | 审计 |
50 56
 | `update_by` / `update_time` | — | — | 审计 |
51 57
 | `remark` | `varchar(500)` | N | 备注 |
52 58
 
53
-**说明**:本表**不使用逻辑删除**,无 `del_flag` 字段;删除操作为物理删除。
59
+**说明**:本表**不使用逻辑删除**;删除为物理删除。旧四档母牛年龄字段(`cow_under_1_count` 等)已废弃,以 `cow_young_count` / `cow_adult_count` 为准
54 60
 
55 61
 **索引与约束**
56 62
 
57
-- `UNIQUE uk_year_month_town (stat_year, stat_month, town_dept_id)` — 年月+乡镇唯一,支撑导入 upsert。  
63
+- `UNIQUE uk_year_month_village (stat_year, stat_month, village_dept_id)`  
58 64
 - `KEY idx_stat_year_month (stat_year, stat_month)`  
59
-- `KEY idx_town_dept_id (town_dept_id)`
65
+- `KEY idx_town_dept_id (town_dept_id)`  
66
+- `KEY idx_village_dept_id (village_dept_id)`
60 67
 
61
-**DDL 文件**:`sql/biz_yak_herd_inventory.sql`(新库);已有库执行 `sql/biz_yak_herd_inventory_stat_month.sql`(历史行 `stat_month` 默认 **12**)。
68
+**DDL 文件**:`sql/biz_yak_herd_inventory.sql`(全量覆盖建表)。已有库若仍为乡镇唯一键,执行 `sql/biz_yak_herd_inventory_village_uk.sql`(去掉 `uk_year_month_town`,改为村唯一)。
62 69
 
63
-### 2.2 乡镇字典查询(`sys_dept`)
70
+### 2.2 派生计算(`YakHerdInventoryValidation.recalculateDerived`)
64 71
 
65
-```sql
66
-SELECT dept_id, dept_name, order_num
67
-FROM sys_dept
68
-WHERE parent_id = 100
69
-  AND order_num > 12
70
-  AND del_flag = '0'
71
-  AND status = '0'
72
-ORDER BY order_num;
72
+```text
73
+bull_total      = breeding_bull_count + bull_castrated_count
74
+cow_total       = cow_young_count + cow_adult_count
75
+dairy_total     = milking_cow_count + fertile_cow_count
76
+yak_total       = bull_total + cow_total + insured_yak_count
77
+grand_total     = yak_total + dairy_total + sheep_total
78
+yak_sheep_total = bull_total + cow_total + insured_yak_count + sheep_total
73 79
 ```
74 80
 
75
-前端乡镇下拉、导入匹配均调用封装方法 `TownDeptSupport.listReportTowns()`(名称与根 `parent_id` 可配置化 `ruoyi.town-dept-parent-id=100`、`ruoyi.town-dept-min-order-num=12`)
81
+保存与导入落库前一律重算覆盖
76 82
 
77
-### 2.3 可选年份
83
+### 2.3 乡镇 / 村字典
78 84
 
79
-```java
80
-// 当前年起向前 5 年,共 6 个
81
-int current = Year.now().getValue();
82
-for (int y = current; y >= current - 5; y--) { ... }
83
-```
85
+- 乡镇:`TownDeptSupport.listReportTowns()`(`parent_id=100` 且 `order_num>12`)。  
86
+- 村树:`TownDeptSupport.buildVillageTree()` → 县→乡镇→村;父节点不 `disabled`(便于展开),前端 `disable-branch-nodes` 仅允许选村。  
87
+- 解析:`resolveTownByName`、`resolveVillageByTownAndName`、`requireReportVillage`。
84 88
 
85
-### 2.3 可选月份
89
+### 2.4 可选年份 / 月份
86 90
 
87
-`YakHerdInventoryMonthSupport.resolveMonthOptions()` 返回 **1~12**;`validateStatPeriod(year, month)` 校验年份在 **§2.2** 范围内、月份合法且**非未来月**。
91
+- `YakHerdInventoryYearSupport`:当前年起向前 5 年,共 6 个。  
92
+- `YakHerdInventoryMonthSupport.resolveMonthOptions()`:1~12;`validateStatPeriod` 拒绝未来月。
88 93
 
89 94
 ---
90 95
 
91 96
 ## 3. 接口设计
92 97
 
93
-**Base Path**:`/dataModel/yakHerdInventory`
94
-
98
+**Base Path**:`/dataModel/yakHerdInventory`  
95 99
 **权限前缀**:`dataModel:yakHerdInventory`
96 100
 
97 101
 | # | 说明 | Method | URI | 权限 |
@@ -102,6 +106,7 @@ for (int y = current; y >= current - 5; y--) { ... }
102 106
 | 3.4 | 修改 | PUT | `/` | `edit` |
103 107
 | 3.5 | 删除 | DELETE | `/{ids}` | `remove` |
104 108
 | 3.6 | 乡镇下拉 | GET | `/townOptions` | `list` |
109
+| 3.6.1 | 村树 | GET | `/villageTree` | `list` |
105 110
 | 3.7 | 年份下拉 | GET | `/yearOptions` | `list` |
106 111
 | 3.7.1 | 月份下拉 | GET | `/monthOptions` | `list` |
107 112
 | 3.8 | 导入 | POST | `/importData` | `import` |
@@ -113,123 +118,71 @@ for (int y = current; y >= current - 5; y--) { ... }
113 118
 | 参数 | 类型 | 说明 |
114 119
 | --- | --- | --- |
115 120
 | `pageNum` / `pageSize` | int | 默认 1 / 20 |
116
-| `statYear` | int | 所属年份,精确 |
117
-| `statMonth` | int | 所属月份(1~12),精确 |
118
-| `townDeptId` | long | 所属乡镇 dept_id,精确 |
119
-
120
-**列表 VO 字段**(列标题与需求 **§5.1** 一致):
121
-
122
-| 列标题 | 字段 |
123
-| --- | --- |
124
-| 所属年份 | `statYear` |
125
-| 所属月份 | `statMonth` |
126
-| 乡镇 | `townName` |
127
-| 村居数 | `villageCount` |
128
-| 牧户数 | `herdsmanHouseholdCount` |
129
-| 存栏数 | `yakTotal` |
130
-| 公牛存栏数 | `bullTotal` |
131
-| 母牛存栏数 | `cowTotal` |
132
-
133
-#### 3.2.1 详情
121
+| `statYear` / `statMonth` | int | 精确 |
122
+| `townDeptId` | long | 乡镇 |
123
+| `villageDeptId` | long | 村 |
134 124
 
135
-返回 `BizYakHerdInventory` **全字段**(驼峰),含预留村居、分龄母牛、种公牛、阉畜、备注及 `createBy` / `createTime` / `updateBy` / `updateTime` 等
125
+**列表主要字段**:`statYear`、`statMonth`、`townName`、`villageName`、`villageCount`、`herdsmanHouseholdCount`、`yakTotal`、`bullTotal`、`cowTotal`。
136 126
 
137 127
 #### 3.3.1 新增/修改 Body
138 128
 
139 129
 | 字段 | 必填 | 校验 |
140 130
 | --- | --- | --- |
141
-| `statYear` | Y | 在 `yearOptions` 范围内 |
142
-| `statMonth` | Y | 1~12;不可为未来月 |
143
-| `townDeptId` | Y | 须在乡镇字典内 |
144
-| `villageDeptId` | N | **预留**;若填写须为有效 `sys_dept.dept_id`,且 `parent_id = townDeptId`(可选校验) |
145
-| `villageName` | N | **预留**村居名称 |
146
-| 各 `*Count` | N | `>= 0` 整数 |
131
+| `statYear` / `statMonth` | Y | 合法且非未来月 |
132
+| `villageDeptId` | Y | 合法村;反推乡镇 |
133
+| 明细 `*Count` | N | `>= 0` |
147 134
 | `id` | 修改必填 | — |
148 135
 
149
-**唯一性**:`countByYearMonthAndTownExcludeId(statYear, statMonth, townDeptId, excludeId) > 0` 则拒绝(**手工新增/修改**);**导入**走 upsert,不受此限
136
+**唯一性**:`countByYearMonthAndVillageExcludeId(...) > 0` →「该年该月该村已填报」。
150 137
 
151 138
 #### 3.8 导入
152 139
 
153
-**请求**:`multipart/form-data`
154
-
155
-| 参数 | 必填 | 说明 |
156
-| --- | --- | --- |
157
-| `file` | Y | `.xlsx`(如 `牦牛存栏数据.xlsx`) |
158
-| `statYear` | Y | 填报年份 |
159
-| `statMonth` | Y | 填报月份(1~12) |
160
-
161
-**Upsert 规则**(`BizYakHerdInventoryServiceImpl.upsertImportRow`):
162
-
163
-| 步骤 | 说明 |
164
-| --- | --- |
165
-| 1 | 按 `statYear` + `statMonth` + `townDeptId` 查询 `selectByYearMonthAndTown` |
166
-| 2 | 无记录 → `insert`;`insertCount++` |
167
-| 3 | 有记录 → `updateBizYakHerdInventory` 全字段覆盖;`updateCount++` |
168
-
169
-同一文件对**同一填报年月**多次导入:各乡镇行均为更新,不产生重复有效记录。
140
+**请求**:`multipart/form-data`:`file` + `statYear` + `statMonth`。
170 141
 
171
-**响应 `data`**
142
+**策略**(`BizYakHerdInventoryServiceImpl.importData`):
172 143
 
173
-| 字段 | 说明 |
174
-| --- | --- |
175
-| `insertCount` | 新增条数 |
176
-| `updateCount` | 更新条数 |
177
-| `failCount` | 失败条数 |
178
-| `failMessages` | 如 `第8行[杂色镇]: 乡镇未识别` |
179
-| `warnMessages` | 分龄加总与母牛合计不一致等警告(可选) |
144
+1. 解析全部有效数据行;任一行乡镇/村无法匹配或校验失败 → 整体失败,不写库。  
145
+2. 全部成功 → `deleteByYearMonth(statYear, statMonth)` → 逐行 `insert`。  
146
+3. Excel 合计列与重算不一致 → 记入 `warnMessages`(非阻断)。
180 147
 
181
-#### 3.8.1 Excel 第一个 Sheet 解析约定(`2026年.xlsx`)
148
+**响应**:`insertCount`、`failCount`、`failMessages`、`warnMessages`。
182 149
 
183
-| 项 | 约定 |
184
-| --- | --- |
185
-| Sheet | `workbook.getSheetAt(0)` |
186
-| 标题行 | 前若干行为「巴青县…畜群结构…统计表」及填表说明,**自动扫描**首列含「序号」且相邻列含「乡镇」的行作为表头行 `headerRow` |
187
-| 数据行 | `headerRow + 1` 起至「乡镇」列为空 |
188
-| 列映射(默认,实施对照模板微调) | |
150
+#### 3.8.1 Excel 列映射(0-based,`YakHerdInventoryExcelParser`)
189 151
 
190
-| 列号(0-based) | Excel 列 | 实体字段 |
152
+| 列号 | Excel | 用途 |
191 153
 | --- | --- | --- |
192
-| 1 | B | 乡镇(按 `dept_name` 解析 `townDeptId`) |
193
-| 2 | C | `villageCount` |
194
-| 3 | D | `herdsmanHouseholdCount` |
195
-| 4 | E | `yakTotal`(存栏数) |
196
-| 5 | F | 与 E 同值(牦牛公母分类合计) |
197
-| 6 | G | `bullTotal`(公牛(头)) |
198
-| 7 | H | `cowTotal`(牦牛公母分类 — 母牛(头)) |
199
-| 8 | I | 与 G 同值(公牛区块合计) |
200
-| 9 | J | `breedingBullCount` |
201
-| 10 | K | `bullCastratedCount` |
202
-| 11 | L | `cowTotal` |
203
-| 12 | M | `cowUnder1Count` |
204
-| 13 | N | `cow13Count` |
205
-| 14 | O | `cow312Count` |
206
-| 15 | P | `cowOver12Count` |
207
-| 16 | Q | `remark` |
208
-
209
-> 数据区起始行:`headerRow + 3`(表头行由「序号」「乡镇」自动识别)。模板存在合并单元格,列号以代码常量为准。
210
-
211
-**导入事务**:逐乡镇独立处理,单乡镇失败不影响其余。
154
+| 1 | B 乡镇 | 匹配乡镇 |
155
+| 2 | C 村 | 匹配村 |
156
+| 3 | D 村居 | `villageCount` |
157
+| 4 | E 牧户 | `herdsmanHouseholdCount` |
158
+| 5 | F 总合计 | Excel 对照 |
159
+| 6~8 | 牦牛公母分类 | Excel 对照 |
160
+| 9 | J 保险 | `insuredYakCount` |
161
+| 10 | K 公牛合计 | Excel 对照 |
162
+| 11 | L 种公牛 | `breedingBullCount` |
163
+| 12 | M 阉畜 | `bullCastratedCount` |
164
+| 13 | N 母牛合计 | Excel 对照 |
165
+| 14 | O 幼畜 | `cowYoungCount` |
166
+| 15 | P 成畜 | `cowAdultCount` |
167
+| 16 | Q 奶牛合计 | Excel 对照 |
168
+| 17 | R 产奶母牛 | `milkingCowCount` |
169
+| 18 | S 能繁母牛 | `fertileCowCount` |
170
+| 19 | T 羊 | `sheepTotal` |
171
+| 20 | U 产奶羊 | `milkingSheepCount` |
172
+| 21 | V 总量 | Excel 对照 |
173
+
174
+数据起始行:`headerRow + 3`(三行表头)。
212 175
 
213 176
 #### 3.10 按年月导出
214 177
 
215
-**请求**:`GET /export?statYear=2026&statMonth=7`
216
-
217
-| 参数 | 必填 | 说明 |
218
-| --- | --- | --- |
219
-| `statYear` | Y | 导出年份,须在 `yearOptions` 范围内 |
220
-| `statMonth` | Y | 导出月份(1~12);须通过 `validateStatPeriod` |
221
-
222
-**响应**:`application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` 文件流;文件名 `{statYear}年{statMonth}月牦牛存栏数据.xlsx`。
178
+**请求**:`GET /export?statYear=&statMonth=`。
223 179
 
224
-**实现**:`BizYakHerdInventoryServiceImpl.exportByYearMonth` → `YakHerdInventoryExcelExporter.export`:
180
+**实现**:查询该年月全部村记录 → `YakHerdInventoryExcelExporter.export`:
225 181
 
226
-1. 加载 classpath 模板 `YakHerdInventoryExcelExporter.TEMPLATE_CLASSPATH`  
227
-2. 替换表头中的四位年份为 `statYear`  
228
-3. 查询 `stat_year = statYear AND stat_month = statMonth` 的全部记录,按 `town_dept_id` 建 Map  
229
-4. 遍历 `TownDeptSupport.listReportTowns()`,逐乡镇写行;无数据则仅写乡镇名  
230
-5. 末行写「合计」,对已填报乡镇数值列求和  
231
-
232
-**与 3.9 区别**:`/importTemplate` 返回空白模板;`/export` 返回填充后的全县报表。
182
+1. 替换标题年、月;填表人;填表时间=**导出当日年月**  
183
+2. 按记录写数据行(含派生合计)  
184
+3. 末行「合计」:合并列 0~2;自列 3(村居)起对各数值列求和写入  
185
+4. 删除合计行之后的空行  
233 186
 
234 187
 ---
235 188
 
@@ -239,52 +192,36 @@ for (int y = current; y >= current - 5; y--) { ... }
239 192
 | --- | --- |
240 193
 | **路径** | `ruoyi-ui/src/views/dataModel/yakHerdInventory/index.vue` |
241 194
 | **API** | `ruoyi-ui/src/api/dataModel/yakHerdInventory.js` |
242
-| **列表** | 年份、**月份**、乡镇筛选;**进入页面不默认年月**;列:所属年份、所属月份、乡镇、村居数、牧户数、存栏数、公牛/母牛存栏数 |
243
-| **详情** | 抽屉/对话框只读展示全部字段 |
244
-| **表单** | 抽屉或对话框;年份/月份/乡镇下拉;新增默认**当前年月**;数字 `el-input-number` `:min="0"` `:precision="0"` |
245
-| **导入** | 先选「填报年份」「填报月份」(默认当前年月)→ 上传 → 展示导入结果摘要 |
246
-| **模板下载** | `GET /importTemplate`;空白模板 |
247
-| **导出** | 弹窗选年月(默认当前年月)→ `GET /export?statYear=&statMonth=` |
195
+| **列表** | 年/月/乡镇/村筛选;列含乡镇、村、存栏等 |
196
+| **表单** | 年/月 + Treeselect 选村;仅明细录入字段;无派生合计输入 |
197
+| **详情** | 含派生合计只读 |
198
+| **导入/导出** | 先选年月 |
248 199
 
249 200
 ---
250 201
 
251 202
 ## 5. 菜单与权限(示例)
252 203
 
253
-| 类型 | 名称 | 权限标识 |
254
-| --- | --- | --- |
255
-| 菜单 | 牦牛存栏数据填报 | `dataModel:yakHerdInventory:list` |
256
-| 按钮 | 查询 | `dataModel:yakHerdInventory:query` |
257
-| 按钮 | 新增 | `dataModel:yakHerdInventory:add` |
258
-| 按钮 | 修改 | `dataModel:yakHerdInventory:edit` |
259
-| 按钮 | 删除 | `dataModel:yakHerdInventory:remove` |
260
-| 按钮 | 导入 | `dataModel:yakHerdInventory:import` |
261
-| 按钮 | 导出 | `dataModel:yakHerdInventory:export` |
262
-
263
-挂载目录:**产业数据模型及服务**。菜单 SQL 见 `sql/biz_sys_menu_yak_herd_inventory.sql`。
204
+同权限标识:`list|query|add|edit|remove|import|export`。菜单 SQL 见 `sql/biz_sys_menu_yak_herd_inventory.sql`。
264 205
 
265 206
 ---
266 207
 
267 208
 ## 6. 与养殖产业数据看板 / 大屏的关系
268 209
 
269
-「养殖产业数据」看板、大屏首页产业总览中的**全县存栏合计**、地图乡镇存栏标注,读取 `biz_yak_herd_inventory` 时:
270
-
271 210
 | 场景 | 口径 |
272 211
 | --- | --- |
273
-| 年度全县合计 | `stat_year = 当前自然年 − 1` 且 **`stat_month = 12`**(`YakHerdInventoryRules.YEAR_END_STAT_MONTH`) |
274
-| 地图乡镇存栏 | 同上:`herdStatYear = 当前年 − 1`,`herdStatMonth = 12` |
275
-
276
-避免同一年多个月份重复累加。月度明细在本模块列表按年月筛选查看。
212
+| 全县存栏合计 | `biz_yak_herd_inventory` **最近填报年月**全县 `yak_total` / `bull_total` / `cow_total` 等合计(按村汇总) |
213
+| 年龄结构 | 两档:`cow_young_count`、`cow_adult_count`;`bandCode`=`COW_YOUNG` / `COW_ADULT` |
214
+| 地图乡镇存栏 | 按乡镇聚合该最近年月村记录 |
277 215
 
278 216
 ---
279 217
 
280 218
 ## 7. 交付清单
281 219
 
282
-- [x] `sql/biz_yak_herd_inventory.sql`  
283
-- [x] `BizYakHerdInventory` Domain / Mapper / XML / Service / Controller  
284
-- [x] `YakHerdInventoryExcelParser` + `YakHerdInventoryExcelExporter`  
285
-- [x] `TownDeptSupport` 乡镇字典  
286
-- [x] 前端列表/表单/导入/导出  
287
-- [x] 单元测试 + 导入/导出集成测试(`yak_herd_inventory_2026.xlsx`)  
220
+- [x] `sql/biz_yak_herd_inventory.sql`、`sql/biz_yak_herd_inventory_village_uk.sql`  
221
+- [x] Domain / Mapper / Service / Controller  
222
+- [x] Parser / Exporter / Validation / TownDeptSupport 村树  
223
+- [x] 前端列表/表单(Treeselect)/导入/导出  
224
+- [x] 单元测试与 Excel 样例  
288 225
 
289 226
 ---
290 227
 
@@ -292,9 +229,5 @@ for (int y = current; y >= current - 5; y--) { ... }
292 229
 
293 230
 | 版本 | 说明 |
294 231
 | --- | --- |
295
-| 1.0 | 初稿:表结构、CRUD、导入、sys_dept 乡镇、年份前推 5 年 |
296
-| 1.1 | 增加预留字段 `village_dept_id`(非必填) |
297
-| 1.2 | 实现按年份导出全县乡镇 Excel;列映射与代码对齐;交付清单勾选 |
298
-| 1.3 | 导入 upsert:`selectByYearAndTown` + `updateBizYakHerdInventory`;支持重复导入 |
299
-| 1.4 | 取消逻辑删除:无 `del_flag`;`deleteBizYakHerdInventoryByIds` 物理删除;`uk_year_town` 唯一约束 |
300
-| 1.5 | 按年月填报:`stat_month`;`uk_year_month_town`;`YakHerdInventoryMonthSupport`;导入/导出/列表/CRUD 支持月份;大屏年度汇总取 12 月 |
232
+| 1.0~1.5 | 乡镇粒度、按年月、四档母牛年龄等历史版本 |
233
+| 1.6 | 村粒度 `uk_year_month_village`;新模板字段与派生合计;导入全量覆盖;导出合计按列求和;`/villageTree`;大屏年龄两档 |

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

@@ -1,11 +1,11 @@
1 1
 # 牦牛存栏数据填报 — 测试用例
2 2
 
3 3
 > 依据:`牦牛存栏数据填报功能需求.md`、`牦牛存栏数据填报技术方案.md`  
4
-> **接口 Base Path(示例)**:`/dataModel/yakHerdInventory`  
5
-> **模板文件**:`2026年.xlsx` 第一个 Sheet  
6
-> **乡镇样本**:巴青乡、江绵乡、岗切乡、雅安镇、拉西镇、本塔乡、杂色镇、贡日乡、玛如乡、阿秀乡(`sys_dept`,`parent_id=100`,`order_num>12`)
4
+> **接口 Base Path**:`/dataModel/yakHerdInventory`  
5
+> **模板文件**:`yak_herd_inventory_import_template.xlsx` / 测试样例 `yak_herd_inventory_2026.xlsx`  
6
+> **组织样本**:`sys_dept` 乡镇(`parent_id=100`,`order_num>12`)及其下村节点
7 7
 
8
-**通用前置**:已登录且具备模块权限;`sys_dept` 已执行 `sql/sys_dept_import_town.sql`;测试库无冲突数据或已隔离。
8
+**通用前置**:已登录且具备模块权限;`sys_dept` 已导入乡镇与村;测试库无冲突数据或已隔离。
9 9
 
10 10
 ---
11 11
 
@@ -13,83 +13,86 @@
13 13
 
14 14
 | 用例编号 | 测试模块 | 测试项 | 测试类型 | 测试工具 | 测试目的 | 前置条件 | 测试步骤 | 预期结果 |
15 15
 | --- | --- | --- | --- | --- | --- | --- | --- | --- |
16
-| ZCZX-MYCLTB-UT-001 | 年份 | 可选年份范围 | 单元测试 | JUnit5 | §2.2 | 固定时钟 2026 | `YearOptionsResolver.resolve()` | 返回 2026~2021 共 6 年 |
17
-| ZCZX-MYCLTB-UT-002 | 乡镇 | 字典过滤 | 单元测试 | JUnit5 | §2.3 | Mock dept 表 | `TownDeptSupport.listReportTowns()` | 仅 `parent_id=100` 且 `order_num>12` |
18
-| ZCZX-MYCLTB-UT-003 | 校验 | 必填年份乡镇 | 单元测试 | JUnit5 | §5.2 | 无 | `validate(row)` 缺 `statYear` | 抛 `ServiceException` |
19
-| ZCZX-MYCLTB-UT-004 | 校验 | 负数拒绝 | 单元测试 | JUnit5 | §2.5 | 无 | `yakTotal=-1` | 校验失败 |
20
-| ZCZX-MYCLTB-UT-005 | 唯一性 | 同年同月同乡镇 | 单元测试 | JUnit5+Mockito | §5.2 | 已存在 2026-05+巴青乡 | `insert` 同键 | 失败「已填报」 |
21
-| ZCZX-MYCLTB-UT-006 | 新增 | 新增成功 | 单元测试 | JUnit5+Mockito | §5.2 | 无同年同乡镇记录 | `insert` | 成功 |
22
-| ZCZX-MYCLTB-UT-007 | 导入解析 | 识别表头行 | 单元测试 | JUnit5 | §3.8.1 | `2026年.xlsx` | `detectHeaderRow(sheet)` | 定位含「乡镇」表头 |
23
-| ZCZX-MYCLTB-UT-008 | 导入解析 | 乡镇名匹配 | 单元测试 | JUnit5 | §5.6 | 行「杂色镇」 | `resolveTownDeptId` | 返回对应 `dept_id` |
24
-| ZCZX-MYCLTB-UT-009 | 导入解析 | 未知乡镇 | 单元测试 | JUnit5 | §5.6 | 行「不存在乡」 | 解析 | 记入 `failMessages` |
25
-| ZCZX-MYCLTB-UT-010 | 导入解析 | 空行终止 | 单元测试 | JUnit5 | §5.6 | 数据后空行 | `parseRows` | 仅 10 乡镇行 |
26
-| ZCZX-MYCLTB-UT-011 | 导入 | upsert 新增 | 单元测试 | JUnit5+Mockito | §5.6 | DB 无 2025+本塔乡 | `importYear(2025,file)` | `insertCount>=1` |
27
-| ZCZX-MYCLTB-UT-012 | 导入 | upsert 更新 | 单元测试 | JUnit5+Mockito | §5.6 | 已有 2026+各乡镇 | 同一 xlsx 再导入 | `updateCount=10`;`insertCount=0` |
16
+| ZCZX-MYCLTB-UT-001 | 年份 | 可选年份范围 | 单元测试 | JUnit5 | §2.2 | 固定时钟 2026 | `YakHerdInventoryYearSupport` | 返回 2026~2021 共 6 年 |
17
+| ZCZX-MYCLTB-UT-002 | 乡镇 | 字典过滤 | 单元测试 | JUnit5 | §2.3 | Mock dept | `listReportTowns()` | 仅合法乡镇 |
18
+| ZCZX-MYCLTB-UT-003 | 校验 | 必填年份村 | 单元测试 | JUnit5 | §5.2 | 无 | `validateRowForSave` 缺 `villageDeptId` | 抛 `ServiceException` |
19
+| ZCZX-MYCLTB-UT-004 | 校验 | 负数拒绝 | 单元测试 | JUnit5 | §2.5 | 无 | `breedingBullCount=-1` | 校验失败 |
20
+| ZCZX-MYCLTB-UT-005 | 唯一性 | 同年同月同村 | 单元测试 | JUnit5+Mockito | §5.2 | 已存在同键 | `insert` 同键 | 失败「该年该月该村已填报」 |
21
+| ZCZX-MYCLTB-UT-006 | 新增 | 新增成功 | 单元测试 | JUnit5+Mockito | §5.2 | 无同键 | `insert` | 成功;乡镇由村反推 |
22
+| ZCZX-MYCLTB-UT-007 | 导入解析 | 识别表头行 | 单元测试 | JUnit5 | §3.8.1 | 测试 xlsx | `detectHeaderRow` | 定位「序号」「乡镇」 |
23
+| ZCZX-MYCLTB-UT-008 | 派生 | 合计计算 | 单元测试 | JUnit5 | §2.5 | 明细样本 | `recalculateDerived` | bull/cow/dairy/yak/grand 正确 |
24
+| ZCZX-MYCLTB-UT-009 | 导入解析 | 未知乡镇 | 单元测试 | JUnit5 | §5.6 | 行「不存在乡」 | 导入 | 记入 `failMessages` |
25
+| ZCZX-MYCLTB-UT-010 | 导入解析 | 不含合计行 | 单元测试 | JUnit5 | §5.6 | 测试 xlsx | `parse` | 无乡镇名含「合计」的行 |
26
+| ZCZX-MYCLTB-UT-011 | 导入 | 全量覆盖 | 单元测试 | JUnit5+Mockito | §5.6 | 已有同年月数据 | 再导入 | 先 `deleteByYearMonth` 再 insert |
27
+| ZCZX-MYCLTB-UT-012 | 校验 | Excel 合计警告 | 单元测试 | JUnit5 | §2.5 | 合计列不一致 | `buildExcelTotalWarns` | 返回警告文案 |
28 28
 | ZCZX-MYCLTB-UT-013 | 修改 | 记录不存在 | 单元测试 | JUnit5+Mockito | §5.3 | id 不存在 | `update` | 抛 `ServiceException` |
29
-| ZCZX-MYCLTB-UT-014 | 删除 | 物理删除 | 单元测试 | JUnit5+Mockito | §5.4 | 有效 id | `deleteBizYakHerdInventoryByIds` | 调用 Mapper 物理删除 |
30
-| ZCZX-MYCLTB-UT-015 | 数据权限 | 乡镇账号过滤 | 单元测试 | JUnit5+Mockito | §6 | 用户 dept=杂色镇 | `selectList` | 仅本乡镇 |
31
-| ZCZX-MYCLTB-UT-016 | 预留字段 | village_dept_id 可空 | 单元测试 | JUnit5 | §2.4 | 无 | `insert` 不传 `villageDeptId` | 成功 |
32
-| ZCZX-MYCLTB-UT-017 | 预留字段 | village_dept_id 父子校验 | 单元测试 | JUnit5+Mockito | §2.4 | 乡镇 A、村居 B 非子节点 | `insert` 带错误 `villageDeptId` | 失败(若启用校验)或成功仅存 id |
33
-| ZCZX-MYCLTB-UT-018 | 导出 | 按年填充模板 | 单元测试 | JUnit5 | §5.7 | 模板+1 乡镇样本 | `YakHerdInventoryExcelExporter.export` | 返回 xlsx;解析含对应存栏数 |
34
-| ZCZX-MYCLTB-UT-019 | 导出 | 全县乡镇行数 | 单元测试 | JUnit5 | §5.7 | 10 乡镇字典 | `export(2026, towns, map)` | 解析数据行 + 合计行 |
35
-| ZCZX-MYCLTB-UT-020 | 月份 | 未来月拒绝 | 单元测试 | JUnit5 | §2.2.1 | 当前年 | `validateStatPeriod(2026, 未来月)` | 抛 `ServiceException` |
29
+| ZCZX-MYCLTB-UT-014 | 删除 | 物理删除 | 单元测试 | JUnit5+Mockito | §5.4 | 有效 id | `deleteByIds` | 调用 Mapper 物理删除 |
30
+| ZCZX-MYCLTB-UT-015 | 村树 | 树结构 | 单元测试 | JUnit5 | §2.4 | Mock dept | `buildVillageTree` | 县→乡镇→村 |
31
+| ZCZX-MYCLTB-UT-016 | 校验 | 村反推乡镇 | 单元测试 | JUnit5 | §2.4 | 合法村 | `validateRowForSave` | 写入 `townDeptId` |
32
+| ZCZX-MYCLTB-UT-017 | 校验 | 非法村 | 单元测试 | JUnit5 | §2.4 | 非法 id | `validateRowForSave` | 失败 |
33
+| ZCZX-MYCLTB-UT-018 | 导出 | 按年月填充 | 单元测试 | JUnit5 | §5.7 | 模板+村样本 | `export` | 返回 xlsx;含存栏数 |
34
+| ZCZX-MYCLTB-UT-019 | 导出 | 合计行求和 | 单元测试 | JUnit5 | §5.7 | ≥2 村数据 | `export` | 合计行村居及后列=各列之和;序号列合并为「合计」 |
35
+| ZCZX-MYCLTB-UT-020 | 月份 | 未来月拒绝 | 单元测试 | JUnit5 | §2.2.1 | 当前年 | `validateStatPeriod` 未来月 | 抛异常 |
36 36
 | ZCZX-MYCLTB-UT-021 | 月份 | 月份选项 | 单元测试 | JUnit5 | §2.2.1 | 无 | `resolveMonthOptions()` | 返回 1~12 |
37 37
 
38 38
 ## 二、接口测试用例
39 39
 
40 40
 | 用例编号 | 测试模块 | 测试项 | 测试类型 | 测试工具 | 测试目的 | 前置条件 | 测试步骤 | 预期结果 |
41 41
 | --- | --- | --- | --- | --- | --- | --- | --- | --- |
42
-| ZCZX-MYCLTB-API-001 | 列表 | 默认分页 | 接口测试 | Postman | §5.1 | 已登录;有样本 | `GET /list` | `code=200`;含所属年份、乡镇、村居数、牧户数、存栏数、公牛/母牛存栏数 |
43
-| ZCZX-MYCLTB-API-002 | 列表 | 按年份筛选 | 接口测试 | Postman | §5.1 | 有 2025、2026 数据 | `statYear=2026` | 仅 2026 |
44
-| ZCZX-MYCLTB-API-002a | 列表 | 按年月筛选 | 接口测试 | Postman | §5.1 | 同 year 多 month | `statYear=2026&statMonth=12` | 仅 2026 年 12 月 |
45
-| ZCZX-MYCLTB-API-003 | 列表 | 按乡镇筛选 | 接口测试 | Postman | §5.1 | 已知 dept_id | `townDeptId=` | 仅该乡镇 |
46
-| ZCZX-MYCLTB-API-004 | 列表 | 组合筛选 | 接口测试 | Postman | §5.1 | 已登录 | 年+月+乡镇 | 交集结果 |
47
-| ZCZX-MYCLTB-API-005 | 详情 | 全字段 | 接口测试 | Postman | §5.5 | 有效 id | `GET /{id}` | 返回全部字段(含 `villageDeptId`、分龄、种公牛等);列表 VO 不含的字段均在详情中 |
48
-| ZCZX-MYCLTB-API-006 | 新增 | 必填成功 | 接口测试 | Postman | §5.2 | 无重复 | `POST` 最小必填 | 成功 |
42
+| ZCZX-MYCLTB-API-001 | 列表 | 默认分页 | 接口测试 | Postman | §5.1 | 有样本 | `GET /list` | `code=200`;含年/月/乡镇/村/存栏等 |
43
+| ZCZX-MYCLTB-API-002 | 列表 | 按年份筛选 | 接口测试 | Postman | §5.1 | 多年数据 | `statYear=2026` | 仅 2026 |
44
+| ZCZX-MYCLTB-API-002a | 列表 | 按年月筛选 | 接口测试 | Postman | §5.1 | 同 year 多 month | `statYear=2026&statMonth=7` | 仅该月 |
45
+| ZCZX-MYCLTB-API-003 | 列表 | 按乡镇筛选 | 接口测试 | Postman | §5.1 | 已知 dept | `townDeptId=` | 仅该乡镇下村记录 |
46
+| ZCZX-MYCLTB-API-003a | 列表 | 按村筛选 | 接口测试 | Postman | §5.1 | 已知村 | `villageDeptId=` | 仅该村 |
47
+| ZCZX-MYCLTB-API-004 | 列表 | 组合筛选 | 接口测试 | Postman | §5.1 | 已登录 | 年+月+乡镇+村 | 交集 |
48
+| ZCZX-MYCLTB-API-005 | 详情 | 全字段 | 接口测试 | Postman | §5.5 | 有效 id | `GET /{id}` | 含明细与派生合计 |
49
+| ZCZX-MYCLTB-API-006 | 新增 | 必填成功 | 接口测试 | Postman | §5.2 | 无重复 | `POST` 含 `villageDeptId` | 成功 |
49 50
 | ZCZX-MYCLTB-API-007 | 新增 | 缺年份 | 接口测试 | Postman | §5.2 | 已登录 | 缺 `statYear` | 失败 |
50
-| ZCZX-MYCLTB-API-008 | 新增 | 缺乡镇 | 接口测试 | Postman | §5.2 | 已登录 | 缺 `townDeptId` | 失败 |
51
-| ZCZX-MYCLTB-API-009 | 新增 | 重复同年月乡镇 | 接口测试 | Postman | §5.2 | 已有记录 | 再 `POST` 同键 | 失败 |
52
-| ZCZX-MYCLTB-API-010 | 修改 | 更新指标 | 接口测试 | Postman | §5.3 | 有效 id | `PUT` 改 `yakTotal` | 成功;详情为新值 |
53
-| ZCZX-MYCLTB-API-011 | 删除 | 物理删除 | 接口测试 | Postman | §5.4 | 有效 id | `DELETE /{id}` | 成功;记录从库中移除 |
51
+| ZCZX-MYCLTB-API-008 | 新增 | 缺村 | 接口测试 | Postman | §5.2 | 已登录 | 缺 `villageDeptId` | 失败 |
52
+| ZCZX-MYCLTB-API-009 | 新增 | 重复同年月 | 接口测试 | Postman | §5.2 | 已有记录 | 再 `POST` 同键 | 失败 |
53
+| ZCZX-MYCLTB-API-010 | 修改 | 更新指标 | 接口测试 | Postman | §5.3 | 有效 id | `PUT` 改明细 | 成功;派生重算 |
54
+| ZCZX-MYCLTB-API-011 | 删除 | 物理删除 | 接口测试 | Postman | §5.4 | 有效 id | `DELETE /{id}` | 记录移除 |
54 55
 | ZCZX-MYCLTB-API-012 | 下拉 | 年份选项 | 接口测试 | Postman | §2.2 | 已登录 | `GET /yearOptions` | 6 个年份 |
55
-| ZCZX-MYCLTB-API-013 | 下拉 | 乡镇选项 | 接口测试 | Postman | §2.3 | 已登录 | `GET /townOptions` | 10 个乡镇;`order_num>12` |
56
-| ZCZX-MYCLTB-API-014 | 导入 | 全量乡镇 | 接口测试 | Postman | §5.6 | 空库 2026-05 | `POST /importData`+`2026年.xlsx`+`statYear=2026&statMonth=5` | `insertCount=10`;`failCount=0` |
57
-| ZCZX-MYCLTB-API-015 | 导入 | 覆盖更新 | 接口测试 | Postman | §5.6 | 已有 2026 全量 | 改 Excel 一行再导入 | `updateCount>=1` |
56
+| ZCZX-MYCLTB-API-013 | 下拉 | 乡镇选项 | 接口测试 | Postman | §2.3 | 已登录 | `GET /townOptions` | 合法乡镇 |
57
+| ZCZX-MYCLTB-API-013a | 下拉 | 村树 | 接口测试 | Postman | §2.4 | 已登录 | `GET /villageTree` | 树含乡镇与村 |
58
+| ZCZX-MYCLTB-API-014 | 导入 | 全量村行 | 接口测试 | Postman | §5.6 | 合法 Excel | `POST /importData` | `insertCount>0`;`failCount=0` |
59
+| ZCZX-MYCLTB-API-015 | 导入 | 覆盖同月 | 接口测试 | Postman | §5.6 | 已有同月 | 再导入 | 旧数据被替换 |
58 60
 | ZCZX-MYCLTB-API-016 | 导入 | 缺填报年份 | 接口测试 | Postman | §5.6 | 已登录 | 仅上传文件 | 失败 |
59 61
 | ZCZX-MYCLTB-API-017 | 导入 | 非 xlsx | 接口测试 | Postman | §5.6 | 已登录 | 上传 `.csv` | 失败 |
60
-| ZCZX-MYCLTB-API-018 | 权限 | 无 add | 接口测试 | Postman | §6 | 仅 list 角色 | `POST /` | 403 |
62
+| ZCZX-MYCLTB-API-017a | 导入 | 无有效行 | 接口测试 | Postman | §5.6 | 空数据区 | 导入 | 「未解析到有效数据行」 |
63
+| ZCZX-MYCLTB-API-018 | 权限 | 无 add | 接口测试 | Postman | §6 | 仅 list | `POST /` | 403 |
61 64
 | ZCZX-MYCLTB-API-019 | 权限 | 无 import | 接口测试 | Postman | §6 | 无 import | `POST /importData` | 403 |
62
-| ZCZX-MYCLTB-API-020 | 端到端 | 导入-查-详情 | 接口测试 | Postman | §4 | 空库 | 导入 2026→list→detail | 数据一致 |
63
-| ZCZX-MYCLTB-API-021 | 导出 | 按年月导出 | 接口测试 | Postman/MockMvc | §5.7 | 有 2026-05 数据 | `GET /export?statYear=2026&statMonth=5` | `200`;xlsx |
64
-| ZCZX-MYCLTB-API-022 | 导出 | 缺 statMonth | 接口测试 | Postman | §5.7 | 已登录 | `GET /export?statYear=2026` | 失败 |
65
+| ZCZX-MYCLTB-API-020 | 端到端 | 导入-查-详情 | 接口测试 | Postman | §4 | 空库 | 导入→list→detail | 数据一致 |
66
+| ZCZX-MYCLTB-API-021 | 导出 | 按年月导出 | 接口测试 | Postman | §5.7 | 有数据 | `GET /export?statYear=&statMonth=` | `200`;xlsx |
67
+| ZCZX-MYCLTB-API-022 | 导出 | 缺 statMonth | 接口测试 | Postman | §5.7 | 已登录 | 仅 `statYear` | 失败 |
65 68
 | ZCZX-MYCLTB-API-023 | 导出 | 非法年份 | 接口测试 | Postman | §5.7 | 已登录 | `statYear=1999` | 失败 |
66
-| ZCZX-MYCLTB-API-024 | 权限 | 无 export | 接口测试 | Postman | §6 | 无 export | `GET /export?statYear=2026&statMonth=5` | 403 |
67
-| ZCZX-MYCLTB-API-025 | 端到端 | 导出-再导入 | 接口测试 | Postman | §5.7 | 有 2026 全量 | 导出→改一行→导入 | upsert 成功 |
69
+| ZCZX-MYCLTB-API-024 | 权限 | 无 export | 接口测试 | Postman | §6 | 无 export | `GET /export` | 403 |
70
+| ZCZX-MYCLTB-API-025 | 端到端 | 导出-再导入 | 接口测试 | Postman | §5.7 | 有数据 | 导出→导入同月 | 全量覆盖成功 |
68 71
 
69 72
 ---
70 73
 
71 74
 ## 三、界面测试用例(UI)
72 75
 
73
-**工具**:Playwright + Chrome(与项目其他模块一致)
76
+**工具**:Playwright + Chrome。
74 77
 
75 78
 | 用例编号 | 测试模块 | 测试项 | 测试类型 | 测试工具 | 测试目的 | 前置条件 | 测试步骤 | 预期结果 |
76 79
 | --- | --- | --- | --- | --- | --- | --- | --- | --- |
77
-| ZCZX-MYCLTB-UI-001 | 列表 | 默认加载 | UI 测试 | Playwright | §5.1 | 有数据 | 进入菜单 | 列含所属份、**所属份**、乡镇…;**年月筛选为空** |
78
-| ZCZX-MYCLTB-UI-002 | 列表 | 年月乡镇筛选 | UI 测试 | Playwright | §5.1 | 有数据 | 选择后搜索 | 结果正确 |
79
-| ZCZX-MYCLTB-UI-003 | 列表 | 重置 | UI 测试 | Playwright | §5.1 | 已筛选 | 点重置 | 条件清空 |
80
-| ZCZX-MYCLTB-UI-004 | 新增 | 必填校验 | UI 测试 | Playwright | §5.2 | 有 add 权限 | 不选乡镇提交 | 表单提示 |
81
-| ZCZX-MYCLTB-UI-005 | 新增 | 年份下拉 6 项 | UI 测试 | Playwright | §2.2 | 打开表单 | 查看年份下拉 | 当年起前 5 年 |
82
-| ZCZX-MYCLTB-UI-006 | 新增 | 乡镇下拉 | UI 测试 | Playwright | §2.3 | 打开表单 | 查看乡镇下拉 | 10 个乡镇 |
83
-| ZCZX-MYCLTB-UI-007 | 修改 | 回显保存 | UI 测试 | Playwright | §5.3 | 有记录 | 改牧户数保存 | 列表更新 |
84
-| ZCZX-MYCLTB-UI-008 | 删除 | 二次确认 | UI 测试 | Playwright | §5.4 | 有记录 | 删除确认 | 行消失 |
85
-| ZCZX-MYCLTB-UI-009 | 详情 | 全字段只读 | UI 测试 | Playwright | §5.5 | 有完整数据 | 点查看 | 展示全部字段;列表未展示的项(如分龄母牛、种公牛)均可见 |
86
-| ZCZX-MYCLTB-UI-010 | 导入 | 选年月上传 | UI 测试 | Playwright | §5.6 | 有 import 权限 | 默认当前年月→上传模板 | 成功摘要 |
87
-| ZCZX-MYCLTB-UI-011 | 导入 | 未选月份 | UI 测试 | Playwright | §5.6 | 有 import 权限 | 缺月份提交 | 表单校验失败 |
88
-| ZCZX-MYCLTB-UI-012 | 权限 | 无导入按钮 | UI 测试 | Playwright | §6 | 无 import | 进入页面 | 无导入入口 |
89
-| ZCZX-MYCLTB-UI-013 | 端到端 | 导入后列表 | UI 集成 | Playwright | §4 | 空库 | 导入→筛选 2026 | 10 行乡镇数据 |
90
-| ZCZX-MYCLTB-UI-014 | 导出 | 按年月导出 | UI 测试 | Playwright | §5.7 | 有 export 权限 | 选年月导出 | 下载 `{年}年{月}月牦牛存栏数据.xlsx` |
91
-| ZCZX-MYCLTB-UI-015 | 导出 | 未选月份 | UI 测试 | Playwright | §5.7 | 有 export 权限 | 缺月份点导出 | 表单校验失败 |
92
-| ZCZX-MYCLTB-UI-016 | 权限 | 无导出按钮 | UI 测试 | Playwright | §6 | 无 export | 进入页 | 无「按年份导出入口 |
80
+| ZCZX-MYCLTB-UI-001 | 列表 | 默认加载 | UI | Playwright | §5.1 | 有数据 | 进入菜单 | 列含年月、乡镇、村…;年月筛选为空 |
81
+| ZCZX-MYCLTB-UI-002 | 列表 | 筛选 | UI | Playwright | §5.1 | 有数据 | 选年月/乡镇/村搜索 | 结果正确 |
82
+| ZCZX-MYCLTB-UI-003 | 列表 | 重置 | UI | Playwright | §5.1 | 已筛选 | 点重置 | 条件清空 |
83
+| ZCZX-MYCLTB-UI-004 | 新增 | 必填校验 | UI | Playwright | §5.2 | 有 add | 不选村提交 | 表单提示 |
84
+| ZCZX-MYCLTB-UI-005 | 新增 | 年份下拉 | UI | Playwright | §2.2 | 打开表单 | 查看年份 | 当年起前 5 年 |
85
+| ZCZX-MYCLTB-UI-006 | 新增 | 村树 | UI | Playwright | §2.4 | 打开表单 | 展开树 | 仅可选村叶子 |
86
+| ZCZX-MYCLTB-UI-007 | 修改 | 回显保存 | UI | Playwright | §5.3 | 有记录 | 改牧户数保存 | 列表更新 |
87
+| ZCZX-MYCLTB-UI-008 | 删除 | 二次确认 | UI | Playwright | §5.4 | 有记录 | 删除确认 | 行消失 |
88
+| ZCZX-MYCLTB-UI-009 | 详情 | 含派生字段 | UI | Playwright | §5.5 | 有完整数据 | 点查看 | 展示公牛/母牛/奶牛/总合计等 |
89
+| ZCZX-MYCLTB-UI-010 | 新增 | 无派生输入 | UI | Playwright | §5.2 | 打开新增 | 查看表单 | 无公牛合计等计算项输入框 |
90
+| ZCZX-MYCLTB-UI-011 | 导入 | 选年月上传 | UI | Playwright | §5.6 | 有 import | 上传模板 | 成功摘要 |
91
+| ZCZX-MYCLTB-UI-012 | 导入 | 未选月份 | UI | Playwright | §5.6 | 有 import | 缺月份 | 校验失败 |
92
+| ZCZX-MYCLTB-UI-013 | 权限 | 无导入按钮 | UI | Playwright | §6 | 无 import | 进入页 | 无导入入口 |
93
+| ZCZX-MYCLTB-UI-014 | 导出 | 按年月导出 | UI | Playwright | §5.7 | 有 export | 选年月导出 | 下载对应文件名 |
94
+| ZCZX-MYCLTB-UI-015 | 导出 | 未选月份 | UI | Playwright | §5.7 | 有 export | 缺月份 | 校验失败 |
95
+| ZCZX-MYCLTB-UI-016 | 权限 | 无导出按钮 | UI | Playwright | §6 | 无 export | 进入页 | 无导出入口 |
93 96
 
94 97
 ---
95 98
 
@@ -97,15 +100,14 @@
97 100
 
98 101
 | 需求章节 | 要点 | 主要用例 |
99 102
 | --- | --- | --- |
100
-| §2.2 年份 | 向前 5 年 | UT-001;API-012;UI-005 |
101
-| §2.3 乡镇 | sys_dept 二级 order_num>12 | UT-002;API-013;UI-006 |
102
-| §2.2.1 月份 | 1~12、非未来月 | UT-020~021 |
103
-| §5.1 列表 | 8 数据列+操作;默认不筛年月 | API-001~004、002a;UI-001~003 |
104
-| §5.2~5.4 CRUD | 必填、唯一、删除 | UT-003~006、013~014;API-006~011 |
105
-| §5.5 详情 | 全字段 | API-005;UI-009 |
106
-| §5.6 导入 | 2026年.xlsx | UT-007~012;API-014~017;UI-010~011 |
107
-| §5.7 导出 | 按年全县乡镇 | UT-018~019;API-021~025;UI-014~015 |
108
-| §6 权限 | 按钮级 | API-018~019、024;UI-012、016 |
103
+| §2.2~2.2.1 | 年月 | UT-001、020~021;API-012 |
104
+| §2.3~2.4 | 乡镇/村 | UT-002、015~017;API-013、013a;UI-006 |
105
+| §2.5 派生 | 合计计算 | UT-008、012;UI-009~010 |
106
+| §5.1 列表 | 乡镇+村列 | API-001~004;UI-001~003 |
107
+| §5.2~5.4 CRUD | 村唯一、物理删 | UT-003~006、013~014;API-006~011 |
108
+| §5.6 导入 | 全量覆盖 | UT-007、010~011;API-014~017a |
109
+| §5.7 导出 | 合计按列求和 | UT-018~019;API-021~025;UI-014 |
110
+| §6 权限 | 按钮级 | API-018~019、024;UI-013、016 |
109 111
 
110 112
 ---
111 113
 
@@ -113,9 +115,5 @@
113 115
 
114 116
 | 版本 | 说明 |
115 117
 | --- | --- |
116
-| 1.0 | 初版:单元 15、接口 20、UI 13;编号前缀 **ZCZX-MYCLTB** |
117
-| 1.1 | 补充 `village_dept_id` 预留字段用例 UT-016~017 |
118
-| 1.2 | 补充按年份导出用例 UT-018~019、API-021~025、UI-014~016 |
119
-| 1.3 | 补充重复导入 upsert 用例 UT-011~012 |
120
-| 1.4 | 取消逻辑删除:UT-014 改为物理删除;移除 UT-020 |
121
-| 1.5 | 按年月填报:UT-005/020~021;API-002a/014/021~022;UI 年月筛选与导出 |
118
+| 1.0~1.5 | 乡镇粒度、按年月、四档年龄等历史版本 |
119
+| 1.6 | 同步村粒度与新模板:唯一键村、村树、派生合计、导入全量覆盖、导出合计求和;用例编号延续 ZCZX-MYCLTB |

+ 5 - 4
doc/大屏/首页/大屏首页功能需求.md

@@ -73,7 +73,7 @@
73 73
 | **产业总览·出栏 / 农牧户** | 出栏表**最近填报年月**全县 `yak_outbound_count` / `farmer_household_count` 合计;接口字段 **`annualOutbound`** / **`pastureCount`** |
74 74
 | **存栏变动·当前公母** | 存栏表**最近填报年月**全县 `bull_total`、`cow_total`;`unknownGenderCount = yak_total − bull − cow`(不足为 0) |
75 75
 | **存栏变动·趋势** | **§2.3 滚动 12 月**;各月全县 `bull_total`、`cow_total` 合计 |
76
-| **年龄结构** | 存栏表**最近填报年月**母牛分龄四档:`1岁以下`、`1~3岁`、`3~12岁`、`12岁以上`(对应填报字段合计) |
76
+| **年龄结构** | 存栏表**最近填报年月**母牛分龄两档:`0~1.5岁幼畜`、`1.5岁以上成畜`(对应填报 `cow_young_count` / `cow_adult_count`) |
77 77
 | **出栏统计** | **§2.3 滚动 12 月**;各月全县 `yak_outbound_count` 合计 |
78 78
 | **地图标注** | 各乡镇在各自表**最近填报年月**下的 `yak_total` / `yak_outbound_count`;**不**随 **Y** 切换 |
79 79
 | **牧场 / 草场** | 同《养殖产业数据》**§2.7** 等(档案/台账口径) |
@@ -311,10 +311,10 @@ flowchart TB
311 311
 | 项 | 规则 |
312 312
 | --- | --- |
313 313
 | 图表类型 | 饼图/环形图(以原型为准) |
314
-| 分档 | **1岁以下**、**1~3岁**、**3~12岁**、**12岁以上**(母牛分龄,取自存栏填报) |
314
+| 分档 | **0~1.5岁幼畜**、**1.5岁以上成畜**(母牛分龄,取自存栏填报) |
315 315
 | 展示内容 | 各档**头数**及**占比(%)** |
316 316
 | 时点 | 存栏表**最近填报年月** |
317
-| 无存栏 | 档均为 0 时走 **§7.1** 空态 |
317
+| 无存栏 | 档均为 0 时走 **§7.1** 空态 |
318 318
 
319 319
 ---
320 320
 
@@ -457,7 +457,7 @@ flowchart TB
457 457
 | SY-02 | 切换年份 | 切换 **Y** 后各区块同步更新,无旧年残留 |
458 458
 | SY-03 | 产业总览 | 八项指标与手工按 **§2.5、§2.6** 核算一致(主数据三项不随 **Y** 变) |
459 459
 | SY-04 | 存栏趋势 | 滚动 12 月;含 `statYear`+`month`;公/母两系列;缺月补 0;横轴仅月、悬停显年月 |
460
-| SY-05 | 年龄结构 | 母牛档占比之和为 100%(有数据时) |
460
+| SY-05 | 年龄结构 | 母牛档占比之和为 100%(有数据时) |
461 461
 | SY-06 | 出栏按月 | 滚动 12 月出栏合计与填报表手工核算一致;纯柱状图 |
462 462
 | SY-07 | 草场占比 | 可用/使用中及五档退化与《养殖产业数据》一致 |
463 463
 | SY-08 | 预约统计 | 三类年度合计与按月堆叠与 **§2.7** 一致 |
@@ -474,6 +474,7 @@ flowchart TB
474 474
 
475 475
 | 版本 | 日期 | 说明 |
476 476
 | --- | --- | --- |
477
+| 1.2 | 2026-07-17 | 年龄结构改为母牛两档(0~1.5 / 1.5+),对齐存栏填报新模板 |
477 478
 | 1.1 | 2026-07-04 | 存栏变动/出栏统计改为填报表滚动 12 月;接口增 `statYear`;前端横轴仅月、悬停显年月;年龄结构改为母牛四档;地图/总览取最近填报年月 |
478 479
 | 1.0 | 2026-05-25 | 由草稿优化:补全流程、边界、十区块需求;对齐养殖产业/预约/补贴/标准/培训口径;不涉及库表与接口;保留草稿不修改 |
479 480
 

+ 5 - 4
doc/大屏/首页/大屏首页技术方案.md

@@ -84,7 +84,7 @@
84 84
 | 存栏变动 header | 最近年月全县 `bull_total`、`cow_total`、`yak_total` |
85 85
 | 滚动 12 月存栏趋势 | `biz_yak_herd_inventory` 按 `stat_year+stat_month` 全县 `bull_total`/`cow_total` 合计 |
86 86
 | 滚动 12 月出栏 | `biz_yak_outbound_report` 按 `stat_year+stat_month` 全县 `yak_outbound_count` 合计 |
87
-| 年龄结构 | 最近年月母牛四档:`cow_under_1_count`、`cow_1_3_count`、`cow_3_12_count`、`cow_over_12_count` |
87
+| 年龄结构 | 最近年月母牛两档:`cow_young_count`(0~1.5 岁)、`cow_adult_count`(1.5 岁以上) |
88 88
 | 地图标注 | 各乡镇在各自表最近填报年月下的 `yak_total` / `yak_outbound_count` |
89 89
 
90 90
 #### 养殖产业档案(草场等,复用 Mapper)
@@ -255,11 +255,11 @@
255 255
 
256 256
 | 字段 | 说明 |
257 257
 | --- | --- |
258
-| `bandCode` | `COW_U1` / `COW_1_3` / `COW_3_12` / `COW_O12` |
259
-| `bandLabel` | `1岁以下` / `1~3岁` / `3~12岁` / `12岁以上` |
258
+| `bandCode` | `COW_YOUNG` / `COW_ADULT` |
259
+| `bandLabel` | `0~1.5岁幼畜` / `1.5岁以上成畜` |
260 260
 | `count` / `ratio` | 头数;占比 **0~100** 数值 |
261 261
 
262
-档固定返回;无存栏时 `count=0`,`ratio=0`。
262
+档固定返回;无存栏时 `count=0`,`ratio=0`。对齐存栏填报 `cow_young_count` / `cow_adult_count`。
263 263
 
264 264
 #### 3.1.4 `outboundMonthly[]`
265 265
 
@@ -489,6 +489,7 @@ SQL 示例:`sql/big_screen_home_perm.sql`(挂载「大屏」父菜单下)
489 489
 
490 490
 | 版本 | 日期 | 说明 |
491 491
 | --- | --- | --- |
492
+| 1.3 | 2026-07-17 | 年龄结构两档 `COW_YOUNG`/`COW_ADULT`,字段 `cow_young_count`/`cow_adult_count` |
492 493
 | 1.2 | 2026-07-04 | 预约堆叠、培训趋势改滚动 12 月 + statYear;前端横轴/Tooltip 与存栏出栏一致 |
493 494
 | 1.1 | 2026-07-04 | 存栏/出栏改填报表滚动 12 月;VO 增 `statYear`;年龄四档;地图/总览最近填报年月;§4 前端横轴/Tooltip |
494 495
 | 1.0 | 2026-05-20 | 初稿:无新表;双接口;对齐 `大屏首页功能需求.md` v1.0 |

+ 6 - 5
doc/大屏/首页/大屏首页测试用例.md

@@ -60,8 +60,8 @@
60 60
 | DP-SY-UT-006 | 存栏趋势 | 不随 statYear | 单元测试 | JUnit5 | 技术§3.1 | `loadDashboard(2025)` 与 `2026` | 比较 `monthlyTrend` | 两次 `monthlyTrend` 相同(Mock 填报一致时) |
61 61
 | DP-SY-UT-007 | 出栏按月 | 滚动 12 月补零 | 单元测试 | JUnit5 | SY-06 | `OUT-R12` | `buildRolling12MonthOutbound(..., 2026-07)` | 12 条;2026-05 对应槽位 `outboundCount=88`;其余 0 |
62 62
 | DP-SY-UT-007b | 出栏按月 | statYear+month | 单元测试 | JUnit5 | §3.1.4 | 同上 | 检查含 `statYear`、`month` | 每条均含年月 |
63
-| DP-SY-UT-008 | 年龄结构 | 四档占比之和 | 单元测试 | JUnit5 | SY-05 | 四档头数 10/20/30/40 | `buildCowAgeStructureFromHerdReport` | 四档固定返回;`sum(ratio)≈100` |
64
-| DP-SY-UT-009 | 年龄结构 | 无数据 | 单元测试 | JUnit5 | §7.1 | `HERD-EMPTY` | `buildCowAgeStructureFromHerdReport(null)` | 档 `count=0`、`ratio=0` |
63
+| DP-SY-UT-008 | 年龄结构 | 两档占比之和 | 单元测试 | JUnit5 | SY-05 | 两档头数 10/40 | `buildCowAgeStructureFromHerdReport` | 两档固定返回;`sum(ratio)≈100` |
64
+| DP-SY-UT-009 | 年龄结构 | 无数据 | 单元测试 | JUnit5 | §7.1 | `HERD-EMPTY` | `buildCowAgeStructureFromHerdReport(null)` | 档 `count=0`、`ratio=0` |
65 65
 | DP-SY-UT-010 | 流通快照 | 切年不变 | 单元测试 | JUnit5 | §2.6、SY-03 | 固定 `TRADE-SNAP` | `loadDashboard(2025)` 与 `2026` | `supplierCount`/`distributorCount`/`tradeMarketCount` 相同 |
66 66
 | DP-SY-UT-011 | 预约 | 状态过滤 | 单元测试 | JUnit5 | 技术§2.4 | `APT-Y`+`APT-EXCL` | `countAppointments(2026)` | 不计入 status 2、3;三条有效计入 |
67 67
 | DP-SY-UT-012 | 预约 | 三类映射 | 单元测试 | JUnit5 | §2.7 | provider_type 1/3/2 各 1 | `buildAppointmentStats` | 兽医/专家/机构年度合计各 1 |
@@ -93,7 +93,7 @@
93 93
 | DP-SY-API-003 | 看板 | 主数据切年不变 | 接口测试 | Postman | §2.6 | `TRADE-SNAP` 固定 | `statYear=2025` 与 `2026` | `supplierCount` 等三项相同 |
94 94
 | DP-SY-API-004 | 看板 | 历史年 statDate | 接口测试 | Postman | §2.2 | 已登录 | `statYear=2024` | `statDate=2024-12-31`;预约/补贴等按 2024;滚动 12 月趋势与 **Y** 无关 |
95 95
 | DP-SY-API-005 | 看板 | 存栏趋势滚动 12 月 | 接口测试 | Postman | SY-04 | `HERD-R12` | `GET dashboard` | `monthlyTrend.length=12`;含 `statYear`、`month`、`bullCount`、`cowCount`;缺月补 0 |
96
-| DP-SY-API-006 | 看板 | 年龄结构四档 | 接口测试 | Postman | SY-05 | 有存栏填报 | `GET dashboard` | `ageStructure.length=4`;有数据时 `sum(ratio)≈100` |
96
+| DP-SY-API-006 | 看板 | 年龄结构两档 | 接口测试 | Postman | SY-05 | 有存栏填报 | `GET dashboard` | `ageStructure.length=2`;有数据时 `sum(ratio)≈100` |
97 97
 | DP-SY-API-007 | 看板 | 出栏滚动 12 月 | 接口测试 | Postman | SY-06 | `OUT-R12` | `GET dashboard` | `outboundMonthly.length=12`;含 `statYear`、`month`;2026-05=88,其余 0 |
98 98
 | DP-SY-API-008 | 看板 | 草场退化五档 | 接口测试 | Postman | SY-07 | `GRASS-MIX` | `GET dashboard` | `degradationStats.length=5`;`sum(count)=grasslandCount` |
99 99
 | DP-SY-API-009 | 看板 | 预约三类年度+堆叠 | 接口测试 | Postman | SY-08 | `APT-Y`+`APT-EXCL` | `GET dashboard` | 年度合计不含拒绝/取消;`monthlyStacked` 12 条 |
@@ -139,7 +139,7 @@
139 139
 | DP-SY-UI-004 | 产业总览 | 八项卡片与单位 | UI 测试 | Playwright+Chrome | §5.2、SY-03 | 2026 有完整样本 | 查看产业总览区 | 8 项名称/数值/单位(头、个、亩)正确;**无**预警牦牛卡片 |
140 140
 | DP-SY-UI-005 | 产业总览 | 流通三项切年不变 | UI 测试 | Playwright+Chrome | §2.6 | `TRADE-SNAP` 固定 | 2025→2026 | 供应商/承销商/市场三数不变(未改主数据前提下) |
141 141
 | DP-SY-UI-006 | 图表 | 存栏公/母双折线 | UI 测试 | Playwright+Chrome | SY-04 | `HERD-R12` | 查看存栏变动图+悬停 | 横轴 12 刻度**仅月份**;悬停显 `YYYY年M月`;公/母两图例 |
142
-| DP-SY-UI-007 | 图表 | 年龄结构饼图 | UI 测试 | Playwright+Chrome | SY-05 | 有存栏填报 | 查看年龄结构 | 档标签;占比之和 100%(有数据时) |
142
+| DP-SY-UI-007 | 图表 | 年龄结构饼图 | UI 测试 | Playwright+Chrome | SY-05 | 有存栏填报 | 查看年龄结构 | 档标签;占比之和 100%(有数据时) |
143 143
 | DP-SY-UI-008 | 图表 | 出栏柱状 12 月 | UI 测试 | Playwright+Chrome | SY-06 | `OUT-R12` | 查看出栏统计+悬停 | 12 柱、**无趋势折线**;横轴仅月;悬停显年月+头数 |
144 144
 | DP-SY-UI-009 | 图表 | 草场可用/使用中 | UI 测试 | Playwright+Chrome | SY-07 | `GRASS-MIX` | 查看草场区上部 | 两数字区分「可用」「使用中」;不与退化表简单相加为总量 |
145 145
 | DP-SY-UI-010 | 图表 | 退化等级占比 | UI 测试 | Playwright+Chrome | SY-07 | 五档均有 | 查看退化表/环图 | 五档名称与 API 一致;占比为 % |
@@ -221,7 +221,7 @@ await page.route('**/bigScreen/home/dashboard**', route =>
221 221
 | SY-02 | 切年一致 | API-021、037;UI-002~003 |
222 222
 | SY-03 | 产业总览八项 | UT-010;API-002~003;UI-004~005 |
223 223
 | SY-04 | 存栏趋势滚动 12 月 | UT-005~006;API-005;UI-006 |
224
-| SY-05 | 年龄结构档 | UT-008~009;API-006;UI-007 |
224
+| SY-05 | 年龄结构档 | UT-008~009;API-006;UI-007 |
225 225
 | SY-06 | 出栏滚动 12 月 | UT-007、007b;API-007;UI-008 |
226 226
 | SY-07 | 草场占比 | API-008;UI-009~010 |
227 227
 | SY-08 | 预约统计 | UT-011~012;API-009;UI-011 |
@@ -242,5 +242,6 @@ await page.route('**/bigScreen/home/dashboard**', route =>
242 242
 
243 243
 | 版本 | 说明 |
244 244
 | --- | --- |
245
+| 1.2 | 年龄结构改为两档;用例 UT-008~009、API-006、UI-007 同步 |
245 246
 | 1.1 | 对齐 v1.1 需求/技术方案:滚动 12 月、`statYear`+`month`、填报表样本、UI 横轴/Tooltip、四档年龄、地图最近填报年月 |
246 247
 | 1.0 | 初版:单元 25、接口 37、UI 29;覆盖 SY-01~14 与正常/异常/约束;Playwright+Chrome;Base `/bigScreen/home` |