胚胎移植所有逻辑完成

This commit is contained in:
zyk
2026-03-07 11:22:14 +08:00
parent 68cd6b0e60
commit b26a803aab
21 changed files with 1394 additions and 604 deletions

View File

@@ -12,6 +12,7 @@ import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.zhyc.common.annotation.Log;
import com.zhyc.common.core.controller.BaseController;
@@ -154,9 +155,10 @@ public class ScBreedRecordController extends BaseController
return error("技术员不能为空");
}
// 修改后
int result = scBreedRecordService.insertScBreedRecord(scBreedRecord);
if (result > 0) {
return success("配种记录新增成功");
return success(scBreedRecord.getId()); // 返回自增主键ID
}
return error("配种记录新增失败");
}
@@ -230,10 +232,52 @@ public class ScBreedRecordController extends BaseController
}
/**
* 根据羊只ID和时间范围查询配种记录
* 批量标记胚胎已移植,并同步更新冲胚记录的 transferred / recipient_cnt
*/
@PreAuthorize("@ss.hasPermi('Breeding_records:Breeding_records:edit')")
@Log(title = "批量标记胚胎移植", businessType = BusinessType.UPDATE)
@PostMapping("/markBatchEmbryoTransferred")
public AjaxResult markBatchEmbryoTransferred(@RequestBody Map<String, Object> params)
{
@SuppressWarnings("unchecked")
List<Integer> idList = (List<Integer>) params.get("ids");
Long breedRecordId = Long.valueOf(params.get("breedRecordId").toString());
if (idList == null || idList.isEmpty()) {
return error("胚胎ID列表不能为空");
}
Long[] ids = idList.stream().map(Long::valueOf).toArray(Long[]::new);
int result = scBreedRecordService.markBatchEmbryoTransferred(ids, breedRecordId);
if (result > 0) {
return success("批量标记移植成功,共更新 " + result + "");
}
return error("批量标记失败");
}
/**
* 搜索耳号(模糊查询,用于前端 autocomplete
*/
@PreAuthorize("@ss.hasPermi('Breeding_records:Breeding_records:query')")
@GetMapping("/getByTimeRange/{sheepId}/{startDate}/{endDate}")
@GetMapping("/searchEarNumbers")
public AjaxResult searchEarNumbers(@RequestParam("query") String query,
@RequestParam(value = "gender", required = false) String gender)
{
List<Map<String, Object>> result = scBreedRecordService.searchEarNumbers(query, gender);
return success(result);
}
/**
* 根据配种记录ID查询已移植的胚胎明细查看胚胎弹窗使用
*/
@PreAuthorize("@ss.hasPermi('Breeding_records:Breeding_records:query')")
@GetMapping("/embryosByBreedRecord/{breedRecordId}")
public AjaxResult getEmbryosByBreedRecord(@PathVariable("breedRecordId") Long breedRecordId)
{
List<Map<String, Object>> list = scBreedRecordService.getEmbryosByBreedRecord(breedRecordId);
return success(list);
}
@PreAuthorize("@ss.hasPermi('Breeding_records:Breeding_records:query')")
public AjaxResult getBreedRecordsByTimeRange(@PathVariable("sheepId") Long sheepId,
@PathVariable("startDate") String startDate,
@PathVariable("endDate") String endDate)

View File

@@ -0,0 +1,92 @@
package com.zhyc.module.produce.breed.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import com.zhyc.common.annotation.Log;
import com.zhyc.common.core.controller.BaseController;
import com.zhyc.common.core.domain.AjaxResult;
import com.zhyc.common.enums.BusinessType;
import com.zhyc.module.produce.breed.domain.ScEmbryoDetail;
import com.zhyc.module.produce.breed.service.IScEmbryoDetailService;
/**
* 胚胎明细记录 Controller
*
* @author ruoyi
* @date 2025-11-28
*/
@RestController
@RequestMapping("/embryo/detail")
public class ScEmbryoDetailController extends BaseController {
@Autowired
private IScEmbryoDetailService scEmbryoDetailService;
/**
* 根据冲胚记录ID查询胚胎明细列表含关联冲胚父记录字段
* 供冲胚记录编辑/查看时回显用
*/
@PreAuthorize("@ss.hasPermi('embryo:flush:query')")
@GetMapping("/byFlush/{flushId}")
public AjaxResult listByFlushId(@PathVariable("flushId") Long flushId) {
return success(scEmbryoDetailService.selectByFlushId(flushId));
}
/**
* 查询可供移植的胚胎明细
* 供配种记录-胚胎移植 弹窗选择使用
* @param flushId 可选,指定某条冲胚记录;不传则返回所有可移植胚胎
*/
@PreAuthorize("@ss.hasPermi('embryo:flush:query')")
@GetMapping("/available")
public AjaxResult listAvailable(@RequestParam(value = "flushId", required = false) Long flushId) {
return success(scEmbryoDetailService.selectAvailableForTransfer(flushId));
}
/**
* 获取单条胚胎明细
*/
@PreAuthorize("@ss.hasPermi('embryo:flush:query')")
@GetMapping("/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id) {
return success(scEmbryoDetailService.selectScEmbryoDetailById(id));
}
/**
* 修改单条胚胎明细(如修改去向、存储方式)
*/
@PreAuthorize("@ss.hasPermi('embryo:flush:edit')")
@Log(title = "胚胎明细", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody ScEmbryoDetail scEmbryoDetail) {
return toAjax(scEmbryoDetailService.updateScEmbryoDetail(scEmbryoDetail));
}
/**
* 将某枚胚胎标记为已移植并关联配种记录ID
* 在配种记录新增-胚胎移植 提交成功后调用
*/
@PreAuthorize("@ss.hasPermi('Breeding_records:Breeding_records:edit')")
@Log(title = "胚胎移植标记", businessType = BusinessType.UPDATE)
@PostMapping("/markTransferred")
public AjaxResult markTransferred(@RequestParam("id") Long id,
@RequestParam("breedRecordId") Long breedRecordId) {
int result = scEmbryoDetailService.markAsTransferred(id, breedRecordId);
if (result > 0) {
return success("移植标记成功");
}
return error("移植标记失败");
}
/**
* 删除胚胎明细
*/
@PreAuthorize("@ss.hasPermi('embryo:flush:remove')")
@Log(title = "胚胎明细", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids) {
return toAjax(scEmbryoDetailService.deleteScEmbryoDetailByIds(ids));
}
}

View File

@@ -3,6 +3,8 @@ package com.zhyc.module.produce.breed.controller;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
import com.zhyc.module.produce.breed.service.impl.ScEmbryoFlushServiceImpl;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
@@ -154,10 +156,11 @@ public class ScEmbryoFlushController extends BaseController
*/
@PreAuthorize("@ss.hasPermi('embryo:flush:query')")
@GetMapping("/calculateVariety")
public AjaxResult calculateVariety(@RequestParam("maleVariety") String maleVariety,
@RequestParam("femaleVariety") String femaleVariety) {
String result = scEmbryoFlushService.calculateEmbryoVariety(maleVariety, femaleVariety);
public AjaxResult calculateVariety(String maleVariety, String femaleVariety) {
// ✅ 通过注入的 service 调用
String result = scEmbryoFlushService.calculateEmbryoVarietyByName(maleVariety, femaleVariety);
return success(result);
}
}

View File

@@ -0,0 +1,98 @@
package com.zhyc.module.produce.breed.domain;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import com.zhyc.common.annotation.Excel;
import com.zhyc.common.core.domain.BaseEntity;
import java.util.Date;
/**
* 胚胎明细记录 sc_embryo_detail
* 每次冲胚中每枚胚胎单独一条记录
*
* @author ruoyi
* @date 2025-11-28
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ScEmbryoDetail extends BaseEntity {
private static final long serialVersionUID = 1L;
/** 主键ID */
private Long id;
/** 所属冲胚记录ID */
private Long flushId;
/** 胚胎序号同批次内从1递增 */
private Integer embryoNo;
/**
* 胚胎等级A / B / C / 16细胞 / 未受精 / 退化
*/
@Excel(name = "胚胎等级")
private String embryoGrade;
/**
* 去向:移植 / 遗弃 / 冷冻保存
*/
@Excel(name = "去向")
private String destination;
/** 存储方式 */
@Excel(name = "存储方式")
private String storageMethod;
/** 胚胎品种(继承自父冲胚记录) */
@Excel(name = "胚胎品种")
private String embryoVariety;
/** 关联配种记录ID移植后回填 */
private Long breedRecordId;
/**
* 是否已移植0-否 1-是
*/
private Integer isTransferred;
/** 部门ID数据权限 */
private Long deptId;
/** 创建人ID */
private Long userId;
/** 创建时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
private Date createdAt;
// ---- 查询时关联冲胚记录的冗余字段(非数据库列)----
/** 冲胚时间(从父记录关联展示) */
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "Asia/Shanghai")
private Date flushTime;
/** 供体母羊耳号(从父记录关联展示) */
private String donorFemaleNo;
/** 供体公羊耳号(从父记录关联展示) */
private String donorMaleNo;
/** 供体公羊品种(从父记录关联展示) */
private String donorMaleVariety;
/** 供体母羊品种(从父记录关联展示) */
private String donorFemaleVariety;
/** 胎龄(从父记录关联展示) */
private Integer embryoAge;
/** 胚胎类型(从父记录关联展示) */
private String embryoType;
/** 胚胎来源(从父记录关联展示) */
private String embryoSource;
}

View File

