# 牦牛资产档案管理 — 技术方案

> 依据：同目录 `牦牛资产档案管理功能需求.md`。档案 **列表/详情只读** + **第三方同步写入**；本期无手工增删改接口。

---

## 1. 技术架构

| 项 | 说明 |
| --- | --- |
| **后端** | RuoYi **v3.9.2**（**springboot2** 分支）：JDK 8、Spring Boot 2.x、Spring MVC、MyBatis、Druid |
| **数据库** | MySQL **5.7.39**，InnoDB，`utf8mb4` |
| **前端** | 若依 Vue2（本方案不展开） |
| **第三方** | HTTP/RPC 客户端（`RestTemplate` / `OkHttp` 等，实现期选型）；配置项：地址、鉴权、超时 |
| **关联主数据** | `biz_pasture`（牧场，可选 `pasture_id` 映射） |

**分层**：`Controller` → `YakAssetSyncService`（拉取、合并入库）→ `IYakAssetService`（查询）→ `Mapper`/XML → `domain`。

**代码位置**：`baqing-admin` → `com.ruoyi.web.modules.industryservice`（`BizYakAsset`、子表 Mapper、`ThirdPartyYakClientImpl`）。

**业务摘要**

| 场景 | 行为 |
| --- | --- |
| 同步 | 按 `yak_no` 合并主表；子表按「先删后插」或「按批次版本」覆盖（**默认：每头牦牛同步时删除其旧子表再批量插入**） |
| 列表/详情 | `del_flag='0'`；排序 `status_change_date DESC, id DESC`（或与 `last_sync_time` 统一） |
| 月龄 | 列表/详情可展示库字段；同步时可写入，亦可用 `birth_date` 现场计算 |
| 删除 | 第三方下架策略**默认不物理删除**：仅更新状态或 `del_flag`（若第三方提供「离场」状态则映射为已出售/死亡等）；全量对账删除须评审 |

---

## 2. 数据库设计

### 2.1 主表 `biz_yak_asset`（牦牛资产档案）

| 字段 | 类型 | 非空 | 说明 |
| --- | --- | --- | --- |
| `id` | `bigint(20)` | Y | 主键 |
| `external_id` | `bigint(20)` | N | 第三方牛只 ID（`OpenYakEntryDto.cattleId`），**唯一** |
| `yak_no` | `varchar(64)` | Y | 牦牛编号（优先 `earTagNumber`），**唯一** |
| `pasture_id` | `bigint(20)` | N | 关联 `biz_pasture.id`（映射成功时） |
| `pasture_name` | `varchar(128)` | N | 所属牧场名称（同步冗余展示） |
| `batch_no` | `varchar(64)` | N | 批次编号 |
| `gender` | `varchar(16)` | N | 性别 |
| `birth_date` | `date` | N | 出生日期 |
| `age_months` | `int(11)` | N | 月龄（月） |
| `entry_date` | `date` | N | 入栏日期 |
| `entry_weight_kg` | `decimal(10,2)` | N | 入栏体重 kg |
| `source` | `varchar(64)` | N | 来源 |
| `breeding_method` | `varchar(64)` | N | 养殖方式 |
| `asset_status` | `tinyint(4)` | Y | 资产状态 **§2.6** |
| `status_change_date` | `date` | N | 变更日期 |
| `status_change_reason` | `varchar(256)` | N | 状态变更原因 |
| `pen_location` | `varchar(128)` | N | 圈舍位置 |
| `expected_out_date` | `date` | N | 预计出栏日期 |
| `supplement_plan` | `varchar(512)` | N | 补饲方案 |
| `realtime_temp` | `decimal(6,2)` | N | 实时体温 ℃ |
| `realtime_steps` | `int(11)` | N | 实时运动量 |
| `env_temp` | `decimal(6,2)` | N | 环境温度 ℃（详情当前值） |
| `location` | `varchar(256)` | N | 位置 |
| `physio_collect_time` | `datetime` | N | 生理快照采集时间 |
| `father_yak_no` | `varchar(64)` | N | 父亲编号 |
| `mother_yak_no` | `varchar(64)` | N | 母亲编号 |
| `last_sync_time` | `datetime` | N | 最近同步时间 |
| `del_flag` | `char(1)` | Y | `0` 存在 `2` 删除 |
| `create_time` / `update_time` | datetime | — | 审计 |

