瀏覽代碼

数字畜牧一张图

wwh 1 月之前
父節點
當前提交
37183ab1b7

+ 157 - 0
baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/domain/BizYakAsset.java

@@ -17,16 +17,53 @@ public class BizYakAsset extends BaseEntity
17 17
     /** 第三方牛只唯一 ID(OpenYakEntryDto.cattleId) */
18 18
     private Long externalId;
19 19
 
20
+    /** 场内牛只编号(OpenYakEntryDto.cattleNo) */
20 21
     private String yakNo;
21 22
 
23
+    /** 耳标编号(OpenYakEntryDto.earTagNumber) */
24
+    private String earTagNumber;
25
+
22 26
     private Long pastureId;
23 27
 
24 28
     private String pastureName;
25 29
 
30
+    /** 第三方养殖场 ID(OpenYakEntryDto.farmId) */
31
+    private Long farmExternalId;
32
+
33
+    /** 第三方圈舍 ID(OpenYakEntryDto.farmEnclosureId) */
34
+    private Long enclosureExternalId;
35
+
36
+    /** 本地圈舍 ID(biz_farm_enclosure.id) */
37
+    private Long enclosureId;
38
+
39
+    /** 圈舍名称(OpenYakEntryDto.farmEnclosureName) */
40
+    private String enclosureName;
41
+
26 42
     private String batchNo;
27 43
 
44
+    /** 第三方养殖批次 ID(OpenYakEntryDto.batchId) */
45
+    private Long batchExternalId;
46
+
47
+    /** 本地养殖批次 ID(biz_farming_batch.id) */
48
+    private Long batchId;
49
+
50
+    /** 入栏周期(OpenYakEntryDto.entryCycle) */
51
+    private String entryCycle;
52
+
28 53
     private String gender;
29 54
 
55
+    /** 牛只品种(OpenYakEntryDto.cattleVariety) */
56
+    private String cattleVariety;
57
+
58
+    /** 牛只图片 URL(OpenYakEntryDto.cattlePicture) */
59
+    private String cattlePicture;
60
+
61
+    /** 登记人(OpenYakEntryDto.registrant) */
62
+    private String registrant;
63
+
64
+    /** 备注(OpenYakEntryDto.remark) */
65
+    private String remark;
66
+
30 67
     @JsonFormat(pattern = "yyyy-MM-dd")
31 68
     private Date birthDate;
32 69
 
@@ -108,6 +145,16 @@ public class BizYakAsset extends BaseEntity
108 145
         this.yakNo = yakNo;
109 146
     }
110 147
 
148
+    public String getEarTagNumber()
149
+    {
150
+        return earTagNumber;
151
+    }
152
+
153
+    public void setEarTagNumber(String earTagNumber)
154
+    {
155
+        this.earTagNumber = earTagNumber;
156
+    }
157
+
111 158
     public Long getPastureId()
112 159
     {
113 160
         return pastureId;
@@ -128,6 +175,46 @@ public class BizYakAsset extends BaseEntity
128 175
         this.pastureName = pastureName;
129 176
     }
130 177
 
178
+    public Long getFarmExternalId()
179
+    {
180
+        return farmExternalId;
181
+    }
182
+
183
+    public void setFarmExternalId(Long farmExternalId)
184
+    {
185
+        this.farmExternalId = farmExternalId;
186
+    }
187
+
188
+    public Long getEnclosureExternalId()
189
+    {
190
+        return enclosureExternalId;
191
+    }
192
+
193
+    public void setEnclosureExternalId(Long enclosureExternalId)
194
+    {
195
+        this.enclosureExternalId = enclosureExternalId;
196
+    }
197
+
198
+    public Long getEnclosureId()
199
+    {
200
+        return enclosureId;
201
+    }
202
+
203
+    public void setEnclosureId(Long enclosureId)
204
+    {
205
+        this.enclosureId = enclosureId;
206
+    }
207
+
208
+    public String getEnclosureName()
209
+    {
210
+        return enclosureName;
211
+    }
212
+
213
+    public void setEnclosureName(String enclosureName)
214
+    {
215
+        this.enclosureName = enclosureName;
216
+    }
217
+
131 218
     public String getBatchNo()
132 219
     {
133 220
         return batchNo;
@@ -138,6 +225,36 @@ public class BizYakAsset extends BaseEntity
138 225
         this.batchNo = batchNo;
139 226
     }
140 227
 
228
+    public Long getBatchExternalId()
229
+    {
230
+        return batchExternalId;
231
+    }
232
+
233
+    public void setBatchExternalId(Long batchExternalId)
234
+    {
235
+        this.batchExternalId = batchExternalId;
236
+    }
237
+
238
+    public Long getBatchId()
239
+    {
240
+        return batchId;
241
+    }
242
+
243
+    public void setBatchId(Long batchId)
244
+    {
245
+        this.batchId = batchId;
246
+    }
247
+
248
+    public String getEntryCycle()
249
+    {
250
+        return entryCycle;
251
+    }
252
+
253
+    public void setEntryCycle(String entryCycle)
254
+    {
255
+        this.entryCycle = entryCycle;
256
+    }
257
+
141 258
     public String getGender()
142 259
     {
143 260
         return gender;
@@ -148,6 +265,46 @@ public class BizYakAsset extends BaseEntity
148 265
         this.gender = gender;
149 266
     }
150 267
 
268
+    public String getCattleVariety()
269
+    {
270
+        return cattleVariety;
271
+    }
272
+
273
+    public void setCattleVariety(String cattleVariety)
274
+    {
275
+        this.cattleVariety = cattleVariety;
276
+    }
277
+
278
+    public String getCattlePicture()
279
+    {
280
+        return cattlePicture;
281
+    }
282
+
283
+    public void setCattlePicture(String cattlePicture)
284
+    {
285
+        this.cattlePicture = cattlePicture;
286
+    }
287
+
288
+    public String getRegistrant()
289
+    {
290
+        return registrant;
291
+    }
292
+
293
+    public void setRegistrant(String registrant)
294
+    {
295
+        this.registrant = registrant;
296
+    }
297
+
298
+    public String getRemark()
299
+    {
300
+        return remark;
301
+    }
302
+
303
+    public void setRemark(String remark)
304
+    {
305
+        this.remark = remark;
306
+    }
307
+
151 308
     public Date getBirthDate()
