Browse Source

warning: health 健康预警

Yi 1 month ago
parent
commit
e91df28130

+ 1 - 1
go.mod

@@ -3,7 +3,7 @@ module kpt-pasture
 go 1.17
 
 require (
-	gitee.com/xuyiping_admin/go_proto v0.0.0-20250421082915-83b5518d3a0c
+	gitee.com/xuyiping_admin/go_proto v0.0.0-20250423080234-2c477a4a0bec
 	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

+ 10 - 0
go.sum

@@ -46,6 +46,16 @@ gitee.com/xuyiping_admin/go_proto v0.0.0-20250420125055-0d4da4a5a6fe h1:FSRq0AIt
 gitee.com/xuyiping_admin/go_proto v0.0.0-20250420125055-0d4da4a5a6fe/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
 gitee.com/xuyiping_admin/go_proto v0.0.0-20250421082915-83b5518d3a0c h1:CuiDP9SfrSEiJ/wG0iz7wgIt9n38SeckUXscY6X5zBU=
 gitee.com/xuyiping_admin/go_proto v0.0.0-20250421082915-83b5518d3a0c/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250423062840-8ac2189a39de h1:HNb5GAbIF+e9lDQ4e+AUSzmwQxpoEEsCbS0iDuC536Y=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250423062840-8ac2189a39de/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250423064947-57beca75393d h1:1UsWm3g4/DXLG8c+GrzYGFLKnFAfDLsV+fvPMryd9CU=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250423064947-57beca75393d/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250423065632-fc64fb76f946 h1:K41rOlpITHu+tn2SINKpBpk052Txj67l50/hQtXeZ7A=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250423065632-fc64fb76f946/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250423070335-3521c22802be h1:fcqMFBsu9P1YwMjGqOh8NppxhBWTvu0sbOfrjmDm6no=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250423070335-3521c22802be/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250423080234-2c477a4a0bec h1:iOaGH+q4slB4oUliGCXVnmBDGLM/+80A2+7VSWPdp0U=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250423080234-2c477a4a0bec/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=

+ 0 - 25
http/handler/event/event_breed.go

@@ -239,31 +239,6 @@ func EstrusBatchMating(c *gin.Context) {
 	})
 }
 
-func NeckRingNoEstrusBatch(c *gin.Context) {
-	var req pasturePb.NeckRingNoEstrusBatchRequest
-	if err := ginutil.BindProto(c, &req); err != nil {
-		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
-		return
-	}
-
-	if err := valid.ValidateStruct(&req,
-		valid.Field(&req.EarNumbers, valid.Required, valid.Required),
-	); err != nil {
-		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
-		return
-	}
-
-	if err := middleware.BackendOperation(c).OpsService.NeckRingNoEstrusBatch(c, &req); err != nil {
-		apierr.ClassifiedAbort(c, err)
-		return
-	}
-
-	ginutil.JSONResp(c, &operationPb.CommonOK{
-		Code: http.StatusOK,
-		Msg:  "ok",
-		Data: &operationPb.Success{Success: true},
-	})
-}
 func SameTimeCreate(c *gin.Context) {
 	var req pasturePb.EventSameTime
 	if err := ginutil.BindProto(c, &req); err != nil {

+ 110 - 0
http/handler/warning/warning.go

@@ -0,0 +1,110 @@
+package warning
+
+import (
+	"kpt-pasture/http/middleware"
+	"net/http"
+
+	operationPb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/operation"
+	"gitee.com/xuyiping_admin/pkg/valid"
+
+	pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
+	"gitee.com/xuyiping_admin/pkg/apierr"
+	"gitee.com/xuyiping_admin/pkg/ginutil"
+	"github.com/gin-gonic/gin"
+)
+
+func NeckRingWarningEstrusItem(c *gin.Context) {
+	var req pasturePb.WarningItemsRequest
+	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.BackendOperation(c).OpsService.NeckRingWarningEstrusOrAbortionCowList(c, &req, pagination)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	ginutil.JSONResp(c, res)
+}
+
+func NeckRingNoEstrusBatch(c *gin.Context) {
+	var req pasturePb.NeckRingNoEstrusBatchRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.EarNumbers, valid.Required, valid.Required),
+	); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := middleware.BackendOperation(c).OpsService.NeckRingNoEstrusBatch(c, &req); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
+}
+
+func NeckRingWarningHealthItem(c *gin.Context) {
+	var req pasturePb.HealthWarningRequest
+	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.BackendOperation(c).OpsService.NeckRingWarningHealthCowList(c, &req, pagination)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	ginutil.JSONResp(c, res)
+}
+
+func NeckRingNoHealthBatch(c *gin.Context) {
+	var req pasturePb.NeckRingNoEstrusBatchRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.EarNumbers, valid.Required, valid.Required),
+	); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := middleware.BackendOperation(c).OpsService.NeckRingNoDiseaseBatch(c, &req); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
+}