**索引**：`UNIQUE uk_external_id (external_id)`；`UNIQUE uk_yak_no (yak_no)`；`KEY idx_asset_status`；`KEY idx_pasture_id`；`KEY idx_status_change_date`；`KEY idx_last_sync_time`；`KEY idx_del_flag`。

#### 2.1.1 第三方入栏建档字段映射（`OpenYakEntryDto` → `biz_yak_asset`）

| OpenAPI 字段 | 库字段 | 说明 |
| --- | --- | --- |
| `cattleId` | `external_id` | 同步 upsert 主键 |
| `earTagNumber` / `cattleNo` | `yak_no` | 优先耳标 |
| `farmId` | `pasture_id` | 经 `biz_pasture.external_id` 解析 |
| `farmName` | `pasture_name` | 冗余展示 |
| `entryCycle` / `batchId` | `batch_no` | 优先 `entryCycle` |
| `farmEnclosureName` | `pen_location` | 并写入 `biz_yak_pen_rel` |
| `farmLocation` | `location` | |
| `cattleSex` | `gender` | 归一公/母 |
| `cattleSource` | `source` | |
| `fatherNumber` | `father_yak_no` | |
| `motherNumber` | `mother_yak_no` | |
| `weight` | `entry_weight_kg` | 当前体重 |
| `entryTime` | `entry_date` | |
| `birthDate` | `birth_date` | 月龄由同步时计算写入 `age_months` |
| `remark` | `status_change_reason` | |
| `id` / `cattlePicture` / `registrant` / `cattleVariety` | — | 本期不落库 |

列表接口未返回的字段（`asset_status`、实时体温/运动量等）保持库内原值或默认「正常」。

### 2.2 子表 `biz_yak_physio_series`（生理时序，曲线图）

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `id` | `bigint(20)` | 主键 |
| `yak_asset_id` | `bigint(20)` | 关联主表 |
| `collect_time` | `datetime` | 采集时间 |
| `body_temp` | `decimal(6,2)` | 体温 ℃ |
| `steps` | `int(11)` | 运动量 |
| `env_temp` | `decimal(6,2)` | 环境温度 ℃ |

**索引**：`KEY idx_yak_collect (yak_asset_id, collect_time)`。

### 2.3 子表 `biz_yak_growth_record`（生长性状）

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `id` | `bigint(20)` | 主键 |
| `yak_asset_id` | `bigint(20)` | 关联主表 |
| `day_age` | `int(11)` | 日龄（天） |
| `weight_kg` | `decimal(10,2)` | 体重 |
| `height_cm` | `decimal(10,2)` | 体高 |
| `chest_cm` | `decimal(10,2)` | 胸围 |
| `length_cm` | `decimal(10,2)` | 体长 |
| `collect_time` | `datetime` | 采集时间 |
| `data_kind` | `tinyint(4)` | `1` 实测 `2` 预测 |

**索引**：`KEY idx_yak_growth (yak_asset_id, collect_time)`。

### 2.4 子表 `biz_yak_reproduction_record`（繁殖性能）

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `id` | `bigint(20)` | 主键 |
| `yak_asset_id` | `bigint(20)` | 关联主表 |
| `day_age` | `int(11)` | 日龄 |
| `parity` | `int(11)` | 胎次 |
| `calving_interval_days` | `int(11)` | 产犊间隔（天） |
| `calf_count` | `int(11)` | 产犊数量（头） |
| `calf_survival_rate` | `decimal(6,4)` | 犊牛成活率 0～1 |
| `calf_birth_weight_kg` | `decimal(10,2)` | 犊牛出生体重 |
| `dystocia_flag` | `char(1)` | `0` 否 `1` 是 |

**索引**：`KEY idx_yak_repro (yak_asset_id, parity)`。

### 2.5 子表 `biz_yak_feeding_record`（饲喂数据）

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `id` | `bigint(20)` | 主键 |
| `yak_asset_id` | `bigint(20)` | 关联主表 |
| `start_day_age` | `int(11)` | 开始日龄 |
| `end_day_age` | `int(11)` | 结束日龄 |
| `daily_supplement_kg` | `decimal(10,2)` | 日补饲量 kg |
| `feed_type` | `varchar(64)` | 饲料类型 |