152 309
     {
153 310
         return birthDate;

+ 9 - 9
baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/domain/dto/OpenYakEntryDto.java

@@ -20,16 +20,16 @@ public class OpenYakEntryDto
20 20
     /** 农场名称,落库 pasture_name */
21 21
     private String farmName;
22 22
 
23
-    /** 所属圈舍 ID */
23
+    /** 所属圈舍 ID,落库 enclosure_external_id */
24 24
     private Long farmEnclosureId;
25 25
 
26
-    /** 圈舍名称,落库 pen_location */
26
+    /** 圈舍名称,落库 enclosure_name */
27 27
     private String farmEnclosureName;
28 28
 
29
-    /** 所属养殖批次 ID */
29
+    /** 所属养殖批次 ID,落库 batch_external_id */
30 30
     private Long batchId;
31 31
 
32
-    /** 入栏批次周期,落库 batch_no(优先于 batchId) */
32
+    /** 入栏批次周期,落库 entry_cycle */
33 33
     private String entryCycle;
34 34
 
35 35
     /** 农场地理位置,落库 location */
@@ -38,10 +38,10 @@ public class OpenYakEntryDto
38 38
     /** 牛只唯一 ID,落库 external_id */
39 39
     private Long cattleId;
40 40
 
41
-    /** 场内牛只编号 */
41
+    /** 场内牛只编号,落库 yak_no */
42 42
     private String cattleNo;
43 43
 
44
-    /** 耳标编号(溯源标识),落库 yak_no(优先于 cattleNo) */
44
+    /** 耳标编号(溯源标识),落库 ear_tag_number */
45 45
     private String earTagNumber;
46 46
 
47 47
     /** 牛只品种 */
@@ -62,10 +62,10 @@ public class OpenYakEntryDto
62 62
     /** 当前体重,落库 entry_weight_kg */
63 63
     private Double weight;
64 64
 
65
-    /** 牛只照片 URL(本期不落库) */
65
+    /** 牛只照片 URL,落库 cattle_picture */
66 66
     private String cattlePicture;
67 67
 
68
-    /** 档案登记人(本期不落库) */
68
+    /** 档案登记人,落库 registrant */
69 69
     private String registrant;
70 70
 
71 71
     /** 入栏时间,落库 entry_date */
@@ -77,7 +77,7 @@ public class OpenYakEntryDto
77 77
     /** 档案创建时间 */
78 78
     private String createTime;
79 79
 
80
-    /** 备注,落库 status_change_reason */
80
+    /** 备注,落库 remark */
81 81
     private String remark;
82 82
 
83 83
     public Long getId()

+ 2 - 0
baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/mapper/BizYakAssetMapper.java

@@ -9,6 +9,8 @@ public interface BizYakAssetMapper
9 9
 
10 10
     BizYakAsset selectBizYakAssetByYakNo(String yakNo);
11 11
 
12
+    BizYakAsset selectBizYakAssetByEarTagNumber(String earTagNumber);
13
+
12 14
     BizYakAsset selectBizYakAssetByExternalId(Long externalId);
13 15
 
14 16
     List<BizYakAsset> selectBizYakAssetList(BizYakAsset query);

+ 41 - 0
baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/service/YakAssetBundleSyncTxService.java

@@ -8,6 +8,7 @@ import org.springframework.transaction.annotation.Transactional;
8 8
 import org.springframework.util.CollectionUtils;
9 9
 import com.ruoyi.common.exception.ServiceException;
10 10
 import com.ruoyi.common.utils.StringUtils;
11
+import com.ruoyi.web.modules.industryservice.domain.BizFarmingBatch;
11 12
 import com.ruoyi.web.modules.industryservice.domain.BizPasture;
12 13
 import com.ruoyi.web.modules.industryservice.domain.BizYakAsset;
13 14
 import com.ruoyi.web.modules.industryservice.domain.BizYakFeedingRecord;
@@ -16,12 +17,15 @@ import com.ruoyi.web.modules.industryservice.domain.BizYakPhysioSeries;
16 17
 import com.ruoyi.web.modules.industryservice.domain.BizYakReproductionRecord;
17 18
 import com.ruoyi.web.modules.industryservice.domain.dto.YakAssetBundleDto;
18 19
 import com.ruoyi.web.modules.industryservice.mapper.BizPastureMapper;
20
+import com.ruoyi.web.modules.industryservice.mapper.BizFarmEnclosureMapper;
21
+import com.ruoyi.web.modules.industryservice.mapper.BizFarmingBatchMapper;
19 22
 import com.ruoyi.web.modules.industryservice.mapper.BizYakAssetMapper;
20 23
 import com.ruoyi.web.modules.industryservice.mapper.BizYakFeedingRecordMapper;
21 24
 import com.ruoyi.web.modules.industryservice.support.YakAssetStatusMapper;
22 25
 import com.ruoyi.web.modules.industryservice.mapper.BizYakGrowthRecordMapper;
23 26
 import com.ruoyi.web.modules.industryservice.mapper.BizYakPhysioSeriesMapper;
24 27
 import com.ruoyi.web.modules.industryservice.mapper.BizYakReproductionRecordMapper;
28
+import com.ruoyi.web.modules.industryservice.support.FarmingSyncSupport;
25 29
 import com.ruoyi.web.modules.industryservice.support.YakAssetRules;
26 30
 import com.ruoyi.web.modules.industryservice.support.YakAssetValidation;
27 31
 
@@ -56,6 +60,12 @@ public class YakAssetBundleSyncTxService
56 60
     @Autowired
57 61
     private BizPastureMapper bizPastureMapper;
58 62
 
63
+    @Autowired
64
+    private BizFarmEnclosureMapper bizFarmEnclosureMapper;
65
+
66
+    @Autowired
67
+    private BizFarmingBatchMapper bizFarmingBatchMapper;
68
+
59 69
     @Autowired
60 70
     private BizYakPhysioSeriesMapper physioSeriesMapper;
61 71
 
@@ -96,6 +106,8 @@ public class YakAssetBundleSyncTxService
96 106
             YakAssetValidation.validateAssetStatus(incoming.getAssetStatus());
97 107
         }
98 108
         resolvePasture(incoming, bundle);
109
+        resolveEnclosure(incoming);
110
+        resolveBatch(incoming);
99 111
         if (incoming.getBirthDate() != null)
100 112
         {
101 113
             incoming.setAgeMonths(YakAssetValidation.resolveAgeMonths(incoming.getBirthDate(), syncTime));
@@ -172,6 +184,35 @@ public class YakAssetBundleSyncTxService
172 184
         }
173 185
     }
174 186
 