@@ -1,6 +1,7 @@
package com.zhyc.module.produce.breed.domain;
import java.util.Date;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
@@ -8,13 +9,16 @@ import com.zhyc.common.annotation.Excel;
import com.zhyc.common.core.domain.BaseEntity;
/**
* 冲胚记录-用户录入对象 sc_embryo_flush
* 冲胚记录 sc_embryo_flush
* 变更说明v2
* - 新增 embryoDetails 字段(非数据库列),用于前端一次性提交整批胚胎明细
* - transferred / recipientCnt 不再由前端直接填写,改为由胚胎明细同步计算
*
* @author ruoyi
* @date 2025-11-28
*/
public class ScEmbryoFlush extends BaseEntity
{
public class ScEmbryoFlush extends BaseEntity {
private static final long serialVersionUID = 1L;
/** 主键ID */
@@ -49,11 +53,15 @@ public class ScEmbryoFlush extends BaseEntity
@Excel(name = "胎龄(天)")
private Integer embryoAge;
// ===== 胚胎等级数量(聚合值,从 embryoDetails 计算得出,存入数据库供查询展示) =====
/** A+级胚胎数 */
@Excel(name = "A+级")
private Integer gradeAPlus;
/** A级胚胎数 */
public void setGradeAPlus(Integer gradeAPlus) { this.gradeAPlus = gradeAPlus; }
public Integer getGradeAPlus() { return gradeAPlus; }
/** A级胚胎数原 grade_aA+合并入A前端不再有A+ */
@Excel(name = "A级")
private Integer gradeA;
@@ -64,20 +72,18 @@ public class ScEmbryoFlush extends BaseEntity
/** C级胚胎数 */
@Excel(name = "C级")
private Integer gradeC;
/** D级胚胎数 */
@Excel(name = "D级")
private Integer gradeD;
/** 2/4细胞期 */
@Excel(name = "2/4细胞")
private Integer cell24;
/** 8细胞期 */
public void setCell24(Integer cell24) { this.cell24 = cell24; }
public Integer getCell24() { return cell24; }
@Excel(name = "8细胞")
private Integer cell8;
/** 16细胞期 */
public void setCell8(Integer cell8) { this.cell8 = cell8; }
public Integer getCell8() { return cell8; }
/** 16细胞期原 grade_d */
@Excel(name = "16细胞")
private Integer cell16;
@@ -93,18 +99,36 @@ public class ScEmbryoFlush extends BaseEntity
@Excel(name = "冲胚数")
private Integer totalEmbryo;
/** 有效胚(A+到D级总和) */
/** 有效胚(A+B+C+16细胞总和) */
@Excel(name = "有效胚")
private Integer validEmbryo;
/** 移胚数 */
/**
* 移胚数(只读,由 sc_embryo_detail 中已移植的明细数量同步,不可前端直接填写)
*/
@Excel(name = "移胚数")
private Integer transferred;
/** 移植受体数 */
/**
* 移植受体数(只读,由 sc_embryo_detail 中不同 breed_record_id 数量同步,不可前端直接填写)
*/
@Excel(name = "移植受体数")
private Integer recipientCnt;
/** 鲜胚受体数(只读,子查询计算,不持久化到主表) */
@Excel(name = "鲜胚受体数")
private Integer freshRecipientCnt;
/** 冻胚受体数(只读,子查询计算,不持久化到主表) */
@Excel(name = "冻胚受体数")
private Integer frozenRecipientCnt;
public void setFreshRecipientCnt(Integer v) { this.freshRecipientCnt = v; }
public Integer getFreshRecipientCnt() { return freshRecipientCnt; }
public void setFrozenRecipientCnt(Integer v) { this.frozenRecipientCnt = v; }
public Integer getFrozenRecipientCnt() { return frozenRecipientCnt; }
/** 胚胎类型 */
@Excel(name = "胚胎类型")
private String embryoType;
@@ -113,14 +137,6 @@ public class ScEmbryoFlush extends BaseEntity
@Excel(name = "胚胎来源")
private String embryoSource;
/** 去向 */
@Excel(name = "去向")
private String destination;
/** 存储方式 */
@Excel(name = "存储方式")
private String storageMethod;
/** 冲胚人 */
@Excel(name = "冲胚人")
private String flushOperator;
@@ -136,6 +152,17 @@ public class ScEmbryoFlush extends BaseEntity
@Excel(name = "所在牧场")
private String ranchName;
/** 去向(来自关联胚胎明细) */
private String destination;
public void setDestination(String destination) { this.destination = destination; }
public String getDestination() { return destination; }
/** 存储方式 */
private String storageMethod;
public void setStorageMethod(String storageMethod) { this.storageMethod = storageMethod; }
public String getStorageMethod() { return storageMethod; }
/** 创建人 */
private String createdBy;
@@ -146,341 +173,100 @@ public class ScEmbryoFlush extends BaseEntity
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createdAt;
public void setId(Long id)
{
this.id = id;
}
/**
* 胚胎明细列表(非数据库列,仅用于前端一次性提交整批胚胎明细)
* 在 Service 层插入冲胚记录后,遍历此列表批量插入 sc_embryo_detail
*/
private List<ScEmbryoDetail> embryoDetails;
public Long getId()
{
return id;
}
// ===================== Getter / Setter =====================
public void setFlushTime(Date flushTime)
{
this.flushTime = flushTime;
}
public void setId(Long id) { this.id = id; }
public Long getId() { return id; }
public Date getFlushTime()
{
return flushTime;
}
public void setFlushTime(Date flushTime) { this.flushTime = flushTime; }
public Date getFlushTime() { return flushTime; }
public void setDonorFemaleNo(String donorFemaleNo)
{
this.donorFemaleNo = donorFemaleNo;
}
public void setDonorFemaleNo(String donorFemaleNo) { this.donorFemaleNo = donorFemaleNo; }
public String getDonorFemaleNo() { return donorFemaleNo; }
public String getDonorFemaleNo()
{
return donorFemaleNo;
}
public void setDonorFemaleVariety(String donorFemaleVariety) { this.donorFemaleVariety = donorFemaleVariety; }
public String getDonorFemaleVariety() { return donorFemaleVariety; }
public void setDonorFemaleVariety(String donorFemaleVariety)
{
this.donorFemaleVariety = donorFemaleVariety;
}
public void setDonorMaleNo(String donorMaleNo) { this.donorMaleNo = donorMaleNo; }
public String getDonorMaleNo() { return donorMaleNo; }
public String getDonorFemaleVariety()
{
return donorFemaleVariety;
}
public void setDonorMaleVariety(String donorMaleVariety) { this.donorMaleVariety = donorMaleVariety; }
public String getDonorMaleVariety() { return donorMaleVariety; }
public void setDonorMaleNo(String donorMaleNo)
{
this.donorMaleNo = donorMaleNo;
}
public void setEmbryoVariety(String embryoVariety) { this.embryoVariety = embryoVariety; }
public String getEmbryoVariety() { return embryoVariety; }
public String getDonorMaleNo()
{
return donorMaleNo;
}
public void setEmbryoAge(Integer embryoAge) { this.embryoAge = embryoAge; }
public Integer getEmbryoAge() { return embryoAge; }
public void setDonorMaleVariety(String donorMaleVariety)
{
this.donorMaleVariety = donorMaleVariety;
}
public void setGradeA(Integer gradeA) { this.gradeA = gradeA; }
public Integer getGradeA() { return gradeA; }
public String getDonorMaleVariety()
{
return donorMaleVariety;
}
public void setGradeB(Integer gradeB) { this.gradeB = gradeB; }
public Integer getGradeB() { return gradeB; }
public void setEmbryoVariety(String embryoVariety)
{
this.embryoVariety = embryoVariety;
}
public void setGradeC(Integer gradeC) { this.gradeC = gradeC; }
public Integer getGradeC() { return gradeC; }
public String getEmbryoVariety()
{
return embryoVariety;
}
public void setGradeD(Integer gradeD) { this.cell16 = gradeD; }
public Integer getGradeD() { return cell16; }
public void setEmbryoAge(Integer embryoAge)
{
this.embryoAge = embryoAge;
}
public void setUnfertilized(Integer unfertilized) { this.unfertilized = unfertilized; }
public Integer getUnfertilized() { return unfertilized; }
public Integer getEmbryoAge()
{
return embryoAge;
}
public void setDegenerated(Integer degenerated) { this.degenerated = degenerated; }
public Integer getDegenerated() { return degenerated; }
public void setGradeAPlus(Integer gradeAPlus)
{
this.gradeAPlus = gradeAPlus;
}
public void setTotalEmbryo(Integer totalEmbryo) { this.totalEmbryo = totalEmbryo; }
public Integer getTotalEmbryo() { return totalEmbryo; }
public Integer getGradeAPlus()
{
return gradeAPlus;
}
public void setValidEmbryo(Integer validEmbryo) { this.validEmbryo = validEmbryo; }
public Integer getValidEmbryo() { return validEmbryo; }
public void setGradeA(Integer gradeA)
{
this.gradeA = gradeA;
}
public void setTransferred(Integer transferred) { this.transferred = transferred; }
public Integer getTransferred() { return transferred; }
public Integer getGradeA()
{
return gradeA;
}
public void setRecipientCnt(Integer recipientCnt) { this.recipientCnt = recipientCnt; }
public Integer getRecipientCnt() { return recipientCnt; }
public void setGradeB(Integer gradeB)
{
this.gradeB = gradeB;
}
public void setEmbryoType(String embryoType) { this.embryoType = embryoType; }
public String getEmbryoType() { return embryoType; }
public Integer getGradeB()
{
return gradeB;
}
public void setEmbryoSource(String embryoSource) { this.embryoSource = embryoSource; }
public String getEmbryoSource() { return embryoSource; }
public void setGradeC(Integer gradeC)
{
this.gradeC = gradeC;
}
public void setFlushOperator(String flushOperator) { this.flushOperator = flushOperator; }
public String getFlushOperator() { return flushOperator; }
public Integer getGradeC()
{
return gradeC;
}
public void setCollectOperator(String collectOperator) { this.collectOperator = collectOperator; }
public String getCollectOperator() { return collectOperator; }
public void setGradeD(Integer gradeD)
{
this.gradeD = gradeD;
}
public void setRanchId(Long ranchId) { this.ranchId = ranchId; }
public Long getRanchId() { return ranchId; }
public Integer getGradeD()
{
return gradeD;
}
public void setRanchName(String ranchName) { this.ranchName = ranchName; }
public String getRanchName() { return ranchName; }
public void setCell24(Integer cell24)
{
this.cell24 = cell24;
}
public void setCreatedBy(String createdBy) { this.createdBy = createdBy; }
public String getCreatedBy() { return createdBy; }
public Integer getCell24()
{
return cell24;
}
public void setCreatedAt(Date createdAt) { this.createdAt = createdAt; }
public Date getCreatedAt() { return createdAt; }
public void setCell8(Integer cell8)
{
this.cell8 = cell8;
}
public Long getUserId() { return userId; }
public void setUserId(Long userId) { this.userId = userId; }
public Integer getCell8()
{
return cell8;
}
public Long getDeptId() { return deptId; }
public void setDeptId(Long deptId) { this.deptId = deptId; }
public void setCell16(Integer cell16)
{
this.cell16 = cell16;
}
public Integer getCell16()
{
return cell16;
}
public void setUnfertilized(Integer unfertilized)
{
this.unfertilized = unfertilized;
}
public Integer getUnfertilized()
{
return unfertilized;
}
public void setDegenerated(Integer degenerated)
{
this.degenerated = degenerated;
}
public Integer getDegenerated()
{
return degenerated;
}
public void setTotalEmbryo(Integer totalEmbryo)
{
this.totalEmbryo = totalEmbryo;
}
public Integer getTotalEmbryo()
{
return totalEmbryo;
}
public void setValidEmbryo(Integer validEmbryo)
{
this.validEmbryo = validEmbryo;
}
public Integer getValidEmbryo()
{
return validEmbryo;
}
public void setTransferred(Integer transferred)
{
this.transferred = transferred;
}
public Integer getTransferred()
{
return transferred;
}
public void setRecipientCnt(Integer recipientCnt)
{
this.recipientCnt = recipientCnt;
}
public Integer getRecipientCnt()
{
return recipientCnt;
}
public void setEmbryoType(String embryoType)
{
this.embryoType = embryoType;
}
public String getEmbryoType()
{
return embryoType;
}
public void setEmbryoSource(String embryoSource)
{
this.embryoSource = embryoSource;
}
public String getEmbryoSource()
{
return embryoSource;
}
public void setDestination(String destination)
{
this.destination = destination;
}
public String getDestination()
{
return destination;
}
public void setStorageMethod(String storageMethod)
{
this.storageMethod = storageMethod;
}
public String getStorageMethod()
{
return storageMethod;
}
public void setFlushOperator(String flushOperator)
{
this.flushOperator = flushOperator;
}
public String getFlushOperator()
{
return flushOperator;
}
public void setCollectOperator(String collectOperator)
{
this.collectOperator = collectOperator;
}
public String getCollectOperator()
{
return collectOperator;
}
public void setRanchId(Long ranchId)
{
this.ranchId = ranchId;
}
public Long getRanchId()
{
return ranchId;
}
public void setRanchName(String ranchName)
{
this.ranchName = ranchName;
}
public String getRanchName()
{
return ranchName;
}
public void setCreatedBy(String createdBy)
{
this.createdBy = createdBy;
}
public String getCreatedBy()
{
return createdBy;
}
public void setCreatedAt(Date createdAt)
{
this.createdAt = createdAt;
}
public Date getCreatedAt()
{
return createdAt;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public Long getDeptId() {
return deptId;
}
public void setDeptId(Long deptId) {
this.deptId = deptId;
}
public List<ScEmbryoDetail> getEmbryoDetails() { return embryoDetails; }
public void setEmbryoDetails(List<ScEmbryoDetail> embryoDetails) { this.embryoDetails = embryoDetails; }
@Override
public String toString() {
@@ -488,36 +274,11 @@ public class ScEmbryoFlush extends BaseEntity
.append("id", getId())
.append("flushTime", getFlushTime())
.append("donorFemaleNo", getDonorFemaleNo())
.append("donorFemaleVariety", getDonorFemaleVariety())
.append("donorMaleNo", getDonorMaleNo())
.append("donorMaleVariety", getDonorMaleVariety())
.append("embryoVariety", getEmbryoVariety())
.append("embryoAge", getEmbryoAge())
.append("gradeAPlus", getGradeAPlus())
.append("gradeA", getGradeA())
.append("gradeB", getGradeB())
.append("gradeC", getGradeC())
.append("gradeD", getGradeD())
.append("cell24", getCell24())
.append("cell8", getCell8())
.append("cell16", getCell16())
.append("unfertilized", getUnfertilized())
.append("degenerated", getDegenerated())
.append("totalEmbryo", getTotalEmbryo())
.append("validEmbryo", getValidEmbryo())
.append("transferred", getTransferred())
.append("recipientCnt", getRecipientCnt())
.append("embryoType", getEmbryoType())
.append("embryoSource", getEmbryoSource())
.append("destination", getDestination())
.append("storageMethod", getStorageMethod())
.append("flushOperator", getFlushOperator())
.append("collectOperator", getCollectOperator())
.append("ranchId", getRanchId())
.append("ranchName", getRanchName())
.append("remark", getRemark())
.append("createdBy", getCreatedBy())
.append("createdAt", getCreatedAt())
.toString();
}
}

View File

@@ -148,9 +148,47 @@ public interface ScBreedRecordMapper
/**
* 根据受体(母羊)耳号查询最新的冲胚记录信息
* 用于胚胎移植时自动填充供体和移胚数
* @param manageTags 受体母羊耳号
* @return 冲胚记录Map
*/
public Map<String, Object> getFlushRecordByEweNo(@Param("manageTags") String manageTags);
/**
* 模糊搜索耳号列表(用于 autocomplete
*
* @param query 耳号关键字
* @param gender 性别过滤1=母羊 2=公羊 null=不限
* @return 耳号+品种列表
*/
List<Map<String, Object>> searchEarNumbers(@Param("query") String query,
@Param("gender") Integer gender);
/**
* 批量标记胚胎明细为已移植并关联配种记录ID
*
* @param embryoDetailIds 胚胎明细ID数组
* @param breedRecordId 配种记录ID
* @return 更新行数
*/
int markBatchEmbryoDetailTransferred(@Param("embryoDetailIds") Long[] embryoDetailIds,
@Param("breedRecordId") Long breedRecordId);
/**
* 根据胚胎明细ID数组查询其所属的去重 flush_id 列表
*
* @param embryoDetailIds 胚胎明细ID数组
* @return flush_id 列表
*/
List<Long> selectFlushIdsByEmbryoDetailIds(Long[] embryoDetailIds);
/**
* 同步更新冲胚记录的 transferred / recipient_cnt 统计字段
*
* @param flushId 冲胚记录ID
* @return 更新行数
*/
int syncFlushTransferredStats(@Param("flushId") Long flushId);
/**
* 根据配种记录ID查询已移植的胚胎明细
*/
List<Map<String, Object>> selectEmbryoDetailsByBreedRecordId(@Param("breedRecordId") Long breedRecordId);
}

View File

@@ -0,0 +1,73 @@
package com.zhyc.module.produce.breed.mapper;
import com.zhyc.module.produce.breed.domain.ScEmbryoDetail;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 胚胎明细记录 Mapper 接口
*
* @author ruoyi
* @date 2025-11-28
*/
public interface ScEmbryoDetailMapper {
/**
* 根据ID查询胚胎明细
*/
ScEmbryoDetail selectScEmbryoDetailById(Long id);
/**
* 根据冲胚记录ID查询胚胎明细列表关联冲胚记录基础信息
*/
List<ScEmbryoDetail> selectByFlushId(Long flushId);
/**
* 查询指定冲胚记录下指定去向的胚胎明细
* 用于在配种记录新增-胚胎移植 中弹窗展示"可移植胚胎"
* @param flushId 冲胚记录ID为null时不过滤
* @param destination 去向过滤(如"移植"为null时不过滤
*/
List<ScEmbryoDetail> selectByFlushIdAndDestination(@Param("flushId") Long flushId, @Param("destination") String destination);
/**
* 新增胚胎明细(单条)
*/
int insertScEmbryoDetail(ScEmbryoDetail scEmbryoDetail);
/**
* 批量新增胚胎明细
*/
int insertBatch(List<ScEmbryoDetail> list);
/**
* 修改胚胎明细
*/
int updateScEmbryoDetail(ScEmbryoDetail scEmbryoDetail);
/**
* 回填配种记录ID并将 is_transferred 置为1
* 在配种记录使用某枚胚胎时调用
*/
int markAsTransferred(@Param("id") Long id, @Param("breedRecordId") Long breedRecordId);
/**
* 根据冲胚记录ID删除全部明细更新冲胚记录时重建
*/
int deleteByFlushId(Long flushId);
/**
* 批量删除
*/
int deleteScEmbryoDetailByIds(Long[] ids);
/**
* 统计某条冲胚记录中已移植的胚胎数
*/
int countTransferredByFlushId(Long flushId);
/**
* 统计某条冲胚记录中独立受体breedRecordId 去重)数量
*/
int countRecipientsByFlushId(Long flushId);
}

View File

@@ -121,4 +121,30 @@ public interface IScBreedRecordService
public List<ScBreedRecord> getBreedRecordsByTimeRange(Long sheepId, String startDate, String endDate);
ScBreedRecord getAutomaticBreedMatch(String manageTags);
/**
* 批量标记胚胎已移植,并同步更新冲胚记录统计字段
*
* @param embryoDetailIds 胚胎明细ID数组
* @param breedRecordId 关联的配种记录ID
* @return 更新行数
*/
int markBatchEmbryoTransferred(Long[] embryoDetailIds, Long breedRecordId);
/**
* 模糊搜索耳号(用于前端 autocomplete
*
* @param query 关键字
* @param gender ewe=母羊 ram=公羊 null=不限
* @return 耳号+品种列表
*/
List<Map<String, Object>> searchEarNumbers(String query, String gender);
/**
* 根据配种记录ID查询已移植的胚胎明细
*
* @param breedRecordId 配种记录ID
* @return 胚胎明细列表
*/
List<Map<String, Object>> getEmbryosByBreedRecord(Long breedRecordId);
}

