# 隔离巡查登记 — 技术方案

> 依据：同目录 `隔离巡查登记功能需求.md`。单表台账 **CRUD** + **逻辑删除**；保存时校验 **P1～P3**；圈位/耳标选项来自 **入场隔离登记** 在栏数据；巡查照片走若依 `/common/upload`。

---

## 1. 技术架构

| 项 | 说明 |
| --- | --- |
| **后端** | RuoYi **v3.9.2**（**springboot2** 分支）：JDK 8、Spring Boot 2.x、Spring MVC、MyBatis、Druid |
| **数据库** | MySQL **5.7.39**，InnoDB，`utf8mb4` |
| **前端** | 若依 Vue2；字段 **小驼峰**（页面见前端技术方案，本文不展开） |
| **文件** | 复用 `/common/upload`；存 `photo_url`、`photo_path`（`/profile/...`） |

**分层**：`Controller` → `Service`（`PatrolValidation`、`EarTagCodec` 复用入场模块）→ `Mapper`/XML → `domain`。

**代码包（建议）**：`com.ruoyi.web.modules.trading`（与入场隔离、隔离圈位同包）

**初始化脚本**：`sql/biz_isolation_patrol.sql`

**业务摘要**

| 场景 | 行为 |
| --- | --- |
| 新增/修改 | 校验圈位在栏；解析耳标；校验 **P1～P3**；校验巡查时间不晚于当前；写入单表 |
| 列表/详情 | `del_flag=0`；`patrol_time DESC`；圈位编号模糊、巡查日期区间筛选 |
| 删除 | `del_flag=2` |
| 表单下拉 | `penOptions`、`penEarTags` 按在栏规则查询（见 **§2.4**） |

**在栏判定（本期）**：入场登记 `del_flag=0` 且 `expected_release_date >= CURDATE()`；分圈明细关联圈位。无独立出圈表时以此代理「未解除隔离」；后续若有 `release_status` 字段，仅改查询条件。

---

## 2. 数据库设计

### 2.1 表 `biz_isolation_patrol`（隔离巡查记录）

| 字段 | 类型 | 非空 | 默认值 | 说明 |
| --- | --- | --- | --- | --- |
| `id` | `bigint(20)` | Y | 自增 | 主键 |
| `patrol_time` | `datetime` | Y | — | 巡查时间 |
| `pen_id` | `bigint(20)` | Y | — | 关联 `biz_isolation_pen.id` |
| `pen_code` | `char(2)` | Y | — | 圈位编号冗余，便于列表模糊筛选 |
| `sick_dead_qty` | `int(11)` | Y | `0` | 病死病害数量（头），≥0 |
| `content_desc` | `varchar(200)` | Y | — | 巡查内容描述 |
| `handle_desc` | `varchar(200)` | Y | — | 处理情况 |
| `ear_tags` | `text` | N | NULL | 耳标逗号分隔；`sick_dead_qty=0` 时为 NULL |
| `photo_url` | `varchar(512)` | N | NULL | 巡查照片访问 URL |
| `photo_path` | `varchar(512)` | N | NULL | 巡查照片存储路径 |
| `del_flag` | `char(1)` | Y | `'0'` | `0` 存在 `2` 删除 |
| `create_by` / `create_time` / `update_by` / `update_time` | 若依惯例 | — | — | 审计 |
| `remark` | `varchar(500)` | N | NULL | 备注 |

**索引**：`PRIMARY KEY (id)`；`KEY idx_patrol_time (patrol_time)`；`KEY idx_pen_id (pen_id)`；`KEY idx_pen_code (pen_code)`；`KEY idx_del_flag (del_flag)`。

### 2.2 DDL（MySQL 5.7）