187
+    private void resolveEnclosure(BizYakAsset incoming)
188
+    {
189
+        Long enclosureId = FarmingSyncSupport.resolveEnclosureId(
190
+                bizFarmEnclosureMapper, incoming.getEnclosureExternalId());
191
+        incoming.setEnclosureId(enclosureId);
192
+    }
193
+
194
+    private void resolveBatch(BizYakAsset incoming)
195
+    {
196
+        if (incoming.getBatchExternalId() == null)
197
+        {
198
+            incoming.setBatchId(null);
199
+            incoming.setBatchNo(null);
200
+            return;
201
+        }
202
+        BizFarmingBatch batch = bizFarmingBatchMapper.selectBizFarmingBatchByExternalId(
203
+                incoming.getBatchExternalId());
204
+        if (batch != null)
205
+        {
206
+            incoming.setBatchId(batch.getId());
207
+            incoming.setBatchNo(batch.getBatchNo());
208
+        }
209
+        else
210
+        {
211
+            incoming.setBatchId(null);
212
+            incoming.setBatchNo(null);
213
+        }
214
+    }
215
+
175 216
     /**
176 217
      * 子表「按需覆盖」:列表为 null 跳过;非 null 则 delete + batchInsert。
177 218
      */

+ 12 - 4
baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/support/FarmingSyncSupport.java

@@ -102,7 +102,8 @@ public final class FarmingSyncSupport
102 102
      * 解析本地牦牛资产主键,供 IoT 数据关联。
103 103
      * <p>
104 104
      * 优先按第三方 {@code cattleId} 匹配 {@code biz_yak_asset.external_id};
105
-     * 其次按耳标/牛只编号匹配 {@code yak_no}(earTagNumber 优先于 cattleNo)。
105
+     * 其次按 {@code earTagNumber} 匹配 {@code ear_tag_number};
106
+     * 再次按 {@code cattleNo} 匹配 {@code yak_no}。
106 107
      * </p>
107 108
      *
108 109
      * @return {@code biz_yak_asset.id},未匹配则 null
@@ -122,10 +123,17 @@ public final class FarmingSyncSupport
122 123
                 return byExternal.getId();
123 124
             }
124 125
         }
125
-        String yakNo = firstNonBlank(earTagNumber, cattleNo);
126
-        if (StringUtils.isNotEmpty(yakNo))
126
+        if (StringUtils.isNotEmpty(earTagNumber))
127 127
         {
128
-            BizYakAsset byYakNo = yakAssetMapper.selectBizYakAssetByYakNo(yakNo.trim());
128
+            BizYakAsset byEarTag = yakAssetMapper.selectBizYakAssetByEarTagNumber(earTagNumber.trim());
129
+            if (byEarTag != null)
130
+            {
131
+                return byEarTag.getId();
132
+            }
133
+        }
134
+        if (StringUtils.isNotEmpty(cattleNo))
135
+        {
136
+            BizYakAsset byYakNo = yakAssetMapper.selectBizYakAssetByYakNo(cattleNo.trim());
129 137
             return byYakNo != null ? byYakNo.getId() : null;
130 138
         }
131 139
         return null;

+ 29 - 37
baqing-admin/src/main/java/com/ruoyi/web/modules/industryservice/support/YakEntryOpenApiMapper.java

@@ -22,19 +22,25 @@ import com.ruoyi.web.modules.industryservice.domain.dto.YakAssetBundleDto;
22 22
  *
23 23
  * <h3>主表字段映射(OpenYakEntryDto → BizYakAsset)</h3>
24 24
  * <ul>
25
- *   <li>cattleId / id → externalId</li>
26
- *   <li>earTagNumber(优先)/ cattleNo → yakNo</li>
27
- *   <li>farmId → thirdPartyPastureCode(字符串,供 pasture_id 解析)</li>
25
+ *   <li>cattleId → externalId</li>
26
+ *   <li>cattleNo → yakNo</li>
27
+ *   <li>earTagNumber → earTagNumber</li>
28
+ *   <li>farmId → farmExternalId(并供 pasture_id 解析)</li>
28 29
  *   <li>farmName → pastureName</li>
29
- *   <li>entryCycle(优先)/ batchId → batchNo</li>
30
- *   <li>farmEnclosureName → penLocation</li>
30
+ *   <li>farmEnclosureId → enclosureExternalId</li>
31
+ *   <li>farmEnclosureName → enclosureName / penLocation</li>
32
+ *   <li>batchId → batchExternalId</li>
33
+ *   <li>entryCycle → entryCycle</li>
31 34
  *   <li>farmLocation → location</li>
32 35
  *   <li>cattleSex → gender(规范为 公/母)</li>
33 36
  *   <li>cattleSource → source</li>
37
+ *   <li>cattleVariety → cattleVariety</li>
38
+ *   <li>cattlePicture → cattlePicture</li>
39
+ *   <li>registrant → registrant</li>
34 40
  *   <li>weight → entryWeightKg</li>
35 41
  *   <li>entryTime / birthDate → entryDate / birthDate</li>
36 42
  *   <li>fatherNumber / motherNumber → fatherYakNo / motherYakNo</li>
37
- *   <li>remark → statusChangeReason</li>
43
+ *   <li>remark → remark</li>
38 44
  * </ul>
39 45
  */
40 46
 public final class YakEntryOpenApiMapper
@@ -59,33 +65,37 @@ public final class YakEntryOpenApiMapper
59 65
         {
60 66
             throw new ServiceException("同步数据无效");
61 67
         }
62
-        String yakNo = resolveYakNo(dto);
63
-        if (StringUtils.isEmpty(yakNo))
64
-        {
65
-            throw new ServiceException("牦牛编号不能为空");
66
-        }
67
-        // external_id:优先 cattleId,其次档案 id
68
-        Long externalId = dto.getCattleId() != null ? dto.getCattleId() : dto.getId();
69
-        if (externalId == null)
68
+        if (dto.getCattleId() == null)
70 69
         {
71 70
             throw new ServiceException("第三方牛只 ID 不能为空");
72 71
         }
72
+        String yakNo = resolveYakNo(dto);
73
+        YakAssetValidation.validateYakNoForSync(yakNo);
73 74
 
74 75
         BizYakAsset asset = new BizYakAsset();
75
-        asset.setExternalId(externalId);
76
+        asset.setExternalId(dto.getCattleId());
76 77
         asset.setYakNo(yakNo);
78
+        asset.setEarTagNumber(trimToNull(dto.getEarTagNumber()));
79
+        asset.setFarmExternalId(dto.getFarmId());
77 80
         asset.setPastureName(trimToNull(dto.getFarmName()));