**索引**：`KEY idx_yak_feeding (yak_asset_id)`。

### 2.6 子表 `biz_yak_pen_rel` / `biz_yak_batch_rel`

圈舍：`pen_name`；批次：`batch_no`；均关联 `yak_asset_id`。详情接口冗余返回 `yakNo`。

### 2.7 枚举 `asset_status`

| 值 | 含义 |
| --- | --- |
| `1` | 正常 |
| `2` | 死淘 |
| `3` | 丢失 |
| `4` | 出栏 |

第三方旧码（如在养/死亡/已出售等）由 `YakAssetStatusMapper` 归一；存量库执行 `sql/biz_yak_asset_migrate_v2.sql`。

`data_kind`（生长）：`1` 实测，`2` 预测。

### 2.8 DDL（MySQL 5.7，节选）

```sql
CREATE TABLE `biz_yak_asset` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `yak_no` varchar(64) NOT NULL COMMENT '牦牛编号',
  `pasture_id` bigint(20) DEFAULT NULL,
  `pasture_name` varchar(128) DEFAULT NULL,
  `batch_no` varchar(64) DEFAULT NULL,
  `gender` varchar(16) DEFAULT NULL,
  `birth_date` date DEFAULT NULL,
  `age_months` int(11) DEFAULT NULL,
  `entry_date` date DEFAULT NULL,
  `entry_weight_kg` decimal(10,2) DEFAULT NULL,
  `source` varchar(64) DEFAULT NULL,
  `breeding_method` varchar(64) DEFAULT NULL,
  `asset_status` tinyint(4) NOT NULL DEFAULT '1',
  `status_change_date` date DEFAULT NULL,
  `realtime_temp` decimal(6,2) DEFAULT NULL,
  `realtime_steps` int(11) DEFAULT NULL,
  `env_temp` decimal(6,2) DEFAULT NULL,
  `location` varchar(256) DEFAULT NULL,
  `physio_collect_time` datetime DEFAULT NULL,
  `father_yak_no` varchar(64) DEFAULT NULL,
  `mother_yak_no` varchar(64) DEFAULT NULL,
  `last_sync_time` datetime DEFAULT NULL,
  `del_flag` char(1) NOT NULL DEFAULT '0',
  `create_time` datetime DEFAULT NULL,
  `update_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_yak_no` (`yak_no`),
  KEY `idx_asset_status` (`asset_status`),
  KEY `idx_status_change_date` (`status_change_date`),
  KEY `idx_del_flag` (`del_flag`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='牦牛资产档案';

-- 子表及迁移见 sql/biz_yak_asset.sql、sql/biz_yak_asset_migrate_v2.sql
```

---

## 3. 接口设计

**统一响应**：`AjaxResult` / `TableDataInfo`。

**权限**：`dataModel:yakAsset:list|query|sync`

**Base Path**：`/dataModel/yakAsset`

| # | 说明 | Method | URI | 权限 | 要点 |
| --- | --- | --- | --- | --- | --- |
| 3.1 | 分页列表 | GET | `/dataModel/yakAsset/list` | `list` | Query **3.1.1** |
| 3.2 | 档案详情 | GET | `/dataModel/yakAsset/{id}` | `query` | 主表 + 六类子表列表 |
| 3.3 | 按编号查详情 | GET | `/dataModel/yakAsset/byNo/{yakNo}` | `query` | 系谱跳转父/母 |
| 3.4 | 生理曲线 | GET | `/dataModel/yakAsset/{id}/physioChart` | `query` | Query `days` 默认 30 |
| 3.5 | 生长曲线 | GET | `/dataModel/yakAsset/{id}/growthChart` | `query` | 实测+预测分 `dataKind` |
| 3.6 | 第三方同步 | POST | `/dataModel/yakAsset/sync` | `sync` | 返回 **3.6.1** 摘要 |

本期**不提供** `POST/PUT/DELETE` 档案维护接口。

#### 3.1.1 列表 Query