+ 0 - 22
http/handler/work/item.go

@@ -109,25 +109,3 @@ func CalvingList(c *gin.Context) {
 	}
 	ginutil.JSONResp(c, res)
 }
-
-func NeckRingWarningEstrusItem(c *gin.Context) {
-	var req pasturePb.WarningItemsRequest
-	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.BackendOperation(c).OpsService.EstrusOrAbortionCowList(c, &req, pagination)
-	if err != nil {
-		apierr.ClassifiedAbort(c, err)
-		return
-	}
-
-	ginutil.JSONResp(c, res)
-}

+ 1 - 1
http/route/event_api.go

@@ -37,7 +37,7 @@ func EventAPI(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
 		// 发情
 		eventRoute.POST("/estrus/list", event.EstrusEventList)
 		eventRoute.POST("/estrus/mating/batch", event.EstrusBatchMating)
-		eventRoute.POST("/neck/ring/no/estrus/batch", event.NeckRingNoEstrusBatch)
+
 		// 同期
 		eventRoute.POST("/same/time/list", event.SameTimeList)
 		eventRoute.POST("/same/time/create", event.SameTimeCreate)

+ 1 - 0
http/route/route.go

@@ -17,6 +17,7 @@ func HTTPServerRoute(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
 		WorkOrderAPI(opts...),
 		FilesManageAPI(opts...),
 		MilkManageAPI(opts...),
+		WarningAPI(opts...),
 		TestAPI(opts...),
 	}
 

+ 23 - 0
http/route/warning_api.go

@@ -0,0 +1,23 @@
+package route
+
+import (
+	"kpt-pasture/http/handler/warning"
+
+	"github.com/gin-gonic/gin"
+)
+
+func WarningAPI(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
+	return func(s *gin.Engine) {
+		for _, opt := range opts {
+			opt(s)
+		}
+		// warning API 组  工作清单
+		waringRoute := authRouteGroup(s, "/api/v1/warning/")
+
+		// 发情和流产预警
+		waringRoute.POST("/estrus/items", warning.NeckRingWarningEstrusItem)
+		waringRoute.POST("/no/estrus/batch", warning.NeckRingNoEstrusBatch)
+		waringRoute.POST("/health/items", warning.NeckRingWarningHealthItem)
+		waringRoute.POST("/no/disease/batch", warning.NeckRingNoHealthBatch)
+	}
+}

+ 0 - 4
http/route/work_api.go

@@ -32,9 +32,5 @@ func WorkOrderAPI(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
 		workRoute.POST("/mating/items", work.MatingCowList)
 		workRoute.POST("/calving/items", work.CalvingList)
 		workRoute.POST("/disease/items", event.CowDiseaseList)
-
-		// 发情预警
-		workRoute.POST("/estrus/items", work.NeckRingWarningEstrusItem)
-		workRoute.POST("/abortion/items", work.NeckRingWarningEstrusItem)
 	}
 }

+ 5 - 0
model/neck_ring_health.go

@@ -29,6 +29,10 @@ type NeckRingHealth struct {
 	Score              int32                      `json:"score"`
 	IsWorse            pasturePb.IsShow_Kind      `json:"isWorse"`
 	CheckResult        pasturePb.CheckResult_Kind `json:"checkResult"`
+	CheckUserId        int64                      `json:"checkUserId"`
+	CheckUserName      string                     `json:"checkUserName"`
+	CheckAt            int64                      `json:"checkAt"`
+	IsShow             pasturePb.IsShow_Kind      `json:"isShow"`
 	CreatedAt          int64                      `json:"createdAt"`
 	UpdatedAt          int64                      `json:"updatedAt"`
 }
