Parcourir la source

event: 发情事件处理

Yi il y a 1 mois
Parent
commit
aabb1f9a6f

+ 1 - 1
go.mod

@@ -3,7 +3,7 @@ module kpt-pasture
 go 1.17
 
 require (
-	gitee.com/xuyiping_admin/go_proto v0.0.0-20250213100800-994fcef7c178
+	gitee.com/xuyiping_admin/go_proto v0.0.0-20250214034351-6a1e63ae61df
 	gitee.com/xuyiping_admin/pkg v0.0.0-20241108060137-caea58c59f5b
 	github.com/dgrijalva/jwt-go v3.2.0+incompatible
 	github.com/eclipse/paho.mqtt.golang v1.4.3

+ 4 - 0
go.sum

@@ -201,6 +201,10 @@ gitee.com/xuyiping_admin/go_proto v0.0.0-20250213100400-33572489c398 h1:bLWaZq0N
 gitee.com/xuyiping_admin/go_proto v0.0.0-20250213100400-33572489c398/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
 gitee.com/xuyiping_admin/go_proto v0.0.0-20250213100800-994fcef7c178 h1:lNDW0YZJxsgpdvf8AjIXXEi4MZuaozJ5cGVn46Wn8Fw=
 gitee.com/xuyiping_admin/go_proto v0.0.0-20250213100800-994fcef7c178/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250214013933-cb3deefdc2c2 h1:6caRCC4vbF35vqwiwVZx2JQiT+xzIdbJpz8o73oSu+I=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250214013933-cb3deefdc2c2/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250214034351-6a1e63ae61df h1:hRX98TBecH9ZhD/fkfaHBMgledNaTjvtPM82TcK/MYU=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250214034351-6a1e63ae61df/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
 gitee.com/xuyiping_admin/pkg v0.0.0-20241108060137-caea58c59f5b h1:w05MxH7yqveRlaRbxHhbif5YjPrJFodRPfOjYhXn7Zk=
 gitee.com/xuyiping_admin/pkg v0.0.0-20241108060137-caea58c59f5b/go.mod h1:8tF25X6pE9WkFCczlNAC0K2mrjwKvhhp02I7o0HtDxY=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=

+ 1 - 1
http/handler/event/event_breed.go

@@ -101,7 +101,7 @@ func PregnantCheckEventCreateBatch(c *gin.Context) {
 	}
 
 	if err := valid.ValidateStruct(&req,
-		valid.Field(&req.Item, valid.Required, valid.Each(valid.By(func(value interface{}) error {
+		valid.Field(&req.Items, valid.Required, valid.Each(valid.By(func(value interface{}) error {
 			item := value.(pasturePb.EventPregnantCheckRequest)
 			return valid.ValidateStruct(&item,
 				valid.Field(&item.CowId, valid.Required),

+ 0 - 100
http/handler/work/calendar.go

@@ -102,103 +102,3 @@ func ImmunizationItems(c *gin.Context) {
 	}
 	ginutil.JSONResp(c, res)
 }
-
-func SameTimeCowList(c *gin.Context) {
-	var req pasturePb.ItemsRequest
-	if err := ginutil.BindProto(c, &req); err != nil {
-		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
-		return
-	}
-	pagination := &pasturePb.PaginationModel{
-		Page:       int32(c.GetInt(middleware.Page)),
-		PageSize:   int32(c.GetInt(middleware.PageSize)),
-		PageOffset: int32(c.GetInt(middleware.PageOffset)),
-	}
-
-	res, err := middleware.Dependency(c).StoreEventHub.OpsService.SameTimeCowList(c, &req, pagination)
-	if err != nil {
-		apierr.ClassifiedAbort(c, err)
-		return
-	}
-	ginutil.JSONResp(c, res)
-}
-
-func PregnancyCheckCowList(c *gin.Context) {
-	var req pasturePb.ItemsRequest
-	if err := ginutil.BindProto(c, &req); err != nil {
-		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
-		return
-	}
-	pagination := &pasturePb.PaginationModel{
-		Page:       int32(c.GetInt(middleware.Page)),
-		PageSize:   int32(c.GetInt(middleware.PageSize)),
-		PageOffset: int32(c.GetInt(middleware.PageOffset)),
-	}
-
-	res, err := middleware.Dependency(c).StoreEventHub.OpsService.PregnancyCheckCowList(c, &req, pagination)
-	if err != nil {
-		apierr.ClassifiedAbort(c, err)
-		return
-	}
-	ginutil.JSONResp(c, res)
-}
-
-func WeaningCowList(c *gin.Context) {
-	var req pasturePb.ItemsRequest
-	if err := ginutil.BindProto(c, &req); err != nil {
-		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
-		return
-	}
-	pagination := &pasturePb.PaginationModel{
-		Page:       int32(c.GetInt(middleware.Page)),
-		PageSize:   int32(c.GetInt(middleware.PageSize)),
-		PageOffset: int32(c.GetInt(middleware.PageOffset)),
-	}
-
-	res, err := middleware.Dependency(c).StoreEventHub.OpsService.WeaningCowList(c, &req, pagination)
-	if err != nil {
-		apierr.ClassifiedAbort(c, err)
-		return
-	}
-	ginutil.JSONResp(c, res)
-}
-
-func MatingCowList(c *gin.Context) {
-	var req pasturePb.ItemsRequest
-	if err := ginutil.BindProto(c, &req); err != nil {
-		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
-		return
-	}
-	pagination := &pasturePb.PaginationModel{
-		Page:       int32(c.GetInt(middleware.Page)),
-		PageSize:   int32(c.GetInt(middleware.PageSize)),
-		PageOffset: int32(c.GetInt(middleware.PageOffset)),
-	}
-
-	res, err := middleware.Dependency(c).StoreEventHub.OpsService.MatingCowList(c, &req, pagination)
-	if err != nil {
-		apierr.ClassifiedAbort(c, err)
-		return
-	}
-	ginutil.JSONResp(c, res)
-}
-
-func CalvingList(c *gin.Context) {
-	var req pasturePb.ItemsRequest
-	if err := ginutil.BindProto(c, &req); err != nil {
-		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
-		return
-	}
-	pagination := &pasturePb.PaginationModel{
-		Page:       int32(c.GetInt(middleware.Page)),
-		PageSize:   int32(c.GetInt(middleware.PageSize)),
-		PageOffset: int32(c.GetInt(middleware.PageOffset)),
-	}
-
-	res, err := middleware.Dependency(c).StoreEventHub.OpsService.CalvingCowList(c, &req, pagination)
-	if err != nil {
-		apierr.ClassifiedAbort(c, err)
-		return
-	}
-	ginutil.JSONResp(c, res)
-}

+ 102 - 4
http/handler/work/item.go

@@ -10,9 +10,107 @@ import (
 	"github.com/gin-gonic/gin"
 )
 
-func EstrusItem(c *gin.Context) {
-	c.JSON(http.StatusOK, gin.H{"result": "ok"})
-	return
+func SameTimeCowList(c *gin.Context) {
+	var req pasturePb.ItemsRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+	pagination := &pasturePb.PaginationModel{
+		Page:       int32(c.GetInt(middleware.Page)),
+		PageSize:   int32(c.GetInt(middleware.PageSize)),
+		PageOffset: int32(c.GetInt(middleware.PageOffset)),
+	}
+
+	res, err := middleware.Dependency(c).StoreEventHub.OpsService.SameTimeCowList(c, &req, pagination)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	ginutil.JSONResp(c, res)
+}
+
+func PregnancyCheckCowList(c *gin.Context) {
+	var req pasturePb.ItemsRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+	pagination := &pasturePb.PaginationModel{
+		Page:       int32(c.GetInt(middleware.Page)),
+		PageSize:   int32(c.GetInt(middleware.PageSize)),
+		PageOffset: int32(c.GetInt(middleware.PageOffset)),
+	}
+
+	res, err := middleware.Dependency(c).StoreEventHub.OpsService.PregnancyCheckCowList(c, &req, pagination)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	ginutil.JSONResp(c, res)
+}
+
+func WeaningCowList(c *gin.Context) {
+	var req pasturePb.ItemsRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+	pagination := &pasturePb.PaginationModel{
+		Page:       int32(c.GetInt(middleware.Page)),
+		PageSize:   int32(c.GetInt(middleware.PageSize)),
+		PageOffset: int32(c.GetInt(middleware.PageOffset)),
+	}
+
+	res, err := middleware.Dependency(c).StoreEventHub.OpsService.WeaningCowList(c, &req, pagination)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	ginutil.JSONResp(c, res)
+}
+
+func MatingCowList(c *gin.Context) {
+	var req pasturePb.ItemsRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+	pagination := &pasturePb.PaginationModel{
+		Page:       int32(c.GetInt(middleware.Page)),
+		PageSize:   int32(c.GetInt(middleware.PageSize)),
+		PageOffset: int32(c.GetInt(middleware.PageOffset)),
+	}
+
+	res, err := middleware.Dependency(c).StoreEventHub.OpsService.MatingCowList(c, &req, pagination)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	ginutil.JSONResp(c, res)
+}
+
+func CalvingList(c *gin.Context) {
+	var req pasturePb.ItemsRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+	pagination := &pasturePb.PaginationModel{
+		Page:       int32(c.GetInt(middleware.Page)),
+		PageSize:   int32(c.GetInt(middleware.PageSize)),
+		PageOffset: int32(c.GetInt(middleware.PageOffset)),
+	}
+
+	res, err := middleware.Dependency(c).StoreEventHub.OpsService.CalvingCowList(c, &req, pagination)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	ginutil.JSONResp(c, res)
+}
+
+func NeckRingWarningEstrusItem(c *gin.Context) {
 	var req pasturePb.EstrusItemsRequest
 	if err := ginutil.BindProto(c, &req); err != nil {
 		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
@@ -25,7 +123,7 @@ func EstrusItem(c *gin.Context) {
 		PageOffset: int32(c.GetInt(middleware.PageOffset)),
 	}
 
-	res, err := middleware.BackendOperation(c).OpsService.EstrusList(c, &req, pagination)
+	res, err := middleware.BackendOperation(c).OpsService.EstrusCowList(c, &req, pagination)
 	if err != nil {
 		apierr.ClassifiedAbort(c, err)
 		return

+ 1 - 1
http/route/work_api.go

@@ -30,7 +30,7 @@ func WorkOrderAPI(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
 		workRoute.POST("/weaning/items", work.WeaningCowList)
 		workRoute.POST("/mating/items", work.MatingCowList)
 		workRoute.POST("/calving/items", work.CalvingList)
-		workRoute.POST("/estrus/items", work.EstrusItem)
+		workRoute.POST("/estrus/items", work.NeckRingWarningEstrusItem)
 		workRoute.POST("/disease/items", event.CowDiseaseList)
 	}
 }

+ 1 - 1
model/cow.go

@@ -150,7 +150,7 @@ func (c *Cow) EventDepartureUpdate(departureAt int64, departureType pasturePb.De
 // EventMatingUpdate 配种更新
 func (c *Cow) EventMatingUpdate(matingAt int64, bullNumber string, isReMating bool) {
 	c.LastMatingAt = matingAt
-	if c.LastBullNumber != "" {
+	if c.FirstMatingAt <= 0 {
 		c.FirstMatingAt = matingAt
 	}
 	c.LastBullNumber = bullNumber

+ 1 - 1
model/event_pregnant_check.go

@@ -141,7 +141,7 @@ func (e EventPregnantCheckSlice) ToPB(
 			CowId:                   int32(v.CowId),
 			DayAge:                  v.DayAge,
 			Lact:                    int32(v.Lact),
-			PregnantCheckAt:         int32(v.PlanDay),
+			PregnantCheckAt:         int32(v.RealityDay),
 			PregnantCheckResult:     v.PregnantCheckResult,
 			PregnantCheckResultName: pregnantCheckResultMap[v.PregnantCheckResult],
 			PregnantCheckMethod:     v.PregnantCheckMethod,

+ 36 - 0
model/neck_ring_estrus.go

@@ -50,3 +50,39 @@ func NewNeckRingEstrus(
 		CheckResult:      checkResult,
 	}
 }
+
+type NeckRingEstrusSlice []*NeckRingEstrus
+
+func (n NeckRingEstrusSlice) ToPB(cowMap map[int64]*Cow, eventLogMap map[int64]string) []*pasturePb.EstrusItems {
+	res := make([]*pasturePb.EstrusItems, len(n))
+	for i, v := range n {
+		cow, ok := cowMap[v.CowId]
+		if !ok {
+			cow = &Cow{Id: v.CowId}
+		}
+		lastBreedEventDetails := ""
+		desc, ok := eventLogMap[cow.Id]
+		if ok {
+			lastBreedEventDetails = desc
+		}
+
+		res[i] = &pasturePb.EstrusItems{
+			Id:                     int32(v.Id),
+			CowId:                  int32(v.CowId),
+			EarNumber:              v.EarNumber,
+			PenId:                  cow.PenId,
+			PenName:                cow.PenName,
+			DayAge:                 cow.DayAge,
+			MatingTimes:            cow.MatingTimes,
+			Lact:                   cow.Lact,
+			CalvingAge:             cow.CalvingAge,
+			AbortionAge:            cow.AbortionAge,
+			OptimumMatingStartTime: "",
+			OptimumMatingEndTime:   "",
+			LastBreedEventDetails:  lastBreedEventDetails,
+			Level:                  v.Level,
+			EstrusInterval:         0,
+		}
+	}
+	return res
+}

+ 0 - 24
model/neck_ring_process.go

@@ -1,24 +0,0 @@
-package model
-
-type NeckRingProcess struct {
-	Id             int64  `json:"id"`
-	HabitId        int64  `json:"habitId"`
-	NeckRingNumber string `json:"neckRingNumber"`
-	ActiveDate     string `json:"activeDate"`
-	Frameid        int32  `json:"frameid"`
-	CreatedAt      int64  `json:"createdAt"`
-	UpdatedAt      int64  `json:"updatedAt"`
-}
-
-func (n *NeckRingProcess) TableName() string {
-	return "neck_ring_process"
-}
-
-func NewNeckRingProcess(neckActiveHabit *NeckActiveHabit) *NeckRingProcess {
-	return &NeckRingProcess{
-		HabitId:        neckActiveHabit.Id,
-		NeckRingNumber: neckActiveHabit.NeckRingNumber,
-		ActiveDate:     neckActiveHabit.HeatDate,
-		Frameid:        neckActiveHabit.Frameid,
-	}
-}

+ 1 - 1
module/backend/analysis_breed.go

@@ -633,7 +633,7 @@ func (s *StoreEntry) MultipleFactorAnalysis(ctx context.Context, req *pasturePb.
 	}
 
 	list := make([]*model.MultiFactorPregnancyRateList, 0)
-	if err := pref.Group(groupMap[fmt.Sprintf("%d%d", req.XAxle, req.YAxle)]).
+	if err = pref.Group(groupMap[fmt.Sprintf("%d%d", req.XAxle, req.YAxle)]).
 		Order(fmt.Sprintf("%s", multiFactorAnalysisMethod[req.XAxle])).Find(&list).Error; err != nil {
 		return nil, xerr.WithStack(err)
 	}

+ 7 - 0
module/backend/config_data_breed.go

@@ -184,30 +184,37 @@ func (s *StoreEntry) MultiFactorAnalysisMethodEnumList(isAll string) []*pastureP
 	configOptions = append(configOptions, &pasturePb.ConfigOptionsList{
 		Value:    int32(pasturePb.MultiFactorAnalysisMethod_Months),
 		Label:    "月份",
+		Props:    "months",
 		Disabled: true,
 	}, &pasturePb.ConfigOptionsList{
 		Value:    int32(pasturePb.MultiFactorAnalysisMethod_Week),
 		Label:    "周",
+		Props:    "week",
 		Disabled: true,
 	}, &pasturePb.ConfigOptionsList{
 		Value:    int32(pasturePb.MultiFactorAnalysisMethod_Operation),
 		Label:    "配种员",
+		Props:    "operationName",
 		Disabled: true,
 	}, &pasturePb.ConfigOptionsList{
 		Value:    int32(pasturePb.MultiFactorAnalysisMethod_Bull),
 		Label:    "公牛",
+		Props:    "bull",
 		Disabled: true,
 	}, &pasturePb.ConfigOptionsList{
 		Value:    int32(pasturePb.MultiFactorAnalysisMethod_Lact),
 		Label:    "胎次",
+		Props:    "lact",
 		Disabled: true,
 	}, &pasturePb.ConfigOptionsList{
 		Value:    int32(pasturePb.MultiFactorAnalysisMethod_Mating_Times),
 		Label:    "配次",
+		Props:    "matingTimes",
 		Disabled: true,
 	}, &pasturePb.ConfigOptionsList{
 		Value:    int32(pasturePb.MultiFactorAnalysisMethod_Breeding_Method),
 		Label:    "配种方式",
+		Props:    "exposeEstrusType",
 		Disabled: true,
 	})
 	return configOptions

+ 36 - 81
module/backend/event_breed.go

@@ -2,12 +2,12 @@ package backend
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"kpt-pasture/model"
 	"kpt-pasture/util"
 	"net/http"
 	"strings"
-	"time"
 
 	"gitee.com/xuyiping_admin/pkg/logger/zaplog"
 	"go.uber.org/zap"
@@ -363,60 +363,10 @@ func (s *StoreEntry) EstrusBatchMating(ctx context.Context, req *pasturePb.Event
 		return xerr.WithStack(err)
 	}
 
-	eventEstrusList := make([]*model.EventEstrus, 0)
-	eventMatingList := make([]*model.EventMating, 0)
-	unMatingReasonsMap := s.UnMatingReasonsMap()
-	for _, item := range req.Items {
-		cowInfo, err := s.GetCowInfoByCowId(ctx, userModel.AppPasture.Id, int64(item.CowId))
-		if err != nil {
-			return xerr.Custom("牛只信息不存在")
-		}
-
-		if cowInfo.Sex != pasturePb.Genders_Female {
-			return xerr.Custom("该牛只不是母牛")
-		}
-
-		existsEventEstrus, isExists, err := s.FindEventEstrusByCowId(ctx, userModel.AppPasture.Id, int64(item.CowId))
-		if err != nil {
-			return xerr.WithStack(err)
-		}
-
-		// 如果存在,并且是脖环揭发则跳过
-		if isExists {
-			if existsEventEstrus.ExposeEstrusType == pasturePb.ExposeEstrusType_Neck_Ring {
-				zaplog.Info("EventNaturalEstrusBatch", zap.Any("existsEventEstrus", existsEventEstrus), zap.Any("item", item))
-				continue
-			}
-			realityDay := time.Unix(existsEventEstrus.RealityDay, 0).Format(model.LayoutDate2)
-			planDay := time.Unix(int64(item.EstrusAt), 0).Format(model.LayoutDate2)
-			if realityDay == planDay {
-				return xerr.Customf("该牛只:%d,今天已经提交过发情数据", item.CowId)
-			}
-		}
-
-		operationUser, _ := s.GetSystemUserById(ctx, userModel.SystemUser.Id)
-		item.OperationName = operationUser.Name
-		newEventEstrus := model.NewEventEstrus(
-			userModel.AppPasture.Id,
-			cowInfo,
-			pasturePb.ExposeEstrusType_Natural_Estrus,
-			pasturePb.IsShow_Ok,
-			item.IsMating,
-			item.EstrusAt,
-			operationUser,
-			userModel.SystemUser,
-		)
-		if item.IsMating == pasturePb.IsShow_Ok {
-			newEventMating := model.NewEventMating(userModel.AppPasture.Id, cowInfo, time.Now().Unix(), pasturePb.ExposeEstrusType_Natural_Estrus)
-			eventMatingList = append(eventMatingList, newEventMating)
-		} else {
-			newEventEstrus.UnMatingReasonsKind = item.UnMatingReasonsKind
-			newEventEstrus.UnMatingReasonsName = unMatingReasonsMap[item.UnMatingReasonsKind]
-		}
-		newEventEstrus.Remarks = item.Remarks
-		eventEstrusList = append(eventEstrusList, newEventEstrus)
+	eventEstrusList, eventMatingList, err := s.EstrusCheckDataCheck(ctx, userModel, req.Items)
+	if err != nil {
+		return xerr.WithStack(err)
 	}
-
 	// 记录事件日志
 	defer func() {
 		if err == nil && len(eventEstrusList) > 0 {
@@ -486,36 +436,41 @@ func (s *StoreEntry) MatingList(ctx context.Context, req *pasturePb.SearchEventR
 
 // MatingCreate 牛只配种
 func (s *StoreEntry) MatingCreate(ctx context.Context, req *pasturePb.EventMating) (err error) {
-	eventCheckModel, err := s.MatingCreateCheck(ctx, req)
+	userModel, err := s.GetUserModel(ctx)
+	if err != nil {
+		return xerr.WithStack(err)
+	}
+
+	eventMatingCheckModel, err := s.MatingCreateCheck(ctx, userModel.AppPasture.Id, req)
 	if err != nil {
 		return xerr.WithStack(err)
 	}
 
-	req.OperationName = eventCheckModel.OperationUser.Name
+	req.OperationName = eventMatingCheckModel.OperationUser.Name
 	if err = s.DB.Transaction(func(tx *gorm.DB) error {
-		for _, cow := range eventCheckModel.CowList {
+		for _, cow := range eventMatingCheckModel.CowList {
 			// 获取牛只最近一次配种信息
-			lastEventMating, ok, err := s.FindLastEventMatingByCowId(ctx, eventCheckModel.UserModel.AppPasture.Id, cow.Id)
+			lastEventMating, err := s.FindLastEventMatingByCowId(ctx, userModel.AppPasture.Id, cow.Id)
 			if err != nil {
-				return xerr.WithStack(err)
-			}
-
-			// 1. 没有配种信息,(第一次参加配种的牛只,并且没有参与同期的牛只 )
-			if !ok {
-				newMating := model.NewEventMatingNaturalEstrus(eventCheckModel.UserModel.AppPasture.Id, cow, req, eventCheckModel.UserModel.SystemUser)
-				if err = tx.Create(newMating).Error; err != nil {
-					return xerr.WithStack(err)
-				}
-				if err = s.MatingCowUpdate(ctx, eventCheckModel.UserModel.AppPasture.Id, cow, req, false); err != nil {
+				// 1. 没有配种信息,(第一次参加配种的牛只,并且没有参与同期的牛只 )
+				if errors.Is(err, gorm.ErrRecordNotFound) {
+					newMating := model.NewEventMatingNaturalEstrus(userModel.AppPasture.Id, cow, req, userModel.SystemUser)
+					if err = tx.Create(newMating).Error; err != nil {
+						return xerr.WithStack(err)
+					}
+					if err = s.MatingCowUpdate(ctx, userModel.AppPasture.Id, cow, req, false); err != nil {
+						return xerr.WithStack(err)
+					}
+					continue
+				} else {
 					return xerr.WithStack(err)
 				}
-				continue
 			}
 
 			// 2. 所有有配种数据的牛只
-			if lastEventMating == nil || lastEventMating.Id <= 0 {
-				zaplog.Error("MatingCreate", zap.Any("cow", cow), zap.Any("UserModel", eventCheckModel.UserModel))
-				return xerr.Customf("异常牛数据: %d", cow.Id)
+			if lastEventMating == nil || lastEventMating.Id <= 0 || lastEventMating.Status != pasturePb.IsShow_No {
+				zaplog.Error("MatingCreate", zap.Any("cow", cow), zap.Any("UserModel", userModel))
+				return xerr.Customf("牛只配种数据异常: %d", cow.EarNumber)
 			}
 
 			// 2.1 复配 => 本胎次配次不加1
@@ -532,7 +487,7 @@ func (s *StoreEntry) MatingCreate(ctx context.Context, req *pasturePb.EventMatin
 			// 2.2.  同期初配
 			IsMatingUpdate := lastEventMating.IsMatingUpdate()
 			if IsMatingUpdate {
-				lastEventMating.EventUpdate(int64(req.MatingAt), req.FrozenSemenNumber, isReMating, eventCheckModel.OperationUser, eventCheckModel.UserModel.SystemUser)
+				lastEventMating.EventUpdate(int64(req.MatingAt), req.FrozenSemenNumber, isReMating, eventMatingCheckModel.OperationUser, userModel.SystemUser)
 				if err = tx.Model(lastEventMating).
 					Select("mating_at", "status", "reality_day", "frozen_semen_number", "operation_id", "operation_name", "message_id", "message_name").
 					Where("id = ?", lastEventMating.Id).
@@ -553,8 +508,8 @@ func (s *StoreEntry) MatingCreate(ctx context.Context, req *pasturePb.EventMatin
 					return xerr.WithStack(err)
 				}
 				// 先创建一条新的配种数据
-				newMating := model.NewEventMating(eventCheckModel.UserModel.AppPasture.Id, cow, int64(req.MatingAt), req.ExposeEstrusType)
-				newMating.EventUpdate(int64(req.MatingAt), req.FrozenSemenNumber, isReMating, eventCheckModel.OperationUser, eventCheckModel.UserModel.SystemUser)
+				newMating := model.NewEventMating(userModel.AppPasture.Id, cow, int64(req.MatingAt), req.ExposeEstrusType)
+				newMating.EventUpdate(int64(req.MatingAt), req.FrozenSemenNumber, isReMating, eventMatingCheckModel.OperationUser, userModel.SystemUser)
 				if err = tx.Model(newMating).Create(newMating).Error; err != nil {
 					return xerr.WithStack(err)
 				}
@@ -566,23 +521,23 @@ func (s *StoreEntry) MatingCreate(ctx context.Context, req *pasturePb.EventMatin
 			}
 
 			// 牛只基本中配种信息更新
-			if err = s.MatingCowUpdate(ctx, eventCheckModel.UserModel.AppPasture.Id, cow, req, isReMating); err != nil {
+			if err = s.MatingCowUpdate(ctx, userModel.AppPasture.Id, cow, req, isReMating); err != nil {
 				return xerr.WithStack(err)
 			}
 		}
 
 		// 创建冻精使用记录日志
-		itemFrozenSemenLog := model.NewEventFrozenSemenLog(eventCheckModel.FrozenSemen.PastureId, req)
+		itemFrozenSemenLog := model.NewEventFrozenSemenLog(eventMatingCheckModel.FrozenSemen.PastureId, req)
 		if err = tx.Create(itemFrozenSemenLog).Error; err != nil {
 			return xerr.WithStack(err)
 		}
 
 		// 减去精液的数量
-		eventCheckModel.FrozenSemen.EventQuantityUpdate(req.FrozenSemenCount)
-		if err = tx.Model(eventCheckModel.FrozenSemen).
+		eventMatingCheckModel.FrozenSemen.EventQuantityUpdate(req.FrozenSemenCount)
+		if err = tx.Model(eventMatingCheckModel.FrozenSemen).
 			Select("quantity").
-			Where("id = ?", eventCheckModel.FrozenSemen.Id).
-			Updates(eventCheckModel.FrozenSemen).Error; err != nil {
+			Where("id = ?", eventMatingCheckModel.FrozenSemen.Id).
+			Updates(eventMatingCheckModel.FrozenSemen).Error; err != nil {
 			return xerr.WithStack(err)
 		}
 		return nil

+ 26 - 18
module/backend/event_breed_more.go

@@ -25,7 +25,8 @@ func (s *StoreEntry) PregnantCheckList(ctx context.Context, req *pasturePb.Searc
 	pregnantCheckList := make([]*model.EventPregnantCheck, 0)
 	var count int64 = 0
 	pref := s.DB.Table(new(model.EventPregnantCheck).TableName()).
-		Where("pasture_id = ?", userModel.AppPasture.Id)
+		Where("pasture_id = ?", userModel.AppPasture.Id).
+		Where("status = ?", pasturePb.IsShow_Ok)
 	if len(req.CowId) > 0 {
 		cowIds := strings.Split(req.CowId, ",")
 		pref.Where("cow_id IN ?", cowIds)
@@ -53,12 +54,19 @@ func (s *StoreEntry) PregnantCheckList(ctx context.Context, req *pasturePb.Searc
 }
 
 func (s *StoreEntry) PregnantCheckCreateBatch(ctx context.Context, req *pasturePb.EventPregnantCheckBatch) (err error) {
-	pregnantCheckBatchModelList, err := s.PregnantCheckDataCheck(ctx, req)
+	userModel, err := s.GetUserModel(ctx)
+	if err != nil {
+		return xerr.WithStack(err)
+	}
+
+	pregnantCheckBatchModelList, err := s.PregnantCheckDataCheck(ctx, userModel.AppPasture.Id, req)
 	if err != nil {
 		return xerr.WithStack(err)
 	}
 
 	abortionReasonsMap := s.AbortionReasonsMap()
+	matingResultMap := s.MatingResultMap()
+
 	if err = s.DB.Transaction(func(tx *gorm.DB) error {
 		for _, item := range pregnantCheckBatchModelList {
 			breedStatus := pasturePb.BreedStatus_Pregnant
@@ -96,33 +104,29 @@ func (s *StoreEntry) PregnantCheckCreateBatch(ctx context.Context, req *pastureP
 			}
 
 			// 更新孕检事件表
-			item.EventPregnancyCheck.EventUpdate(
-				int64(item.PregnantCheckAt),
-				item.PregnantCheckResult,
-				item.PregnantCheckMethod,
-				item.OperationUser,
-				item.UserModel.SystemUser,
-				item.Remarks,
-			)
+			item.EventPregnancyCheck.EventUpdate(int64(item.PregnantCheckAt), item.PregnantCheckResult, item.PregnantCheckMethod,
+				item.OperationUser, userModel.SystemUser, item.Remarks)
 			if err = tx.Model(new(model.EventPregnantCheck)).
-				Select("reality_day,pregnant_check_result,pregnant_check_method,operation_id,operation_name,message_id,message_name,remarks,status").
+				Select("reality_day", "pregnant_check_result", "pregnant_check_method", "operation_id", "operation_name", "message_id", "message_name", "remarks", "status").
 				Where("id = ?", item.EventPregnancyCheck.Id).
 				Updates(item.EventPregnancyCheck).Error; err != nil {
 				return xerr.WithStack(err)
 			}
 
-			// 记录日志
-			cowLogs := s.SubmitEventLog(ctx, item.UserModel.AppPasture.Id, item.Cow, pasturePb.EventType_Pregnancy_Check, pasturePb.ExposeEstrusType_Invalid, req)
-			tx.Table(cowLogs.TableName()).Create(cowLogs)
-
 			// 更新上次配种结果
-			if err = s.UpdateMatingResultEventCowLogByCowId(ctx, item.Cow.Id, s.MatingResultMap()[matingResult]); err != nil {
+			matingResultName := ""
+			if matingRName, ok := matingResultMap[matingResult]; ok {
+				matingResultName = matingRName
+			}
+
+			if err = s.UpdateMatingResultEventCowLogByCowId(ctx, item.Cow.Id, matingResultName); err != nil {
 				zaplog.Error("PregnantCheckCreateBatch", zap.Any("UpdateMatingResultEventCowLogByCowId", err))
+				return xerr.WithStack(err)
 			}
 
 			// 如果确定是流产
 			if matingResult == pasturePb.MatingResult_Abort {
-				newEventAbortion := model.NewEventAbortion(item.UserModel.AppPasture.Id, item.Cow, &pasturePb.EventAbortionRequest{
+				newEventAbortion := model.NewEventAbortion(userModel.AppPasture.Id, item.Cow, &pasturePb.EventAbortionRequest{
 					CowId:               int32(item.Cow.Id),
 					AbortionAt:          item.PregnantCheckAt,
 					IsAfterbirth:        pasturePb.IsShow_No,
@@ -137,8 +141,12 @@ func (s *StoreEntry) PregnantCheckCreateBatch(ctx context.Context, req *pastureP
 					return xerr.WithStack(err)
 				}
 			}
+			// 记录日志
+			cowLogs := s.SubmitEventLog(ctx, userModel.AppPasture.Id, item.Cow, pasturePb.EventType_Pregnancy_Check, pasturePb.ExposeEstrusType_Invalid, item)
+			if err = tx.Table(cowLogs.TableName()).Create(cowLogs).Error; err != nil {
+				return xerr.WithStack(err)
+			}
 		}
-
 		return nil
 	}); err != nil {
 		return xerr.WithStack(err)

+ 98 - 44
module/backend/event_check.go

@@ -2,7 +2,9 @@ package backend
 
 import (
 	"context"
+	"fmt"
 	"kpt-pasture/model"
+	"time"
 
 	"go.uber.org/zap"
 
@@ -12,11 +14,10 @@ import (
 	"gitee.com/xuyiping_admin/pkg/xerr"
 )
 
-// EventCheckBatchModel 批量事件
-type EventCheckBatchModel struct {
+// EventMatingCheckBatchModel 批量配种
+type EventMatingCheckBatchModel struct {
 	CowList       []*model.Cow
 	FrozenSemen   *model.FrozenSemen
-	UserModel     *model.UserModel
 	OperationUser *model.SystemUser
 }
 
@@ -29,7 +30,6 @@ type PregnantCheckBatchModel struct {
 	PregnantCheckResult pasturePb.PregnantCheckResult_Kind // 孕检结果
 	PregnantCheckMethod pasturePb.PregnantCheckMethod_Kind // 孕检方式
 	Remarks             string
-	UserModel           *model.UserModel
 	LastMating          *model.EventMating
 }
 
@@ -51,16 +51,11 @@ func (s *StoreEntry) EnterCheck(ctx context.Context, req *pasturePb.EventEnterRe
 	return nil
 }
 
-func (s *StoreEntry) MatingCreateCheck(ctx context.Context, req *pasturePb.EventMating) (*EventCheckBatchModel, error) {
+func (s *StoreEntry) MatingCreateCheck(ctx context.Context, pastureId int64, req *pasturePb.EventMating) (*EventMatingCheckBatchModel, error) {
 	if len(req.CowIds) <= 0 {
 		return nil, xerr.Custom("请选择相关牛只")
 	}
 
-	userModel, err := s.GetUserModel(ctx)
-	if err != nil {
-		return nil, xerr.WithStack(err)
-	}
-
 	cowIds := make([]int64, 0)
 	for _, v := range req.CowIds {
 		cowIds = append(cowIds, int64(v))
@@ -70,7 +65,7 @@ func (s *StoreEntry) MatingCreateCheck(ctx context.Context, req *pasturePb.Event
 		return nil, xerr.Custom("最多只能选择50只牛只")
 	}
 
-	cowList, err := s.GetCowInfoByCowIds(ctx, userModel.AppPasture.Id, cowIds)
+	cowList, err := s.GetCowInfoByCowIds(ctx, pastureId, cowIds)
 	if err != nil {
 		return nil, xerr.WithStack(err)
 	}
@@ -116,69 +111,128 @@ func (s *StoreEntry) MatingCreateCheck(ctx context.Context, req *pasturePb.Event
 		}
 	}
 
-	return &EventCheckBatchModel{
+	return &EventMatingCheckBatchModel{
 		CowList:       cowList,
 		FrozenSemen:   frozenSemen,
-		UserModel:     userModel,
 		OperationUser: operationUser,
 	}, nil
 }
 
-func (s *StoreEntry) PregnantCheckDataCheck(ctx context.Context, req *pasturePb.EventPregnantCheckBatch) ([]*PregnantCheckBatchModel, error) {
-	if len(req.Item) <= 0 {
-		return nil, xerr.Custom("请选择相关牛只")
-	}
-
-	userModel, err := s.GetUserModel(ctx)
-	if err != nil {
-		return nil, xerr.WithStack(err)
+func (s *StoreEntry) PregnantCheckDataCheck(ctx context.Context, pastureId int64, req *pasturePb.EventPregnantCheckBatch) ([]*PregnantCheckBatchModel, error) {
+	if len(req.Items) <= 0 {
+		return nil, xerr.Custom("请选择相关牛只数据")
 	}
 
 	pregnantCheckBatchModelList := make([]*PregnantCheckBatchModel, 0)
-	for _, pregnantCheckData := range req.Item {
-		operationUser, err := s.GetSystemUserById(ctx, int64(pregnantCheckData.OperationId))
+	cowInfo := &model.Cow{}
+	var err error
+	for _, item := range req.Items {
+		cowInfo, err = s.GetCowInfoByCowId(ctx, pastureId, int64(item.CowId))
 		if err != nil {
-			zaplog.Error("PregnantCheckDataCheck", zap.Any("id", pregnantCheckData.OperationId), zap.Any("error", err.Error()))
-			return nil, xerr.Customf("获取操作人员信息失败")
+			return nil, xerr.WithStack(err)
+		}
+
+		if cowInfo.Sex != pasturePb.Genders_Female {
+			return nil, xerr.Customf("牛只: %d,不是母牛", cowInfo.Id)
 		}
-		pregnantCheckData.OperationName = operationUser.Name
-		cow, err := s.GetCowInfoByCowId(ctx, userModel.AppPasture.Id, int64(pregnantCheckData.CowId))
+
+		operationUser, err := s.GetSystemUserById(ctx, int64(item.OperationId))
 		if err != nil {
-			return nil, xerr.WithStack(err)
+			zaplog.Error("PregnantCheckDataCheck", zap.Any("id", item.OperationId), zap.Any("error", err.Error()))
+			return nil, xerr.Customf("获取操作人员信息失败")
 		}
 
 		// 过滤掉没有配种状态的牛只
-		if cow.BreedStatus != pasturePb.BreedStatus_Breeding {
-			return nil, xerr.Customf("牛只: %d 未参加配种,不能进行孕检", cow.Id)
+		if cowInfo.BreedStatus != pasturePb.BreedStatus_Breeding && cowInfo.BreedStatus != pasturePb.BreedStatus_Pregnant {
+			return nil, xerr.Customf("牛只: %s 未参加配种,不能进行孕检", cowInfo.EarNumber)
+		}
+
+		if cowInfo.LastMatingAt <= 0 {
+			return nil, xerr.Customf("牛只: %s,最近一次配种数据异常", cowInfo.EarNumber)
 		}
 
-		itemEventPregnantCheck, ok, err := s.FindEventPregnantCheckIsExIstByCowId(ctx, cow)
+		itemEventPregnantCheck, err := s.FindEventPregnantCheckIsExIstByCowId(ctx, cowInfo)
 		if err != nil {
-			zaplog.Error("GetEventPregnantCheckIsExIstByCowId", zap.Any("err", err), zap.Any("cow", cow))
-			return nil, xerr.Customf("该牛只孕检事件数据异常, cowId: %d", pregnantCheckData.CowId)
+			return nil, xerr.WithStack(err)
 		}
 
-		if itemEventPregnantCheck.Id <= 0 || !ok {
-			return nil, xerr.Customf("该牛只孕检事件数据异常, cowId: %d", pregnantCheckData.CowId)
+		if itemEventPregnantCheck.Id <= 0 {
+			return nil, xerr.Customf("未发现该牛只: %s 孕检数据", cowInfo.EarNumber)
 		}
 
-		lastEventMating, ok, err := s.FindLastEventMatingByCowId(ctx, userModel.AppPasture.Id, cow.Id)
-		if err != nil || !ok {
-			return nil, xerr.Customf("该牛只配种事件数据异常, cowId: %d", pregnantCheckData.CowId)
+		lastEventMating, err := s.FindLastEventMatingByCowId(ctx, pastureId, cowInfo.Id)
+		if err != nil {
+			return nil, xerr.WithStack(err)
+		}
+
+		if lastEventMating == nil || lastEventMating.Status == pasturePb.IsShow_No {
+			return nil, xerr.Customf("未发现该牛只: %s 配种数据", cowInfo.EarNumber)
 		}
 
 		pregnantCheckBatchModelList = append(pregnantCheckBatchModelList, &PregnantCheckBatchModel{
-			Cow:                 cow,
+			Cow:                 cowInfo,
 			OperationUser:       operationUser,
-			PregnantCheckAt:     pregnantCheckData.PregnantCheckAt,
-			PregnantCheckMethod: pregnantCheckData.PregnantCheckMethod,
-			PregnantCheckResult: pregnantCheckData.PregnantCheckResult,
-			Remarks:             pregnantCheckData.Remarks,
+			PregnantCheckAt:     item.PregnantCheckAt,
+			PregnantCheckMethod: item.PregnantCheckMethod,
+			PregnantCheckResult: item.PregnantCheckResult,
+			Remarks:             item.Remarks,
 			EventPregnancyCheck: itemEventPregnantCheck,
-			UserModel:           userModel,
 			LastMating:          lastEventMating,
 		})
 	}
 
 	return pregnantCheckBatchModelList, nil
 }
+
+func (s *StoreEntry) EstrusCheckDataCheck(ctx context.Context, userModel *model.UserModel, items []*pasturePb.EventNaturalEstrusItems) ([]*model.EventEstrus, []*model.EventMating, error) {
+	eventEstrusList := make([]*model.EventEstrus, 0)
+	eventMatingList := make([]*model.EventMating, 0)
+	unMatingReasonsMap := s.UnMatingReasonsMap()
+
+	for _, item := range items {
+		cowInfo, err := s.GetCowInfoByCowId(ctx, userModel.AppPasture.Id, int64(item.CowId))
+		if err != nil {
+			return nil, nil, xerr.Custom("牛只信息不存在")
+		}
+
+		if cowInfo.Sex != pasturePb.Genders_Female {
+			return nil, nil, xerr.Custom("该牛只不是母牛")
+		}
+
+		existsEventEstrus, isExists, err := s.FindEventEstrusByCowId(ctx, userModel.AppPasture.Id, int64(item.CowId))
+		if err != nil {
+			return nil, nil, xerr.WithStack(err)
+		}
+
+		// 如果存在,并且是脖环揭发则跳过
+		if isExists {
+			if existsEventEstrus.ExposeEstrusType == pasturePb.ExposeEstrusType_Neck_Ring {
+				zaplog.Info("EventNaturalEstrusBatch", zap.Any("existsEventEstrus", existsEventEstrus), zap.Any("item", item))
+				continue
+			}
+			realityDay := time.Unix(existsEventEstrus.RealityDay, 0).Format(model.LayoutDate2)
+			planDay := time.Unix(int64(item.EstrusAt), 0).Format(model.LayoutDate2)
+			if realityDay == planDay {
+				return nil, nil, xerr.Customf("该牛只:%d,今天已经提交过发情数据", item.CowId)
+			}
+		}
+
+		operationUser, _ := s.GetSystemUserById(ctx, int64(item.OperationId))
+		item.OperationName = operationUser.Name
+		newEventEstrus := model.NewEventEstrus(userModel.AppPasture.Id, cowInfo, pasturePb.ExposeEstrusType_Natural_Estrus,
+			pasturePb.IsShow_Ok, item.IsMating, item.EstrusAt, operationUser, userModel.SystemUser)
+
+		fmt.Println("item.IsMating", item.IsMating, item.UnMatingReasonsKind)
+		if item.IsMating == pasturePb.IsShow_Ok {
+			newEventMating := model.NewEventMating(userModel.AppPasture.Id, cowInfo, time.Now().Unix(), pasturePb.ExposeEstrusType_Natural_Estrus)
+			eventMatingList = append(eventMatingList, newEventMating)
+		} else {
+			newEventEstrus.UnMatingReasonsKind = item.UnMatingReasonsKind
+			newEventEstrus.UnMatingReasonsName = unMatingReasonsMap[item.UnMatingReasonsKind]
+		}
+
+		newEventEstrus.Remarks = item.Remarks
+		eventEstrusList = append(eventEstrusList, newEventEstrus)
+	}
+	return eventEstrusList, eventMatingList, nil
+}

+ 18 - 31
module/backend/event_cow_log.go

@@ -41,14 +41,8 @@ func (s *StoreEntry) SubmitEventLog(
 		if data.Sex == pasturePb.Genders_Female {
 			sex = "母"
 		}
-		desc = fmt.Sprintf("入场时间: %s; 性别: %s; 栏舍: %s; 体重: %s kg; price: %s; 来源: %s;",
-			time.Unix(eventAt, 0).Format(model.LayoutDate2),
-			sex,
-			penMap[cow.PenId].Name,
-			strconv.FormatFloat(float64(data.Weight), 'f', 2, 64),
-			strconv.FormatFloat(float64(data.Price), 'f', 2, 64),
-			sourceMap[cow.SourceId],
-		)
+		desc = fmt.Sprintf("性别: %s; 栏舍: %s; 体重: %s kg; price: %s; 来源: %s;", sex, penMap[cow.PenId].Name, strconv.FormatFloat(float64(data.Weight), 'f', 2, 64),
+			strconv.FormatFloat(float64(data.Price), 'f', 2, 64), sourceMap[cow.SourceId])
 	case pasturePb.EventType_Transfer_Ben:
 		data := req.(*model.EventTransferGroup)
 		transferAt, _ := time.Parse(model.LayoutDate2, data.TransferDate)
@@ -59,28 +53,23 @@ func (s *StoreEntry) SubmitEventLog(
 		desc = fmt.Sprintf("转出栏舍: %s; 转入栏舍: %s", penMap[data.PenOutId].Name, penMap[data.PenInId].Name)
 	case pasturePb.EventType_Body_Score:
 	case pasturePb.EventType_Pregnancy_Check:
-		data := req.(*pasturePb.EventPregnantCheckBatch)
-		for _, v := range data.Item {
-			if int64(v.CowId) != cow.Id {
-				continue
-			}
-			eventAt = int64(v.PregnantCheckAt)
-			if v.PregnantCheckResult == pasturePb.PregnantCheckResult_Pregnant {
-				desc += fmt.Sprintf("孕检方式: %s; 孕检结果: %s", s.PregnantCheckMethodMap()[v.PregnantCheckMethod], s.MatingResultMap()[pasturePb.MatingResult_Pregnant])
+		data := req.(*PregnantCheckBatchModel)
+		eventAt = int64(data.PregnantCheckAt)
+		if data.PregnantCheckResult == pasturePb.PregnantCheckResult_Pregnant {
+			desc += fmt.Sprintf("孕检方式: %s; 孕检结果: %s", s.PregnantCheckMethodMap()[data.PregnantCheckMethod], s.MatingResultMap()[pasturePb.MatingResult_Pregnant])
+		}
+		if data.PregnantCheckResult == pasturePb.PregnantCheckResult_UnPregnant {
+			desc += fmt.Sprintf("孕检方式: %s", s.PregnantCheckMethodMap()[data.PregnantCheckMethod])
+			if cow.BreedStatus == pasturePb.BreedStatus_Abort {
+				desc += fmt.Sprintf("; 复检结果: %s", s.MatingResultMap()[pasturePb.MatingResult_Abort])
 			}
-			if v.PregnantCheckResult == pasturePb.PregnantCheckResult_UnPregnant {
-				desc += fmt.Sprintf("孕检方式: %s", s.PregnantCheckMethodMap()[v.PregnantCheckMethod])
-				if cow.BreedStatus == pasturePb.BreedStatus_Abort {
-					desc += fmt.Sprintf("; 复检结果: %s", s.MatingResultMap()[pasturePb.MatingResult_Abort])
-				}
-				if cow.BreedStatus == pasturePb.BreedStatus_Empty {
-					desc += fmt.Sprintf("; 孕检结果: %s", s.MatingResultMap()[pasturePb.MatingResult_Empty])
-				}
+			if cow.BreedStatus == pasturePb.BreedStatus_Empty {
+				desc += fmt.Sprintf("; 孕检结果: %s", s.MatingResultMap()[pasturePb.MatingResult_Empty])
 			}
-			operationUser.Id = int64(v.OperationId)
-			operationUser.Name = v.OperationName
-			remarks = v.Remarks
 		}
+		operationUser.Id = data.OperationUser.Id
+		operationUser.Name = data.OperationUser.Name
+		remarks = data.Remarks
 	case pasturePb.EventType_Estrus:
 		data := req.(*model.EventEstrus)
 		eventAt = data.PlanDay
@@ -194,11 +183,9 @@ func (s *StoreEntry) UpdateMatingResultEventCowLogByCowId(ctx context.Context, c
 		First(newEventCowLog).Error; err != nil {
 		return xerr.WithStack(err)
 	} else {
-		desc := strings.ReplaceAll(newEventCowLog.EventDescription, "未知 ", newResult)
+		desc := strings.ReplaceAll(newEventCowLog.EventDescription, "未知", newResult)
 		if err = s.DB.Table(newEventCowLog.TableName()).
-			Where("cow_id = ?", cowId).
-			Where("event_category_kind = ?", pasturePb.EventCategory_Breed).
-			Where("event_type = ?", pasturePb.EventType_Mating).
+			Where("id = ?", newEventCowLog.Id).
 			Update("event_description", desc).Error; err != nil {
 			return xerr.WithStack(err)
 		}

+ 65 - 0
module/backend/event_health.go

@@ -4,6 +4,7 @@ import (
 	"context"
 	"fmt"
 	"kpt-pasture/model"
+	"kpt-pasture/util"
 	"net/http"
 	"strings"
 	"time"
@@ -170,6 +171,70 @@ func (s *StoreEntry) CowDiseaseCreate(ctx context.Context, req *pasturePb.EventC
 	return nil
 }
 
+func (s *StoreEntry) EstrusCowList(ctx context.Context, req *pasturePb.EstrusItemsRequest, pagination *pasturePb.PaginationModel) (*pasturePb.EventEstrusResponse, error) {
+	userModel, err := s.GetUserModel(ctx)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	var count int64
+	neckRingEstrusList := make([]*model.NeckRingEstrus, 0)
+	pref := s.DB.Model(new(model.NeckRingEstrus)).
+		Where("pasture_id = ?", userModel.AppPasture.Id).
+		Where("is_show = ?", pasturePb.IsShow_Ok)
+
+	if len(req.CowIds) > 0 {
+		cowIds := strings.Split(util.ArrayInt32ToStrings(req.CowIds, ","), ",")
+		pref.Where("cow_id IN ?", cowIds)
+	}
+
+	if req.Level > 0 {
+		pref.Where("level = ?", req.Level)
+	}
+
+	if len(req.PenIds) > 0 {
+		penIds := strings.Split(util.ArrayInt32ToStrings(req.PenIds, ","), ",")
+		pref.Where("pen_id IN ?", penIds)
+	}
+
+	if err = pref.Order("level desc").
+		Count(&count).
+		Limit(int(pagination.PageSize)).
+		Offset(int(pagination.PageOffset)).
+		Find(&neckRingEstrusList).Error; err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	cowMap := make(map[int64]*model.Cow)
+	eventLogMap := make(map[int64]string)
+	cowIds := make([]int64, 0)
+	for _, v := range neckRingEstrusList {
+		cowIds = append(cowIds, v.CowId)
+		lastEventLog := s.GetCowLastEvent(userModel.AppPasture.Id, v.CowId, pasturePb.EventCategory_Breed)
+		if lastEventLog != nil {
+			eventLogMap[v.CowId] = lastEventLog.EventDescription
+		}
+	}
+
+	if len(cowIds) > 0 {
+		cowList, _ := s.GetCowInfoByCowIds(ctx, userModel.AppPasture.Id, cowIds)
+		for _, cow := range cowList {
+			cowMap[cow.Id] = cow
+		}
+	}
+
+	return &pasturePb.EventEstrusResponse{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &pasturePb.EstrusItemsData{
+			List:     model.NeckRingEstrusSlice(neckRingEstrusList).ToPB(cowMap, eventLogMap),
+			Total:    int32(count),
+			PageSize: pagination.PageSize,
+			Page:     pagination.Page,
+		},
+	}, nil
+}
+
 // CowDiseaseList 发病牛只清单
 func (s *StoreEntry) CowDiseaseList(ctx context.Context, req *pasturePb.SearchEventCowTreatmentRequest, pagination *pasturePb.PaginationModel) (*pasturePb.EventCowDiseaseResponse, error) {
 	userModel, err := s.GetUserModel(ctx)

+ 3 - 1
module/backend/interface.go

@@ -174,6 +174,8 @@ type EventService interface {
 	MatingCreate(ctx context.Context, req *pasturePb.EventMating) error
 	// EstrusBatchMating 发情批量处理配种
 	EstrusBatchMating(ctx context.Context, req *pasturePb.EventNaturalEstrusBatch) error
+	EstrusList(ctx context.Context, req *pasturePb.EstrusItemsRequest, pagination *pasturePb.PaginationModel) (*pasturePb.SearchEventEstrusResponse, error)
+
 	// AbortionList 流产
 	AbortionList(ctx context.Context, req *pasturePb.SearchEventRequest, pagination *pasturePb.PaginationModel) (*pasturePb.EventAbortionResponse, error)
 	AbortionCreate(ctx context.Context, req *pasturePb.EventAbortionRequest) error
@@ -287,7 +289,7 @@ type WorkService interface {
 	MatingCowList(ctx context.Context, req *pasturePb.ItemsRequest, pagination *pasturePb.PaginationModel) (*pasturePb.MatingItemsResponse, error)
 	CalvingCowList(ctx context.Context, req *pasturePb.ItemsRequest, pagination *pasturePb.PaginationModel) (*pasturePb.CalvingItemsResponse, error)
 	CowDiseaseList(ctx context.Context, req *pasturePb.SearchEventCowTreatmentRequest, pagination *pasturePb.PaginationModel) (*pasturePb.EventCowDiseaseResponse, error)
-	EstrusList(ctx context.Context, req *pasturePb.EstrusItemsRequest, pagination *pasturePb.PaginationModel) (*pasturePb.SearchEventEstrusResponse, error)
+	EstrusCowList(ctx context.Context, req *pasturePb.EstrusItemsRequest, pagination *pasturePb.PaginationModel) (*pasturePb.EventEstrusResponse, error)
 }
 
 type TestService interface {

+ 12 - 27
module/backend/sql.go

@@ -169,8 +169,9 @@ func (s *StoreEntry) GetCowInfoByCowId(ctx context.Context, pastureId, cowId int
 		First(cowData).Error; err != nil {
 		if errors.Is(err, gorm.ErrRecordNotFound) {
 			return nil, xerr.Customf("该牛只数据不存在: %d", cowId)
+		} else {
+			return nil, xerr.WithStack(err)
 		}
-		return nil, xerr.WithStack(err)
 	}
 	return cowData, nil
 }
@@ -351,19 +352,16 @@ func (s *StoreEntry) GetSystemBasicByName(ctx context.Context, name string) (*mo
 }
 
 // FindLastEventMatingByCowId 根据cowId获取最近一次配种表信息
-func (s *StoreEntry) FindLastEventMatingByCowId(ctx context.Context, pastureId, cowId int64) (*model.EventMating, bool, error) {
+func (s *StoreEntry) FindLastEventMatingByCowId(ctx context.Context, pastureId, cowId int64) (*model.EventMating, error) {
 	newEventMating := &model.EventMating{}
 	if err := s.DB.Model(new(model.EventMating)).
 		Where("cow_id = ?", cowId).
 		Where("pasture_id = ?", pastureId).
 		Order("id desc").
 		First(newEventMating).Error; err != nil {
-		if errors.Is(err, gorm.ErrRecordNotFound) {
-			return nil, false, nil
-		}
-		return nil, false, xerr.WithStack(err)
+		return nil, xerr.WithStack(err)
 	}
-	return newEventMating, true, nil
+	return newEventMating, nil
 }
 
 func (s *StoreEntry) FindEventEstrusByCowId(ctx context.Context, pastureId, cowId int64) (*model.EventEstrus, bool, error) {
@@ -402,10 +400,7 @@ func (s *StoreEntry) GetOutboundDetailByOutboundId(ctx context.Context, id int64
 	return list, nil
 }
 
-func (s *StoreEntry) FindEventPregnantCheckIsExIstByCowId(
-	ctx context.Context,
-	cow *model.Cow,
-) (*model.EventPregnantCheck, bool, error) {
+func (s *StoreEntry) FindEventPregnantCheckIsExIstByCowId(ctx context.Context, cow *model.Cow) (*model.EventPregnantCheck, error) {
 	newEventPregnantCheck := &model.EventPregnantCheck{}
 	if err := s.DB.Model(new(model.EventPregnantCheck)).
 		Where("cow_id = ?", cow.Id).
@@ -414,12 +409,12 @@ func (s *StoreEntry) FindEventPregnantCheckIsExIstByCowId(
 		Order("id desc").
 		First(newEventPregnantCheck).Error; err != nil {
 		if errors.Is(err, gorm.ErrRecordNotFound) {
-			return nil, false, nil
+			return nil, xerr.Customf("该牛只: %d 孕检数据未找到", cow.Id)
 		} else {
-			return nil, false, xerr.WithStack(err)
+			return nil, xerr.WithStack(err)
 		}
 	}
-	return newEventPregnantCheck, true, nil
+	return newEventPregnantCheck, nil
 }
 
 func (s *StoreEntry) DiseaseMaps(ctx context.Context) (map[int64]*model.Disease, error) {
@@ -497,20 +492,10 @@ func GetCowPenInfoByCowId(DB *kptstore.DB, cowId int64) *model.Pen {
 	return penData
 }
 
-func GetCowInfoByCowId(DB *kptstore.DB, cowId int64) *model.Cow {
-	cowData := &model.Cow{}
-	if err := DB.Model(new(model.Cow)).
-		Where("id = ?", cowId).
-		First(cowData).Error; err != nil {
-		return nil
-	}
-	return cowData
-}
-
-func GetCowLastEvent(DB *kptstore.DB, cowId int64, eventCategoryId pasturePb.EventCategory_Kind) *model.EventCowLog {
+func (s *StoreEntry) GetCowLastEvent(pastureId, cowId int64, eventCategoryId pasturePb.EventCategory_Kind) *model.EventCowLog {
 	newEventCowLog := &model.EventCowLog{CowId: cowId}
-	pref := DB.Table(newEventCowLog.TableName()).
-		Where("cow_id = ?", cowId)
+	pref := s.DB.Table(newEventCowLog.TableName()).
+		Where("cow_id = ?", cowId).Where("pasture_id = ?", pastureId)
 	if eventCategoryId > 0 {
 		pref.Where("event_category_id = ?", eventCategoryId)
 	}

+ 10 - 3
module/crontab/neck_ring_estrus.go

@@ -20,6 +20,7 @@ const (
 	MinCalvingAge   = 20
 	MinLact         = 0
 	NormalChangJust = 10
+	B48             = 48
 )
 
 func (e *Entry) UpdateCowEstrus() (err error) {
@@ -78,6 +79,7 @@ func (e *Entry) CowEstrusWarning(pastureId int64, xToday *XToday) (err error) {
 		return xerr.WithStack(err)
 	}
 
+	zaplog.Info("CowEstrusWarning", zap.Any("neckActiveHabitList", neckActiveHabitList))
 	neckActiveHabitMap := make(map[int64][]*model.NeckActiveHabit)
 	for _, habit := range neckActiveHabitList {
 		cft := calculateCFT(habit)
@@ -90,6 +92,8 @@ func (e *Entry) CowEstrusWarning(pastureId int64, xToday *XToday) (err error) {
 		neckActiveHabitMap[habit.CowId] = append(neckActiveHabitMap[habit.CowId], habit)
 	}
 
+	zaplog.Info("CowEstrusWarning", zap.Any("neckActiveHabitMap", neckActiveHabitMap))
+
 	neckRingEstrusList := make([]*model.NeckRingEstrus, 0)
 	for cowId, cowHabitList := range neckActiveHabitMap {
 		// 最近3天最大发情记录,小于该变化趋势的不再插入
@@ -129,7 +133,7 @@ func (e *Entry) CowEstrusWarning(pastureId int64, xToday *XToday) (err error) {
 			b48 = t3.Sub(t1).Hours()
 		}
 
-		if (int32(maxCft) > before3Data.DayHigh || b48 > 48) && int32(maxCft)+cowEstrus.HadJust > xToday.ActiveLow {
+		if (int32(maxCft) > before3Data.DayHigh || b48 > B48) && int32(maxCft)+cowEstrus.HadJust > xToday.ActiveLow {
 			level := calculateLevel(maxCft, cowEstrus, xToday)
 			cowInfo := e.FindCowInfoByNeckRingNumber(cowHabitList[0].NeckRingNumber)
 			isShow := pasturePb.IsShow_Ok
@@ -163,6 +167,9 @@ func (e *Entry) CowEstrusWarning(pastureId int64, xToday *XToday) (err error) {
 			neckRingEstrusList = append(neckRingEstrusList, newNeckRingEstrus)
 		}
 	}
+
+	zaplog.Info("CowEstrusWarning", zap.Any("neckRingEstrusList", neckRingEstrusList))
+
 	if len(neckRingEstrusList) > 0 {
 		if err = e.DB.Model(new(model.NeckRingEstrus)).Create(neckRingEstrusList).Error; err != nil {
 			zaplog.Error("CowEstrusWarningNew", zap.Any("eventEstrusList", neckRingEstrusList), zap.Any("err", err))
@@ -230,11 +237,11 @@ func calculateCFT(habit *model.NeckActiveHabit) (cft float32) {
 // calculateLevel 计算发情等级
 func calculateLevel(cft float32, cowEstrus *CowEstrus, xToday *XToday) pasturePb.EstrusLevel_Kind {
 	level := pasturePb.EstrusLevel_High
-	if int32(cft)+cowEstrus.HadJust < int32(xToday.ActiveMiddle) {
+	if int32(cft)+cowEstrus.HadJust < xToday.ActiveMiddle {
 		level = pasturePb.EstrusLevel_Low
 	}
 
-	if int32(cft)+cowEstrus.HadJust >= int32(xToday.ActiveHigh) {
+	if int32(cft)+cowEstrus.HadJust >= xToday.ActiveHigh {
 		level = pasturePb.EstrusLevel_Middle
 	}
 	return level

+ 11 - 0
module/crontab/neck_ring_estus_test.go

@@ -0,0 +1,11 @@
+package crontab
+
+import (
+	"fmt"
+	"testing"
+)
+
+func TestCalculateCFT(t *testing.T) {
+	str := `event_mating`
+	fmt.Println(str)
+}

+ 12 - 23
module/crontab/neck_ring_handle.go

@@ -25,35 +25,24 @@ const (
 
 var (
 	defaultLimit       = int32(1000)
-	isDelete           bool
-	mergeIsRunning     bool
 	calculateIsRunning bool
 )
 
 // NeckRingOriginalMergeData 把脖环数据合并成2个小时的
 func (e *Entry) NeckRingOriginalMergeData() (err error) {
-	if mergeIsRunning {
-		return nil
+	if ok := e.IsExistCrontabLog(NeckRingOriginal); !ok {
+		newTime := time.Now()
+		e.CreateCrontabLog(NeckRingOriginal)
+		// 原始数据删除15天前的
+		e.DB.Model(new(model.NeckRingOriginal)).
+			Where("created_at < ?", newTime.AddDate(0, 0, -7).Unix()).
+			Delete(new(model.NeckRingOriginal))
+		// 活动数据删除6个月前的数据
+		e.DB.Model(new(model.NeckActiveHabit)).
+			Where("created_at < ?", newTime.AddDate(0, -2, 0).Unix()).
+			Delete(new(model.NeckActiveHabit))
 	}
-	newTime := time.Now()
-	mergeIsRunning = true
-	defer func() {
-		mergeIsRunning = false
-		if newTime.Day()%15 == 0 && !isDelete {
-			// 原始数据删除15天前的
-			e.DB.Model(new(model.NeckRingOriginal)).
-				Where("created_at < ?", newTime.AddDate(0, 0, -15).Unix()).
-				Delete(new(model.NeckRingOriginal))
-			e.DB.Model(new(model.NeckRingProcess)).
-				Where("created_at < ?", newTime.AddDate(0, 0, -15).Unix()).
-				Delete(new(model.NeckRingProcess))
-			// 活动数据删除6个月前的数据
-			e.DB.Model(new(model.NeckActiveHabit)).
-				Where("created_at < ?", newTime.AddDate(0, -6, 0).Unix()).
-				Delete(new(model.NeckActiveHabit))
-			isDelete = true
-		}
-	}()
+
 	limit := e.Cfg.NeckRingLimit
 	if limit <= 0 {
 		limit = defaultLimit

+ 1 - 3
module/crontab/other.go

@@ -30,14 +30,12 @@ func (e *Entry) CreateCrontabLog(name string) {
 	// 日志保留15天以内的
 	nowDay := time.Now()
 	defer func() {
-		if nowDay.Day()%15 == 0 && !isDelete {
+		if nowDay.Day()%15 == 0 {
 			beforeDay := nowDay.AddDate(0, 0, -15)
 			beforeDayFormat := beforeDay.Format(model.LayoutDate2)
 			e.DB.Model(&model.CronLog{}).
 				Where("date < ?", beforeDayFormat).
-				Where("name != ?", NeckRingOriginal).
 				Delete(&model.CronLog{})
-			isDelete = true
 		}
 	}()
 	crontabLog := model.NewCronLog(name)