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
     /** 第三方牛只唯一 ID(OpenYakEntryDto.cattleId) */
17
     /** 第三方牛只唯一 ID(OpenYakEntryDto.cattleId) */
18
     private Long externalId;
18
     private Long externalId;
19
 
19
 
20
+    /** 场内牛只编号(OpenYakEntryDto.cattleNo) */
20
     private String yakNo;
21
     private String yakNo;
21
 
22
 
23
+    /** 耳标编号(OpenYakEntryDto.earTagNumber) */
24
+    private String earTagNumber;
25
+
22
     private Long pastureId;
26
     private Long pastureId;
23
 
27
 
24
     private String pastureName;
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
     private String batchNo;
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
     private String gender;
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
     @JsonFormat(pattern = "yyyy-MM-dd")
67
     @JsonFormat(pattern = "yyyy-MM-dd")
31
     private Date birthDate;
68
     private Date birthDate;
32
 
69
 
@@ -108,6 +145,16 @@ public class BizYakAsset extends BaseEntity
108
         this.yakNo = yakNo;
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
     public Long getPastureId()
158
     public Long getPastureId()
112
     {
159
     {
113
         return pastureId;
160
         return pastureId;
@@ -128,6 +175,46 @@ public class BizYakAsset extends BaseEntity
128
         this.pastureName = pastureName;
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
     public String getBatchNo()
218
     public String getBatchNo()
132
     {
219
     {
133
         return batchNo;
220
         return batchNo;
@@ -138,6 +225,36 @@ public class BizYakAsset extends BaseEntity
138
         this.batchNo = batchNo;
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
     public String getGender()
258
     public String getGender()
142
     {
259
     {
143
         return gender;
260
         return gender;
@@ -148,6 +265,46 @@ public class BizYakAsset extends BaseEntity
148
         this.gender = gender;
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
     public Date getBirthDate()
308
     public Date getBirthDate()
152
     {
309
     {
153
         return birthDate;
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
     /** 农场名称,落库 pasture_name */
20
     /** 农场名称,落库 pasture_name */
21
     private String farmName;
21
     private String farmName;
22
 
22
 
23
-    /** 所属圈舍 ID */
23
+    /** 所属圈舍 ID,落库 enclosure_external_id */
24
     private Long farmEnclosureId;
24
     private Long farmEnclosureId;
25
 
25
 
26
-    /** 圈舍名称,落库 pen_location */
26
+    /** 圈舍名称,落库 enclosure_name */
27
     private String farmEnclosureName;
27
     private String farmEnclosureName;
28
 
28
 
29
-    /** 所属养殖批次 ID */
29
+    /** 所属养殖批次 ID,落库 batch_external_id */
30
     private Long batchId;
30
     private Long batchId;
31
 
31
 
32
-    /** 入栏批次周期,落库 batch_no(优先于 batchId) */
32
+    /** 入栏批次周期,落库 entry_cycle */
33
     private String entryCycle;
33
     private String entryCycle;
34
 
34
 
35
     /** 农场地理位置,落库 location */
35
     /** 农场地理位置,落库 location */
@@ -38,10 +38,10 @@ public class OpenYakEntryDto
38
     /** 牛只唯一 ID,落库 external_id */
38
     /** 牛只唯一 ID,落库 external_id */
39
     private Long cattleId;
39
     private Long cattleId;
40
 
40
 
41
-    /** 场内牛只编号 */
41
+    /** 场内牛只编号,落库 yak_no */
42
     private String cattleNo;
42
     private String cattleNo;
43
 
43
 
44
-    /** 耳标编号(溯源标识),落库 yak_no(优先于 cattleNo) */
44
+    /** 耳标编号(溯源标识),落库 ear_tag_number */
45
     private String earTagNumber;
45
     private String earTagNumber;
46
 
46
 
47
     /** 牛只品种 */
47
     /** 牛只品种 */
@@ -62,10 +62,10 @@ public class OpenYakEntryDto
62
     /** 当前体重,落库 entry_weight_kg */
62
     /** 当前体重,落库 entry_weight_kg */
63
     private Double weight;
63
     private Double weight;
64
 
64
 
65
-    /** 牛只照片 URL(本期不落库) */
65
+    /** 牛只照片 URL,落库 cattle_picture */
66
     private String cattlePicture;
66
     private String cattlePicture;
67
 
67
 
68
-    /** 档案登记人(本期不落库) */
68
+    /** 档案登记人,落库 registrant */
69
     private String registrant;
69
     private String registrant;
70
 
70
 
71
     /** 入栏时间,落库 entry_date */
71
     /** 入栏时间,落库 entry_date */
@@ -77,7 +77,7 @@ public class OpenYakEntryDto
77
     /** 档案创建时间 */
77
     /** 档案创建时间 */
78
     private String createTime;
78
     private String createTime;
79
 
79
 
80
-    /** 备注,落库 status_change_reason */
80
+    /** 备注,落库 remark */
81
     private String remark;
81
     private String remark;
82
 
82
 
83
     public Long getId()
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
     BizYakAsset selectBizYakAssetByYakNo(String yakNo);
10
     BizYakAsset selectBizYakAssetByYakNo(String yakNo);
11
 
11
 
12
+    BizYakAsset selectBizYakAssetByEarTagNumber(String earTagNumber);
13
+
12
     BizYakAsset selectBizYakAssetByExternalId(Long externalId);
14
     BizYakAsset selectBizYakAssetByExternalId(Long externalId);
13
 
15
 
14
     List<BizYakAsset> selectBizYakAssetList(BizYakAsset query);
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
 import org.springframework.util.CollectionUtils;
8
 import org.springframework.util.CollectionUtils;
9
 import com.ruoyi.common.exception.ServiceException;
9
 import com.ruoyi.common.exception.ServiceException;
10
 import com.ruoyi.common.utils.StringUtils;
10
 import com.ruoyi.common.utils.StringUtils;
11
+import com.ruoyi.web.modules.industryservice.domain.BizFarmingBatch;
11
 import com.ruoyi.web.modules.industryservice.domain.BizPasture;
12
 import com.ruoyi.web.modules.industryservice.domain.BizPasture;
12
 import com.ruoyi.web.modules.industryservice.domain.BizYakAsset;
13
 import com.ruoyi.web.modules.industryservice.domain.BizYakAsset;
13
 import com.ruoyi.web.modules.industryservice.domain.BizYakFeedingRecord;
14
 import com.ruoyi.web.modules.industryservice.domain.BizYakFeedingRecord;
@@ -16,12 +17,15 @@ import com.ruoyi.web.modules.industryservice.domain.BizYakPhysioSeries;
16
 import com.ruoyi.web.modules.industryservice.domain.BizYakReproductionRecord;
17
 import com.ruoyi.web.modules.industryservice.domain.BizYakReproductionRecord;
17
 import com.ruoyi.web.modules.industryservice.domain.dto.YakAssetBundleDto;
18
 import com.ruoyi.web.modules.industryservice.domain.dto.YakAssetBundleDto;
18
 import com.ruoyi.web.modules.industryservice.mapper.BizPastureMapper;
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
 import com.ruoyi.web.modules.industryservice.mapper.BizYakAssetMapper;
22
 import com.ruoyi.web.modules.industryservice.mapper.BizYakAssetMapper;
20
 import com.ruoyi.web.modules.industryservice.mapper.BizYakFeedingRecordMapper;
23
 import com.ruoyi.web.modules.industryservice.mapper.BizYakFeedingRecordMapper;
21
 import com.ruoyi.web.modules.industryservice.support.YakAssetStatusMapper;
24
 import com.ruoyi.web.modules.industryservice.support.YakAssetStatusMapper;
22
 import com.ruoyi.web.modules.industryservice.mapper.BizYakGrowthRecordMapper;
25
 import com.ruoyi.web.modules.industryservice.mapper.BizYakGrowthRecordMapper;
23
 import com.ruoyi.web.modules.industryservice.mapper.BizYakPhysioSeriesMapper;
26
 import com.ruoyi.web.modules.industryservice.mapper.BizYakPhysioSeriesMapper;
24
 import com.ruoyi.web.modules.industryservice.mapper.BizYakReproductionRecordMapper;
27
 import com.ruoyi.web.modules.industryservice.mapper.BizYakReproductionRecordMapper;
28
+import com.ruoyi.web.modules.industryservice.support.FarmingSyncSupport;
25
 import com.ruoyi.web.modules.industryservice.support.YakAssetRules;
29
 import com.ruoyi.web.modules.industryservice.support.YakAssetRules;
26
 import com.ruoyi.web.modules.industryservice.support.YakAssetValidation;
30
 import com.ruoyi.web.modules.industryservice.support.YakAssetValidation;
27
 
31
 
@@ -56,6 +60,12 @@ public class YakAssetBundleSyncTxService
56
     @Autowired
60
     @Autowired
57
     private BizPastureMapper bizPastureMapper;
61
     private BizPastureMapper bizPastureMapper;
58
 
62
 
63
+    @Autowired
64
+    private BizFarmEnclosureMapper bizFarmEnclosureMapper;
65
+
66
+    @Autowired
67
+    private BizFarmingBatchMapper bizFarmingBatchMapper;
68
+
59
     @Autowired
69
     @Autowired
60
     private BizYakPhysioSeriesMapper physioSeriesMapper;
70
     private BizYakPhysioSeriesMapper physioSeriesMapper;
61
 
71
 
@@ -96,6 +106,8 @@ public class YakAssetBundleSyncTxService
96
             YakAssetValidation.validateAssetStatus(incoming.getAssetStatus());
106
             YakAssetValidation.validateAssetStatus(incoming.getAssetStatus());
97
         }
107
         }
98
         resolvePasture(incoming, bundle);
108
         resolvePasture(incoming, bundle);
109
+        resolveEnclosure(incoming);
110
+        resolveBatch(incoming);
99
         if (incoming.getBirthDate() != null)
111
         if (incoming.getBirthDate() != null)
100
         {
112
         {
101
             incoming.setAgeMonths(YakAssetValidation.resolveAgeMonths(incoming.getBirthDate(), syncTime));
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
      * 子表「按需覆盖」:列表为 null 跳过;非 null 则 delete + batchInsert。
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
      * 解析本地牦牛资产主键,供 IoT 数据关联。
102
      * 解析本地牦牛资产主键,供 IoT 数据关联。
103
      * <p>
103
      * <p>
104
      * 优先按第三方 {@code cattleId} 匹配 {@code biz_yak_asset.external_id};
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
      * </p>
107
      * </p>
107
      *
108
      *
108
      * @return {@code biz_yak_asset.id},未匹配则 null
109
      * @return {@code biz_yak_asset.id},未匹配则 null
@@ -122,10 +123,17 @@ public final class FarmingSyncSupport
122
                 return byExternal.getId();
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
             return byYakNo != null ? byYakNo.getId() : null;
137
             return byYakNo != null ? byYakNo.getId() : null;
130
         }
138
         }
131
         return null;
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
  * <h3>主表字段映射(OpenYakEntryDto → BizYakAsset)</h3>
23
  * <h3>主表字段映射(OpenYakEntryDto → BizYakAsset)</h3>
24
  * <ul>
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
  *   <li>farmName → pastureName</li>
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
  *   <li>farmLocation → location</li>
34
  *   <li>farmLocation → location</li>
32
  *   <li>cattleSex → gender(规范为 公/母)</li>
35
  *   <li>cattleSex → gender(规范为 公/母)</li>
33
  *   <li>cattleSource → source</li>
36
  *   <li>cattleSource → source</li>
37
+ *   <li>cattleVariety → cattleVariety</li>
38
+ *   <li>cattlePicture → cattlePicture</li>
39
+ *   <li>registrant → registrant</li>
34
  *   <li>weight → entryWeightKg</li>
40
  *   <li>weight → entryWeightKg</li>
35
  *   <li>entryTime / birthDate → entryDate / birthDate</li>
41
  *   <li>entryTime / birthDate → entryDate / birthDate</li>
36
  *   <li>fatherNumber / motherNumber → fatherYakNo / motherYakNo</li>
42
  *   <li>fatherNumber / motherNumber → fatherYakNo / motherYakNo</li>
37
- *   <li>remark → statusChangeReason</li>
43
+ *   <li>remark → remark</li>
38
  * </ul>
44
  * </ul>
39
  */
45
  */
40
 public final class YakEntryOpenApiMapper
46
 public final class YakEntryOpenApiMapper
@@ -59,33 +65,37 @@ public final class YakEntryOpenApiMapper
59
         {
65
         {
60
             throw new ServiceException("同步数据无效");
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
             throw new ServiceException("第三方牛只 ID 不能为空");
70
             throw new ServiceException("第三方牛只 ID 不能为空");
72
         }
71
         }
72
+        String yakNo = resolveYakNo(dto);
73
+        YakAssetValidation.validateYakNoForSync(yakNo);
73
 
74
 
74
         BizYakAsset asset = new BizYakAsset();
75
         BizYakAsset asset = new BizYakAsset();
75
-        asset.setExternalId(externalId);
76
+        asset.setExternalId(dto.getCattleId());
76
         asset.setYakNo(yakNo);
77
         asset.setYakNo(yakNo);
78
+        asset.setEarTagNumber(trimToNull(dto.getEarTagNumber()));
79
+        asset.setFarmExternalId(dto.getFarmId());
77
         asset.setPastureName(trimToNull(dto.getFarmName()));
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
         asset.setGender(normalizeGender(dto.getCattleSex()));
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
         asset.setBirthDate(parseDate(dto.getBirthDate()));
91
         asset.setBirthDate(parseDate(dto.getBirthDate()));
81
         asset.setEntryDate(parseDate(dto.getEntryTime()));
92
         asset.setEntryDate(parseDate(dto.getEntryTime()));
82
         asset.setEntryWeightKg(toBigDecimal(dto.getWeight()));
93
         asset.setEntryWeightKg(toBigDecimal(dto.getWeight()));
83
         asset.setSource(trimToNull(dto.getCattleSource()));
94
         asset.setSource(trimToNull(dto.getCattleSource()));
84
-        asset.setPenLocation(trimToNull(dto.getFarmEnclosureName()));
85
         asset.setLocation(trimToNull(dto.getFarmLocation()));
95
         asset.setLocation(trimToNull(dto.getFarmLocation()));
86
         asset.setFatherYakNo(trimToNull(dto.getFatherNumber()));
96
         asset.setFatherYakNo(trimToNull(dto.getFatherNumber()));
87
         asset.setMotherYakNo(trimToNull(dto.getMotherNumber()));
97
         asset.setMotherYakNo(trimToNull(dto.getMotherNumber()));
88
-        asset.setStatusChangeReason(trimToNull(dto.getRemark()));
98
+        asset.setRemark(trimToNull(dto.getRemark()));
89
         asset.setAssetStatus(YakAssetRules.ASSET_STATUS_MIN);
99
         asset.setAssetStatus(YakAssetRules.ASSET_STATUS_MIN);
90
 
100
 
91
         YakAssetBundleDto bundle = new YakAssetBundleDto();
101
         YakAssetBundleDto bundle = new YakAssetBundleDto();
@@ -97,13 +107,9 @@ public final class YakEntryOpenApiMapper
97
         return bundle;
107
         return bundle;
98
     }
108
     }
99
 
109
 
100
-    /** 耳标号优先,其次牛只编号 */
110
+    /** 场内牛只编号 cattleNo */
101
     private static String resolveYakNo(OpenYakEntryDto dto)
111
     private static String resolveYakNo(OpenYakEntryDto dto)
102
     {
112
     {
103
-        if (StringUtils.isNotEmpty(dto.getEarTagNumber()))
104
-        {
105
-            return dto.getEarTagNumber().trim();
106
-        }
107
         if (StringUtils.isNotEmpty(dto.getCattleNo()))
113
         if (StringUtils.isNotEmpty(dto.getCattleNo()))
108
         {
114
         {
109
             return dto.getCattleNo().trim();
115
             return dto.getCattleNo().trim();
@@ -111,20 +117,6 @@ public final class YakEntryOpenApiMapper
111
         return null;
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
     private static String normalizeGender(String cattleSex)
120
     private static String normalizeGender(String cattleSex)
129
     {
121
     {
130
         if (StringUtils.isEmpty(cattleSex))
122
         if (StringUtils.isEmpty(cattleSex))

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

@@ -3,44 +3,60 @@
3
 <mapper namespace="com.ruoyi.web.modules.industryservice.mapper.BizYakAssetMapper">
3
 <mapper namespace="com.ruoyi.web.modules.industryservice.mapper.BizYakAssetMapper">
4
 
4
 
5
     <resultMap type="com.ruoyi.web.modules.industryservice.domain.BizYakAsset" id="BizYakAssetResult">
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
     </resultMap>
48
     </resultMap>
37
 
49
 
38
     <sql id="selectVo">
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
         from biz_yak_asset
60
         from biz_yak_asset
45
     </sql>
61
     </sql>
46
 
62
 
@@ -54,6 +70,11 @@
54
         where yak_no = #{yakNo} and del_flag = '0'
70
         where yak_no = #{yakNo} and del_flag = '0'
55
     </select>
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
     <select id="selectBizYakAssetByExternalId" parameterType="long" resultMap="BizYakAssetResult">
78
     <select id="selectBizYakAssetByExternalId" parameterType="long" resultMap="BizYakAssetResult">
58
         <include refid="selectVo"/>
79
         <include refid="selectVo"/>
59
         where external_id = #{externalId} and del_flag = '0'
80
         where external_id = #{externalId} and del_flag = '0'
@@ -64,7 +85,8 @@
64
         <where>
85
         <where>
65
             del_flag = '0'
86
             del_flag = '0'
66
             <if test="keyword != null and keyword != ''">
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
             </if>
90
             </if>
69
             <if test="assetStatus != null">
91
             <if test="assetStatus != null">
70
                 and asset_status = #{assetStatus}
92
                 and asset_status = #{assetStatus}
@@ -75,17 +97,25 @@
75
 
97
 
76
     <insert id="insertBizYakAsset" parameterType="com.ruoyi.web.modules.industryservice.domain.BizYakAsset" useGeneratedKeys="true" keyProperty="id">
98
     <insert id="insertBizYakAsset" parameterType="com.ruoyi.web.modules.industryservice.domain.BizYakAsset" useGeneratedKeys="true" keyProperty="id">
77
         insert into biz_yak_asset (
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
         ) values (
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
     </insert>
120
     </insert>
91
 
121
 
@@ -93,10 +123,22 @@
93
         update biz_yak_asset set
123
         update biz_yak_asset set
94
             external_id = #{externalId},
124
             external_id = #{externalId},
95
             yak_no = #{yakNo},
125
             yak_no = #{yakNo},
126
+            ear_tag_number = #{earTagNumber},
96
             pasture_id = #{pastureId},
127
             pasture_id = #{pastureId},
97
             pasture_name = #{pastureName},
128
             pasture_name = #{pastureName},
129
+            farm_external_id = #{farmExternalId},
130
+            enclosure_external_id = #{enclosureExternalId},
131
+            enclosure_id = #{enclosureId},
132
+            enclosure_name = #{enclosureName},
98
             batch_no = #{batchNo},
133
             batch_no = #{batchNo},
134
+            batch_external_id = #{batchExternalId},
135
+            batch_id = #{batchId},
136
+            entry_cycle = #{entryCycle},
99
             gender = #{gender},
137
             gender = #{gender},
138
+            cattle_variety = #{cattleVariety},
139
+            cattle_picture = #{cattlePicture},
140
+            registrant = #{registrant},
141
+            remark = #{remark},
100
             birth_date = #{birthDate},
142
             birth_date = #{birthDate},
101
             age_months = #{ageMonths},
143
             age_months = #{ageMonths},
102
             entry_date = #{entryDate},
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
         assertTrueContains(xml, "del_flag = '0'");
60
         assertTrueContains(xml, "del_flag = '0'");
61
         assertTrueContains(xml, "status_change_date desc");
61
         assertTrueContains(xml, "status_change_date desc");
62
         assertTrueContains(xml, "yak_no like");
62
         assertTrueContains(xml, "yak_no like");
63
+        assertTrueContains(xml, "ear_tag_number like");
64
+        assertTrueContains(xml, "ear_tag_number");
63
     }
65
     }
64
 
66
 
65
     private static void assertTrueContains(String haystack, String needle)
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
         dto.setCattleNo("CN-001");
23
         dto.setCattleNo("CN-001");
24
         dto.setFarmId(1L);
24
         dto.setFarmId(1L);
25
         dto.setFarmName("示范牧场");
25
         dto.setFarmName("示范牧场");
26
+        dto.setFarmEnclosureId(201L);
26
         dto.setFarmEnclosureName("1号圈舍");
27
         dto.setFarmEnclosureName("1号圈舍");
28
+        dto.setBatchId(301L);
27
         dto.setEntryCycle("B2025-01");
29
         dto.setEntryCycle("B2025-01");
28
         dto.setFarmLocation("东经91.0,北纬32.0");
30
         dto.setFarmLocation("东经91.0,北纬32.0");
29
         dto.setCattleSex("母");
31
         dto.setCattleSex("母");
30
         dto.setCattleSource("自繁");
32
         dto.setCattleSource("自繁");
33
+        dto.setCattleVariety("牦牛");
34
+        dto.setCattlePicture("https://example.com/cattle.jpg");
35
+        dto.setRegistrant("张三");
31
         dto.setFatherNumber("F-01");
36
         dto.setFatherNumber("F-01");
32
         dto.setMotherNumber("M-01");
37
         dto.setMotherNumber("M-01");
33
         dto.setWeight(220.5);
38
         dto.setWeight(220.5);
@@ -39,17 +44,26 @@ class YakEntryOpenApiMapperTest
39
         BizYakAsset asset = bundle.getAsset();
44
         BizYakAsset asset = bundle.getAsset();
40
         assertNotNull(asset);
45
         assertNotNull(asset);
41
         assertEquals(90001L, asset.getExternalId());
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
         assertEquals("示范牧场", asset.getPastureName());
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
         assertEquals("母", asset.getGender());
55
         assertEquals("母", asset.getGender());
46
         assertEquals("自繁", asset.getSource());
56
         assertEquals("自繁", asset.getSource());
57
+        assertEquals("1号圈舍", asset.getEnclosureName());
47
         assertEquals("1号圈舍", asset.getPenLocation());
58
         assertEquals("1号圈舍", asset.getPenLocation());
48
         assertEquals("东经91.0,北纬32.0", asset.getLocation());
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
         assertEquals("F-01", asset.getFatherYakNo());
63
         assertEquals("F-01", asset.getFatherYakNo());
50
         assertEquals("M-01", asset.getMotherYakNo());
64
         assertEquals("M-01", asset.getMotherYakNo());
51
         assertEquals(new BigDecimal("220.5"), asset.getEntryWeightKg());
65
         assertEquals(new BigDecimal("220.5"), asset.getEntryWeightKg());
52
-        assertEquals("测试备注", asset.getStatusChangeReason());
66
+        assertEquals("测试备注", asset.getRemark());
53
         assertEquals("1", bundle.getThirdPartyPastureCode());
67
         assertEquals("1", bundle.getThirdPartyPastureCode());
54
     }
68
     }
55
 }
69
 }

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

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

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

@@ -38,11 +38,23 @@
38
 | --- | --- | --- | --- |
38
 | --- | --- | --- | --- |
39
 | `id` | `bigint(20)` | Y | 主键 |
39
 | `id` | `bigint(20)` | Y | 主键 |
40
 | `external_id` | `bigint(20)` | N | 第三方牛只 ID(`OpenYakEntryDto.cattleId`),**唯一** |
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
 | `pasture_id` | `bigint(20)` | N | 关联 `biz_pasture.id`(映射成功时) |
43
 | `pasture_id` | `bigint(20)` | N | 关联 `biz_pasture.id`(映射成功时) |
43
 | `pasture_name` | `varchar(128)` | N | 所属牧场名称(同步冗余展示) |
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
 | `gender` | `varchar(16)` | N | 性别 |
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
 | `birth_date` | `date` | N | 出生日期 |
58
 | `birth_date` | `date` | N | 出生日期 |
47
 | `age_months` | `int(11)` | N | 月龄(月) |
59
 | `age_months` | `int(11)` | N | 月龄(月) |
48
 | `entry_date` | `date` | N | 入栏日期 |
60
 | `entry_date` | `date` | N | 入栏日期 |
@@ -73,21 +85,26 @@
73
 | OpenAPI 字段 | 库字段 | 说明 |
85
 | OpenAPI 字段 | 库字段 | 说明 |
74
 | --- | --- | --- |
86
 | --- | --- | --- |
75
 | `cattleId` | `external_id` | 同步 upsert 主键 |
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
 | `farmName` | `pasture_name` | 冗余展示 |
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
 | `farmLocation` | `location` | |
96
 | `farmLocation` | `location` | |
82
 | `cattleSex` | `gender` | 归一公/母 |
97
 | `cattleSex` | `gender` | 归一公/母 |
83
 | `cattleSource` | `source` | |
98
 | `cattleSource` | `source` | |
99
+| `cattleVariety` | `cattle_variety` | |
100
+| `cattlePicture` | `cattle_picture` | |
101
+| `registrant` | `registrant` | |
84
 | `fatherNumber` | `father_yak_no` | |
102
 | `fatherNumber` | `father_yak_no` | |
85
 | `motherNumber` | `mother_yak_no` | |
103
 | `motherNumber` | `mother_yak_no` | |
86
 | `weight` | `entry_weight_kg` | 当前体重 |
104
 | `weight` | `entry_weight_kg` | 当前体重 |
87
 | `entryTime` | `entry_date` | |
105
 | `entryTime` | `entry_date` | |
88
 | `birthDate` | `birth_date` | 月龄由同步时计算写入 `age_months` |
106
 | `birthDate` | `birth_date` | 月龄由同步时计算写入 `age_months` |
89
-| `remark` | `status_change_reason` | |
90
-| `id` / `cattlePicture` / `registrant` / `cattleVariety` | — | 本期不落库 |
107
+| `remark` | `remark` | |
91
 
108
 
92
 列表接口未返回的字段(`asset_status`、实时体温/运动量等)保持库内原值或默认「正常」。
109
 列表接口未返回的字段(`asset_status`、实时体温/运动量等)保持库内原值或默认「正常」。
93
 
110
 
@@ -166,7 +183,7 @@
166
 | `biz_yak_collar_location` | `GET /open-api/v1/iot/collar-locations` | `(yak_no, report_time)` | `sql/biz_yak_collar_location.sql` |
183
 | `biz_yak_collar_location` | `GET /open-api/v1/iot/collar-locations` | `(yak_no, report_time)` | `sql/biz_yak_collar_location.sql` |
167
 | `biz_env_monitoring_record` | `GET /open-api/v1/iot/env-monitoring` | `external_id` | `sql/biz_env_monitoring_record.sql` |
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
 ### 2.8 枚举 `asset_status`
188
 ### 2.8 枚举 `asset_status`
172
 
189
 

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

@@ -21,7 +21,7 @@
21
 | ZCZX-MYNZDA-UT-004 | 列表校验 | 关键字 trim | 单元测试 | JUnit5 | 模糊查询 | 无 | `keyword` 含前后空格 | trim 后参与 `LIKE` |
21
 | ZCZX-MYNZDA-UT-004 | 列表校验 | 关键字 trim | 单元测试 | JUnit5 | 模糊查询 | 无 | `keyword` 含前后空格 | trim 后参与 `LIKE` |
22
 | ZCZX-MYNZDA-UT-005 | 同步合并 | 按 external_id 新增 | 单元测试 | JUnit5+Mockito | §5.3 | DB 无该 cattleId | `syncBundle` 新记录 | `insert` 主表;圈舍/批次关联写入 |
22
 | ZCZX-MYNZDA-UT-005 | 同步合并 | 按 external_id 新增 | 单元测试 | JUnit5+Mockito | §5.3 | DB 无该 cattleId | `syncBundle` 新记录 | `insert` 主表;圈舍/批次关联写入 |
23
 | ZCZX-MYNZDA-UT-006 | 同步合并 | 按 external_id 更新 | 单元测试 | JUnit5+Mockito | §5.3 | DB 已有同 cattleId | `syncBundle` 字段变更 | `update` 主表;圈舍/批次覆盖 |
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
 | ZCZX-MYNZDA-UT-007 | 同步安全 | 失败不清库 | 单元测试 | JUnit5+Mockito | §5.3 | 已有档案 A | Mock 第三方抛超时;执行 sync | 档案 A 主表及子表条数不变 |
25
 | ZCZX-MYNZDA-UT-007 | 同步安全 | 失败不清库 | 单元测试 | JUnit5+Mockito | §5.3 | 已有档案 A | Mock 第三方抛超时;执行 sync | 档案 A 主表及子表条数不变 |
26
 | ZCZX-MYNZDA-UT-008 | 同步安全 | 单头失败不回滚全局 | 单元测试 | JUnit5+Mockito | 事务边界 | 批次含 2 头,第 2 头校验失败 | `syncAll` | 第 1 头成功落库;`failCount≥1`;第 1 头数据保留 |
26
 | ZCZX-MYNZDA-UT-008 | 同步安全 | 单头失败不回滚全局 | 单元测试 | JUnit5+Mockito | 事务边界 | 批次含 2 头,第 2 头校验失败 | `syncAll` | 第 1 头成功落库;`failCount≥1`;第 1 头数据保留 |
27
 | ZCZX-MYNZDA-UT-009 | 牧场映射 | 映射成功写 pasture_id | 单元测试 | JUnit5+Mockito | §5.3 | `biz_pasture` 有匹配编码 | 同步带第三方牧场码 | `pasture_id` 非空;`pasture_name` 与主数据一致 |
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
 CREATE TABLE IF NOT EXISTS `biz_yak_asset` (
4
 CREATE TABLE IF NOT EXISTS `biz_yak_asset` (
5
   `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
5
   `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
6
   `external_id` bigint(20) DEFAULT NULL COMMENT '第三方牛只唯一ID OpenYakEntryDto.cattleId',
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
   `pasture_id` bigint(20) DEFAULT NULL COMMENT '牧场ID',
9
   `pasture_id` bigint(20) DEFAULT NULL COMMENT '牧场ID',
9
   `pasture_name` varchar(128) DEFAULT NULL COMMENT '所属牧场名称',
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
   `gender` varchar(16) DEFAULT NULL COMMENT '性别',
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
   `birth_date` date DEFAULT NULL COMMENT '出生日期',
24
   `birth_date` date DEFAULT NULL COMMENT '出生日期',
13
   `age_months` int(11) DEFAULT NULL COMMENT '月龄',
25
   `age_months` int(11) DEFAULT NULL COMMENT '月龄',
14
   `entry_date` date DEFAULT NULL COMMENT '入栏日期',
26
   `entry_date` date DEFAULT NULL COMMENT '入栏日期',
@@ -37,6 +49,10 @@ CREATE TABLE IF NOT EXISTS `biz_yak_asset` (
37
   UNIQUE KEY `uk_yak_no` (`yak_no`),
49
   UNIQUE KEY `uk_yak_no` (`yak_no`),
38
   KEY `idx_asset_status` (`asset_status`),
50
   KEY `idx_asset_status` (`asset_status`),
39
   KEY `idx_pasture_id` (`pasture_id`),
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
   KEY `idx_status_change_date` (`status_change_date`),
56
   KEY `idx_status_change_date` (`status_change_date`),
41
   KEY `idx_last_sync_time` (`last_sync_time`),
57
   KEY `idx_last_sync_time` (`last_sync_time`),
42
   KEY `idx_del_flag` (`del_flag`)
58
   KEY `idx_del_flag` (`del_flag`)