@@ -59,6 +63,7 @@ func NewNeckRingHealth(habit *NeckActiveHabit, sumChew, chew3dago int32) *NeckRi
 		Score:              habit.Score,
 		IsWorse:            0,
 		CheckResult:        pasturePb.CheckResult_Pending,
+		IsShow:             pasturePb.IsShow_Ok,
 	}
 }
 

+ 73 - 16
model/neck_ring_health_warning.go

@@ -1,21 +1,24 @@
 package model
 
+import pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
+
 type NeckRingHealthWarning struct {
-	Id                 int64  `json:"id"`
-	NeckRingHealthId   int64  `json:"neckRingHealthId"`
-	PastureId          int64  `json:"pastureId"`
-	CowId              int64  `json:"cowId"`
-	EarNumber          string `json:"earNumber"`
-	NeckRingNumber     string `json:"neckRingNumber"`
-	HeatDate           string `json:"heatDate"`
-	MinHigh            int32  `json:"minHigh"`
-	MinChew            int32  `json:"minChew"`
-	MinIntake          int32  `json:"minIntake"`
-	ChewSum            int32  `json:"chewSum"`
-	BeforeThreeSumChew int32  `json:"beforeThreeSumChew"`
-	Score              int32  `json:"score"`
-	CreatedAt          int64  `json:"created"`
-	UpdatedAt          int64  `json:"updated"`
+	Id                 int64                             `json:"id"`
+	NeckRingHealthId   int64                             `json:"neckRingHealthId"`
+	PastureId          int64                             `json:"pastureId"`
+	CowId              int64                             `json:"cowId"`
+	EarNumber          string                            `json:"earNumber"`
+	NeckRingNumber     string                            `json:"neckRingNumber"`
+	HeatDate           string                            `json:"heatDate"`
+	MinHigh            int32                             `json:"minHigh"`
+	MinChew            int32                             `json:"minChew"`
+	MinIntake          int32                             `json:"minIntake"`
+	ChewSum            int32                             `json:"chewSum"`
+	BeforeThreeSumChew int32                             `json:"beforeThreeSumChew"`
+	Score              int32                             `json:"score"`
+	Level              pasturePb.WarningHealthLevel_Kind `json:"level"`
+	CreatedAt          int64                             `json:"created"`
+	UpdatedAt          int64                             `json:"updated"`
 }
 
 func (n *NeckRingHealthWarning) TableName() string {
@@ -23,18 +26,72 @@ func (n *NeckRingHealthWarning) TableName() string {
 }
 
 func NewNeckRingHealthWarning(pastureId int64, neckRingHealth *NeckRingHealth, cow *Cow, newScore int32) *NeckRingHealthWarning {
+	level := pasturePb.WarningHealthLevel_Preliminary
+	if newScore < 60 {
+		level = pasturePb.WarningHealthLevel_Height
+	} else if newScore >= 60 && newScore <= 74 {
+		level = pasturePb.WarningHealthLevel_Moderate
+	}
+
 	return &NeckRingHealthWarning{
 		NeckRingHealthId:   pastureId,
 		PastureId:          neckRingHealth.Id,
 		CowId:              cow.Id,
 		EarNumber:          cow.EarNumber,
 		NeckRingNumber:     cow.NeckRingNumber,
-		HeatDate:           "",
+		HeatDate:           neckRingHealth.HeatDate,
 		MinHigh:            neckRingHealth.MinHigh,
 		MinChew:            neckRingHealth.MinChew,
 		MinIntake:          neckRingHealth.MinInactive,
 		ChewSum:            neckRingHealth.SumChew,
 		BeforeThreeSumChew: neckRingHealth.BeforeThreeSumChew,
 		Score:              newScore,
+		Level:              level,
+	}
+}
+
+type NeckRingHealthWarningSlice []*NeckRingHealthWarning
+
+func (n NeckRingHealthWarningSlice) ToPB(
+	warningHealthLevelMap map[pasturePb.WarningHealthLevel_Kind]string,
+	cowMap map[int64]*Cow,
+	eventLogMap map[int64]string,
+) []*pasturePb.HealthWarningItem {
+	res := make([]*pasturePb.HealthWarningItem, len(n))
+	for i, v := range n {
+		levelName := ""
+		if ln, ok := warningHealthLevelMap[v.Level]; ok {
+			levelName = ln
+		}
+
+		data := &pasturePb.HealthWarningItem{
+			Id:                     int32(v.Id),
+			CowId:                  int32(v.CowId),
+			EarNumber:              v.EarNumber,
+			PenId:                  0,
+			PenName:                "",
+			Score:                  v.Score,
+			HeatDate:               v.HeatDate,
+			MinHigh:                v.MinHigh,
+			MinChew:                v.MinChew,
+			MinIntake:              v.MinIntake,
+			SumChew:                v.ChewSum,
+			BeforeThreeDaysSumChew: v.BeforeThreeSumChew,
+			LastBreedEventDetails:  "",
+			Level:                  v.Level,
+			LevelName:              levelName,
+		}
+
+		if cow, ok := cowMap[v.CowId]; ok {
+			data.PenId = cow.PenId
+			data.PenName = cow.PenName
+			data.Lact = cow.Lact
+		}
+
+		if desc, ok := eventLogMap[v.CowId]; ok {
+			data.LastBreedEventDetails = desc
+		}
+		res[i] = data
 	}
+	return res
 }

+ 26 - 0
module/backend/config_data_base.go

@@ -383,3 +383,29 @@ func (s *StoreEntry) CowDeathDestinationList(isAll string) []*pasturePb.ConfigOp
 	})
 	return configOptions
 }