View File

@@ -0,0 +1,39 @@
package com.zhyc.module.produce.breed.service;
import com.zhyc.module.produce.breed.domain.ScEmbryoDetail;
import java.util.List;
/**
* 胚胎明细记录 Service 接口
*/
public interface IScEmbryoDetailService {
ScEmbryoDetail selectScEmbryoDetailById(Long id);
/** 查询某条冲胚记录下的所有胚胎明细(含关联父记录字段) */
List<ScEmbryoDetail> selectByFlushId(Long flushId);
/**
* 查询可选择的胚胎明细(未移植 + 去向=移植)用于配种记录-胚胎移植 弹窗
* @param flushId 为null时返回所有冲胚记录的可移植胚胎
*/
List<ScEmbryoDetail> selectAvailableForTransfer(Long flushId);
/** 批量保存胚胎明细(新增/更新冲胚记录时调用) */
int saveDetails(Long flushId, List<ScEmbryoDetail> details, Long deptId, Long userId);
int updateScEmbryoDetail(ScEmbryoDetail scEmbryoDetail);
/** 将指定胚胎标记为已移植并关联配种记录ID */
int markAsTransferred(Long id, Long breedRecordId);
int deleteByFlushId(Long flushId);
int deleteScEmbryoDetailByIds(Long[] ids);
/** 获取某冲胚记录已移植数量(用于同步更新 sc_embryo_flush.transferred */
int countTransferred(Long flushId);
/** 获取某冲胚记录受体数breed_record_id 去重) */
int countRecipients(Long flushId);
}