78
-        asset.setBatchNo(resolveBatchNo(dto));
81
+        asset.setEnclosureExternalId(dto.getFarmEnclosureId());
82
+        String enclosureName = trimToNull(dto.getFarmEnclosureName());
83
+        asset.setEnclosureName(enclosureName);
84
+        asset.setPenLocation(enclosureName);
85
+        asset.setBatchExternalId(dto.getBatchId());
86
+        asset.setEntryCycle(trimToNull(dto.getEntryCycle()));
79 87
         asset.setGender(normalizeGender(dto.getCattleSex()));
88
+        asset.setCattleVariety(trimToNull(dto.getCattleVariety()));
89
+        asset.setCattlePicture(trimToNull(dto.getCattlePicture()));
90
+        asset.setRegistrant(trimToNull(dto.getRegistrant()));
80 91
         asset.setBirthDate(parseDate(dto.getBirthDate()));
81 92
         asset.setEntryDate(parseDate(dto.getEntryTime()));
82 93
         asset.setEntryWeightKg(toBigDecimal(dto.getWeight()));
83 94
         asset.setSource(trimToNull(dto.getCattleSource()));
84
-        asset.setPenLocation(trimToNull(dto.getFarmEnclosureName()));
85 95
         asset.setLocation(trimToNull(dto.getFarmLocation()));
86 96
         asset.setFatherYakNo(trimToNull(dto.getFatherNumber()));
87 97
         asset.setMotherYakNo(trimToNull(dto.getMotherNumber()));
88
-        asset.setStatusChangeReason(trimToNull(dto.getRemark()));
98
+        asset.setRemark(trimToNull(dto.getRemark()));
89 99
         asset.setAssetStatus(YakAssetRules.ASSET_STATUS_MIN);
90 100
 
91 101
         YakAssetBundleDto bundle = new YakAssetBundleDto();
@@ -97,13 +107,9 @@ public final class YakEntryOpenApiMapper
97 107
         return bundle;
98 108
     }
99 109
 
100
-    /** 耳标号优先,其次牛只编号 */
110
+    /** 场内牛只编号 cattleNo */
101 111
     private static String resolveYakNo(OpenYakEntryDto dto)