+
+func (s *StoreEntry) WarningHealthLevel(isAll string) []*pasturePb.ConfigOptionsList {
+	configOptions := make([]*pasturePb.ConfigOptionsList, 0)
+	if isAll == model.IsAllYes {
+		configOptions = append(configOptions,
+			&pasturePb.ConfigOptionsList{
+				Value:    int32(0),
+				Label:    "全部",
+				Disabled: true,
+			})
+	}
+	configOptions = append(configOptions, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.WarningHealthLevel_Preliminary),
+		Label:    "初级疑似",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.WarningHealthLevel_Moderate),
+		Label:    "中级疑似",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.WarningHealthLevel_Height),
+		Label:    "高度疑似",
+		Disabled: true,
+	})
+	return configOptions
+}

+ 8 - 0
module/backend/enum_map.go

@@ -359,6 +359,14 @@ func (s *StoreEntry) CowDeathDestinationMap() map[pasturePb.DeathDestination_Kin
 	return res
 }
 
+func (s *StoreEntry) WarningHealthLevelMap() map[pasturePb.WarningHealthLevel_Kind]string {
+	res := make(map[pasturePb.WarningHealthLevel_Kind]string)
+	for _, v := range s.WarningHealthLevel("") {
+		res[pasturePb.WarningHealthLevel_Kind(v.Value)] = v.Label
+	}
+	return res
+}
+
 func (s *StoreEntry) eventCategoryMap() map[pasturePb.EventType_Kind]pasturePb.EventCategory_Kind {
 	return map[pasturePb.EventType_Kind]pasturePb.EventCategory_Kind{
 		pasturePb.EventType_Enter:             pasturePb.EventCategory_Base,

+ 1 - 0
module/backend/enum_options.go

@@ -220,6 +220,7 @@ func (s *StoreEntry) SystemBaseConfigOptions(ctx context.Context, optionsName, i
 		"forbiddenMatingReasons":     s.ForbiddenMatingReasonsEnumList,
 		"cowOutReason":               s.CowOutReasonList,
 		"deathDestination":           s.CowDeathDestinationList,
+		"warningHealthLevel":         s.WarningHealthLevel,
 	}
 
 	getConfigFunc, ok := getConfigFuncMap[optionsName]

+ 0 - 80
module/backend/event_breed.go

@@ -480,83 +480,3 @@ func (s *StoreEntry) EstrusBatchMating(ctx context.Context, req *pasturePb.Event
 	}
 	return nil
 }
-
-func (s *StoreEntry) NeckRingNoEstrusBatch(ctx context.Context, req *pasturePb.NeckRingNoEstrusBatchRequest) error {
-	userModel, err := s.GetUserModel(ctx)
-	if err != nil {
-		return xerr.WithStack(err)
-	}
-	if len(req.EarNumbers) <= 0 {
-		return xerr.Custom("请选择牛号")
-	}
-
-	nowTime := time.Now().Local()
-	startTime := nowTime.AddDate(0, 0, -1).Format(model.LayoutDate2)
-	endTime := nowTime.AddDate(0, 0, 1).Format(model.LayoutDate2)
-
-	startTimeUnix := util.TimeParseLocalUnix(startTime)
-	endTimeUnix := util.TimeParseLocalUnix(endTime)
-
-	startTime = time.Unix(startTimeUnix, 0).Local().Format(model.LayoutTime)
-	endTime = time.Unix(endTimeUnix, 0).Local().Format(model.LayoutTime)
-	neckRingEstrusWarningList := make([]*model.NeckRingEstrusWarning, 0)
-	if err = s.DB.Model(new(model.NeckRingEstrusWarning)).
-		Where("pasture_id = ?", userModel.AppPasture.Id).
-		Where("date_time BETWEEN ? AND ?", startTime, endTime).
-		Where("ear_number IN ?", req.EarNumbers).
-		Where("is_show = ?", pasturePb.IsShow_Ok).
-		Find(&neckRingEstrusWarningList).Error; err != nil {
-		return xerr.WithStack(err)
-	}
-
-	if len(neckRingEstrusWarningList) <= 0 {
-		return nil
-	}
-
-	neckRingEstrusIds := make([]int64, 0)
-	for _, v := range neckRingEstrusWarningList {
-		neckRingEstrus := &model.NeckRingEstrus{}
-		if err = s.DB.Model(neckRingEstrus).
-			Where("pasture_id = ?", userModel.AppPasture.Id).
-			Where("id = ?", v.NeckRingEstrusId).
-			Where("is_show = ?", pasturePb.IsShow_Ok).
-			First(neckRingEstrus).Error; err != nil {
-			zaplog.Error("NeckRingNoEstrusBatch", zap.Any("err", err), zap.Any("neckRingEstrusWarning", v))
-			return xerr.Customf("数据异常: %s", v.EarNumber)
-		}
-		neckRingEstrusIds = append(neckRingEstrusIds, neckRingEstrus.Id)
-	}
-
-	if len(neckRingEstrusIds) <= 0 {
-		return nil
-	}
-
-	if err = s.DB.Transaction(func(tx *gorm.DB) error {
-		for _, id := range neckRingEstrusIds {
-			if err = tx.Model(new(model.NeckRingEstrus)).
-				Where("id = ?", id).
-				Updates(map[string]interface{}{
-					"check_result":    pasturePb.CheckResult_Fail,
-					"check_user_id":   userModel.SystemUser.Id,
-					"check_user_name": userModel.SystemUser.Name,
-					"check_at":        time.Now().Local().Unix(),
-					"is_show":         pasturePb.IsShow_No,
-				}).Error; err != nil {
-				return xerr.WithStack(err)
-			}
-
-			if err = tx.Model(new(model.NeckRingEstrusWarning)).
-				Where("neck_ring_estrus_id = ?", id).
-				Updates(map[string]interface{}{
-					"is_show": pasturePb.IsShow_No,
-				}).Error; err != nil {
-				return xerr.WithStack(err)
-			}
-		}
-		return nil
-	}); err != nil {
-		return xerr.WithStack(err)
-	}
-
-	return nil
-}

+ 8 - 2
module/backend/interface.go

@@ -55,6 +55,7 @@ type KptService interface {
 	WorkService          // 日常工作相关
 	MilkHallService      // 奶厅数据相关
 	UploadService        // 上传文件相关
+	WarningService       // 预警相关
 	TestService          // 测试相关
 }
 
@@ -188,7 +189,6 @@ type EventService interface {
 	// EstrusBatchMating 发情批量处理配种
 	EstrusBatchMating(ctx context.Context, req *pasturePb.EventEstrusBatch) error
 	EstrusList(ctx context.Context, req *pasturePb.SearchEventRequest, pagination *pasturePb.PaginationModel) (*pasturePb.SearchEventEstrusResponse, error)
-	NeckRingNoEstrusBatch(ctx context.Context, req *pasturePb.NeckRingNoEstrusBatchRequest) error
 
 	// AbortionList 流产
 	AbortionList(ctx context.Context, req *pasturePb.SearchEventRequest, pagination *pasturePb.PaginationModel) (*pasturePb.EventAbortionResponse, error)
@@ -329,7 +329,13 @@ 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)
-	EstrusOrAbortionCowList(ctx context.Context, req *pasturePb.WarningItemsRequest, pagination *pasturePb.PaginationModel) (*pasturePb.EstrusResponse, error)
+}
+
+type WarningService interface {
+	NeckRingWarningEstrusOrAbortionCowList(ctx context.Context, req *pasturePb.WarningItemsRequest, pagination *pasturePb.PaginationModel) (*pasturePb.EstrusResponse, error)
+	NeckRingWarningHealthCowList(ctx context.Context, req *pasturePb.HealthWarningRequest, pagination *pasturePb.PaginationModel) (*pasturePb.HealthWarningResponse, error)
+	NeckRingNoEstrusBatch(ctx context.Context, req *pasturePb.NeckRingNoEstrusBatchRequest) error
+	NeckRingNoDiseaseBatch(ctx context.Context, req *pasturePb.NeckRingNoEstrusBatchRequest) error
 }
 
 type MilkHallService interface {

+ 223 - 1
module/backend/neck_ring_warning.go

@@ -8,13 +8,15 @@ import (
 	"net/http"
 	"time"
 
+	"go.uber.org/zap"
 	"gorm.io/gorm"
 
 	pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
+	"gitee.com/xuyiping_admin/pkg/logger/zaplog"
 	"gitee.com/xuyiping_admin/pkg/xerr"
 )
 
-func (s *StoreEntry) EstrusOrAbortionCowList(ctx context.Context, req *pasturePb.WarningItemsRequest, pagination *pasturePb.PaginationModel) (*pasturePb.EstrusResponse, error) {
+func (s *StoreEntry) NeckRingWarningEstrusOrAbortionCowList(ctx context.Context, req *pasturePb.WarningItemsRequest, pagination *pasturePb.PaginationModel) (*pasturePb.EstrusResponse, error) {
 	userModel, err := s.GetUserModel(ctx)
 	if err != nil {
 		return nil, xerr.WithStack(err)
@@ -86,6 +88,226 @@ func (s *StoreEntry) EstrusOrAbortionCowList(ctx context.Context, req *pasturePb
 	}, nil
 }
 
+func (s *StoreEntry) NeckRingWarningHealthCowList(ctx context.Context, req *pasturePb.HealthWarningRequest, pagination *pasturePb.PaginationModel) (*pasturePb.HealthWarningResponse, error) {
+	userModel, err := s.GetUserModel(ctx)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	neckWaringHealthList := make([]*model.NeckRingHealthWarning, 0)
+	pref := s.DB.Table(fmt.Sprintf("%s as a", new(model.NeckRingHealthWarning).TableName())).
+		Joins(fmt.Sprintf("JOIN cow AS b on a.cow_id = b.id")).
+		Where("a.score >= ?", 0).Where("a.is_show = ?", pasturePb.IsShow_Ok)
+
+	if len(req.PenIds) > 0 {
+		pref.Where("b.pen_id IN ?", req.PenIds)
+	}
+	if len(req.EarNumber) > 0 {
+		pref.Where("a.ear_number = ?", req.EarNumber)
+	}
+
+	if req.Level > 0 {
+		pref.Where("a.level = ?", req.Level)
+	}
+
+	var count int64
+	if err = pref.Order("a.level DESC").
+		Count(&count).
+		Limit(int(pagination.PageSize)).
+		Offset(int(pagination.PageOffset)).
+		Find(&neckWaringHealthList).Error; err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	warningHealthLevelMap := s.WarningHealthLevelMap()
+	cowMap := make(map[int64]*model.Cow)
+	eventLogMap := make(map[int64]string)
+	cowIds := make([]int64, 0)
+	for _, v := range neckWaringHealthList {
+		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.HealthWarningResponse{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &pasturePb.HealthWarningData{
+			Total:    int32(count),
+			Page:     pagination.Page,
+			PageSize: pagination.PageSize,
+			List:     model.NeckRingHealthWarningSlice(neckWaringHealthList).ToPB(warningHealthLevelMap, cowMap, eventLogMap),
+		},
+	}, nil
+
+}
+
+func (s *StoreEntry) NeckRingNoEstrusBatch(ctx context.Context, req *pasturePb.NeckRingNoEstrusBatchRequest) error {
+	userModel, err := s.GetUserModel(ctx)
+	if err != nil {
+		return xerr.WithStack(err)
+	}
+	if len(req.EarNumbers) <= 0 {
+		return xerr.Custom("请选择牛号")
+	}
+
+	nowTime := time.Now().Local()
+	startTime := nowTime.AddDate(0, 0, -1).Format(model.LayoutDate2)
+	endTime := nowTime.AddDate(0, 0, 1).Format(model.LayoutDate2)
+
+	startTimeUnix := util.TimeParseLocalUnix(startTime)
+	endTimeUnix := util.TimeParseLocalUnix(endTime)
+
+	startTime = time.Unix(startTimeUnix, 0).Local().Format(model.LayoutTime)
+	endTime = time.Unix(endTimeUnix, 0).Local().Format(model.LayoutTime)
+	neckRingEstrusWarningList := make([]*model.NeckRingEstrusWarning, 0)
+	if err = s.DB.Model(new(model.NeckRingEstrusWarning)).
+		Where("pasture_id = ?", userModel.AppPasture.Id).
+		Where("date_time BETWEEN ? AND ?", startTime, endTime).
+		Where("ear_number IN ?", req.EarNumbers).
+		Where("is_show = ?", pasturePb.IsShow_Ok).
+		Find(&neckRingEstrusWarningList).Error; err != nil {
+		return xerr.WithStack(err)
+	}
+
+	if len(neckRingEstrusWarningList) <= 0 {
+		return nil
+	}
+
+	neckRingEstrusIds := make([]int64, 0)
+	for _, v := range neckRingEstrusWarningList {
+		neckRingEstrus := &model.NeckRingEstrus{}
+		if err = s.DB.Model(new(model.NeckRingEstrus)).
+			Where("pasture_id = ?", userModel.AppPasture.Id).
+			Where("id = ?", v.NeckRingEstrusId).
+			Where("is_show = ?", pasturePb.IsShow_Ok).
+			First(neckRingEstrus).Error; err != nil {
+			zaplog.Error("NeckRingNoEstrusBatch", zap.Any("err", err), zap.Any("neckRingEstrusWarning", v))
+			return xerr.Customf("数据异常: %s", v.EarNumber)
+		}
+		neckRingEstrusIds = append(neckRingEstrusIds, neckRingEstrus.Id)
+	}
+
+	if len(neckRingEstrusIds) <= 0 {
+		return nil
+	}
+
+	if err = s.DB.Transaction(func(tx *gorm.DB) error {
+		for _, id := range neckRingEstrusIds {
+			if err = tx.Model(new(model.NeckRingEstrus)).
+				Where("id = ?", id).
+				Updates(map[string]interface{}{
+					"check_result":    pasturePb.CheckResult_Fail,
+					"check_user_id":   userModel.SystemUser.Id,
+					"check_user_name": userModel.SystemUser.Name,
+					"check_at":        time.Now().Local().Unix(),
+					"is_show":         pasturePb.IsShow_No,
+				}).Error; err != nil {
+				return xerr.WithStack(err)
+			}
+
+			if err = tx.Model(new(model.NeckRingEstrusWarning)).
+				Where("neck_ring_estrus_id = ?", id).
+				Updates(map[string]interface{}{
+					"is_show": pasturePb.IsShow_No,
+				}).Error; err != nil {
+				return xerr.WithStack(err)
+			}
+		}
+		return nil
+	}); err != nil {
+		return xerr.WithStack(err)
+	}
+
+	return nil
+}
+
+func (s *StoreEntry) NeckRingNoDiseaseBatch(ctx context.Context, req *pasturePb.NeckRingNoEstrusBatchRequest) error {
+	userModel, err := s.GetUserModel(ctx)
+	if err != nil {
+		return xerr.WithStack(err)
+	}
+	if len(req.EarNumbers) <= 0 {
+		return xerr.Custom("请选择牛号")
+	}
+
+	nowTime := time.Now().Local()
+	startTime := nowTime.AddDate(0, 0, -1).Format(model.LayoutDate2)
+	endTime := nowTime.AddDate(0, 0, 1).Format(model.LayoutDate2)
+
+	startTimeUnix := util.TimeParseLocalUnix(startTime)
+	endTimeUnix := util.TimeParseLocalUnix(endTime)
+
+	startTime = time.Unix(startTimeUnix, 0).Local().Format(model.LayoutTime)
+	endTime = time.Unix(endTimeUnix, 0).Local().Format(model.LayoutTime)
+	neckRingHealthWarningList := make([]*model.NeckRingHealthWarning, 0)
+	if err = s.DB.Model(new(model.NeckRingHealthWarning)).
+		Where("pasture_id = ?", userModel.AppPasture.Id).
+		Where("date_time BETWEEN ? AND ?", startTime, endTime).
+		Where("ear_number IN ?", req.EarNumbers).
+		Where("is_show = ?", pasturePb.IsShow_Ok).
+		Find(&neckRingHealthWarningList).Error; err != nil {
+		return xerr.WithStack(err)
+	}
+
+	if len(neckRingHealthWarningList) <= 0 {
+		return nil
+	}
+
+	neckRingHealthIds := make([]int64, 0)
+	for _, v := range neckRingHealthWarningList {
+		neckRingHealth := &model.NeckRingHealth{}
+		if err = s.DB.Model(new(model.NeckRingHealth)).
+			Where("pasture_id = ?", userModel.AppPasture.Id).
+			Where("id = ?", v.NeckRingHealthId).
+			Where("is_show = ?", pasturePb.IsShow_Ok).
+			First(neckRingHealth).Error; err != nil {
+			zaplog.Error("NeckRingNoEstrusBatch", zap.Any("err", err), zap.Any("neckRingEstrusWarning", v))
+			return xerr.Customf("数据异常: %s", v.EarNumber)
+		}
+		neckRingHealthIds = append(neckRingHealthIds, neckRingHealth.Id)
+	}
+
+	if len(neckRingHealthIds) <= 0 {
+		return nil
+	}
+
+	if err = s.DB.Transaction(func(tx *gorm.DB) error {
+		for _, id := range neckRingHealthIds {
+			if err = tx.Model(new(model.NeckRingHealth)).
+				Where("id = ?", id).
+				Updates(map[string]interface{}{
+					"check_result":    pasturePb.CheckResult_Fail,
+					"check_user_id":   userModel.SystemUser.Id,
+					"check_user_name": userModel.SystemUser.Name,
+					"check_at":        time.Now().Local().Unix(),
+					"is_show":         pasturePb.IsShow_No,
+				}).Error; err != nil {
+				return xerr.WithStack(err)
+			}
+
+			if err = tx.Model(new(model.NeckRingHealthWarning)).
+				Where("neck_ring_health_id = ?", id).
+				Updates(map[string]interface{}{
+					"is_show": pasturePb.IsShow_No,
+				}).Error; err != nil {
+				return xerr.WithStack(err)
+			}
+		}
+		return nil
+	}); err != nil {
+		return xerr.WithStack(err)
+	}
+
+	return nil
+}
 func (s *StoreEntry) EstrusWarningQuery(ctx context.Context, pastureId int64) (*gorm.DB, error) {
 	nowTime := time.Now().Local()
 	startTime := time.Unix(util.TimeParseLocalUnix(nowTime.Format(model.LayoutDate2)), 0).Local().Format(model.LayoutTime)

+ 1 - 1
module/crontab/cow_cron.go

@@ -451,7 +451,7 @@ func (e *Entry) SystemBasicCrontab() error {
 
 func (e *Entry) DeleteOldOriginal() error {
 	if err := e.DB.Model(new(model.NeckRingOriginal)).
-		Where("active_date <= ?", time.Now().Local().AddDate(0, 0, -7).Format(model.LayoutDate2)).
+		Where("active_date <= ?", time.Now().Local().AddDate(0, 0, -30).Format(model.LayoutDate2)).
 		Delete(&model.NeckRingOriginal{}).Error; err != nil {
 		zaplog.Error("crontab", zap.Any("DeleteOldOriginal", err))
 	}