| 参数 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `pageNum` / `pageSize` | int | N | 默认 `1` / `20` |
| `keyword` | string | N | `yak_no` **模糊** |
| `assetStatus` | int | N | `1`～`4` **精确** |

#### 3.2.1 详情响应结构（`data`）

| 节点 | 内容 |
| --- | --- |
| `asset` | 主表字段（驼峰，含 `yakNo`、`pastureName`、`assetStatus` 等） |
| `physioSeries` | 生理时序数组（近一月可由独立接口或详情内截断） |
| `growthRecords` | 生长列表（含 `dataKind`） |
| `reproductionRecords` | 繁殖列表 |
| `feedingRecords` | 饲喂列表 |
| `penRecords` | 圈舍列表 |
| `batchRecords` | 批次列表 |

#### 3.4.1 生理曲线 Query

| 参数 | 说明 |
| --- | --- |
| `days` | 默认 `30`；按 `collect_time` 倒序取最近 N 天 |

返回序列：`[{ collectTime, bodyTemp, steps, envTemp }, ...]`，供 ECharts 多折线。

#### 3.5.1 生长曲线 Query

| 参数 | 说明 |
| --- | --- |
| `metric` | 可选 `weight`/`height`/`chest`/`length`，默认 `weight` |

返回：`{ actual: [...], forecast: [...] }`，按 `data_kind` 分组。

#### 3.6.1 同步响应（`data`）

| 字段 | 说明 |
| --- | --- |
| `insertCount` | 新增头数 |
| `updateCount` | 更新头数 |
| `failCount` | 失败头数 |
| `failMessages` | 失败摘要列表（可选） |
| `syncTime` | 本次同步完成时间 |

同步过程建议 `@Log` + `biz_yak_sync_log`（可选表：操作人、起止时间、结果 JSON）。

### 3.7 第三方对接（实现要点）

| 项 | 说明 |
| --- | --- |
| 配置 | `application.yml`：`third-party.farming`（与牧场管理同源） |
| 接口 | `GET /open-api/v1/farming/entry-filings`；分页 `OpenApiPageResult<OpenYakEntryDto>` |
| 客户端 | `ThirdPartyYakClientImpl` → `YakEntryOpenApiMapper.toBundles()` → `YakAssetBundleSyncTxService` |
| 映射 | 字段对照 **§2.1.1**；`YakAssetStatusMapper` 供后续含状态字段的接口 |
| 牧场 | `farmId` → `biz_pasture.external_id`；失败则仅写 `farmName` |
| 事务 | 每头牦牛独立事务（`REQUIRES_NEW`）；子表仅当 bundle 携带对应列表时覆盖 |
| Stub | `thirdparty/stub-yak-entries.json`（OpenAPI 记录样例） |
| 并发 | `synchronized` 防重复点击 |

### 3.8 查询约定

- 列表、详情：`del_flag = '0'`。  
- 列表排序：`status_change_date DESC, id DESC`。  
- 子表列表：`collect_time` 或业务字段倒序。

---

## 4. 菜单与权限（示例）

| 类型 | 名称 | 权限标识 |
| --- | --- | --- |
| 菜单 | 牦牛资产档案 | `dataModel:yakAsset:list` |
| 按钮 | 查询 | `dataModel:yakAsset:query` |
| 按钮 | 同步 | `dataModel:yakAsset:sync` |

组件路径：`livestockFinance/yakAsset/index`（挂载「牧业金融 · 生物资产」目录下）。

---

## 5. 交付清单

- [ ] `sql/biz_yak_asset.sql`（主表 + 4 子表 + 可选同步日志表）  
- [ ] `ThirdPartyYakClient`、`YakAssetSyncService`  
- [ ] `BizYakAsset` Domain / Mapper / `IYakAssetService` / `BizYakAssetController`  
- [ ] 单元测试（同步合并、状态映射、曲线查询）  
- [ ] MockMvc：list、detail、sync（Mock 第三方）  

---

## 6. 修订记录

| 版本 | 说明 |
| --- | --- |
| 1.0 | 初稿：主表+四子表；只读查询+同步接口；生长实测/预测分 `data_kind`；对接可配置 |
| 1.1 | 对齐入栏建档 OpenAPI：`OpenYakEntryDto`、`external_id`、分页拉取、字段映射 **§2.1.1** |