102 112
     {
103
-        if (StringUtils.isNotEmpty(dto.getEarTagNumber()))
104
-        {
105
-            return dto.getEarTagNumber().trim();
106
-        }
107 113
         if (StringUtils.isNotEmpty(dto.getCattleNo()))
108 114
         {
109 115
             return dto.getCattleNo().trim();
@@ -111,20 +117,6 @@ public final class YakEntryOpenApiMapper
111 117
         return null;
112 118
     }
113 119
 
114
-    /** 入栏周期优先,其次 batchId 转字符串 */
115
-    private static String resolveBatchNo(OpenYakEntryDto dto)
116
-    {
117
-        if (StringUtils.isNotEmpty(dto.getEntryCycle()))
118
-        {
119
-            return dto.getEntryCycle().trim();
120
-        }
121
-        if (dto.getBatchId() != null)
122
-        {
123
-            return String.valueOf(dto.getBatchId());
124
-        }
125
-        return null;
126
-    }
127
-
128 120
     private static String normalizeGender(String cattleSex)
129 121
     {
130 122
         if (StringUtils.isEmpty(cattleSex))

+ 88 - 46
baqing-admin/src/main/resources/mapper/industryservice/BizYakAssetMapper.xml

@@ -3,44 +3,60 @@
3 3
 <mapper namespace="com.ruoyi.web.modules.industryservice.mapper.BizYakAssetMapper">
4 4
 
5 5
     <resultMap type="com.ruoyi.web.modules.industryservice.domain.BizYakAsset" id="BizYakAssetResult">
6
-        <id     property="id"                 column="id"/>
7
-        <result property="externalId"         column="external_id"/>
8
-        <result property="yakNo"              column="yak_no"/>
9
-        <result property="pastureId"          column="pasture_id"/>
10
-        <result property="pastureName"        column="pasture_name"/>
11
-        <result property="batchNo"            column="batch_no"/>
12
-        <result property="gender"             column="gender"/>
13
-        <result property="birthDate"          column="birth_date"/>
14
-        <result property="ageMonths"          column="age_months"/>
15
-        <result property="entryDate"          column="entry_date"/>
16
-        <result property="entryWeightKg"      column="entry_weight_kg"/>
17
-        <result property="source"             column="source"/>
18
-        <result property="breedingMethod"     column="breeding_method"/>
19
-        <result property="assetStatus"        column="asset_status"/>
20
-        <result property="statusChangeDate"   column="status_change_date"/>
21
-        <result property="statusChangeReason" column="status_change_reason"/>
22
-        <result property="penLocation"        column="pen_location"/>
23
-        <result property="expectedOutDate"    column="expected_out_date"/>
24
-        <result property="supplementPlan"     column="supplement_plan"/>
25
-        <result property="realtimeTemp"       column="realtime_temp"/>
26
-        <result property="realtimeSteps"      column="realtime_steps"/>
27
-        <result property="envTemp"            column="env_temp"/>
28
-        <result property="location"           column="location"/>
29
-        <result property="physioCollectTime"  column="physio_collect_time"/>
30
-        <result property="fatherYakNo"        column="father_yak_no"/>
31
-        <result property="motherYakNo"        column="mother_yak_no"/>
32
-        <result property="lastSyncTime"       column="last_sync_time"/>
33
-        <result property="delFlag"            column="del_flag"/>
34
-        <result property="createTime"         column="create_time"/>
35
-        <result property="updateTime"         column="update_time"/>
6
+        <id     property="id"                   column="id"/>
7
+        <result property="externalId"           column="external_id"/>
8
+        <result property="yakNo"                column="yak_no"/>
9
+        <result property="earTagNumber"         column="ear_tag_number"/>
10
+        <result property="pastureId"            column="pasture_id"/>
11
+        <result property="pastureName"          column="pasture_name"/>
12
+        <result property="farmExternalId"       column="farm_external_id"/>
13
+        <result property="enclosureExternalId"  column="enclosure_external_id"/>
14
+        <result property="enclosureId"          column="enclosure_id"/>
15
+        <result property="enclosureName"        column="enclosure_name"/>
16
+        <result property="batchNo"              column="batch_no"/>
17
+        <result property="batchExternalId"        column="batch_external_id"/>
18
+        <result property="batchId"              column="batch_id"/>
19
+        <result property="entryCycle"           column="entry_cycle"/>
20
+        <result property="gender"               column="gender"/>
21
+        <result property="cattleVariety"        column="cattle_variety"/>
22
+        <result property="cattlePicture"        column="cattle_picture"/>
23
+        <result property="registrant"           column="registrant"/>
24
+        <result property="remark"               column="remark"/>
25
+        <result property="birthDate"            column="birth_date"/>
26
+        <result property="ageMonths"            column="age_months"/>
27
+        <result property="entryDate"            column="entry_date"/>
28
+        <result property="entryWeightKg"        column="entry_weight_kg"/>
29
+        <result property="source"               column="source"/>
30
+        <result property="breedingMethod"       column="breeding_method"/>
31
+        <result property="assetStatus"          column="asset_status"/>
32
+        <result property="statusChangeDate"     column="status_change_date"/>
33
+        <result property="statusChangeReason"   column="status_change_reason"/>
34
+        <result property="penLocation"          column="pen_location"/>
35
+        <result property="expectedOutDate"      column="expected_out_date"/>
36
+        <result property="supplementPlan"       column="supplement_plan"/>
37
+        <result property="realtimeTemp"         column="realtime_temp"/>
38
+        <result property="realtimeSteps"        column="realtime_steps"/>
39
+        <result property="envTemp"              column="env_temp"/>
40
+        <result property="location"             column="location"/>
41
+        <result property="physioCollectTime"    column="physio_collect_time"/>
42
+        <result property="fatherYakNo"          column="father_yak_no"/>
43
+        <result property="motherYakNo"          column="mother_yak_no"/>
44
+        <result property="lastSyncTime"         column="last_sync_time"/>
45
+        <result property="delFlag"              column="del_flag"/>
46
+        <result property="createTime"           column="create_time"/>
47
+        <result property="updateTime"           column="update_time"/>
36 48
     </resultMap>
37 49
 
38 50
     <sql id="selectVo">
39
-        select id, external_id, yak_no, pasture_id, pasture_name, batch_no, gender, birth_date, age_months,
40
-               entry_date, entry_weight_kg, source, breeding_method, asset_status, status_change_date,
41
-               status_change_reason, pen_location, expected_out_date, supplement_plan,
42
-               realtime_temp, realtime_steps, env_temp, location, physio_collect_time,
43
-               father_yak_no, mother_yak_no, last_sync_time, del_flag, create_time, update_time
51
+        select id, external_id, yak_no, ear_tag_number, pasture_id, pasture_name,
52
+               farm_external_id, enclosure_external_id, enclosure_id, enclosure_name,
53
+               batch_no, batch_external_id, batch_id, entry_cycle,
54
+               gender, cattle_variety, cattle_picture, registrant, remark,
55
+               birth_date, age_months, entry_date, entry_weight_kg, source, breeding_method,
56
+               asset_status, status_change_date, status_change_reason, pen_location,
57
+               expected_out_date, supplement_plan, realtime_temp, realtime_steps, env_temp,
58
+               location, physio_collect_time, father_yak_no, mother_yak_no,
59
+               last_sync_time, del_flag, create_time, update_time
44 60
         from biz_yak_asset
45 61
     </sql>
46 62
 
@@ -54,6 +70,11 @@
54 70
         where yak_no = #{yakNo} and del_flag = '0'
55 71
     </select>
56 72
 
73
+    <select id="selectBizYakAssetByEarTagNumber" parameterType="string" resultMap="BizYakAssetResult">
74
+        <include refid="selectVo"/>
75
+        where ear_tag_number = #{earTagNumber} and del_flag = '0'
76
+    </select>
77
+
57 78
     <select id="selectBizYakAssetByExternalId" parameterType="long" resultMap="BizYakAssetResult">
58 79
         <include refid="selectVo"/>
59 80
         where external_id = #{externalId} and del_flag = '0'
@@ -64,7 +85,8 @@
64 85
         <where>
65 86
             del_flag = '0'
66 87
             <if test="keyword != null and keyword != ''">
67
-                and yak_no like concat('%', #{keyword}, '%')
88
+                and (yak_no like concat('%', #{keyword}, '%')
89
+                  or ear_tag_number like concat('%', #{keyword}, '%'))
68 90
             </if>
69 91
             <if test="assetStatus != null">
70 92
                 and asset_status = #{assetStatus}
@@ -75,17 +97,25 @@
75 97
 
76 98
     <insert id="insertBizYakAsset" parameterType="com.ruoyi.web.modules.industryservice.domain.BizYakAsset" useGeneratedKeys="true" keyProperty="id">
77 99
         insert into biz_yak_asset (
78
-            external_id, yak_no, pasture_id, pasture_name, batch_no, gender, birth_date, age_months,
79
-            entry_date, entry_weight_kg, source, breeding_method, asset_status, status_change_date,
80
-            status_change_reason, pen_location, expected_out_date, supplement_plan,
81
-            realtime_temp, realtime_steps, env_temp, location, physio_collect_time,
82
-            father_yak_no, mother_yak_no, last_sync_time, del_flag, create_time
100
+            external_id, yak_no, ear_tag_number, pasture_id, pasture_name,
101
+            farm_external_id, enclosure_external_id, enclosure_id, enclosure_name,
102
+            batch_no, batch_external_id, batch_id, entry_cycle,
103
+            gender, cattle_variety, cattle_picture, registrant, remark,
104
+            birth_date, age_months, entry_date, entry_weight_kg, source, breeding_method,
105
+            asset_status, status_change_date, status_change_reason, pen_location,
106
+            expected_out_date, supplement_plan, realtime_temp, realtime_steps, env_temp,
107
+            location, physio_collect_time, father_yak_no, mother_yak_no,
108
+            last_sync_time, del_flag, create_time
83 109
         ) values (
84
-            #{externalId}, #{yakNo}, #{pastureId}, #{pastureName}, #{batchNo}, #{gender}, #{birthDate}, #{ageMonths},
85
-            #{entryDate}, #{entryWeightKg}, #{source}, #{breedingMethod}, #{assetStatus}, #{statusChangeDate},
86
-            #{statusChangeReason}, #{penLocation}, #{expectedOutDate}, #{supplementPlan},
87
-            #{realtimeTemp}, #{realtimeSteps}, #{envTemp}, #{location}, #{physioCollectTime},
88
-            #{fatherYakNo}, #{motherYakNo}, #{lastSyncTime}, #{delFlag}, sysdate()
110
+            #{externalId}, #{yakNo}, #{earTagNumber}, #{pastureId}, #{pastureName},
111
+            #{farmExternalId}, #{enclosureExternalId}, #{enclosureId}, #{enclosureName},
112
+            #{batchNo}, #{batchExternalId}, #{batchId}, #{entryCycle},
113
+            #{gender}, #{cattleVariety}, #{cattlePicture}, #{registrant}, #{remark},
114
+            #{birthDate}, #{ageMonths}, #{entryDate}, #{entryWeightKg}, #{source}, #{breedingMethod},
115
+            #{assetStatus}, #{statusChangeDate}, #{statusChangeReason}, #{penLocation},
116
+            #{expectedOutDate}, #{supplementPlan}, #{realtimeTemp}, #{realtimeSteps}, #{envTemp},
117
+            #{location}, #{physioCollectTime}, #{fatherYakNo}, #{motherYakNo},
118
+            #{lastSyncTime}, #{delFlag}, sysdate()
89 119
         )
90 120
     </insert>
91 121
 
@@ -93,10 +123,22 @@
93 123
         update biz_yak_asset set
94 124
             external_id = #{externalId},
95 125
             yak_no = #{yakNo},
126
+            ear_tag_number = #{earTagNumber},
96 127
             pasture_id = #{pastureId},
97 128
             pasture_name = #{pastureName},
129
+            farm_external_id = #{farmExternalId},
130
+            enclosure_external_id = #{enclosureExternalId},
131
+            enclosure_id = #{enclosureId},
132
+            enclosure_name = #{enclosureName},
98 133
             batch_no = #{batchNo},
134
+            batch_external_id = #{batchExternalId},
135
+            batch_id = #{batchId},
136
+            entry_cycle = #{entryCycle},
99 137
             gender = #{gender},
138
+            cattle_variety = #{cattleVariety},
139
+            cattle_picture = #{cattlePicture},
140
+            registrant = #{registrant},
141
+            remark = #{remark},
100 142
             birth_date = #{birthDate},
101 143
             age_months = #{ageMonths},
102 144
             entry_date = #{entryDate},

+ 2 - 0
baqing-admin/src/test/java/com/ruoyi/web/modules/industryservice/support/YakAssetValidationTest.java

@@ -60,6 +60,8 @@ class YakAssetValidationTest
60 60
         assertTrueContains(xml, "del_flag = '0'");
61 61
         assertTrueContains(xml, "status_change_date desc");
62 62
         assertTrueContains(xml, "yak_no like");
63
+        assertTrueContains(xml, "ear_tag_number like");
64
+        assertTrueContains(xml, "ear_tag_number");
63 65
     }
64 66
 
65 67
     private static void assertTrueContains(String haystack, String needle)

+ 17 - 3
baqing-admin/src/test/java/com/ruoyi/web/modules/industryservice/support/YakEntryOpenApiMapperTest.java

@@ -23,11 +23,16 @@ class YakEntryOpenApiMapperTest
23 23
         dto.setCattleNo("CN-001");
24 24
         dto.setFarmId(1L);
25 25
         dto.setFarmName("示范牧场");
26
+        dto.setFarmEnclosureId(201L);
26 27
         dto.setFarmEnclosureName("1号圈舍");
28
+        dto.setBatchId(301L);
27 29
         dto.setEntryCycle("B2025-01");
28 30
         dto.setFarmLocation("东经91.0,北纬32.0");
29 31
         dto.setCattleSex("母");
30 32
         dto.setCattleSource("自繁");
33
+        dto.setCattleVariety("牦牛");
34
+        dto.setCattlePicture("https://example.com/cattle.jpg");
35
+        dto.setRegistrant("张三");
31 36
         dto.setFatherNumber("F-01");
32 37
         dto.setMotherNumber("M-01");
33 38
         dto.setWeight(220.5);
@@ -39,17 +44,26 @@ class YakEntryOpenApiMapperTest
39 44
         BizYakAsset asset = bundle.getAsset();
40 45
         assertNotNull(asset);
41 46
         assertEquals(90001L, asset.getExternalId());
42
-        assertEquals("ET-001", asset.getYakNo());
47
+        assertEquals("CN-001", asset.getYakNo());
48
+        assertEquals("ET-001", asset.getEarTagNumber());
49
+        assertEquals(1L, asset.getFarmExternalId().longValue());
43 50
         assertEquals("示范牧场", asset.getPastureName());
44
-        assertEquals("B2025-01", asset.getBatchNo());
51
+        assertEquals(201L, asset.getEnclosureExternalId().longValue());
52
+        assertEquals("B2025-01", asset.getEntryCycle());
53
+        assertEquals(301L, asset.getBatchExternalId().longValue());
54
+        assertEquals(null, asset.getBatchNo());
45 55
         assertEquals("母", asset.getGender());
46 56
         assertEquals("自繁", asset.getSource());
57
+        assertEquals("1号圈舍", asset.getEnclosureName());
47 58
         assertEquals("1号圈舍", asset.getPenLocation());
48 59
         assertEquals("东经91.0,北纬32.0", asset.getLocation());
60
+        assertEquals("牦牛", asset.getCattleVariety());
61
+        assertEquals("https://example.com/cattle.jpg", asset.getCattlePicture());
62
+        assertEquals("张三", asset.getRegistrant());
49 63
         assertEquals("F-01", asset.getFatherYakNo());
50 64
         assertEquals("M-01", asset.getMotherYakNo());
51 65
         assertEquals(new BigDecimal("220.5"), asset.getEntryWeightKg());
52
-        assertEquals("测试备注", asset.getStatusChangeReason());
66
+        assertEquals("测试备注", asset.getRemark());
53 67
         assertEquals("1", bundle.getThirdPartyPastureCode());
54 68
     }
55 69
 }

+ 1 - 1
doc/产业数据模型及服务/牧业金融生物资产管理/牦牛资产档案管理功能需求.md

@@ -136,7 +136,7 @@ flowchart TD
136 136
 | 范围 | **五段顺序**全量:养殖场 → 圈舍 → 生产计划 → 批次 → **入栏建档(牦牛档案)** |
137 137
 | 入栏接口 | `GET /open-api/v1/farming/entry-filings`(字段见草稿 `20260611111807_455_398.jpg`) |
138 138
 | 鉴权 | 与牧场管理同源:`third-party.farming` 配置 `base-url`、`app-key`、`app-secret` |
139
-| 合并键 | 优先 **cattleId**(`external_id`);辅以 **earTagNumber**(无则 cattleNo)作为 `yak_no` |
139
+| 合并键 | 优先 **cattleId**(`external_id`);辅以 **cattleNo** 作为 `yak_no`;**earTagNumber** 独立落库 `ear_tag_number` |
140 140
 | 分页 | `pageNum`/`pageSize`≤200 直至取完 |
141 141
 | 反馈 | 提示成功/失败;摘要含各段及入栏建档新增/更新/失败头数(前端兼容顶层 `insertCount` 等) |
142 142
 | 牧场映射 | `farmId` → `biz_pasture.external_id`;失败则冗余 `farmName` |

+ 26 - 9
doc/产业数据模型及服务/牧业金融生物资产管理/牦牛资产档案管理技术方案.md

@@ -38,11 +38,23 @@
38 38
 | --- | --- | --- | --- |
39 39
 | `id` | `bigint(20)` | Y | 主键 |
40 40
 | `external_id` | `bigint(20)` | N | 第三方牛只 ID(`OpenYakEntryDto.cattleId`),**唯一** |
41
-| `yak_no` | `varchar(64)` | Y | 牦牛编号(优先 `earTagNumber`),**唯一** |
41
+| `yak_no` | `varchar(64)` | Y | 场内牛只编号(`OpenYakEntryDto.cattleNo`),**唯一** |
42
+| `ear_tag_number` | `varchar(64)` | N | 耳标编号(`OpenYakEntryDto.earTagNumber`) |
42 43
 | `pasture_id` | `bigint(20)` | N | 关联 `biz_pasture.id`(映射成功时) |
43 44
 | `pasture_name` | `varchar(128)` | N | 所属牧场名称(同步冗余展示) |
44
-| `batch_no` | `varchar(64)` | N | 批次编号 |
45
+| `farm_external_id` | `bigint(20)` | N | 第三方养殖场 ID(`farmId`) |
46
+| `enclosure_external_id` | `bigint(20)` | N | 第三方圈舍 ID(`farmEnclosureId`) |
47
+| `enclosure_id` | `bigint(20)` | N | 本地圈舍 `biz_farm_enclosure.id` |
48
+| `enclosure_name` | `varchar(128)` | N | 圈舍名称 |
49
+| `batch_no` | `varchar(64)` | N | 批次编号;由 `batch_external_id` 关联 `biz_farming_batch.batch_no` |
50
+| `batch_external_id` | `bigint(20)` | N | 第三方养殖批次 ID(`batchId`) |
51
+| `batch_id` | `bigint(20)` | N | 本地养殖批次 `biz_farming_batch.id` |
52
+| `entry_cycle` | `varchar(64)` | N | 入栏周期(`entryCycle`) |
45 53
 | `gender` | `varchar(16)` | N | 性别 |
54
+| `cattle_variety` | `varchar(128)` | N | 牛只品种 |
55
+| `cattle_picture` | `varchar(512)` | N | 牛只图片 URL |
56
+| `registrant` | `varchar(64)` | N | 登记人 |
57
+| `remark` | `varchar(500)` | N | 备注 |
46 58
 | `birth_date` | `date` | N | 出生日期 |
47 59
 | `age_months` | `int(11)` | N | 月龄(月) |
48 60
 | `entry_date` | `date` | N | 入栏日期 |
@@ -73,21 +85,26 @@
73 85
 | OpenAPI 字段 | 库字段 | 说明 |
74 86
 | --- | --- | --- |
75 87
 | `cattleId` | `external_id` | 同步 upsert 主键 |
76
-| `earTagNumber` / `cattleNo` | `yak_no` | 优先耳标 |
77
-| `farmId` | `pasture_id` | 经 `biz_pasture.external_id` 解析 |
88
+| `cattleNo` | `yak_no` | 场内牛只编号 |
89
+| `earTagNumber` | `ear_tag_number` | 耳标编号 |
90
+| `farmId` | `farm_external_id` / `pasture_id` | 前者直存;后者经 `biz_pasture.external_id` 解析 |
78 91
 | `farmName` | `pasture_name` | 冗余展示 |
79
-| `entryCycle` / `batchId` | `batch_no` | 优先 `entryCycle` |
80
-| `farmEnclosureName` | `pen_location` | 并写入 `biz_yak_pen_rel` |
92
+| `farmEnclosureId` | `enclosure_external_id` / `enclosure_id` | 后者经 `biz_farm_enclosure.external_id` 解析 |
93
+| `farmEnclosureName` | `enclosure_name` / `pen_location` | 兼容保留 `pen_location` |
94
+| `batchId` | `batch_external_id` / `batch_id` / `batch_no` | `batch_id`、`batch_no` 经 `biz_farming_batch.external_id` 解析 |
95
+| `entryCycle` | `entry_cycle` | |
81 96
 | `farmLocation` | `location` | |
82 97
 | `cattleSex` | `gender` | 归一公/母 |
83 98
 | `cattleSource` | `source` | |
99
+| `cattleVariety` | `cattle_variety` | |
100
+| `cattlePicture` | `cattle_picture` | |
101
+| `registrant` | `registrant` | |
84 102
 | `fatherNumber` | `father_yak_no` | |
85 103
 | `motherNumber` | `mother_yak_no` | |
86 104
 | `weight` | `entry_weight_kg` | 当前体重 |
87 105
 | `entryTime` | `entry_date` | |
88 106
 | `birthDate` | `birth_date` | 月龄由同步时计算写入 `age_months` |
89
-| `remark` | `status_change_reason` | |
90
-| `id` / `cattlePicture` / `registrant` / `cattleVariety` | — | 本期不落库 |
107
+| `remark` | `remark` | |
91 108
 
92 109
 列表接口未返回的字段(`asset_status`、实时体温/运动量等)保持库内原值或默认「正常」。
93 110
 
@@ -166,7 +183,7 @@
166 183
 | `biz_yak_collar_location` | `GET /open-api/v1/iot/collar-locations` | `(yak_no, report_time)` | `sql/biz_yak_collar_location.sql` |
167 184
 | `biz_env_monitoring_record` | `GET /open-api/v1/iot/env-monitoring` | `external_id` | `sql/biz_env_monitoring_record.sql` |
168 185
 
169
-关联解析:称重/项圈可关联 `biz_yak_asset`(优先 `cattleId`→`external_id`,其次耳标/`cattleNo`→`yak_no`);环境/称重可关联 `biz_pasture`、`biz_farm_enclosure`。
186
+关联解析:称重/项圈可关联 `biz_yak_asset`(优先 `cattleId`→`external_id`,其次 `earTagNumber`→`ear_tag_number`,再次 `cattleNo`→`yak_no`);环境/称重可关联 `biz_pasture`、`biz_farm_enclosure`。
170 187
 
171 188
 ### 2.8 枚举 `asset_status`
172 189
 

+ 1 - 1
doc/产业数据模型及服务/牧业金融生物资产管理/牦牛资产档案管理测试用例.md

@@ -21,7 +21,7 @@
21 21
 | ZCZX-MYNZDA-UT-004 | 列表校验 | 关键字 trim | 单元测试 | JUnit5 | 模糊查询 | 无 | `keyword` 含前后空格 | trim 后参与 `LIKE` |
22 22
 | ZCZX-MYNZDA-UT-005 | 同步合并 | 按 external_id 新增 | 单元测试 | JUnit5+Mockito | §5.3 | DB 无该 cattleId | `syncBundle` 新记录 | `insert` 主表;圈舍/批次关联写入 |
23 23
 | ZCZX-MYNZDA-UT-006 | 同步合并 | 按 external_id 更新 | 单元测试 | JUnit5+Mockito | §5.3 | DB 已有同 cattleId | `syncBundle` 字段变更 | `update` 主表;圈舍/批次覆盖 |
24
-| ZCZX-MYNZDA-UT-006A | OpenAPI 映射 | 入栏建档字段 | 单元测试 | JUnit5 | §2.1.1 | `OpenYakEntryDto` 样例 | `YakEntryOpenApiMapper.toBundle` | `yak_no`=耳标;`farmId`→牧场编码;字段与表一致 |
24
+| ZCZX-MYNZDA-UT-006A | OpenAPI 映射 | 入栏建档字段 | 单元测试 | JUnit5 | §2.1.1 | `OpenYakEntryDto` 样例 | `YakEntryOpenApiMapper.toBundle` | `external_id`=cattleId;`yak_no`=cattleNo;`ear_tag_number`=耳标;扩展字段与表一致 |
25 25
 | ZCZX-MYNZDA-UT-007 | 同步安全 | 失败不清库 | 单元测试 | JUnit5+Mockito | §5.3 | 已有档案 A | Mock 第三方抛超时;执行 sync | 档案 A 主表及子表条数不变 |
26 26
 | ZCZX-MYNZDA-UT-008 | 同步安全 | 单头失败不回滚全局 | 单元测试 | JUnit5+Mockito | 事务边界 | 批次含 2 头,第 2 头校验失败 | `syncAll` | 第 1 头成功落库;`failCount≥1`;第 1 头数据保留 |
27 27
 | ZCZX-MYNZDA-UT-009 | 牧场映射 | 映射成功写 pasture_id | 单元测试 | JUnit5+Mockito | §5.3 | `biz_pasture` 有匹配编码 | 同步带第三方牧场码 | `pasture_id` 非空;`pasture_name` 与主数据一致 |

+ 18 - 2
sql/biz_yak_asset.sql

@@ -4,11 +4,23 @@ SET NAMES utf8mb4;
4 4
 CREATE TABLE IF NOT EXISTS `biz_yak_asset` (
5 5
   `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
6 6
   `external_id` bigint(20) DEFAULT NULL COMMENT '第三方牛只唯一ID OpenYakEntryDto.cattleId',
7
-  `yak_no` varchar(64) NOT NULL COMMENT '牦牛编号(优先耳标 earTagNumber)',
7
+  `yak_no` varchar(64) NOT NULL COMMENT '场内牛只编号 OpenYakEntryDto.cattleNo',
8
+  `ear_tag_number` varchar(64) DEFAULT NULL COMMENT '耳标编号 OpenYakEntryDto.earTagNumber',
8 9
   `pasture_id` bigint(20) DEFAULT NULL COMMENT '牧场ID',
9 10
   `pasture_name` varchar(128) DEFAULT NULL COMMENT '所属牧场名称',
10
-  `batch_no` varchar(64) DEFAULT NULL COMMENT '批次编号',
11
+  `farm_external_id` bigint(20) DEFAULT NULL COMMENT '第三方养殖场ID OpenYakEntryDto.farmId',
12
+  `enclosure_external_id` bigint(20) DEFAULT NULL COMMENT '第三方圈舍ID OpenYakEntryDto.farmEnclosureId',
13
+  `enclosure_id` bigint(20) DEFAULT NULL COMMENT '本地圈舍ID biz_farm_enclosure.id',
14
+  `enclosure_name` varchar(128) DEFAULT NULL COMMENT '圈舍名称 OpenYakEntryDto.farmEnclosureName',
15
+  `batch_no` varchar(64) DEFAULT NULL COMMENT '批次编号(同步冗余)',
16
+  `batch_external_id` bigint(20) DEFAULT NULL COMMENT '第三方养殖批次ID OpenYakEntryDto.batchId',
17
+  `batch_id` bigint(20) DEFAULT NULL COMMENT '本地养殖批次ID biz_farming_batch.id',
18
+  `entry_cycle` varchar(64) DEFAULT NULL COMMENT '入栏周期 OpenYakEntryDto.entryCycle',
11 19
   `gender` varchar(16) DEFAULT NULL COMMENT '性别',
20
+  `cattle_variety` varchar(128) DEFAULT NULL COMMENT '牛只品种 OpenYakEntryDto.cattleVariety',
21
+  `cattle_picture` varchar(512) DEFAULT NULL COMMENT '牛只图片URL OpenYakEntryDto.cattlePicture',
22
+  `registrant` varchar(64) DEFAULT NULL COMMENT '登记人 OpenYakEntryDto.registrant',
23
+  `remark` varchar(500) DEFAULT NULL COMMENT '备注 OpenYakEntryDto.remark',
12 24
   `birth_date` date DEFAULT NULL COMMENT '出生日期',
13 25
   `age_months` int(11) DEFAULT NULL COMMENT '月龄',
14 26
   `entry_date` date DEFAULT NULL COMMENT '入栏日期',
@@ -37,6 +49,10 @@ CREATE TABLE IF NOT EXISTS `biz_yak_asset` (
37 49
   UNIQUE KEY `uk_yak_no` (`yak_no`),
38 50
   KEY `idx_asset_status` (`asset_status`),
39 51
   KEY `idx_pasture_id` (`pasture_id`),
52
+  KEY `idx_farm_external_id` (`farm_external_id`),
53
+  KEY `idx_enclosure_id` (`enclosure_id`),
54
+  KEY `idx_batch_id` (`batch_id`),
55
+  KEY `idx_ear_tag_number` (`ear_tag_number`),
40 56
   KEY `idx_status_change_date` (`status_change_date`),
41 57
   KEY `idx_last_sync_time` (`last_sync_time`),
42 58
   KEY `idx_del_flag` (`del_flag`)