Просмотр исходного кода

crontab: neckRing calculate update

Yi 1 месяц назад
Родитель
Сommit
3e6c76c1a4

+ 1 - 1
go.mod

@@ -3,7 +3,7 @@ module kpt-pasture
 go 1.17
 
 require (
-	gitee.com/xuyiping_admin/go_proto v0.0.0-20250429032832-f43ab362a21b
+	gitee.com/xuyiping_admin/go_proto v0.0.0-20250430023822-7c5bf763451e
 	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

+ 2 - 0
go.sum

@@ -86,6 +86,8 @@ gitee.com/xuyiping_admin/go_proto v0.0.0-20250429024839-b282b4e1465a h1:slKL5wf3
 gitee.com/xuyiping_admin/go_proto v0.0.0-20250429024839-b282b4e1465a/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
 gitee.com/xuyiping_admin/go_proto v0.0.0-20250429032832-f43ab362a21b h1:9Shzi/Jp8uHcb47IVRtbuWPqbgv483UJaxsEoy3RD5s=
 gitee.com/xuyiping_admin/go_proto v0.0.0-20250429032832-f43ab362a21b/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250430023822-7c5bf763451e h1:IWZC/FOxItSt33n+qzMdJhclW9oFzOnrze0SGC3jT+c=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250430023822-7c5bf763451e/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 - 27
http/handler/analysis/analysis.go

@@ -339,31 +339,6 @@ func PenBehaviorAnalysis(c *gin.Context) {
 	ginutil.JSONResp(c, res)
 }
 
-func PenBehaviorAnalysis2(c *gin.Context) {
-	var req pasturePb.BarnBehaviorCurveRequest
-	if err := ginutil.BindProto(c, &req); err != nil {
-		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
-		return
-	}
-
-	if err := valid.ValidateStruct(&req,
-		valid.Field(&req.StartAt, valid.Required),
-		valid.Field(&req.EndAt, valid.Required),
-		valid.Field(&req.PenId, valid.Required),
-	); err != nil {
-		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
-		return
-	}
-
-	res, err := middleware.Dependency(c).StoreEventHub.OpsService.PenBehavior2(c, &req)
-	if err != nil {
-		apierr.ClassifiedAbort(c, err)
-		return
-	}
-
-	c.JSON(http.StatusOK, res)
-}
-
 func PenBehaviorDaily(c *gin.Context) {
 	var req pasturePb.BarnMonitorRequest
 	if err := ginutil.BindProto(c, &req); err != nil {
@@ -384,8 +359,7 @@ func PenBehaviorDaily(c *gin.Context) {
 		apierr.ClassifiedAbort(c, err)
 		return
 	}
-
-	ginutil.JSONResp(c, res)
+	c.JSON(http.StatusOK, res)
 }
 
 func CowBehaviorDistribution(c *gin.Context) {

+ 0 - 1
http/route/analysis_api.go

@@ -28,7 +28,6 @@ func AnalysisAPI(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
 		analysisRoute.POST("/multi/factor/pregnant/report", analysis.MultiFactorInfantSurvivalRate)   // 多因素受胎率
 
 		analysisRoute.POST("/pen/behavior", analysis.PenBehaviorAnalysis)                  // 栏舍行为数据
-		analysisRoute.POST("/pen/behavior2", analysis.PenBehaviorAnalysis2)                // 栏舍行为数据
 		analysisRoute.POST("/pen/behavior/monitor", analysis.PenBehaviorDaily)             // 栏舍饲喂监测
 		analysisRoute.POST("/cow/behavior/distribution", analysis.CowBehaviorDistribution) // 牛只行为分布
 	}

+ 1 - 1
model/data_waring.go

@@ -216,7 +216,7 @@ type DataWarningSlice []*DataWarning
 func (d DataWarningSlice) ToPB() []*pasturePb.WarningDataShow {
 	res := make([]*pasturePb.WarningDataShow, 0)
 	for _, warningData := range d {
-		if warningData.IsShow != pasturePb.IsShow_Ok || warningData.DataValue == "0" {
+		if warningData.DataValue == "0" {
 			continue
 		}
 		dataUpdateTimeFormat := ""

+ 18 - 17
model/data_warning_items.go

@@ -7,18 +7,19 @@ import (
 )
 
 type DataWarningItems struct {
-	Id        int64                 `json:"id"`
-	PastureId int64                 `json:"pastureId"`
-	UserId    int64                 `json:"userId"`
-	WarningId int64                 `json:"warningId"`
-	GroupId   int32                 `json:"groupId"`
-	FieldName string                `json:"fieldName"`
-	FieldDesc string                `json:"fieldDesc"`
-	Operator  string                `json:"operator"`
-	Value     string                `json:"value"`
-	IsShow    pasturePb.IsShow_Kind `json:"isShow"`
-	CreatedAt int64                 `json:"createdAt"`
-	UpdatedAt int64                 `json:"updatedAt"`
+	Id          int64                 `json:"id"`
+	PastureId   int64                 `json:"pastureId"`
+	UserId      int64                 `json:"userId"`
+	WarningId   int64                 `json:"warningId"`
+	GroupId     int32                 `json:"groupId"`
+	FieldName   string                `json:"fieldName"`
+	FieldDesc   string                `json:"fieldDesc"`
+	Operator    string                `json:"operator"`
+	Value       string                `json:"value"`
+	IsCondition pasturePb.IsShow_Kind `json:"isCondition"`
+	IsShow      pasturePb.IsShow_Kind `json:"isShow"`
+	CreatedAt   int64                 `json:"createdAt"`
+	UpdatedAt   int64                 `json:"updatedAt"`
 }
 
 func (d *DataWarningItems) TableName() string {
@@ -153,8 +154,8 @@ func NewDataWarningItems(pastureId, userId int64, dataWarning *DataWarning, req
 type DataWarningItemsSlice []*DataWarningItems
 
 func (d DataWarningItemsSlice) ToPB(dataWarning []*DataWarning) []*pasturePb.WarningDataSet {
-	res := make([]*pasturePb.WarningDataSet, len(d))
-	for i, v := range d {
+	res := make([]*pasturePb.WarningDataSet, 0)
+	for _, v := range d {
 		name := ""
 		kind := pasturePb.DataWarningType_Invalid
 		for _, w := range dataWarning {
@@ -164,10 +165,10 @@ func (d DataWarningItemsSlice) ToPB(dataWarning []*DataWarning) []*pasturePb.War
 			}
 		}
 
-		if name == "" || v.Id <= 0 {
+		if name == "" || v.Id <= 0 || v.IsCondition == pasturePb.IsShow_Ok {
 			continue
 		}
-		res[i] = &pasturePb.WarningDataSet{
+		res = append(res, &pasturePb.WarningDataSet{
 			Id:        int32(v.Id),
 			WarningId: int32(v.WarningId),
 			GroupId:   v.GroupId,
@@ -178,7 +179,7 @@ func (d DataWarningItemsSlice) ToPB(dataWarning []*DataWarning) []*pasturePb.War
 			Operator:  v.Operator,
 			Value:     v.Value,
 			IsShow:    v.IsShow,
-		}
+		})
 	}
 	return res
 }

+ 1 - 1
model/pen_behavior.go

@@ -114,7 +114,7 @@ func (p PenBehaviorSlice) ToPB() *pasturePb.BarnBehaviorCurveItem {
 		res.WeekAvgIntakeMaxShadow = append(res.WeekAvgIntakeMaxShadow, intakeMaxShadow)
 		res.WeekAvgIntakeMinShadow = append(res.WeekAvgIntakeMinShadow, intakeMinShadow)
 		restMaxShadow := float32(v.WeekRestRate + v.RestStd) //- float32(v.WeekRestRate-v.RestStd)
-		restMinShadow := float32(v.WeekRestRate + v.RestStd)
+		restMinShadow := float32(v.WeekRestRate - v.RestStd)
 		if restMinShadow < 0 {
 			restMinShadow = 0
 		}

+ 74 - 20
model/pen_behavior_day.go

@@ -67,30 +67,84 @@ type PenBehaviorDayModel struct {
 
 type PenBehaviorDayModelSlice []*PenBehaviorDayModel
 
-func (p PenBehaviorDayModelSlice) ToPB(dataTimeRange []string) *pasturePb.BarnMonitorItem {
-	res := &pasturePb.BarnMonitorItem{
-		Headers:       dataTimeRange,
-		DateTime:      make([]string, 0),
-		DayMilk:       make([]float32, 0),
-		DayRumina:     make([]int32, 0),
-		DayIntake:     make([]int32, 0),
-		DayImmobility: make([]int32, 0),
-		DayGasp:       make([]int32, 0),
-		DayChew:       make([]int32, 0),
-		DayStd:        make([]int32, 0),
+func (p PenBehaviorDayModelSlice) ToPB(dataTimeRange, headers []string, behaviorKind pasturePb.Behavior_Kind) *BarnMonitorItem {
+	res := &BarnMonitorItem{
+		Headers:  headers,
+		DateTime: dataTimeRange,
+		DataList: make([][]int32, len(headers)),
 	}
-	for _, dt := range dataTimeRange {
+
+	// 预分配内存
+	for i := range res.DataList {
+		res.DataList[i] = make([]int32, len(dataTimeRange))
+	}
+
+	// 创建日期到索引的映射,避免重复查找
+	dateIndexMap := make(map[string]int, len(dataTimeRange))
+	for i, dt := range dataTimeRange {
+		dateIndexMap[dt] = i
+	}
+
+	// 创建牛舍名称到索引的映射
+	headerIndexMap := make(map[string]int, len(headers))
+	for i, h := range headers {
+		headerIndexMap[h] = i
+	}
+
+	if behaviorKind == pasturePb.Behavior_Invalid {
+		// 使用映射优化查找
 		for _, v := range p {
-			if dt == v.HeatDate {
-				res.DateTime = append(res.DateTime, v.HeatDate)
-				res.DayMilk = append(res.DayMilk, v.DayMilk)
-				res.DayRumina = append(res.DayRumina, v.DayRumina)
-				res.DayIntake = append(res.DayIntake, v.DayIntake)
-				res.DayImmobility = append(res.DayImmobility, v.DayImmobility)
-				res.DayChew = append(res.DayChew, v.DayChew)
-				res.DayStd = append(res.DayStd, int32(v.RuminaStd))
+			if dateIdx, exists := dateIndexMap[v.HeatDate]; exists {
+				for i, header := range headers {
+					switch header {
+					case "反刍":
+						res.DataList[i][dateIdx] = v.DayRumina
+					case "采食":
+						res.DataList[i][dateIdx] = v.DayIntake
+					case "休息":
+						res.DataList[i][dateIdx] = v.DayInactive
+					case "咀嚼":
+						res.DataList[i][dateIdx] = v.DayChew
+					case "静止":
+						res.DataList[i][dateIdx] = v.DayImmobility
+					case "方差":
+						res.DataList[i][dateIdx] = int32(v.RuminaStd)
+					}
+				}
+			}
+		}
+	} else {
+		// 使用映射优化查找
+		for _, v := range p {
+			if dateIdx, exists := dateIndexMap[v.HeatDate]; exists {
+				if headerIdx, exists := headerIndexMap[v.PenName]; exists {
+					switch behaviorKind {
+					case pasturePb.Behavior_Rumina:
+						res.DataList[headerIdx][dateIdx] = v.DayRumina
+					case pasturePb.Behavior_Intake:
+						res.DataList[headerIdx][dateIdx] = v.DayIntake
+					case pasturePb.Behavior_Reset:
+						res.DataList[headerIdx][dateIdx] = v.DayInactive
+					case pasturePb.Behavior_Immobility:
+						res.DataList[headerIdx][dateIdx] = v.DayImmobility
+					case pasturePb.Behavior_Chew:
+						res.DataList[headerIdx][dateIdx] = v.DayChew
+					}
+				}
 			}
 		}
 	}
 	return res
 }
+
+type BarnMonitorResponse struct {
+	Code int32            `json:"code"`
+	Msg  string           `json:"msg"`
+	Data *BarnMonitorItem `json:"data"`
+}
+
+type BarnMonitorItem struct {
+	DateTime []string  `json:"dateTime"`
+	Headers  []string  `json:"headers"`
+	DataList [][]int32 `json:"dataList"`
+}

+ 21 - 36
module/backend/analysis_more.go

@@ -40,35 +40,7 @@ func (s *StoreEntry) PenBehavior(ctx context.Context, req *pasturePb.BarnBehavio
 		Data: model.PenBehaviorSlice(penBehaviorList).ToPB(),
 	}, nil
 }
-
-func (s *StoreEntry) PenBehavior2(ctx context.Context, req *pasturePb.BarnBehaviorCurveRequest) (*model.BarnBehaviorCurveResponse, error) {
-	userModel, err := s.GetUserModel(ctx)
-	if err != nil {
-		return nil, err
-	}
-
-	if req.StartAt == 0 || req.EndAt == 0 || req.EndAt < req.StartAt {
-		return nil, xerr.Customf("时间范围错误")
-	}
-	startTime := time.Unix(int64(req.StartAt), 0).Local().Format(model.LayoutDate2)
-	endTime := time.Unix(int64(req.EndAt), 0).Local().Format(model.LayoutDate2)
-	penBehaviorList := make([]*model.PenBehavior, 0)
-	if err = s.DB.Model(new(model.PenBehavior)).
-		Where("pasture_id = ?", userModel.AppPasture.Id).
-		Where("pen_id = ?", req.PenId).
-		Where("heat_date BETWEEN ? AND ?", startTime, endTime).
-		Find(&penBehaviorList).Error; err != nil {
-		return nil, err
-	}
-
-	return &model.BarnBehaviorCurveResponse{
-		Code: http.StatusOK,
-		Msg:  "ok",
-		Data: model.PenBehaviorSlice(penBehaviorList).ToPB2(),
-	}, nil
-}
-
-func (s *StoreEntry) PenBehaviorDaily(ctx context.Context, req *pasturePb.BarnMonitorRequest) (*pasturePb.BarnMonitorResponse, error) {
+func (s *StoreEntry) PenBehaviorDaily(ctx context.Context, req *pasturePb.BarnMonitorRequest) (*model.BarnMonitorResponse, error) {
 	userModel, err := s.GetUserModel(ctx)
 	if err != nil {
 		return nil, xerr.WithStack(err)
@@ -85,30 +57,43 @@ func (s *StoreEntry) PenBehaviorDaily(ctx context.Context, req *pasturePb.BarnMo
 		return nil, xerr.WithStack(err)
 	}
 
+	headers := make([]string, 0)
+	if req.PenId <= 0 && req.BehaviorKind > 0 {
+		penList, _ := s.GetPenList(ctx, userModel.AppPasture.Id)
+		for _, v := range penList {
+			headers = append(headers, v.Name)
+		}
+	} else {
+		behaviorEnumList := s.Behavior("")
+		for _, v := range behaviorEnumList {
+			headers = append(headers, v.Label)
+		}
+		headers = append(headers, "方差")
+	}
 	penBehaviorDayModelList := make([]*model.PenBehaviorDayModel, 0)
 	pref := s.DB.Model(new(model.PenBehaviorDay)).
 		Select(`distinct(heat_date) as heat_date,pen_id,pen_name,rumina_std,cow_count,day_rumina,day_intake,
 			day_inactive,day_milk,day_rumina+day_intake as day_chew,24*60 - day_active as day_immobility`).
 		Where("pasture_id = ?", userModel.AppPasture.Id).
 		Where("heat_date BETWEEN ? AND ?", startDate, endDate)
-	if len(req.BarnIds) == 0 && req.BehaviorKind > 0 {
-		if err = pref.Where("pen_id IN (?)", req.BarnIds).
-			Order("heat_date").
+	if req.PenId <= 0 && req.BehaviorKind > 0 {
+		if err = pref.Group("heat_date,pen_id").
+			Order("heat_date,pen_id").
 			Find(&penBehaviorDayModelList).Error; err != nil {
 			return nil, xerr.WithStack(err)
 		}
 	} else {
-		if err = pref.Group("heat_date,pen_id").
-			Order("heat_date,pen_id").
+		if err = pref.Where("pen_id = ?", req.PenId).
+			Order("heat_date").
 			Find(&penBehaviorDayModelList).Error; err != nil {
 			return nil, xerr.WithStack(err)
 		}
 	}
 
-	return &pasturePb.BarnMonitorResponse{
+	return &model.BarnMonitorResponse{
 		Code: http.StatusOK,
 		Msg:  "ok",
-		Data: model.PenBehaviorDayModelSlice(penBehaviorDayModelList).ToPB(dataTimeRange),
+		Data: model.PenBehaviorDayModelSlice(penBehaviorDayModelList).ToPB(dataTimeRange, headers, req.BehaviorKind),
 	}, err
 }
 

+ 6 - 8
module/backend/dashboard.go

@@ -425,10 +425,10 @@ func (s *StoreEntry) addUserDataWarning(ctx context.Context, pastureId, userId i
 // 更新用户预警数据
 func (s *StoreEntry) updateUserDataWarning(ctx context.Context, pastureId, userId int64, userDataWarningList []*model.DataWarning, warningDataSet []*pasturePb.WarningDataSet) error {
 	// 将请求数据按 WarningId 和 Id 映射
-	warningIsShowMap := make(map[int32]pasturePb.IsShow_Kind)
+	warningIsShowMap := make(map[int32]*pasturePb.WarningDataSet)
 	warningItemDataMap := make(map[int32]*pasturePb.WarningDataSet)
 	for _, set := range warningDataSet {
-		warningIsShowMap[set.WarningId] = set.IsShow
+		warningIsShowMap[set.WarningId] = set
 		warningItemDataMap[set.Id] = set
 	}
 
@@ -446,16 +446,16 @@ func (s *StoreEntry) updateUserDataWarning(ctx context.Context, pastureId, userI
 	return s.DB.Transaction(func(tx *gorm.DB) error {
 		// 更新预警数据的 IsShow 字段
 		for _, warning := range userDataWarningList {
-			if isShow, ok := warningIsShowMap[int32(warning.Id)]; ok {
+			if data, ok := warningIsShowMap[int32(warning.Id)]; ok {
 				if strings.ContainsAny(warning.Name, "0123456789") {
 					re := regexp.MustCompile(`\d+`)
-					warning.Name = re.ReplaceAllString(warning.Name, warning.DataValue)
-					warning.Description = re.ReplaceAllString(warning.Description, warning.DataValue)
+					warning.Name = re.ReplaceAllString(warning.Name, data.Value)
+					warning.Description = re.ReplaceAllString(warning.Description, data.Value)
 				}
 				if err = tx.Model(&model.DataWarning{}).
 					Where("id = ?", warning.Id).
 					Updates(map[string]interface{}{
-						"is_show":     isShow,
+						"is_show":     data.IsShow,
 						"name":        warning.Name,
 						"description": warning.Description,
 					}).Error; err != nil {
@@ -487,7 +487,6 @@ func (s *StoreEntry) FindDataWarning(ctx context.Context, pastureId, userId int6
 	if err := s.DB.Model(new(model.DataWarning)).
 		Where("user_id = ?", userId).
 		Where("pasture_id = ?", pastureId).
-		Where("is_show = ?", pasturePb.IsShow_Ok).
 		Find(&dataWarningList).Error; err != nil {
 		return nil, xerr.WithStack(err)
 	}
@@ -499,7 +498,6 @@ func (s *StoreEntry) FindDataWarningItems(ctx context.Context, pastureId, userId
 	if err := s.DB.Model(new(model.DataWarningItems)).
 		Where("pasture_id = ?", pastureId).
 		Where("user_id = ?", userId).
-		Where("is_show = ?", pasturePb.IsShow_Ok).
 		Find(&dataWarningItemsList).Error; err != nil {
 		return nil, xerr.WithStack(err)
 	}

+ 7 - 5
module/backend/event_breed.go

@@ -380,6 +380,7 @@ func (s *StoreEntry) EstrusBatchMating(ctx context.Context, req *pasturePb.Event
 	if err != nil {
 		return xerr.WithStack(err)
 	}
+	pastureId := userModel.AppPasture.Id
 
 	eventEstrusBatch, err := s.EstrusCheckDataCheck(ctx, userModel, req)
 	if err != nil {
@@ -403,7 +404,7 @@ func (s *StoreEntry) EstrusBatchMating(ctx context.Context, req *pasturePb.Event
 
 		// 记录事件日志
 		for _, v := range eventEstrusBatch.EventEstrusList {
-			cow, err := s.GetCowInfoByCowId(ctx, userModel.AppPasture.Id, v.CowId)
+			cow, err := s.GetCowInfoByCowId(ctx, pastureId, v.CowId)
 			if err != nil {
 				return xerr.WithStack(err)
 			}
@@ -415,7 +416,7 @@ func (s *StoreEntry) EstrusBatchMating(ctx context.Context, req *pasturePb.Event
 				Updates(cow).Error; err != nil {
 				return xerr.WithStack(err)
 			}
-			cowLogs := s.SubmitEventLog(ctx, userModel.AppPasture.Id, cow, pasturePb.EventType_Estrus, v)
+			cowLogs := s.SubmitEventLog(ctx, pastureId, cow, pasturePb.EventType_Estrus, v)
 			if err = tx.Table(cowLogs.TableName()).Create(cowLogs).Error; err != nil {
 				return xerr.WithStack(err)
 			}
@@ -438,7 +439,7 @@ func (s *StoreEntry) EstrusBatchMating(ctx context.Context, req *pasturePb.Event
 
 		neckRingEstrusWarningList := make([]*model.NeckRingEstrusWarning, 0)
 		if err = tx.Model(new(model.NeckRingEstrusWarning)).
-			Where("pasture_id = ?", userModel.AppPasture.Id).
+			Where("pasture_id = ?", pastureId).
 			Where("ear_number IN ?", eventEstrusBatch.EarNumbers).
 			Where("date_time BETWEEN ? AND ?", startTime, endTime).
 			Where("is_show = ?", pasturePb.IsShow_Ok).
@@ -461,8 +462,9 @@ func (s *StoreEntry) EstrusBatchMating(ctx context.Context, req *pasturePb.Event
 			}
 
 			if err = tx.Model(new(model.NeckRingEstrus)).
-				Where("pasture_id = ?", userModel.AppPasture.Id).
-				Where("id = ?", v.NeckRingEstrusId).
+				Where("pasture_id = ?", pastureId).
+				Where("ear_number = ?", v.EarNumber).
+				Where("first_time = ?", v.FirstTime).
 				Updates(map[string]interface{}{
 					"check_result":    pasturePb.CheckResult_Correct,
 					"check_user_id":   userModel.SystemUser.Id,

+ 111 - 93
module/backend/event_breed_more.go

@@ -82,16 +82,17 @@ func (s *StoreEntry) PregnantCheckCreateBatch(ctx context.Context, req *pastureP
 			breedStatus := pasturePb.BreedStatus_Pregnant
 			isPregnant := pasturePb.IsShow_Ok
 			matingResult := pasturePb.MatingResult_Pregnant
+
 			if item.PregnantCheckResult == pasturePb.PregnantCheckResult_UnPregnant {
 				breedStatus = pasturePb.BreedStatus_Empty
 				isPregnant = pasturePb.IsShow_No
 				matingResult = pasturePb.MatingResult_Empty
-			}
 
-			// 更新上一次配种结果数据,如何是复检无胎则更新为流产
-			if item.EventPregnancyCheck.PregnantCheckName == model.PregnantCheckForSecond {
-				breedStatus = pasturePb.BreedStatus_Abort
-				matingResult = pasturePb.MatingResult_Abort
+				// 更新上一次配种结果数据,如何是复检无胎则更新为流产
+				if item.EventPregnancyCheck.PregnantCheckName == model.PregnantCheckForSecond {
+					breedStatus = pasturePb.BreedStatus_Abort
+					matingResult = pasturePb.MatingResult_Abort
+				}
 			}
 
 			item.Cow.EventPregnantCheckUpdate(breedStatus, int64(item.PregnantCheckAt), isPregnant, item.EventPregnancyCheck.PregnantCheckName)
@@ -341,97 +342,11 @@ func (s *StoreEntry) MatingBatch(ctx context.Context, req *pasturePb.EventMating
 			// 脖环发情揭发数据
 			if item.ExposeEstrusType == pasturePb.ExposeEstrusType_Neck_Ring {
 				// 新增发情事件数据
-				newEstrus := model.NewEventEstrus(
-					userModel.AppPasture.Id, item.Cow, pasturePb.ExposeEstrusType_Neck_Ring,
-					pasturePb.IsShow_Ok, pasturePb.IsShow_Ok, item.MatingAt,
-					item.OperationUser, userModel.SystemUser,
-				)
-				if err = tx.Create(newEstrus).Error; err != nil {
-					return xerr.WithStack(err)
-				}
-				// 新增配种事件数据
-				newMating := model.NewEventMating(userModel.AppPasture.Id, item.Cow, item.MatingAt, pasturePb.ExposeEstrusType_Neck_Ring)
-				newMating.EventUpdate(item.MatingAt, item.FrozenSemen.BullId, false, item.OperationUser, userModel.SystemUser)
-				if err = s.MatingCowUpdate(ctx, userModel.AppPasture.Id, item, false); err != nil {
-					return xerr.WithStack(err)
-				}
-				if err = tx.Model(newMating).Create(newMating).Error; err != nil {
+				if err = s.NeckRingEstrusToMating(ctx, userModel, tx, item); err != nil {
 					return xerr.WithStack(err)
 				}
 			} else {
-				// 获取牛只最近一次配种信息
-				lastEventMating, err := s.FindLastEventMatingByCowId(ctx, userModel.AppPasture.Id, item.Cow.Id)
-				if err != nil {
-					// 1. 没有配种信息,(第一次参加配种的牛只,并且没有参与同期的牛只 )
-					if errors.Is(err, gorm.ErrRecordNotFound) {
-						newMating := model.NewEventMatingNaturalEstrus(userModel.AppPasture.Id, item, userModel.SystemUser)
-						if err = tx.Create(newMating).Error; err != nil {
-							return xerr.WithStack(err)
-						}
-						if err = s.MatingCowUpdate(ctx, userModel.AppPasture.Id, item, false); err != nil {
-							return xerr.WithStack(err)
-						}
-						continue
-					} else {
-						return xerr.WithStack(err)
-					}
-				}
-
-				// 2. 所有有配种数据的牛只
-				if lastEventMating == nil || lastEventMating.Id <= 0 || lastEventMating.Status != pasturePb.IsShow_No {
-					zaplog.Error("MatingCreate", zap.Any("cow", item.Cow), zap.Any("UserModel", userModel))
-					return xerr.Customf("牛只配种数据异常: %d", item.Cow.EarNumber)
-				}
-
-				// 2.1 复配 => 本胎次配次不加1
-				isReMating := lastEventMating.IsReMating(item.Cow, item.MatingAt)
-				if isReMating {
-					lastEventMating.EventReMatingUpdate(item.MatingAt)
-					if err = tx.Model(lastEventMating).
-						Select("mating_result", "mating_result_at", "status").
-						Where("id = ?", lastEventMating.Id).
-						Updates(lastEventMating).Error; err != nil {
-					}
-				}
-
-				// 2.2.  同期初配
-				IsMatingUpdate := lastEventMating.IsMatingUpdate()
-				if IsMatingUpdate {
-					lastEventMating.EventUpdate(item.MatingAt, item.FrozenSemen.BullId, isReMating, item.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).
-						Updates(lastEventMating).Error; err != nil {
-						return xerr.WithStack(err)
-					}
-				}
-
-				// 2.3. 提交的配种数据中,定位出空怀的牛只,
-				isEmptyMating := lastEventMating.IsEmptyMating(item.Cow, item.MatingAt)
-				if isEmptyMating {
-					// 把上次配种结果信息更新成空怀
-					lastEventMating.EventMatingResultUpdate(pasturePb.MatingResult_Empty, item.MatingAt)
-					if err = tx.Model(lastEventMating).
-						Select("mating_result", "mating_result_at").
-						Where("id = ?", lastEventMating.Id).
-						Updates(lastEventMating).Error; err != nil {
-						return xerr.WithStack(err)
-					}
-					// 先创建一条新的配种数据
-					newMating := model.NewEventMating(userModel.AppPasture.Id, item.Cow, item.MatingAt, pasturePb.ExposeEstrusType_Natural_Estrus)
-					newMating.EventUpdate(item.MatingAt, item.FrozenSemen.BullId, isReMating, item.OperationUser, userModel.SystemUser)
-					if err = tx.Model(newMating).Create(newMating).Error; err != nil {
-						return xerr.WithStack(err)
-					}
-
-					// 更新牛只配种结果日志
-					if err = s.UpdateMatingResultEventCowLogByCowId(ctx, item.Cow.Id, s.MatingResultMap()[pasturePb.MatingResult_Empty]); err != nil {
-						zaplog.Error("MatingCreate", zap.Any("UpdateEventCowLogByCowId", err), zap.Any("cow", item.Cow))
-					}
-				}
-
-				// 牛只基本中配种信息更新
-				if err = s.MatingCowUpdate(ctx, userModel.AppPasture.Id, item, isReMating); err != nil {
+				if err = s.NaturalEstrusToMating(ctx, userModel, tx, item); err != nil {
 					return xerr.WithStack(err)
 				}
 			}
@@ -460,6 +375,109 @@ func (s *StoreEntry) MatingBatch(ctx context.Context, req *pasturePb.EventMating
 	return nil
 }
 
+// NeckRingEstrusToMating 脖环揭发发情去配种牛只
+func (s *StoreEntry) NeckRingEstrusToMating(ctx context.Context, userModel *model.UserModel, tx *gorm.DB, item *model.EventMatingCheckBatchModel) error {
+	// 新增发情事件数据
+	newEstrus := model.NewEventEstrus(
+		userModel.AppPasture.Id, item.Cow, pasturePb.ExposeEstrusType_Neck_Ring,
+		pasturePb.IsShow_Ok, pasturePb.IsShow_Ok, item.MatingAt,
+		item.OperationUser, userModel.SystemUser,
+	)
+	if err := tx.Model(new(model.EventEstrus)).Create(newEstrus).Error; err != nil {
+		return xerr.WithStack(err)
+	}
+	// 新增配种事件数据
+	newMating := model.NewEventMating(userModel.AppPasture.Id, item.Cow, item.MatingAt, pasturePb.ExposeEstrusType_Neck_Ring)
+	newMating.EventUpdate(item.MatingAt, item.FrozenSemen.BullId, false, item.OperationUser, userModel.SystemUser)
+	if err := s.MatingCowUpdate(ctx, userModel.AppPasture.Id, item, false); err != nil {
+		return xerr.WithStack(err)
+	}
+	if err := tx.Model(newMating).Create(newMating).Error; err != nil {
+		return xerr.WithStack(err)
+	}
+	return nil
+}
+
+// NaturalEstrusToMating 自然发情或者同期去配种牛只
+func (s *StoreEntry) NaturalEstrusToMating(ctx context.Context, userModel *model.UserModel, tx *gorm.DB, item *model.EventMatingCheckBatchModel) error {
+	// 获取牛只最近一次配种信息
+	lastEventMating, err := s.FindLastEventMatingByCowId(ctx, userModel.AppPasture.Id, item.Cow.Id)
+	if err != nil {
+		// 1. 没有配种信息,(第一次参加配种的牛只,并且没有参与同期的牛只 )
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			newMating := model.NewEventMatingNaturalEstrus(userModel.AppPasture.Id, item, userModel.SystemUser)
+			if err = tx.Create(newMating).Error; err != nil {
+				return xerr.WithStack(err)
+			}
+			if err = s.MatingCowUpdate(ctx, userModel.AppPasture.Id, item, false); err != nil {
+				return xerr.WithStack(err)
+			}
+			return nil
+		} else {
+			return xerr.WithStack(err)
+		}
+	}
+
+	// 2. 所有有配种数据的牛只
+	if lastEventMating == nil || lastEventMating.Id <= 0 || lastEventMating.Status != pasturePb.IsShow_No {
+		zaplog.Error("MatingCreate", zap.Any("cow", item.Cow), zap.Any("UserModel", userModel))
+		return xerr.Customf("牛只配种数据异常: %d", item.Cow.EarNumber)
+	}
+
+	// 2.1 复配 => 本胎次配次不加1
+	isReMating := lastEventMating.IsReMating(item.Cow, item.MatingAt)
+	if isReMating {
+		lastEventMating.EventReMatingUpdate(item.MatingAt)
+		if err = tx.Model(lastEventMating).
+			Select("mating_result", "mating_result_at", "status").
+			Where("id = ?", lastEventMating.Id).
+			Updates(lastEventMating).Error; err != nil {
+		}
+	}
+
+	// 2.2.  同期初配
+	IsMatingUpdate := lastEventMating.IsMatingUpdate()
+	if IsMatingUpdate {
+		lastEventMating.EventUpdate(item.MatingAt, item.FrozenSemen.BullId, isReMating, item.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).
+			Updates(lastEventMating).Error; err != nil {
+			return xerr.WithStack(err)
+		}
+	}
+
+	// 2.3. 提交的配种数据中,定位出空怀的牛只,
+	isEmptyMating := lastEventMating.IsEmptyMating(item.Cow, item.MatingAt)
+	if isEmptyMating {
+		// 把上次配种结果信息更新成空怀
+		lastEventMating.EventMatingResultUpdate(pasturePb.MatingResult_Empty, item.MatingAt)
+		if err = tx.Model(lastEventMating).
+			Select("mating_result", "mating_result_at").
+			Where("id = ?", lastEventMating.Id).
+			Updates(lastEventMating).Error; err != nil {
+			return xerr.WithStack(err)
+		}
+		// 先创建一条新的配种数据
+		newMating := model.NewEventMating(userModel.AppPasture.Id, item.Cow, item.MatingAt, pasturePb.ExposeEstrusType_Natural_Estrus)
+		newMating.EventUpdate(item.MatingAt, item.FrozenSemen.BullId, isReMating, item.OperationUser, userModel.SystemUser)
+		if err = tx.Model(newMating).Create(newMating).Error; err != nil {
+			return xerr.WithStack(err)
+		}
+
+		// 更新牛只配种结果日志
+		if err = s.UpdateMatingResultEventCowLogByCowId(ctx, item.Cow.Id, s.MatingResultMap()[pasturePb.MatingResult_Empty]); err != nil {
+			zaplog.Error("MatingCreate", zap.Any("UpdateEventCowLogByCowId", err), zap.Any("cow", item.Cow))
+		}
+	}
+
+	// 牛只基本中配种信息更新
+	if err = s.MatingCowUpdate(ctx, userModel.AppPasture.Id, item, isReMating); err != nil {
+		return xerr.WithStack(err)
+	}
+	return nil
+}
+
 func (s *StoreEntry) MatingCowUpdate(ctx context.Context, pastureId int64, item *model.EventMatingCheckBatchModel, isReMating bool) error {
 	// 牛只基本中配种信息更新
 	item.Cow.EventMatingUpdate(item.MatingAt, item.FrozenSemen.BullId, isReMating)

+ 17 - 18
module/backend/event_check.go

@@ -4,6 +4,7 @@ import (
 	"context"
 	"errors"
 	"kpt-pasture/model"
+	"kpt-pasture/util"
 	"time"
 
 	"gorm.io/gorm"
@@ -192,6 +193,7 @@ func (s *StoreEntry) PregnantCheckDataCheck(ctx context.Context, pastureId int64
 }
 
 func (s *StoreEntry) EstrusCheckDataCheck(ctx context.Context, userModel *model.UserModel, req *pasturePb.EventEstrusBatch) (*model.EventEstrusBatch, error) {
+	pastureId := userModel.AppPasture.Id
 	exposeEstrusType := pasturePb.ExposeEstrusType_Natural_Estrus
 	if req.ExposeEstrusType == pasturePb.ExposeEstrusType_Neck_Ring {
 		exposeEstrusType = pasturePb.ExposeEstrusType_Neck_Ring
@@ -206,7 +208,7 @@ func (s *StoreEntry) EstrusCheckDataCheck(ctx context.Context, userModel *model.
 	}
 
 	for _, item := range req.Items {
-		cowInfo, err := s.GetCowInfoByEarNumber(ctx, userModel.AppPasture.Id, item.EarNumber)
+		cowInfo, err := s.GetCowInfoByEarNumber(ctx, pastureId, item.EarNumber)
 		if err != nil {
 			return nil, xerr.Custom("牛只信息不存在")
 		}
@@ -227,31 +229,28 @@ func (s *StoreEntry) EstrusCheckDataCheck(ctx context.Context, userModel *model.
 			return nil, xerr.Custom("发情时间不能小于产犊时间")
 		}
 
-		existsEventEstrus, isExists, err := s.FindEventEstrusByCowId(ctx, userModel.AppPasture.Id, int64(item.CowId))
-		if err != nil {
-			return nil, xerr.WithStack(err)
-		}
-
+		estrusAt := time.Unix(int64(item.EstrusAt), 0).Local().Format(model.LayoutDate2)
+		estrusLocalStartTime := util.TimeParseLocalUnix(estrusAt)
+		estrusLocalEndTime := util.TimeParseLocalEndUnix(estrusAt)
+		isExists := s.FindEventEstrusByCowId(pastureId, cowInfo.Id, estrusLocalStartTime, estrusLocalEndTime)
 		// 如果存在,并且当天已经提交过则跳过
 		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).Local().Format(model.LayoutDate2)
-			planDay := time.Unix(int64(item.EstrusAt), 0).Local().Format(model.LayoutDate2)
-			if realityDay == planDay {
-				return nil, xerr.Customf("该牛只:%d,今天已经提交过发情数据", item.CowId)
-			}
+			return nil, xerr.Customf("该牛只:%s,今天已经提交过发情数据", item.EarNumber)
 		}
 
 		operationUser, _ := s.GetSystemUserById(ctx, int64(item.OperationId))
 		item.OperationName = operationUser.Name
-		newEventEstrus := model.NewEventEstrus(userModel.AppPasture.Id, cowInfo, exposeEstrusType,
-			pasturePb.IsShow_Ok, item.IsMating, int64(item.EstrusAt), operationUser, userModel.SystemUser)
+		newEventEstrus := model.NewEventEstrus(
+			pastureId, cowInfo, exposeEstrusType, pasturePb.IsShow_Ok,
+			item.IsMating, int64(item.EstrusAt), operationUser, userModel.SystemUser,
+		)
 
 		if item.IsMating == pasturePb.IsShow_Ok {
-			newEventMating := model.NewEventMating(userModel.AppPasture.Id, cowInfo, time.Now().Local().Unix(), exposeEstrusType)
+			isExists = s.FindEventMatingByCowId(pastureId, cowInfo.Id)
+			if isExists {
+				return nil, xerr.Customf("配种清单已经有该牛只数据 :%s,今天已经提交过配种数据", item.EarNumber)
+			}
+			newEventMating := model.NewEventMating(pastureId, cowInfo, time.Now().Local().Unix(), exposeEstrusType)
 			res.EventMatingList = append(res.EventMatingList, newEventMating)
 		} else {
 			newEventEstrus.UnMatingReasonsKind = item.UnMatingReasonsKind

+ 1 - 2
module/backend/interface.go

@@ -296,8 +296,7 @@ type AnalyseService interface {
 	MultipleFactorAnalysis(ctx context.Context, req *pasturePb.MultiFactorPregnancyRateRequest) (*model.MultiFactorPregnancyRateResponse, error)
 
 	PenBehavior(ctx context.Context, req *pasturePb.BarnBehaviorCurveRequest) (*pasturePb.BarnBehaviorCurveResponse, error)
-	PenBehavior2(ctx context.Context, req *pasturePb.BarnBehaviorCurveRequest) (*model.BarnBehaviorCurveResponse, error)
-	PenBehaviorDaily(ctx context.Context, req *pasturePb.BarnMonitorRequest) (*pasturePb.BarnMonitorResponse, error)
+	PenBehaviorDaily(ctx context.Context, req *pasturePb.BarnMonitorRequest) (*model.BarnMonitorResponse, error)
 	CowBehaviorDistribution(ctx context.Context, req *pasturePb.CowBehaviorDistributionRequest) (*pasturePb.CowBehaviorDistributionResponse, error)
 }
 

+ 18 - 9
module/backend/sql.go

@@ -409,20 +409,29 @@ func (s *StoreEntry) FindLastEventMatingByCowId(ctx context.Context, pastureId,
 	return newEventMating, nil
 }
 
-func (s *StoreEntry) FindEventEstrusByCowId(ctx context.Context, pastureId, cowId int64) (*model.EventEstrus, bool, error) {
-	newEventEstrus := &model.EventEstrus{}
+func (s *StoreEntry) FindEventEstrusByCowId(pastureId, cowId int64, startTime, endTime int64) bool {
+	var count int64
 	if err := s.DB.Model(new(model.EventEstrus)).
 		Where("cow_id = ?", cowId).
 		Where("pasture_id = ?", pastureId).
+		Where("reality_day BETWEEN ? AND ?", startTime, endTime).
 		Where("is_show = ? ", pasturePb.IsShow_Ok).
-		First(newEventEstrus).Error; err != nil {
-		if errors.Is(err, gorm.ErrRecordNotFound) {
-			return nil, false, nil
-		} else {
-			return nil, false, xerr.WithStack(err)
-		}
+		Count(&count).Error; err != nil {
+		return false
+	}
+	return count > 0
+}
+
+func (s *StoreEntry) FindEventMatingByCowId(pastureId, cowId int64) bool {
+	var count int64
+	if err := s.DB.Model(new(model.EventMating)).
+		Where("cow_id = ?", cowId).
+		Where("pasture_id = ?", pastureId).
+		Where("is_show = ? ", pasturePb.IsShow_Ok).
+		Count(&count).Error; err != nil {
+		return false
 	}
-	return newEventEstrus, true, nil
+	return count > 0
 }
 
 func (s *StoreEntry) GetOutboundById(ctx context.Context, pastureId, id int64) (*model.Outbound, error) {

+ 4 - 17
module/crontab/neck_ring_calculate.go

@@ -87,27 +87,17 @@ func (e *Entry) FirstFilterUpdate(pastureId int64, xToDay *XToday) (processIds [
 		limit = defaultLimit
 	}
 
-	heatDate := time.Now().Local().AddDate(0, 0, -30).Format(model.LayoutDate2)
+	firstHeatDate := time.Now().Local().AddDate(0, 0, -30).Format(model.LayoutDate2)
 	querySql := `SELECT * FROM neck_active_habit WHERE pasture_id = ? AND is_show = ? AND heat_date >= ? AND high >= ?
 			UNION
 				 SELECT * FROM neck_active_habit WHERE pasture_id = ? AND is_show = ? AND heat_date >= ? AND rumina >= ?
-			ORDER BY heat_date, neck_ring_number, frameid LIMIT ?`
+			ORDER BY active_time,neck_ring_number,frameid LIMIT ?`
 
 	newNeckActiveHabitList := make([]*model.NeckActiveHabit, 0)
-	/*if err = e.DB.Model(new(model.NeckActiveHabit)).
-		Where("heat_date >= ?", time.Now().Local().AddDate(0, 0, -30).Format(model.LayoutDate2)).
-		Where("pasture_id = ?", pastureId).
-		Where("is_show = ?", pasturePb.IsShow_No).
-		Where(e.DB.Where("high >= ?", xToDay.High).Or("rumina >= ?", xToDay.Rumina)).
-		Order("heat_date,neck_ring_number,frameid").
-		Limit(int(limit)).
-		Find(&newNeckActiveHabitList).Error; err != nil {
-		return nil, xerr.WithStack(err)
-	}*/
 	if err = e.DB.Raw(
 		querySql,
-		pastureId, pasturePb.IsShow_No, heatDate, xToDay.High,
-		pastureId, pasturePb.IsShow_No, heatDate, xToDay.Rumina,
+		pastureId, pasturePb.IsShow_No, firstHeatDate, xToDay.High,
+		pastureId, pasturePb.IsShow_No, firstHeatDate, xToDay.Rumina,
 		limit,
 	).Find(&newNeckActiveHabitList).Error; err != nil {
 		return nil, xerr.WithStack(err)
@@ -115,9 +105,6 @@ func (e *Entry) FirstFilterUpdate(pastureId int64, xToDay *XToday) (processIds [
 
 	// 活动量滤波
 	for _, v := range newNeckActiveHabitList {
-		/*if !(v.High >= xToDay.High || v.Rumina >= xToDay.Rumina) {
-			continue
-		}*/
 		// 4小时数据不全的不参与滤波
 		activeTime, _ := util.TimeParseLocal(model.LayoutTime, v.ActiveTime)
 		if v.RecordCount != model.DefaultRecordCount && time.Now().Local().Sub(activeTime).Hours() <= 4 {

+ 2 - 1
module/crontab/neck_ring_estrus.go

@@ -167,6 +167,7 @@ func (e *Entry) CowEstrusWarning(pastureId int64, xToday *XToday, nowTime time.T
 				zap.Any("cowEstrus", cow21Estrus),
 				zap.Any("cowInfo", cowInfo),
 				zap.Any("cowHabitList", cowHabitList),
+				zap.Any("pasture", pastureId),
 			)
 			newNeckRingEstrus := model.NewNeckRingEstrus(pastureId, cowInfo, level, int32(maxCft), checkResult, isShow)
 			newNeckRingEstrus.LastTime = lastEstrusDate
@@ -186,8 +187,8 @@ func (e *Entry) CowEstrusWarning(pastureId int64, xToday *XToday, nowTime time.T
 	}
 }
 
+// UpdateNewNeckRingEstrus 更新牛只首次发情时间和是否是高峰期
 func (e *Entry) UpdateNewNeckRingEstrus(pastureId int64, xToday *XToday, nowTime time.Time) {
-	// 更新牛只首次发情时间
 	e.UpdateEstrusFirstTime1(pastureId, nowTime)
 	e.UpdateEstrusIsPeak(pastureId, nowTime)
 	e.UpdateEstrusFirstTime2(pastureId, xToday, nowTime)

+ 1 - 1
module/crontab/pen_behavior_day.go

@@ -89,7 +89,7 @@ func (e *Entry) updateMilkDaily(pastureId int64, targetDate string) error {
 func (e *Entry) insertBarBehaviorDay(pastureId int64, targetDate string) error {
 	penBehaviorList := make([]*model.PenBehaviorDay, 0)
 	if err := e.DB.Model(new(model.MilkDaily)).
-		Select(`heat_date, pasture_id, pen_id, COUNT(1) as cow_count, ROUND(AVG(day_yield), 1) as day_milk,
+		Select(`heat_date, pasture_id, pen_id,pen_name, COUNT(1) as cow_count, ROUND(AVG(day_yield), 1) as day_milk,
 		ROUND(AVG(week_avg), 1) as week_milk,ROUND(AVG(day_high), 0) as day_high,
 		ROUND(AVG(day_rumina), 0) as day_rumina,ROUND(AVG(day_intake), 0) as day_intake,
 		ROUND(AVG(day_inactive), 0) as day_inactive,ROUND(AVG(day_gasp), 0) as day_gasp,