```sql
CREATE TABLE `biz_isolation_patrol` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `patrol_time` datetime NOT NULL COMMENT '巡查时间',
  `pen_id` bigint(20) NOT NULL COMMENT '圈位ID',
  `pen_code` char(2) NOT NULL COMMENT '圈位编号',
  `sick_dead_qty` int(11) NOT NULL DEFAULT '0' COMMENT '病死病害数量头',
  `content_desc` varchar(200) NOT NULL COMMENT '巡查内容描述',
  `handle_desc` varchar(200) NOT NULL COMMENT '处理情况',
  `ear_tags` text COMMENT '耳标逗号分隔',
  `photo_url` varchar(512) DEFAULT NULL COMMENT '巡查照片URL',
  `photo_path` varchar(512) DEFAULT NULL COMMENT '巡查照片路径',
  `del_flag` char(1) NOT NULL DEFAULT '0' COMMENT '0存在 2删除',
  `create_by` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_by` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`id`),
  KEY `idx_patrol_time` (`patrol_time`),
  KEY `idx_pen_id` (`pen_id`),
  KEY `idx_pen_code` (`pen_code`),
  KEY `idx_del_flag` (`del_flag`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='隔离巡查登记';
```

### 2.3 关联与枚举

| 关联 | 说明 |
| --- | --- |
| `pen_id` | `biz_isolation_pen`，`del_flag=0` 且须在在栏集合内（**§2.4**） |
| 入场分圈 | `biz_entry_isolation` + `biz_entry_isolation_alloc` 提供在栏耳标 |

**删除标记 `del_flag`**

| 值 | 含义 |
| --- | --- |
| 0 | 正常 |
| 2 | 已逻辑删除 |

### 2.4 在栏圈位与耳标（查询约定）

**在栏圈位**（`penOptions`）：

```sql
SELECT DISTINCT p.id AS pen_id, p.pen_code
FROM biz_isolation_pen p
INNER JOIN biz_entry_isolation_alloc a ON a.pen_id = p.id
INNER JOIN biz_entry_isolation e ON e.id = a.entry_id
WHERE p.del_flag = '0'
  AND e.del_flag = '0'
  AND e.expected_release_date >= CURDATE()
ORDER BY p.pen_code ASC;
```

**指定圈位在栏耳标**（`penEarTags`，`penId` 必填）：

- 取满足上式且 `a.pen_id = #{penId}` 的所有 `a.ear_tags`；
- `EarTagCodec.parse` 合并去重后返回 `string[]`（单段 1～32 字）。

无在栏圈位时 `penOptions` 返回空数组，前端提示「暂无在栏圈位…」。

### 2.5 耳标入库（复用 `EarTagCodec`）

- `sick_dead_qty = 0`：`ear_tags` 置 `NULL`，`earTagList` 忽略。  
- `sick_dead_qty > 0`：列表 → 英文逗号写 `ear_tags`；去空白、去重。  
- 出库：详情/列表返回 `earTags`（原文）+ `earTagList`（数组）。

### 2.6 一致性校验（`PatrolValidation`）

| 规则 | 实现要点 |
| --- | --- |
| P1 | `sick_dead_qty == 0` → `earTagList` 为空 |
| P2 | `sick_dead_qty > 0` → `earTagList.size() == sick_dead_qty` |
| P3 | 每个耳标 ∈ 当前 `penId` 在栏集合；登记内不重复 |
| 其他 | `pen_id` 在栏；`patrol_time` ≤ `now`；`content_desc`/`handle_desc` 1～200 字；`sick_dead_qty` 非负整数；照片后缀 `jpg,jpeg,png`（上传阶段） |

---

## 3. 接口设计

**统一响应**：`AjaxResult` / `TableDataInfo`（`code`、`msg`、`data` / `rows`、`total`）。

**权限标识**：`tradeMarket:isolationPatrol:list|query|add|edit|remove`

**Base Path**：`/tradeMarket/isolationPatrol`

| # | 说明 | Method | URI | 权限 |
| --- | --- | --- | --- | --- |
| 3.1 | 分页列表 | GET | `/tradeMarket/isolationPatrol/list` | `list` |
| 3.2 | 详情 | GET | `/tradeMarket/isolationPatrol/{id}` | `query` |
| 3.3 | 新增 | POST | `/tradeMarket/isolationPatrol` | `add` |
| 3.4 | 修改 | PUT | `/tradeMarket/isolationPatrol` | `edit` |
| 3.5 | 删除 | DELETE | `/tradeMarket/isolationPatrol/{ids}` | `remove` |
| 3.6 | 在栏圈位下拉 | GET | `/tradeMarket/isolationPatrol/penOptions` | `list` 或 `query` |
| 3.7 | 圈位在栏耳标 | GET | `/tradeMarket/isolationPatrol/penEarTags` | `list` 或 `query` |

### 3.1 列表

**Query（驼峰）**

| 参数 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `pageNum` / `pageSize` | int | N | 默认 `1` / `20` |
| `patrolDateStart` | string | N | `yyyy-MM-dd`，`DATE(patrol_time) >=` |
| `patrolDateEnd` | string | N | `yyyy-MM-dd`，`DATE(patrol_time) <=` |
| `penCode` | string | N | 圈位编号**模糊** `LIKE %penCode%` |

**条件**：`del_flag='0'`；起止日期均填时要求 `patrolDateStart <= patrolDateEnd`，否则 400。

**排序**：`patrol_time DESC, id DESC`。

**行字段（驼峰）**：`id`、`patrolTime`、`penId`、`penCode`、`sickDeadQty`、`contentDesc`、`handleDesc`、`earTags`、`earTagList`（可选，列表可仅 `earTags` 拼接展示）、`photoUrl` + 审计字段。

### 3.2 详情 `data`

全字段 + `earTagList`；`photoUrl` 可访问。

### 3.3 新增 / 修改 Body（驼峰）

| 字段 | 新增 | 修改 | 说明 |
| --- | --- | --- | --- |
| `id` | — | Y | 主键 |
| `patrolTime` | Y | Y | `yyyy-MM-dd HH:mm:ss`，≤ 当前时间 |
| `penId` | Y | Y | 在栏圈位 ID；服务端写 `pen_code` |
| `sickDeadQty` | Y | Y | 非负整数，默认 0 |
| `contentDesc` | Y | Y | 1～200 字 |
| `handleDesc` | Y | Y | 1～200 字 |
| `earTagList` | 条件 | 条件 | `sickDeadQty>0` 必填；或与 `earTags` 二选一 |
| `earTags` | N | N | 逗号分隔（兼容） |
| `photoUrl` / `photoPath` | N | N | 巡查照片；传空串可清空 |
| `remark` | N | N | ≤500 字 |

**服务端流程**

1. 校验 `penId` 在栏，读取 `pen_code` 写入。  
2. `EarTagCodec` + **P1～P3**。  
3. 插入/更新 `biz_isolation_patrol`。

### 3.4 删除

- `DELETE /tradeMarket/isolationPatrol/{ids}`，单条传一个 id。  
- 记录须 `del_flag=0`，否则「记录不存在或已删除」。  
- 更新 `del_flag='2'`（**本期不校验**下游引用）。

### 3.5 在栏圈位 `penOptions`

**响应 `data`**：`[{ penId, penCode }]`，按 `penCode` 升序。

### 3.6 圈位在栏耳标 `penEarTags`

**Query**：`penId`（long，必填）。

**响应 `data`**：`string[]`；`penId` 非在栏或无效时 `[]` 或 400「圈位无效或已无在栏牲畜」。

### 3.7 校验与错误（摘要）

| 项 | 规则 |
| --- | --- |
| 圈位 | `pen_id` 在 **§2.4** 集合内 |
| 耳标 | **P1～P3**；失败文案对齐功能需求 **§8** |
| 巡查时间 | 非空且 `patrol_time <= now()` |
| 照片 | 后缀 `jpg,jpeg,png`；路径 `/profile` 前缀；≤10MB（上传接口校验） |
| 失败示例 | 「病死病害数量与已勾选耳标号数量不一致」「耳标 xxx 不在当前圈位或已出圈」 |

### 3.8 本期不提供

- 巡查记录导入/导出、审批流接口。  
- 与出圈、无害化处理自动联动接口。

---

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

| 类型 | 名称 | 权限标识 |
| --- | --- | --- |
| 菜单 | 隔离巡查登记 | `tradeMarket:isolationPatrol:list` |
| 按钮 | 查询 | `tradeMarket:isolationPatrol:query` |
| 按钮 | 新增 | `tradeMarket:isolationPatrol:add` |
| 按钮 | 修改 | `tradeMarket:isolationPatrol:edit` |
| 按钮 | 删除 | `tradeMarket:isolationPatrol:remove` |

组件路径：`tradeMarket/isolationPatrol/index`（「交易市场平台」目录下）。

---

## 5. 交付清单

- [ ] `sql/biz_isolation_patrol.sql`、菜单权限 SQL  
- [ ] `BizIsolationPatrol` Domain / Mapper / Service / Controller  
- [ ] `PatrolValidation`；复用 `EarTagCodec`  
- [ ] `penOptions`、`penEarTags` 查询 Mapper  
- [ ] 单元测试：P1～P3、在栏圈位/耳标、逻辑删除、巡查时间校验  

---

## 6. 修订记录

| 版本 | 说明 |
| --- | --- |
| 1.0 | 初版：单表 `biz_isolation_patrol`、CRUD + 在栏下拉接口、P1～P3；对齐功能需求与 RuoYi/MySQL 5.7 |