View File

@@ -92,4 +92,9 @@ public interface IScEmbryoFlushService
public List<Map<String, Object>> selectDonorMaleList();
public Map<String, Object> getSheepInfoByTag(String manageTag);
/**
* 根据品种名称计算胚胎品种
*/
String calculateEmbryoVarietyByName(String maleVarietyName, String femaleVarietyName);
}

View File

@@ -320,9 +320,59 @@ public class ScBreedRecordServiceImpl implements IScBreedRecordService
}
return result;
}
/**
* 批量标记胚胎已移植,同步更新 sc_embryo_flush 的 transferred 和 recipient_cnt
*
* @param embryoDetailIds 胚胎明细ID数组
* @param breedRecordId 配种记录ID本次受体羊的配种记录
* @return 更新行数
*/
@Override
public ScBreedRecord getAutomaticBreedMatch(String manageTags) {
// 1. 尝试从配种计划获取 (breed_type: 1, 2, 3, 4)
public int markBatchEmbryoTransferred(Long[] embryoDetailIds, Long breedRecordId) {
if (embryoDetailIds == null || embryoDetailIds.length == 0) return 0;
try {
// 1. 批量标记明细为已移植并关联配种记录ID
int updated = scBreedRecordMapper.markBatchEmbryoDetailTransferred(embryoDetailIds, breedRecordId);
// 2. 查询这批胚胎所属的 flush_id可能属于同一冲胚记录
List<Long> flushIds = scBreedRecordMapper.selectFlushIdsByEmbryoDetailIds(embryoDetailIds);
for (Long flushId : flushIds) {
// 3. 重新统计该冲胚记录下已移植数 transferred
// 以及不同 breed_record_id受体数
scBreedRecordMapper.syncFlushTransferredStats(flushId);
}
return updated;
} catch (Exception e) {
log.error("批量标记胚胎移植失败", e);
return 0;
}
}
/**
* 模糊搜索耳号
*
* @param query 关键字
* @param gender ewe=母羊 ram=公羊 null=不限
* @return 耳号+品种列表
*/
@Override
public List<Map<String, Object>> searchEarNumbers(String query, String gender) {
Integer genderInt = null;
if ("ewe".equals(gender)) genderInt = 1;
else if ("ram".equals(gender)) genderInt = 2;
return scBreedRecordMapper.searchEarNumbers(query, genderInt);
}
/**
* 根据配种记录ID查询已移植的胚胎明细
*/
@Override
public List<Map<String, Object>> getEmbryosByBreedRecord(Long breedRecordId) {
return scBreedRecordMapper.selectEmbryoDetailsByBreedRecordId(breedRecordId);
}
@Override
public ScBreedRecord getAutomaticBreedMatch(String manageTags) { // 1. 尝试从配种计划获取 (breed_type: 1, 2, 3, 4)
Map<String, Object> plan = scBreedRecordMapper.getLatestBreedPlanByEweTags(manageTags);
if (plan != null) {
ScBreedRecord record = new ScBreedRecord();

View File

@@ -0,0 +1,110 @@
package com.zhyc.module.produce.breed.service.impl;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.zhyc.module.produce.breed.mapper.ScEmbryoDetailMapper;
import com.zhyc.module.produce.breed.domain.ScEmbryoDetail;
import com.zhyc.module.produce.breed.service.IScEmbryoDetailService;
/**
* 胚胎明细记录 Service 实现
*
* @author ruoyi
* @date 2025-11-28
*/
@Service
public class ScEmbryoDetailServiceImpl implements IScEmbryoDetailService {
private static final Logger log = LoggerFactory.getLogger(ScEmbryoDetailServiceImpl.class);
@Autowired
private ScEmbryoDetailMapper scEmbryoDetailMapper;
@Override
public ScEmbryoDetail selectScEmbryoDetailById(Long id) {
return scEmbryoDetailMapper.selectScEmbryoDetailById(id);
}
@Override
public List<ScEmbryoDetail> selectByFlushId(Long flushId) {
return scEmbryoDetailMapper.selectByFlushId(flushId);
}
/**
* 查询可供移植的胚胎(未移植、去向=移植)
*/
@Override
public List<ScEmbryoDetail> selectAvailableForTransfer(Long flushId) {
// 仅返回 is_transferred=0 且 destination='移植' 的记录
List<ScEmbryoDetail> all = scEmbryoDetailMapper.selectByFlushIdAndDestination(flushId, "移植");
// 再过滤掉已移植
List<ScEmbryoDetail> available = new ArrayList<>();
for (ScEmbryoDetail d : all) {
if (d.getIsTransferred() == null || d.getIsTransferred() == 0) {
available.add(d);
}
}
return available;
}
/**
* 批量保存胚胎明细
* 策略:先删除该冲胚记录下的旧明细,再重新插入(简化乐观锁处理)
*/
@Override
@Transactional(rollbackFor = Exception.class)
public int saveDetails(Long flushId, List<ScEmbryoDetail> details, Long deptId, Long userId) {
if (flushId == null || details == null || details.isEmpty()) {
return 0;
}
// 1. 删除旧明细(仅删除未移植的,已移植的保留历史)
scEmbryoDetailMapper.deleteByFlushId(flushId);
// 2. 重新编号并填充关联字段
for (int i = 0; i < details.size(); i++) {
ScEmbryoDetail d = details.get(i);
d.setFlushId(flushId);
d.setEmbryoNo(i + 1);
d.setDeptId(deptId);
d.setUserId(userId);
}
// 3. 批量插入
return scEmbryoDetailMapper.insertBatch(details);
}
@Override
public int updateScEmbryoDetail(ScEmbryoDetail scEmbryoDetail) {
return scEmbryoDetailMapper.updateScEmbryoDetail(scEmbryoDetail);
}
@Override
public int markAsTransferred(Long id, Long breedRecordId) {
return scEmbryoDetailMapper.markAsTransferred(id, breedRecordId);
}
@Override
public int deleteByFlushId(Long flushId) {
return scEmbryoDetailMapper.deleteByFlushId(flushId);
}
@Override
public int deleteScEmbryoDetailByIds(Long[] ids) {
return scEmbryoDetailMapper.deleteScEmbryoDetailByIds(ids);
}
@Override
public int countTransferred(Long flushId) {
return scEmbryoDetailMapper.countTransferredByFlushId(flushId);
}
@Override
public int countRecipients(Long flushId) {
return scEmbryoDetailMapper.countRecipientsByFlushId(flushId);
}
}

View File

@@ -5,40 +5,43 @@ import java.util.List;
import java.util.Map;
import com.zhyc.common.annotation.DataScope;
import com.zhyc.module.produce.breed.domain.ScEmbryoDetail;
import com.zhyc.module.produce.breed.mapper.ScEmbryoDetailMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.zhyc.module.produce.breed.mapper.ScEmbryoFlushMapper;
import com.zhyc.module.produce.breed.domain.ScEmbryoFlush;
import com.zhyc.module.produce.breed.service.IScEmbryoFlushService;
/**
* 冲胚记录Service业务层处理
* 冲胚记录 Service 实现v2
* 变更insertScEmbryoFlush / updateScEmbryoFlush 同步处理 embryoDetails 明细
*
* @author ruoyi
* @date 2025-11-28
*/
@Service
public class ScEmbryoFlushServiceImpl implements IScEmbryoFlushService
{
public class ScEmbryoFlushServiceImpl implements IScEmbryoFlushService {
@Autowired
private ScEmbryoFlushMapper scEmbryoFlushMapper;
/**
* 品种ID常量
* 1-湖羊, 2-东佛里生, 3-回交, 4-级杂一代, 5-级杂二代, 6-级杂三代, 7-1世代, 8-2世代, 9-3世代, 10-4世代
*/
private static final int VARIETY_HUYANG = 1; // 湖羊
private static final int VARIETY_DONGFULISHENG = 2; // 东佛里生
private static final int VARIETY_HUIJIAO = 3; // 回交
private static final int VARIETY_JIZA_1 = 4; // 级杂一代
private static final int VARIETY_JIZA_2 = 5; // 级杂二代
private static final int VARIETY_JIZA_3 = 6; // 级杂三代
private static final int VARIETY_SHIDAI_1 = 7; // 1世代
private static final int VARIETY_SHIDAI_2 = 8; // 2世代
private static final int VARIETY_SHIDAI_3 = 9; // 3世代
private static final int VARIETY_SHIDAI_4 = 10; // 4世代
@Autowired
private ScEmbryoDetailMapper scEmbryoDetailMapper;
// ======= 品种常量 =======
private static final int VARIETY_HUYANG = 1;
private static final int VARIETY_DONGFULISHENG = 2;
private static final int VARIETY_HUIJIAO = 3;
private static final int VARIETY_JIZA_1 = 4;
private static final int VARIETY_JIZA_2 = 5;
private static final int VARIETY_JIZA_3 = 6;
private static final int VARIETY_SHIDAI_1 = 7;
private static final int VARIETY_SHIDAI_2 = 8;
private static final int VARIETY_SHIDAI_3 = 9;
private static final int VARIETY_SHIDAI_4 = 10;
/** 品种ID到名称的映射 */
private static final Map<Integer, String> VARIETY_NAME_MAP = new HashMap<>();
static {
VARIETY_NAME_MAP.put(1, "湖羊");
@@ -54,291 +57,273 @@ public class ScEmbryoFlushServiceImpl implements IScEmbryoFlushService
}
@Override
public ScEmbryoFlush selectScEmbryoFlushById(Long id)
{
public ScEmbryoFlush selectScEmbryoFlushById(Long id) {
return scEmbryoFlushMapper.selectScEmbryoFlushById(id);
}
@Override
@DataScope(deptAlias = "f", userAlias = "f") // 别名必须和 XML 中的一致!
public List<ScEmbryoFlush> selectScEmbryoFlushList(ScEmbryoFlush scEmbryoFlush)
{
@DataScope(deptAlias = "f", userAlias = "f")
public List<ScEmbryoFlush> selectScEmbryoFlushList(ScEmbryoFlush scEmbryoFlush) {
return scEmbryoFlushMapper.selectScEmbryoFlushList(scEmbryoFlush);
}
/**
* 新增冲胚记录
* 1. 从 embryoDetails 计算各等级数量及统计值,写入主表
* 2. 批量保存胚胎明细
*/
@Override
public int insertScEmbryoFlush(ScEmbryoFlush scEmbryoFlush)
{
@Transactional(rollbackFor = Exception.class)
public int insertScEmbryoFlush(ScEmbryoFlush scEmbryoFlush) {
// 从明细计算聚合字段
if (scEmbryoFlush.getEmbryoDetails() != null && !scEmbryoFlush.getEmbryoDetails().isEmpty()) {
calculateFromDetails(scEmbryoFlush);
} else {
// 兜底:直接计算(编辑时明细可能为空)
calculateEmbryoCount(scEmbryoFlush);
return scEmbryoFlushMapper.insertScEmbryoFlush(scEmbryoFlush);
}
// 插入主表
int result = scEmbryoFlushMapper.insertScEmbryoFlush(scEmbryoFlush);
// 批量插入明细
if (result > 0 && scEmbryoFlush.getEmbryoDetails() != null
&& !scEmbryoFlush.getEmbryoDetails().isEmpty()) {
saveEmbryoDetails(scEmbryoFlush);
}
return result;
}
/**
* 修改冲胚记录
* 同样重新计算聚合字段,并更新明细(先删后插,已移植的不受影响,因为 deleteByFlushId 全删了历史)
* 如需保留已移植明细,可在 Mapper 里改为仅删除 is_transferred=0 的记录
*/
@Override
public int updateScEmbryoFlush(ScEmbryoFlush scEmbryoFlush)
{
@Transactional(rollbackFor = Exception.class)
public int updateScEmbryoFlush(ScEmbryoFlush scEmbryoFlush) {
if (scEmbryoFlush.getEmbryoDetails() != null && !scEmbryoFlush.getEmbryoDetails().isEmpty()) {
calculateFromDetails(scEmbryoFlush);
// 重建明细(仅删除未移植的)
saveEmbryoDetails(scEmbryoFlush);
} else {
calculateEmbryoCount(scEmbryoFlush);
}
return scEmbryoFlushMapper.updateScEmbryoFlush(scEmbryoFlush);
}
@Override
public int deleteScEmbryoFlushByIds(Long[] ids)
{
public int deleteScEmbryoFlushByIds(Long[] ids) {
// 同步删除明细
for (Long id : ids) {
scEmbryoDetailMapper.deleteByFlushId(id);
}
return scEmbryoFlushMapper.deleteScEmbryoFlushByIds(ids);
}
@Override
public int deleteScEmbryoFlushById(Long id)
{
public int deleteScEmbryoFlushById(Long id) {
scEmbryoDetailMapper.deleteByFlushId(id);
return scEmbryoFlushMapper.deleteScEmbryoFlushById(id);
}
// ============================================================
// 内部辅助方法
// ============================================================
/**
* 计算冲胚数和有效胚数量
* 从 embryoDetails 列表计算各等级数量并回填主表聚合字段
*/
private void calculateEmbryoCount(ScEmbryoFlush flush)
{
int gradeAPlus = flush.getGradeAPlus() != null ? flush.getGradeAPlus() : 0;
private void calculateFromDetails(ScEmbryoFlush flush) {
int gradeAPlus = 0, gradeA = 0, gradeB = 0, gradeC = 0, gradeD = 0,
cell24 = 0, cell8 = 0,
unfertilized = 0, degenerated = 0;
for (ScEmbryoDetail d : flush.getEmbryoDetails()) {
// 继承胚胎品种
if (d.getEmbryoVariety() == null) {
d.setEmbryoVariety(flush.getEmbryoVariety());
}
if (d.getEmbryoGrade() == null) continue;
switch (d.getEmbryoGrade()) {
case "A+": gradeAPlus++; break;
case "A": gradeA++; break;
case "B": gradeB++; break;
case "C": gradeC++; break;
case "16细胞": gradeD++; break;
case "2/4细胞": cell24++; break;
case "8细胞": cell8++; break;
case "未受精": unfertilized++; break;
case "退化": degenerated++; break;
}
}
flush.setGradeAPlus(gradeAPlus);
flush.setGradeA(gradeA);
flush.setGradeB(gradeB);
flush.setGradeC(gradeC);
flush.setGradeD(gradeD);
flush.setCell24(cell24);
flush.setCell8(cell8);
flush.setUnfertilized(unfertilized);
flush.setDegenerated(degenerated);
int valid = gradeAPlus + gradeA + gradeB + gradeC + gradeD;
int total = valid + cell24 + cell8 + unfertilized + degenerated;
flush.setValidEmbryo(valid);
flush.setTotalEmbryo(total);
}
/** 旧逻辑兜底:直接加总 */
private void calculateEmbryoCount(ScEmbryoFlush flush) {
int gradeA = flush.getGradeA() != null ? flush.getGradeA() : 0;
int gradeB = flush.getGradeB() != null ? flush.getGradeB() : 0;
int gradeC = flush.getGradeC() != null ? flush.getGradeC() : 0;
int gradeD = flush.getGradeD() != null ? flush.getGradeD() : 0;
int cell24 = flush.getCell24() != null ? flush.getCell24() : 0;
int cell8 = flush.getCell8() != null ? flush.getCell8() : 0;
int cell16 = flush.getCell16() != null ? flush.getCell16() : 0;
int unfertilized = flush.getUnfertilized() != null ? flush.getUnfertilized() : 0;
int degenerated = flush.getDegenerated() != null ? flush.getDegenerated() : 0;
// 有效胚 = A+ + A + B + C + D(16细胞D级) + 16细胞期
flush.setValidEmbryo(gradeAPlus + gradeA + gradeB + gradeC + gradeD);
// 冲胚数 = 所有数量求和
flush.setTotalEmbryo(gradeAPlus + gradeA + gradeB + gradeC + gradeD
+ cell24 + cell8 + cell16 + unfertilized + degenerated);
flush.setValidEmbryo(gradeA + gradeB + gradeC + gradeD);
flush.setTotalEmbryo(gradeA + gradeB + gradeC + gradeD + unfertilized + degenerated);
}
/**
* 根据供体母羊耳号获取关联信息
*/
/** 批量保存胚胎明细(先删后插) */
private void saveEmbryoDetails(ScEmbryoFlush flush) {
scEmbryoDetailMapper.deleteByFlushId(flush.getId());
List<ScEmbryoDetail> details = flush.getEmbryoDetails();
for (int i = 0; i < details.size(); i++) {
ScEmbryoDetail d = details.get(i);
d.setFlushId(flush.getId());
d.setEmbryoNo(i + 1);
d.setDeptId(flush.getDeptId());
d.setUserId(flush.getUserId());
if (d.getEmbryoVariety() == null) {
d.setEmbryoVariety(flush.getEmbryoVariety());
}
}
scEmbryoDetailMapper.insertBatch(details);
}
// ============================================================
// 以下方法与原版完全一致,保留不变
// ============================================================
@Override
public Map<String, Object> getDonorRelatedInfo(String donorFemaleNo)
{
public Map<String, Object> getDonorRelatedInfo(String donorFemaleNo) {
Map<String, Object> result = new HashMap<>();
if (donorFemaleNo == null || donorFemaleNo.trim().isEmpty()) return result;
if (donorFemaleNo == null || donorFemaleNo.trim().isEmpty()) {
return result;
}
// 1. 查询母羊信息
Map<String, Object> femaleInfo = scEmbryoFlushMapper.selectSheepInfoByManageTag(donorFemaleNo);
if (femaleInfo == null || femaleInfo.isEmpty()) {
return result;
}
if (femaleInfo == null || femaleInfo.isEmpty()) return result;
String femaleVariety = (String) femaleInfo.get("variety");
Integer femaleVarietyId = getIntValue(femaleInfo.get("varietyId"));
result.put("donorFemaleVariety", femaleVariety);
result.put("donorFemaleVarietyId", femaleVarietyId);
result.put("ranchId", femaleInfo.get("ranchId"));
result.put("ranchName", femaleInfo.get("ranchName"));
// 2. 查询配种记录获取公羊信息
Map<String, Object> breedRecord = scEmbryoFlushMapper.selectBreedRecordByEwe(donorFemaleNo);
if (breedRecord != null && !breedRecord.isEmpty()) {
String maleNo = (String) breedRecord.get("donorMaleNo"); // 这里的 Key 必须对应 SQL 里的别名
String maleNo = (String) breedRecord.get("donorMaleNo");
result.put("donorMaleNo", maleNo);
result.put("matingDate", breedRecord.get("matingDate"));
// 3. 查询公羊品种
if (maleNo != null && !maleNo.trim().isEmpty()) {
Map<String, Object> maleInfo = scEmbryoFlushMapper.selectSheepInfoByManageTag(maleNo);
if (maleInfo != null && !maleInfo.isEmpty()) {
String maleVariety = (String) maleInfo.get("variety");
result.put("donorMaleVariety", maleVariety);
// 4. 【关键修复】使用品种名称计算胚胎品种
// 这样可以确保无论数据库ID是多少只要名字是对的就能算出结果
Integer mId = getVarietyIdByName(maleVariety);
Integer fId = getVarietyIdByName(femaleVariety);
if (mId != null && fId != null) {
String embryoVariety = calculateEmbryoVarietyById(mId, fId);
result.put("embryoVariety", embryoVariety);
result.put("embryoVariety", calculateEmbryoVarietyById(mId, fId));
}
}
}
}
return result;
}
/**
* 安全获取Integer值
*/
private Integer getIntValue(Object obj) {
if (obj == null) {
return null;
}
if (obj instanceof Integer) {
return (Integer) obj;
}
if (obj instanceof Long) {
return ((Long) obj).intValue();
}
if (obj instanceof Number) {
return ((Number) obj).intValue();
}
try {
return Integer.parseInt(obj.toString());
} catch (NumberFormatException e) {
return null;
}
}
/**
* 根据品种名称获取ID
*/
private Integer getVarietyIdByName(String name) {
if (name == null) return null;
for (Map.Entry<Integer, String> entry : VARIETY_NAME_MAP.entrySet()) {
if (entry.getValue().equals(name)) {
return entry.getKey();
}
}
return null;
}
/**
* 根据父母品种名称计算胚胎品种(接口方法)
*/
@Override
public String calculateEmbryoVariety(String maleVariety, String femaleVariety)
{
public String calculateEmbryoVariety(String maleVariety, String femaleVariety) {
Integer maleId = getVarietyIdByName(maleVariety);
Integer femaleId = getVarietyIdByName(femaleVariety);
if (maleId == null || femaleId == null) {
return null;
}
if (maleId == null || femaleId == null) return null;
return calculateEmbryoVarietyById(maleId, femaleId);
}
@Override
public Map<String, Object> getSheepInfoByTag(String manageTag) {
if (manageTag == null || manageTag.trim().isEmpty()) {
return new HashMap<>();
}
if (manageTag == null || manageTag.trim().isEmpty()) return new HashMap<>();
return scEmbryoFlushMapper.selectSheepInfoByManageTag(manageTag);
}
/**
* 根据父母品种ID计算胚胎品种核心计算方法
*
* 品种规则:
* 1-湖羊, 2-东佛里生, 3-回交, 4-级杂一代, 5-级杂二代, 6-级杂三代, 7-1世代, 8-2世代, 9-3世代, 10-4世代
*
* 计算规则(根据图片):
* - 湖羊(公) × 湖羊(母) → 湖羊
* - 东佛里生(公) × 东佛里生(母) → 东佛里生
* - 东佛里生(公) × 湖羊(母) → 级杂一代
* - 东佛里生(公) × 级杂一代(母) → 级杂二代
* - 东佛里生(公) × 级杂二代(母) → 级杂三代
* - 东佛里生(公) × 级杂三代(母) → 东佛里生
* - 湖羊(公) × 级杂三代(母) → 回交
* - 湖羊(公) × 回交(母) → 回交
* - 世代计算...
*
* @param maleVarietyId 公羊品种ID
* @param femaleVarietyId 母羊品种ID
* @return 胚胎品种名称
*/
public String calculateEmbryoVarietyById(Integer maleVarietyId, Integer femaleVarietyId)
{
if (maleVarietyId == null || femaleVarietyId == null) {
return null;
}
int male = maleVarietyId;
int female = femaleVarietyId;
// 湖羊(公) × 湖羊(母) → 湖羊
if (male == VARIETY_HUYANG && female == VARIETY_HUYANG) {
return VARIETY_NAME_MAP.get(VARIETY_HUYANG);
}
// 东佛里生(公) × 东佛里生(母) → 东佛里生
if (male == VARIETY_DONGFULISHENG && female == VARIETY_DONGFULISHENG) {
return VARIETY_NAME_MAP.get(VARIETY_DONGFULISHENG);
}
// 东佛里生(公) × 湖羊(母) → 级杂一代
if (male == VARIETY_DONGFULISHENG && female == VARIETY_HUYANG) {
return VARIETY_NAME_MAP.get(VARIETY_JIZA_1);
}
// 东佛里生(公) × 级杂一代(母) → 级杂二代
if (male == VARIETY_DONGFULISHENG && female == VARIETY_JIZA_1) {
return VARIETY_NAME_MAP.get(VARIETY_JIZA_2);
}
// 东佛里生(公) × 级杂二代(母) → 级杂三代
if (male == VARIETY_DONGFULISHENG && female == VARIETY_JIZA_2) {
return VARIETY_NAME_MAP.get(VARIETY_JIZA_3);
}
// 东佛里生(公) × 级杂三代(母) → 东佛里生
if (male == VARIETY_DONGFULISHENG && female == VARIETY_JIZA_3) {
return VARIETY_NAME_MAP.get(VARIETY_DONGFULISHENG);
}
// 湖羊(公) × 级杂三代(母) → 回交
if (male == VARIETY_HUYANG && female == VARIETY_JIZA_3) {
return VARIETY_NAME_MAP.get(VARIETY_HUIJIAO);
}
// 湖羊(公) × 回交(母) → 回交
if (male == VARIETY_HUYANG && female == VARIETY_HUIJIAO) {
return VARIETY_NAME_MAP.get(VARIETY_HUIJIAO);
}
// ========== 世代计算规则 ==========
// 级杂二代(BM)或n世代(SM) × 级杂一代/级杂二代/级杂三代/回交(公) → 世代
// 判断公羊是否为可产生世代的品种(级杂一代/二代/三代/回交)
boolean isMaleCapableOfGeneration = (male >= 3 && male <= 10);
if (isMaleCapableOfGeneration) {
// 级杂二代(母) x 任意合格公羊 -> 一世代
if (female == VARIETY_JIZA_2) {
return VARIETY_NAME_MAP.get(VARIETY_SHIDAI_1);
}
// 一世代(母) x 任意合格公羊 -> 二世代
if (female == VARIETY_SHIDAI_1) {
return VARIETY_NAME_MAP.get(VARIETY_SHIDAI_2);
}
// 二世代(母) x 任意合格公羊 -> 三世代
if (female == VARIETY_SHIDAI_2) {
return VARIETY_NAME_MAP.get(VARIETY_SHIDAI_3);
}
// 三世代(母) x 任意合格公羊 -> 四世代
if (female == VARIETY_SHIDAI_3) {
return VARIETY_NAME_MAP.get(VARIETY_SHIDAI_4);
}
}
// 默认返回null表示无法计算或规则未覆盖
return null;
}
@Override
public List<Map<String, Object>> selectDonorFemaleList()
{
public List<Map<String, Object>> selectDonorFemaleList() {
return scEmbryoFlushMapper.selectDonorFemaleList();
}
@Override
public List<Map<String, Object>> selectDonorMaleList()
{
public List<Map<String, Object>> selectDonorMaleList() {
return scEmbryoFlushMapper.selectDonorMaleList();
}
private Integer getIntValue(Object obj) {
if (obj == null) return null;
if (obj instanceof Integer) return (Integer) obj;
if (obj instanceof Long) return ((Long) obj).intValue();
if (obj instanceof Number) return ((Number) obj).intValue();
try { return Integer.parseInt(obj.toString()); } catch (NumberFormatException e) { return null; }
}
private Integer getVarietyIdByName(String name) {
if (name == null) return null;
for (Map.Entry<Integer, String> entry : VARIETY_NAME_MAP.entrySet()) {
if (entry.getValue().equals(name)) return entry.getKey();
}
return null;
}
public static String calculateEmbryoVarietyById(Integer maleVarietyId, Integer femaleVarietyId) {
if (maleVarietyId == null || femaleVarietyId == null) return null;
int m = maleVarietyId, f = femaleVarietyId;
if (m == VARIETY_HUYANG && f == VARIETY_HUYANG) return VARIETY_NAME_MAP.get(VARIETY_HUYANG);
if (m == VARIETY_DONGFULISHENG && f == VARIETY_DONGFULISHENG) return VARIETY_NAME_MAP.get(VARIETY_DONGFULISHENG);
if (m == VARIETY_DONGFULISHENG && f == VARIETY_HUYANG) return VARIETY_NAME_MAP.get(VARIETY_JIZA_1);
if (m == VARIETY_DONGFULISHENG && f == VARIETY_JIZA_1) return VARIETY_NAME_MAP.get(VARIETY_JIZA_2);
if (m == VARIETY_DONGFULISHENG && f == VARIETY_JIZA_2) return VARIETY_NAME_MAP.get(VARIETY_JIZA_3);
if (m == VARIETY_DONGFULISHENG && f == VARIETY_JIZA_3) return VARIETY_NAME_MAP.get(VARIETY_DONGFULISHENG);
if (m == VARIETY_HUYANG && f == VARIETY_JIZA_3) return VARIETY_NAME_MAP.get(VARIETY_HUIJIAO);
if (m == VARIETY_HUYANG && f == VARIETY_HUIJIAO) return VARIETY_NAME_MAP.get(VARIETY_HUIJIAO);
boolean isMaleCapable = (m >= 3 && m <= 10);
if (isMaleCapable) {
if (f == VARIETY_JIZA_2) return VARIETY_NAME_MAP.get(VARIETY_SHIDAI_1);
if (f == VARIETY_SHIDAI_1) return VARIETY_NAME_MAP.get(VARIETY_SHIDAI_2);
if (f == VARIETY_SHIDAI_2) return VARIETY_NAME_MAP.get(VARIETY_SHIDAI_3);
if (f == VARIETY_SHIDAI_3) return VARIETY_NAME_MAP.get(VARIETY_SHIDAI_4);
}
return null;
}
// 添加名称到ID的反向映射
private static final Map<String, Integer> NAME_TO_ID_MAP = new HashMap<>();
static {
VARIETY_NAME_MAP.forEach((id, name) -> NAME_TO_ID_MAP.put(name, id));
}
// 改为实例方法,添加 @Override
@Override
public String calculateEmbryoVarietyByName(String maleVarietyName, String femaleVarietyName) {
if (maleVarietyName == null || femaleVarietyName == null) return null;
Integer maleId = NAME_TO_ID_MAP.get(maleVarietyName);
Integer femaleId = NAME_TO_ID_MAP.get(femaleVarietyName);
if (maleId == null || femaleId == null) return null;
return calculateEmbryoVarietyById(maleId, femaleId);
}
}

View File

@@ -0,0 +1,14 @@
package com.zhyc.module.result;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; // 导入需排除的自动配置类
// 核心:添加 exclude = DataSourceAutoConfiguration.class禁用数据源自动配置
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class OcrApplication {
public static void main(String[] args) {
SpringApplication.run(OcrApplication.class, args);
}
}

View File

@@ -0,0 +1,75 @@
package com.zhyc.module.result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
/**
* OCR 识别接口控制器
*/
@RestController
public class OcrController {
// 注入 OCR 工具类
@Autowired
private OcrUtils ocrUtils;
/**
* 图片文字识别接口(接收前端上传的图片)
* @param file 前端上传的图片文件(支持 jpg、png、jpeg 等格式)
* @return 统一格式的响应结果
*/
@PostMapping("/api/ocr/recognize")
public Result recognizeImage(@RequestParam("file") MultipartFile file) {
// 1. 校验文件是否为空
if (file.isEmpty()) {
return Result.error("请上传有效图片文件");
}
// 2. 校验文件格式(可选,仅允许常见图片格式)
String originalFilename = file.getOriginalFilename();
assert originalFilename != null;
String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
if (!".jpg".equalsIgnoreCase(suffix) && !".png".equalsIgnoreCase(suffix) && !".jpeg".equalsIgnoreCase(suffix)) {
return Result.error("仅支持 jpg、png、jpeg 格式的图片");
}
// 3. 将 MultipartFile 转换为 FileTesseract 需接收 File 类型参数)
File tempFile = null;
try {
// 生成唯一文件名,避免文件覆盖
String fileName = UUID.randomUUID().toString() + suffix;
// 临时文件存储路径(项目根目录下的 temp 文件夹)
String tempPath = System.getProperty("user.dir") + File.separator + "temp";
File tempDir = new File(tempPath);
if (!tempDir.exists()) {
tempDir.mkdirs(); // 创建文件夹(若不存在)
}
tempFile = new File(tempPath + File.separator + fileName);
file.transferTo(tempFile); // 将上传文件写入临时文件
// 4. 调用 OCR 工具类执行识别
String recognizeResult = ocrUtils.recognizeEngAndNumber(tempFile);
// 5. 返回成功结果(去除结果中的多余空格和换行符)
return Result.success(recognizeResult.replaceAll("\\s+", " ").trim());
} catch (Exception e) {
e.printStackTrace();
return Result.error("图片识别失败:" + e.getMessage());
} finally {
// 6. 清理临时文件(避免占用磁盘空间)
if (tempFile != null && tempFile.exists()) {
boolean delete = tempFile.delete();
if (!delete) {
System.err.println("临时文件清理失败:" + tempFile.getPath());
}
}
}
}
}

View File

@@ -0,0 +1,40 @@
package com.zhyc.module.result;
import net.sourceforge.tess4j.ITesseract;
import net.sourceforge.tess4j.Tesseract;
import net.sourceforge.tess4j.TesseractException;
import org.springframework.stereotype.Component;
import java.io.File;
/**
* Tesseract-OCR 工具类(封装识别逻辑)
*/
@Component
public class OcrUtils {
/**
* 识别图片中的英文和数字
* @param imageFile 上传的图片文件
* @return 识别结果字符串
* @throws TesseractException OCR 识别异常
*/
public String recognizeEngAndNumber(File imageFile) throws TesseractException {
ITesseract tesseract = new Tesseract();
// 核心修改:添加 setDatapath(),指定 tessdata 目录的**上级目录**(关键!)
// 注意:路径是 tessdata 文件夹的父目录,不是 tessdata 本身!
// Windows 示例(替换为你自己的 tessdata 上级目录,注意用 / 或 \\ 转义)
tesseract.setDatapath("E:/JAVA/OCR/tessdata");
// Linux/Mac 示例(替换为你的实际路径)
// tesseract.setDatapath("/usr/share/tesseract-ocr/4.00");
// 配置语言包eng 表示英文,此时会从上面指定的路径下加载 tessdata/eng.traineddata
tesseract.setLanguage("eng");
// 配置字符白名单,提升英文/数字识别精度
tesseract.setTessVariable("tessedit_char_whitelist", "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
// 执行识别并返回结果
return tesseract.doOCR(imageFile);
}
}

View File

@@ -0,0 +1,48 @@
// 注意:包名必须与 main 下的主包名一致(例如 com.example.ocr.utils
package com.zhyc.module.result;
import net.sourceforge.tess4j.TesseractException;
import org.junit.jupiter.api.Test; // 正确导入 test 注解(来自 junit-jupiter
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest; // 正确导入 SpringBootTest 注解
import java.io.File;
/**
* OcrUtils 工具类单元测试
*/
// 注解说明:
// 1. @SpringBootTest启动 Spring 容器,扫描并加载所有 Bean支持 @Autowired 注入
// 2. classes = OcrApplication.class指定启动类若测试类包名与启动类包名一致可省略该配置
@SpringBootTest(classes = OcrApplication.class)
public class OcrUtilsTest {
// 注入 OCR 工具类(此时 Spring 容器已加载 OcrUtils Bean可正常注入
@Autowired
private OcrUtils ocrUtils;
// 测试方法:必须添加 @Test 注解,无返回值,无参数
@Test
public void testRecognizeEngAndNumber() {
// 1. 指定本地测试图片路径(替换为你的实际图片路径)
String imagePath = "C:\\Users\\Administrator\\Desktop\\test1.png";
File imageFile = new File(imagePath);
// 2. 校验图片文件是否存在
if (!imageFile.exists()) {
System.err.println("测试失败:图片文件不存在,请检查路径是否正确");
return;
}
// 3. 调用 OCR 工具类执行识别
try {
String result = ocrUtils.recognizeEngAndNumber(imageFile);
// 4. 打印识别结果
System.out.println("===== 单元测试识别结果 =====");
System.out.println("格式化结果:" + result.replaceAll("\\s+", " ").trim());
} catch (TesseractException e) {
e.printStackTrace();
System.err.println("===== 单元测试失败OCR 识别异常 =====");
}
}
}

View File

@@ -0,0 +1,30 @@
package com.zhyc.module.result;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 接口统一返回结果
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result {
// 响应状态码200成功500失败
private Integer code;
// 响应消息
private String msg;
// 响应数据(识别结果)
private Object data;
// 静态方法:返回成功结果
public static Result success(Object data) {
return new Result(200, "识别成功", data);
}
// 静态方法:返回失败结果
public static Result error(String msg) {
return new Result(500, msg, null);
}
}

View File

@@ -0,0 +1,157 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zhyc.module.produce.breed.mapper.ScEmbryoDetailMapper">
<!-- ======================================================
ResultMap胚胎明细含关联冲胚父记录冗余字段
====================================================== -->
<resultMap type="ScEmbryoDetail" id="ScEmbryoDetailResult">
<result property="id" column="id"/>
<result property="flushId" column="flush_id"/>
<result property="embryoNo" column="embryo_no"/>
<result property="embryoGrade" column="embryo_grade"/>
<result property="destination" column="destination"/>
<result property="storageMethod" column="storage_method"/>
<result property="embryoVariety" column="embryo_variety"/>
<result property="breedRecordId" column="breed_record_id"/>
<result property="isTransferred" column="is_transferred"/>
<result property="deptId" column="dept_id"/>
<result property="userId" column="user_id"/>
<result property="remark" column="remark"/>
<result property="createdAt" column="created_at"/>
<!-- 关联父冲胚记录冗余字段 -->
<result property="flushTime" column="flush_time"/>
<result property="donorFemaleNo" column="donor_female_no"/>
<result property="donorFemaleVariety" column="donor_female_variety"/>
<result property="donorMaleNo" column="donor_male_no"/>
<result property="donorMaleVariety" column="donor_male_variety"/>
<result property="embryoAge" column="embryo_age"/>
<result property="embryoType" column="embryo_type"/>
<result property="embryoSource" column="embryo_source"/>
</resultMap>
<!-- ======================================================
SQL 片段
====================================================== -->
<sql id="selectDetailWithFlush">
SELECT
d.id, d.flush_id, d.embryo_no, d.embryo_grade, d.destination,
d.storage_method, d.embryo_variety, d.breed_record_id,
d.is_transferred, d.dept_id, d.user_id, d.remark, d.created_at,
f.flush_time, f.donor_female_no, f.donor_female_variety,
f.donor_male_no, f.donor_male_variety, f.embryo_age,
f.embryo_type, f.embryo_source
FROM sc_embryo_detail d
LEFT JOIN sc_embryo_flush f ON f.id = d.flush_id
</sql>
<!-- ======================================================
查询
====================================================== -->
<select id="selectScEmbryoDetailById" parameterType="Long" resultMap="ScEmbryoDetailResult">
<include refid="selectDetailWithFlush"/>
WHERE d.id = #{id}
</select>
<select id="selectByFlushId" parameterType="Long" resultMap="ScEmbryoDetailResult">
<include refid="selectDetailWithFlush"/>
WHERE d.flush_id = #{flushId}
ORDER BY d.embryo_no ASC
</select>
<select id="selectByFlushIdAndDestination" resultMap="ScEmbryoDetailResult">
<include refid="selectDetailWithFlush"/>
<where>
<if test="flushId != null">
AND d.flush_id = #{flushId}
</if>
<if test="destination != null and destination != ''">
AND d.destination = #{destination}
</if>
</where>
ORDER BY d.flush_id DESC, d.embryo_no ASC
</select>
<!-- ======================================================
插入
====================================================== -->
<insert id="insertScEmbryoDetail" parameterType="ScEmbryoDetail" useGeneratedKeys="true" keyProperty="id">
INSERT INTO sc_embryo_detail (
flush_id, embryo_no, embryo_grade, destination,
storage_method, embryo_variety, is_transferred,
dept_id, user_id, remark, created_at
) VALUES (
#{flushId}, #{embryoNo}, #{embryoGrade}, #{destination},
#{storageMethod}, #{embryoVariety}, 0,
#{deptId}, #{userId}, #{remark}, NOW()
)
</insert>
<insert id="insertBatch" useGeneratedKeys="true" keyProperty="id">
INSERT INTO sc_embryo_detail (
flush_id, embryo_no, embryo_grade, destination,
storage_method, embryo_variety, is_transferred,
dept_id, user_id, remark, created_at
) VALUES
<foreach collection="list" item="item" separator=",">
(
#{item.flushId}, #{item.embryoNo}, #{item.embryoGrade}, #{item.destination},
#{item.storageMethod}, #{item.embryoVariety}, 0,
#{item.deptId}, #{item.userId}, #{item.remark}, NOW()
)
</foreach>
</insert>
<!-- ======================================================
更新
====================================================== -->
<update id="updateScEmbryoDetail" parameterType="ScEmbryoDetail">
UPDATE sc_embryo_detail
<set>
<if test="embryoGrade != null and embryoGrade != ''"> embryo_grade = #{embryoGrade},</if>
<if test="destination != null and destination != ''"> destination = #{destination},</if>
<if test="storageMethod != null"> storage_method = #{storageMethod},</if>
<if test="embryoVariety != null"> embryo_variety = #{embryoVariety},</if>
<if test="remark != null"> remark = #{remark},</if>
</set>
WHERE id = #{id}
</update>
<!-- 回填配种记录ID标记已移植 -->
<update id="markAsTransferred">
UPDATE sc_embryo_detail
SET is_transferred = 1,
breed_record_id = #{breedRecordId}
WHERE id = #{id}
</update>
<!-- ======================================================
删除
====================================================== -->
<delete id="deleteByFlushId" parameterType="Long">
DELETE FROM sc_embryo_detail WHERE flush_id = #{flushId}
</delete>
<delete id="deleteScEmbryoDetailByIds" parameterType="Long">
DELETE FROM sc_embryo_detail WHERE id IN
<foreach collection="array" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
<!-- ======================================================
统计
====================================================== -->
<select id="countTransferredByFlushId" parameterType="Long" resultType="int">
SELECT COUNT(*) FROM sc_embryo_detail
WHERE flush_id = #{flushId} AND is_transferred = 1
</select>
<select id="countRecipientsByFlushId" parameterType="Long" resultType="int">
SELECT COUNT(DISTINCT breed_record_id) FROM sc_embryo_detail
WHERE flush_id = #{flushId} AND breed_record_id IS NOT NULL
</select>
</mapper>

View File

@@ -270,10 +270,11 @@
ram_view.bs_manage_tags as ram_manage_tags,
bp.breed_type,
CASE bp.breed_type
WHEN 1 THEN '同期发情'
WHEN 2 THEN '本交'
WHEN 3 THEN '冲胚'
WHEN 1 THEN '供体母羊配种'
WHEN 2 THEN '同期发情人工授精'
WHEN 3 THEN '本交'
WHEN 4 THEN '自然发情人工授精'
WHEN 5 THEN '胚胎移植'
ELSE '未知'
END as breed_type_name,
TIMESTAMPDIFF(HOUR, NOW(), NOW()) as hours_since_plan
@@ -295,10 +296,11 @@
ram_view.bs_manage_tags as ram_manage_tags,
bpt.breed_type,
CASE bpt.breed_type
WHEN 1 THEN '同期发情'
WHEN 2 THEN '本交'
WHEN 3 THEN '冲胚'
WHEN 1 THEN '供体母羊配种'
WHEN 2 THEN '同期发情人工授精'
WHEN 3 THEN '本交'
WHEN 4 THEN '自然发情人工授精'
WHEN 5 THEN '胚胎移植'
ELSE '未知'
END as breed_type_name,
bpg.create_time as plan_create_time
@@ -437,11 +439,90 @@
and is_delete = 0
</update>
<!-- 统计该公羊配了多少母羊(去重 -->
<select id="countMatedEwesByRamId" resultType="Long">
SELECT COUNT(DISTINCT ewe_id)
FROM sc_breed_record
WHERE ram_id = #{ramManageTags} AND is_delete = 0
<!-- 模糊搜索耳号(用于前端 autocomplete -->
<select id="searchEarNumbers" resultType="map">
SELECT DISTINCT
sf.bs_manage_tags AS manageTags,
sf.variety
FROM sheep_file sf
WHERE sf.bs_manage_tags LIKE CONCAT('%', #{query}, '%')
AND sf.is_delete = 0
<if test="gender != null">AND sf.gender = #{gender}</if>
ORDER BY sf.bs_manage_tags
LIMIT 30
</select>
<!-- 批量标记胚胎明细为已移植,关联 breed_record_id -->
<update id="markBatchEmbryoDetailTransferred">
UPDATE sc_embryo_detail
SET is_transferred = 1,
breed_record_id = #{breedRecordId}
WHERE id IN
<foreach collection="embryoDetailIds" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</update>
<!-- 查询这批胚胎明细所属的 flush_id去重-->
<select id="selectFlushIdsByEmbryoDetailIds" resultType="Long">
SELECT DISTINCT flush_id
FROM sc_embryo_detail
WHERE id IN
<foreach collection="array" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
<!--
同步冲胚记录的统计字段:
transferred = 该 flush 下已移植明细数
recipient_cnt = 按鲜胚/冻胚分开统计不同受体数
由于两种类型共用同一张冲胚记录,这里分别用子查询统计:
fresh_recipient_cnt = 鲜胚中不同 breed_record_id 的受体数
frozen_recipient_cnt = 冻胚中不同 breed_record_id 的受体数
recipient_cnt 取二者之和如只有一种则另一种为0
-->
<update id="syncFlushTransferredStats">
UPDATE sc_embryo_flush ef
SET
ef.transferred = (
SELECT COUNT(*) FROM sc_embryo_detail ed
WHERE ed.flush_id = #{flushId} AND ed.is_transferred = 1
),
ef.recipient_cnt = (
SELECT COUNT(DISTINCT ed2.breed_record_id)
FROM sc_embryo_detail ed2
WHERE ed2.flush_id = #{flushId}
AND ed2.is_transferred = 1
AND ed2.breed_record_id IS NOT NULL
)
WHERE ef.id = #{flushId}
</update>
<select id="selectBreedRecordById" parameterType="Long" resultMap="ScBreedRecordResult">
<include refid="selectScBreedRecordVo"/>
where br.id = #{id}
</select>
<!-- 根据配种记录ID查询已移植的胚胎明细 -->
<select id="selectEmbryoDetailsByBreedRecordId" parameterType="Long" resultType="map">
SELECT
ed.id,
ed.embryo_no AS embryoNo,
ed.embryo_grade AS embryoGrade,
ed.embryo_variety AS embryoVariety,
ef.embryo_type AS embryoType,
ef.storage_method AS storageMethod,
ed.flush_id AS flushId,
ed.breed_record_id AS breedRecordId,
ef.donor_female_no AS donorFemaleNo,
ef.donor_male_no AS donorMaleNo,
ed.remark
FROM sc_embryo_detail ed
LEFT JOIN sc_embryo_flush ef ON ef.id = ed.flush_id
WHERE ed.breed_record_id = #{breedRecordId}
AND ed.is_transferred = 1
ORDER BY ed.embryo_no
</select>
</mapper>

View File

@@ -39,6 +39,9 @@
<!-- 新增数据权限字段映射 -->
<result property="userId" column="user_id" />
<result property="deptId" column="dept_id" />
<!-- 鲜胚/冻胚分开受体数(只读,由子查询计算) -->
<result property="freshRecipientCnt" column="fresh_recipient_cnt" />
<result property="frozenRecipientCnt" column="frozen_recipient_cnt" />
</resultMap>
<!-- SQL片段表加别名 f新增 user_id, dept_id -->
@@ -48,7 +51,21 @@
f.cell_2_4, f.cell_8, f.cell_16, f.unfertilized, f.degenerated, f.total_embryo, f.valid_embryo,
f.transferred, f.recipient_cnt, f.embryo_type, f.embryo_source, f.destination, f.storage_method,
f.flush_operator, f.collect_operator, f.ranch_id, f.ranch_name, f.remark,
f.user_id, f.dept_id
f.user_id, f.dept_id,
/* 鲜胚受体数:鲜胚明细中不重复的 breed_record_id 数 */
(SELECT COUNT(DISTINCT ed.breed_record_id)
FROM sc_embryo_detail ed
WHERE ed.flush_id = f.id
AND ed.is_transferred = 1
AND ed.breed_record_id IS NOT NULL
AND ed.storage_method NOT LIKE '%冻%') AS fresh_recipient_cnt,
/* 冻胚受体数 */
(SELECT COUNT(DISTINCT ed.breed_record_id)
FROM sc_embryo_detail ed
WHERE ed.flush_id = f.id
AND ed.is_transferred = 1
AND ed.breed_record_id IS NOT NULL
AND ed.storage_method LIKE '%冻%') AS frozen_recipient_cnt
from sc_embryo_flush f
</sql>
@@ -215,14 +232,18 @@
LIMIT 1
</select>
<!-- 根据母羊耳号查询最近一条配种记录,获取公羊耳号和配种日期 -->
<!-- 根据母羊耳号查询最近一条配种记录,获取公羊耳号和配种日期 -->
<select id="selectBreedRecordByEwe" resultType="java.util.Map">
SELECT
donor_ram AS donorMaleNo,
create_time AS matingDate
FROM sc_breed_record
WHERE donor_ewe = #{eweManageTag} AND (is_delete = 0 OR is_delete IS NULL)
ORDER BY create_time DESC
br.donor_ram AS donorMaleNo,
br.create_time AS matingDate,
sf.variety AS donorMaleVariety
FROM sc_breed_record br
LEFT JOIN sheep_file sf ON sf.bs_manage_tags = br.donor_ram
WHERE br.donor_ewe = #{eweManageTag}
AND (br.is_delete = 0 OR br.is_delete IS NULL)
ORDER BY br.create_time DESC
LIMIT 1
</select>