# 牦牛存栏数据填报 — 技术方案 > 依据:同目录 `牦牛存栏数据填报功能需求.md`;模板 `baqing-admin/src/main/resources/templates/yak_herd_inventory_import_template.xlsx`。 --- ## 1. 技术架构 | 项 | 说明 | | --- | --- | | **后端** | RuoYi **v3.9.2**(springboot2):JDK 8、Spring Boot 2.x、Spring MVC、MyBatis、Druid | | **数据库** | MySQL **5.7.39**,InnoDB,`utf8mb4` | | **前端** | 若依 Vue2 + Element UI + `@riophae/vue-treeselect` | | **Excel** | Apache POI | | **组织** | `sys_dept` 乡镇/村字典;`TownDeptSupport` | **分层**:`BizYakHerdInventoryController` → `IBizYakHerdInventoryService` → `BizYakHerdInventoryMapper` → `domain`;Excel 由 `YakHerdInventoryExcelParser`(导入)、`YakHerdInventoryExcelExporter`(导出)承担;校验与派生计算由 `YakHerdInventoryValidation`。 **代码位置**:`baqing-admin` → `com.ruoyi.web.modules.industryservice`。 **模板文件**:`baqing-admin/src/main/resources/templates/yak_herd_inventory_import_template.xlsx`(导入、导出、下载模板共用)。 --- ## 2. 数据库设计 ### 2.1 表 `biz_yak_herd_inventory`(牦牛存栏数据填报) | 字段 | 类型 | 非空 | 说明 | | --- | --- | --- | --- | | `id` | `bigint(20)` | Y | 主键 | | `stat_year` | `int(11)` | Y | 所属年份 | | `stat_month` | `int(11)` | Y | 所属月份(1~12) | | `town_dept_id` | `bigint(20)` | Y | 所属乡镇,`sys_dept.dept_id`(由村反推) | | `town_name` | `varchar(64)` | Y | 乡镇名称冗余 | | `village_dept_id` | `bigint(20)` | Y | 所属村,`sys_dept.dept_id` | | `village_name` | `varchar(128)` | Y | 村名称冗余 | | `village_count` | `int(11)` | N | 村居数 | | `herdsman_household_count` | `int(11)` | N | 牧户数 | | `breeding_bull_count` | `int(11)` | N | 种公牛 | | `bull_castrated_count` | `int(11)` | N | 公牛/阉畜 | | `bull_total` | `int(11)` | N | 公牛合计(派生) | | `cow_young_count` | `int(11)` | N | 0~1.5 岁幼畜(不含 1.5 岁) | | `cow_adult_count` | `int(11)` | N | 1.5 岁以上成畜(含 1.5 岁) | | `cow_total` | `int(11)` | N | 母牛合计(派生) | | `milking_cow_count` | `int(11)` | N | 产奶母牛 | | `fertile_cow_count` | `int(11)` | N | 能繁母牛 | | `dairy_total` | `int(11)` | N | 奶牛合计(派生) | | `sheep_total` | `int(11)` | N | 羊总数 | | `milking_sheep_count` | `int(11)` | N | 产奶羊 | | `insured_yak_count` | `int(11)` | N | 保险牦牛 | | `yak_total` | `int(11)` | N | 牦牛总存栏(派生) | | `grand_total` | `int(11)` | N | 总合计(派生) | | `yak_sheep_total` | `int(11)` | N | 总量牦牛+羊(派生) | | `create_by` / `create_time` | — | — | 审计 | | `update_by` / `update_time` | — | — | 审计 | | `remark` | `varchar(500)` | N | 备注 | **说明**:本表**不使用逻辑删除**;删除为物理删除。旧四档母牛年龄字段(`cow_under_1_count` 等)已废弃,以 `cow_young_count` / `cow_adult_count` 为准。 **索引与约束** - `UNIQUE uk_year_month_village (stat_year, stat_month, village_dept_id)` - `KEY idx_stat_year_month (stat_year, stat_month)` - `KEY idx_town_dept_id (town_dept_id)` - `KEY idx_village_dept_id (village_dept_id)` **DDL 文件**:`sql/biz_yak_herd_inventory.sql`(全量覆盖建表)。已有库若仍为乡镇唯一键,执行 `sql/biz_yak_herd_inventory_village_uk.sql`(去掉 `uk_year_month_town`,改为村唯一)。 ### 2.2 派生计算(`YakHerdInventoryValidation.recalculateDerived`) ```text bull_total = breeding_bull_count + bull_castrated_count cow_total = cow_young_count + cow_adult_count dairy_total = milking_cow_count + fertile_cow_count yak_total = bull_total + cow_total + insured_yak_count grand_total = yak_total + dairy_total + sheep_total yak_sheep_total = bull_total + cow_total + insured_yak_count + sheep_total ``` 保存与导入落库前一律重算覆盖。 ### 2.3 乡镇 / 村字典 - 乡镇:`TownDeptSupport.listReportTowns()`(`parent_id=100` 且 `order_num>12`)。 - 村树:`TownDeptSupport.buildVillageTree()` → 县→乡镇→村;父节点不 `disabled`(便于展开),前端 `disable-branch-nodes` 仅允许选村。 - 解析:`resolveTownByName`、`resolveVillageByTownAndName`、`requireReportVillage`。 ### 2.4 可选年份 / 月份 - `YakHerdInventoryYearSupport`:当前年起向前 5 年,共 6 个。 - `YakHerdInventoryMonthSupport.resolveMonthOptions()`:1~12;`validateStatPeriod` 拒绝未来月。 --- ## 3. 接口设计 **Base Path**:`/dataModel/yakHerdInventory` **权限前缀**:`dataModel:yakHerdInventory` | # | 说明 | Method | URI | 权限 | | --- | --- | --- | --- | --- | | 3.1 | 分页列表 | GET | `/list` | `list` | | 3.2 | 详情 | GET | `/{id}` | `query` | | 3.3 | 新增 | POST | `/` | `add` | | 3.4 | 修改 | PUT | `/` | `edit` | | 3.5 | 删除 | DELETE | `/{ids}` | `remove` | | 3.6 | 乡镇下拉 | GET | `/townOptions` | `list` | | 3.6.1 | 村树 | GET | `/villageTree` | `list` | | 3.7 | 年份下拉 | GET | `/yearOptions` | `list` | | 3.7.1 | 月份下拉 | GET | `/monthOptions` | `list` | | 3.8 | 导入 | POST | `/importData` | `import` | | 3.9 | 下载导入模板 | GET | `/importTemplate` | `import` | | 3.10 | 按年月导出(可选乡镇) | GET | `/export` | `export` | #### 3.1.1 列表 Query | 参数 | 类型 | 说明 | | --- | --- | --- | | `pageNum` / `pageSize` | int | 默认 1 / 20 | | `statYear` / `statMonth` | int | 精确 | | `townDeptId` | long | 乡镇 | | `villageDeptId` | long | 村 | **列表主要字段**:`statYear`、`statMonth`、`townName`、`villageName`、`villageCount`、`herdsmanHouseholdCount`、`yakTotal`、`bullTotal`、`cowTotal`。 #### 3.3.1 新增/修改 Body | 字段 | 必填 | 校验 | | --- | --- | --- | | `statYear` / `statMonth` | Y | 合法且非未来月 | | `villageDeptId` | Y | 合法村;反推乡镇 | | 明细 `*Count` | N | `>= 0`;**产奶羊与羊总均非空时**:`milkingSheepCount < sheepTotal` | | `id` | 修改必填 | — | **唯一性**:`countByYearMonthAndVillageExcludeId(...) > 0` →「该年该月该村已填报」。 #### 3.8 导入 **请求**:`multipart/form-data`:`file` + `statYear` + `statMonth`。 **策略**(`BizYakHerdInventoryServiceImpl.importData`): 1. 解析全部有效数据行;任一行乡镇/村无法匹配或校验失败 → 整体失败,不写库。 2. 全部成功 → 按 `(statYear, statMonth, villageDeptId)` 查重,存在 `update`、不存在 `insert`。 3. Excel 合计列与重算不一致 → 记入 `warnMessages`(非阻断)。 **响应**:`insertCount`、`failCount`、`failMessages`、`warnMessages`。 #### 3.8.1 Excel 列映射(0-based,`YakHerdInventoryExcelParser`) | 列号 | Excel | 用途 | | --- | --- | --- | | 1 | B 乡镇 | 匹配乡镇 | | 2 | C 村 | 匹配村 | | 3 | D 村居 | `villageCount` | | 4 | E 牧户 | `herdsmanHouseholdCount` | | 5 | F 总合计 | Excel 对照 | | 6~8 | 牦牛公母分类 | Excel 对照 | | 9 | J 保险 | `insuredYakCount` | | 10 | K 公牛合计 | Excel 对照 | | 11 | L 种公牛 | `breedingBullCount` | | 12 | M 阉畜 | `bullCastratedCount` | | 13 | N 母牛合计 | Excel 对照 | | 14 | O 幼畜 | `cowYoungCount` | | 15 | P 成畜 | `cowAdultCount` | | 16 | Q 奶牛合计 | Excel 对照 | | 17 | R 产奶母牛 | `milkingCowCount` | | 18 | S 能繁母牛 | `fertileCowCount` | | 19 | T 羊 | `sheepTotal` | | 20 | U 产奶羊 | `milkingSheepCount` | | 21 | V 总量 | Excel 对照 | 数据起始行:`headerRow + 3`(三行表头)。 #### 3.10 按年月导出 **请求**:`GET /export?statYear=&statMonth=`。 **实现**:查询该年月全部村记录 → `YakHerdInventoryExcelExporter.export`: 1. 替换标题年、月;填表人;填表时间=**导出当日年月** 2. 按记录写数据行(含派生合计) 3. 末行「合计」:合并列 0~2;自列 3(村居)起对各数值列求和写入 4. 删除合计行之后的空行 --- ## 4. 前端设计(要点) | 项 | 说明 | | --- | --- | | **路径** | `ruoyi-ui/src/views/dataModel/yakHerdInventory/index.vue` | | **API** | `ruoyi-ui/src/api/dataModel/yakHerdInventory.js` | | **列表** | 年/月/乡镇/村筛选;列含乡镇、村、存栏等 | | **表单** | 年/月 + Treeselect 选村;仅明细录入字段;无派生合计输入 | | **详情** | 含派生合计只读 | | **导入/导出** | 先选年月 | --- ## 5. 菜单与权限(示例) 同权限标识:`list|query|add|edit|remove|import|export`。菜单 SQL 见 `sql/biz_sys_menu_yak_herd_inventory.sql`。 --- ## 6. 与养殖产业数据看板 / 大屏的关系 | 场景 | 口径 | | --- | --- | | 全县存栏合计 | `biz_yak_herd_inventory` **最近填报年月**全县 `yak_total` / `bull_total` / `cow_total` 等合计(按村汇总) | | 年龄结构 | 两档:`cow_young_count`、`cow_adult_count`;`bandCode`=`COW_YOUNG` / `COW_ADULT` | | 地图乡镇存栏 | 按乡镇聚合该最近年月村记录 | --- ## 7. 交付清单 - [x] `sql/biz_yak_herd_inventory.sql`、`sql/biz_yak_herd_inventory_village_uk.sql` - [x] Domain / Mapper / Service / Controller - [x] Parser / Exporter / Validation / TownDeptSupport 村树 - [x] 前端列表/表单(Treeselect)/导入/导出 - [x] 单元测试与 Excel 样例 --- ## 8. 修订记录 | 版本 | 说明 | | --- | --- | | 1.0~1.5 | 乡镇粒度、按年月、四档母牛年龄等历史版本 | | 1.6 | 村粒度 `uk_year_month_village`;新模板字段与派生合计;导入全量覆盖;导出合计按列求和;`/villageTree`;大屏年龄两档 |