Browse Source

dashboard: dataWarning pop

Yi 1 month ago
parent
commit
b29a2b04f0

+ 1 - 1
go.mod

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

@@ -76,6 +76,16 @@ gitee.com/xuyiping_admin/go_proto v0.0.0-20250427084730-8129eb58bb83 h1:BlREMV1B
 gitee.com/xuyiping_admin/go_proto v0.0.0-20250427084730-8129eb58bb83/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
 gitee.com/xuyiping_admin/go_proto v0.0.0-20250427091240-4d1fa51de52c h1:oRoz0BJWx7zMsXWA1x/2EILg9e0iPFprs23NWDcU2zA=
 gitee.com/xuyiping_admin/go_proto v0.0.0-20250427091240-4d1fa51de52c/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250428030038-1fa928429b8b h1:hBfSqTM9XibBmMG5y7X3rQx2uvPJZge/SP/HP0/FDXA=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250428030038-1fa928429b8b/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250428072657-d9a291b08ae9 h1:UQV4c+yPVVz50fDyCUweeezb3crtIV05nJq9cAGvPEs=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250428072657-d9a291b08ae9/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250428074830-fb40d5445257 h1:zDyc5DgdWguRUqWYAL5nbRC3o5G30x+tRdjD+4VTUS0=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250428074830-fb40d5445257/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250429024839-b282b4e1465a h1:slKL5wf3QH5ufX5CkJ+HDqkFjdp7VEujNV9C+q7nAbw=
+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/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=

+ 20 - 0
http/handler/dashboard/dashboard.go

@@ -100,3 +100,23 @@ func DataWarningList(c *gin.Context) {
 	}
 	ginutil.JSONResp(c, res)
 }
+
+func DataWarningPop(c *gin.Context) {
+	var req pasturePb.WarningDataListRequest
+	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.DataWarningPop(c, &req, pagination)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	c.JSON(http.StatusOK, res)
+}

+ 1 - 0
http/route/dashboard_api.go

@@ -17,6 +17,7 @@ func DashboardApi(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
 		dashboardRoute.GET("/focus/indicators/:dimension", dashboard.FocusIndicators)
 		dashboardRoute.POST("/focus/indicators/set", dashboard.FocusIndicatorsSet)
 		dashboardRoute.GET("/data/warning/list", dashboard.DataWarningList)
+		dashboardRoute.POST("/data/warning/pop", dashboard.DataWarningPop)
 		dashboardRoute.POST("/data/warning/set", dashboard.DataWarningSet)
 		dashboardRoute.GET("/todo/count", dashboard.TodoCount)
 	}

+ 125 - 112
model/cow.go

@@ -22,6 +22,7 @@ type Cow struct {
 	DayAge                int32                          `json:"dayAge"`                // 日龄
 	CalvingAge            int32                          `json:"calvingAge"`            // 产后天使
 	PregnancyAge          int32                          `json:"pregnancyAge"`          // 怀孕天数 孕检结果有阳性更新,产犊后至0
+	MatingAge             int32                          `json:"matingAge"`             // 配种天数
 	AdmissionAge          int32                          `json:"admissionAge"`          // 入场日龄
 	AbortionAge           int32                          `json:"abortionAge"`           // 流产天数
 	LactationAge          int32                          `json:"lactationAge"`          // 泌乳天数
@@ -50,6 +51,8 @@ type Cow struct {
 	DeparturePrice        float32                        `json:"departurePrice"`        // 离场价格
 	DepartureAvgWeight    int32                          `json:"departureAvgWeight"`    // 离场平均体重
 	FirstMatingAt         int64                          `json:"firstMatingAt"`         // 首次配种时间
+	AllMatingTimes        int32                          `json:"allMatingTimes"`        // 总配次
+	AllAbortionTimes      int32                          `json:"allAbortionTimes"`      // 总流产次数
 	MatingTimes           int32                          `json:"matingTimes"`           // 配种次数
 	AbortionTimes         int32                          `json:"abortionTimes"`         // 流产次数
 	PregnancyCheckName    string                         `json:"pregnancyCheckName"`    // 孕检名称
@@ -86,6 +89,7 @@ func (c *Cow) EventUpdate(weeklyActive int32) {
 	c.WeeklyActive = weeklyActive
 	c.LactationAge = c.GetLactationAge()
 	c.DryMilkAge = c.GetDryMilkAge()
+	c.MatingAge = c.GetMatingAge()
 }
 
 // EventCalvingUpdate 产犊更新
@@ -125,6 +129,7 @@ func (c *Cow) EventAbortionUpdate(abortionAt int64, isLact pasturePb.IsShow_Kind
 	c.LastAbortionAt = abortionAt
 	c.BreedStatus = pasturePb.BreedStatus_Abort
 	c.AbortionTimes += 1
+	c.AllAbortionTimes += 1
 	if isLact == pasturePb.IsShow_Ok {
 		c.Lact += 1
 	}
@@ -177,7 +182,7 @@ func (c *Cow) EventSaleUpdate(eventSale *EventSale) {
 	c.NeckRingNumber = ""
 	c.DeparturePrice = float32(eventSale.SalePrice)
 	if eventSale.SaleCowCount > 0 {
-		c.DepartureAvgWeight = int32(eventSale.SaleAllWeight / eventSale.SaleCowCount)
+		c.DepartureAvgWeight = eventSale.SaleAllWeight / eventSale.SaleCowCount
 	}
 }
 
@@ -192,6 +197,7 @@ func (c *Cow) EventMatingUpdate(matingAt int64, bullNumber string, isReMating bo
 	c.BreedStatus = pasturePb.BreedStatus_Breeding
 	if !isReMating {
 		c.MatingTimes += 1
+		c.AllMatingTimes += 1
 	}
 	if c.Lact == 0 {
 		c.CowType = pasturePb.CowType_Reserve_Calf
@@ -237,6 +243,124 @@ func (c *Cow) GetAvgDailyWeight() float32 {
 	return float32(c.CurrentWeight-c.AdmissionWeight) / 1000 / float32(c.AdmissionAge)
 }
 
+// GetDayAge 日龄
+func (c *Cow) GetDayAge() int32 {
+	if c.BirthAt <= 0 {
+		return 0
+	}
+	return int32(math.Floor(float64(time.Now().Local().Unix()-c.BirthAt) / 86400))
+}
+
+// GetCalvingAge 产后天数
+func (c *Cow) GetCalvingAge() int32 {
+	if c.LastCalvingAt <= 0 {
+		return 0
+	}
+	return int32(math.Floor(float64(time.Now().Local().Unix()-c.LastCalvingAt) / 86400))
+}
+
+// GetDaysPregnant 怀孕天数
+func (c *Cow) GetDaysPregnant() int32 {
+	if c.BreedStatus == pasturePb.BreedStatus_Pregnant &&
+		c.AdmissionStatus == pasturePb.AdmissionStatus_Admission &&
+		c.IsPregnant == pasturePb.IsShow_Ok {
+		return int32(math.Floor(float64(time.Now().Local().Unix()-c.LastMatingAt) / 86400))
+	}
+	return 0
+}
+
+// GetDaysPregnancy 牛只预产日期
+func (c *Cow) GetDaysPregnancy(pregnancyAgeValue int32) int32 {
+	if c.BreedStatus == pasturePb.BreedStatus_Pregnant &&
+		c.AdmissionStatus == pasturePb.AdmissionStatus_Admission &&
+		c.IsPregnant == pasturePb.IsShow_Ok {
+		return int32(math.Floor(float64(c.LastMatingAt + int64(pregnancyAgeValue)*86400)))
+	}
+	return 0
+}
+
+// GetLactationDays 泌乳天数
+func (c *Cow) GetLactationDays() int32 {
+	if c.BreedStatus == pasturePb.BreedStatus_Calving && c.AdmissionStatus == pasturePb.AdmissionStatus_Admission {
+		return int32(math.Floor(float64(time.Now().Local().Unix()-c.LastCalvingAt) / 86400))
+	}
+	return 0
+}
+
+// GetAdmissionAge 入场天数
+func (c *Cow) GetAdmissionAge() int32 {
+	if c.AdmissionAt > 0 && c.AdmissionStatus == pasturePb.AdmissionStatus_Admission {
+		return int32(math.Floor(float64(time.Now().Local().Unix()-c.AdmissionAt) / 86400))
+	}
+	return 0
+}
+
+// GetAverageDailyWeight 平均日增重(最后一次称重 - 入场体重)÷在群天数
+func (c *Cow) GetAverageDailyWeight() float64 {
+	if c.AdmissionAge <= 0 {
+		c.AdmissionAge = c.GetAdmissionAge()
+	}
+
+	if c.AdmissionAge <= 0 {
+		return 0
+	}
+
+	if c.CurrentWeight-c.AdmissionWeight <= 0 {
+		return 0
+	}
+	return float64(c.CurrentWeight-c.AdmissionWeight) / 1000 / float64(c.AdmissionAge)
+}
+
+// GetPreviousStageDailyWeight 上一个阶段日增重
+func (c *Cow) GetPreviousStageDailyWeight() float64 {
+	if c.CurrentWeight-c.LastSecondWeight > 0 && c.LastWeightAt-c.LastSecondWeightAt > 0 {
+		days := int32(math.Floor(float64(c.LastWeightAt-c.LastSecondWeightAt) / 86400))
+		if days <= 0 {
+			return float64(c.CurrentWeight - c.LastSecondWeight)
+		}
+		dayWeight := math.Round(1.0 * float64(c.CurrentWeight-c.LastSecondWeight) / float64(days))
+		return dayWeight / 1000
+	}
+	return 0
+}
+
+// GetAbortionAge 流产天数
+func (c *Cow) GetAbortionAge() int32 {
+	if c.LastAbortionAt > 0 && c.AdmissionStatus == pasturePb.AdmissionStatus_Admission {
+		return int32(math.Floor(float64(time.Now().Local().Unix()-c.LastAbortionAt) / 86400))
+	}
+	return 0
+}
+
+// GetLactationAge 泌乳天数
+func (c *Cow) GetLactationAge() int32 {
+	if c.LastCalvingAt <= 0 {
+		return 0
+	}
+
+	if c.MilkKind == pasturePb.CowMilk_Lactation ||
+		c.BreedStatus == pasturePb.BreedStatus_Calving ||
+		c.BreedStatus == pasturePb.BreedStatus_Abort {
+		return int32(math.Floor(float64(time.Now().Local().Unix()-c.LastCalvingAt) / 86400))
+	}
+	return c.LactationAge
+}
+
+// GetDryMilkAge 干奶天数
+func (c *Cow) GetDryMilkAge() int32 {
+	if c.MilkKind == pasturePb.CowMilk_Dry_Milk {
+		return int32(math.Floor(float64(time.Now().Local().Unix()-c.LastDryMilkAt) / 86400))
+	}
+	return c.DryMilkAge
+}
+
+func (c *Cow) GetMatingAge() int32 {
+	if c.LastMatingAt <= 0 {
+		return 0
+	}
+	return int32(math.Floor(float64(time.Now().Local().Unix()-c.LastMatingAt) / 86400))
+}
+
 type CowSlice []*Cow
 
 func (c CowSlice) ToPB(
@@ -612,117 +736,6 @@ func (b BarCowStructSlice) ToPB(cowTypeMap map[pasturePb.CowType_Kind]string, co
 	return pb
 }
 
-// GetDayAge 日龄
-func (c *Cow) GetDayAge() int32 {
-	if c.BirthAt <= 0 {
-		return 0
-	}
-	return int32(math.Floor(float64(time.Now().Local().Unix()-c.BirthAt) / 86400))
-}
-
-// GetCalvingAge 产后天数
-func (c *Cow) GetCalvingAge() int32 {
-	if c.LastCalvingAt <= 0 {
-		return 0
-	}
-	return int32(math.Floor(float64(time.Now().Local().Unix()-c.LastCalvingAt) / 86400))
-}
-
-// GetDaysPregnant 怀孕天数
-func (c *Cow) GetDaysPregnant() int32 {
-	if c.BreedStatus == pasturePb.BreedStatus_Pregnant &&
-		c.AdmissionStatus == pasturePb.AdmissionStatus_Admission &&
-		c.IsPregnant == pasturePb.IsShow_Ok {
-		return int32(math.Floor(float64(time.Now().Local().Unix()-c.LastMatingAt) / 86400))
-	}
-	return 0
-}
-
-// GetDaysPregnancy 牛只预产日期
-func (c *Cow) GetDaysPregnancy(pregnancyAgeValue int32) int32 {
-	if c.BreedStatus == pasturePb.BreedStatus_Pregnant &&
-		c.AdmissionStatus == pasturePb.AdmissionStatus_Admission &&
-		c.IsPregnant == pasturePb.IsShow_Ok {
-		return int32(math.Floor(float64(c.LastMatingAt + int64(pregnancyAgeValue)*86400)))
-	}
-	return 0
-}
-
-// GetLactationDays 泌乳天数
-func (c *Cow) GetLactationDays() int32 {
-	if c.BreedStatus == pasturePb.BreedStatus_Calving && c.AdmissionStatus == pasturePb.AdmissionStatus_Admission {
-		return int32(math.Floor(float64(time.Now().Local().Unix()-c.LastCalvingAt) / 86400))
-	}
-	return 0
-}
-
-// GetAdmissionAge 入场天数
-func (c *Cow) GetAdmissionAge() int32 {
-	if c.AdmissionAt > 0 && c.AdmissionStatus == pasturePb.AdmissionStatus_Admission {
-		return int32(math.Floor(float64(time.Now().Local().Unix()-c.AdmissionAt) / 86400))
-	}
-	return 0
-}
-
-// GetAverageDailyWeight 平均日增重(最后一次称重 - 入场体重)÷在群天数
-func (c *Cow) GetAverageDailyWeight() float64 {
-	if c.AdmissionAge <= 0 {
-		c.AdmissionAge = c.GetAdmissionAge()
-	}
-
-	if c.AdmissionAge <= 0 {
-		return 0
-	}
-
-	if c.CurrentWeight-c.AdmissionWeight <= 0 {
-		return 0
-	}
-	return float64(c.CurrentWeight-c.AdmissionWeight) / 1000 / float64(c.AdmissionAge)
-}
-
-// GetPreviousStageDailyWeight 上一个阶段日增重
-func (c *Cow) GetPreviousStageDailyWeight() float64 {
-	if c.CurrentWeight-c.LastSecondWeight > 0 && c.LastWeightAt-c.LastSecondWeightAt > 0 {
-		days := int32(math.Floor(float64(c.LastWeightAt-c.LastSecondWeightAt) / 86400))
-		if days <= 0 {
-			return float64(c.CurrentWeight - c.LastSecondWeight)
-		}
-		dayWeight := math.Round(1.0 * float64(c.CurrentWeight-c.LastSecondWeight) / float64(days))
-		return dayWeight / 1000
-	}
-	return 0
-}
-
-// GetAbortionAge 流产天数
-func (c *Cow) GetAbortionAge() int32 {
-	if c.LastAbortionAt > 0 && c.AdmissionStatus == pasturePb.AdmissionStatus_Admission {
-		return int32(math.Floor(float64(time.Now().Local().Unix()-c.LastAbortionAt) / 86400))
-	}
-	return 0
-}
-
-// GetLactationAge 泌乳天数
-func (c *Cow) GetLactationAge() int32 {
-	if c.LastCalvingAt <= 0 {
-		return 0
-	}
-
-	if c.MilkKind == pasturePb.CowMilk_Lactation ||
-		c.BreedStatus == pasturePb.BreedStatus_Calving ||
-		c.BreedStatus == pasturePb.BreedStatus_Abort {
-		return int32(math.Floor(float64(time.Now().Local().Unix()-c.LastCalvingAt) / 86400))
-	}
-	return c.LactationAge
-}
-
-// GetDryMilkAge 干奶天数
-func (c *Cow) GetDryMilkAge() int32 {
-	if c.MilkKind == pasturePb.CowMilk_Dry_Milk {
-		return int32(math.Floor(float64(time.Now().Local().Unix()-c.LastDryMilkAt) / 86400))
-	}
-	return c.DryMilkAge
-}
-
 type CowWeightRange struct {
 	WeightRange string `json:"weight_range"`
 	Count       int32  `json:"count"`

+ 172 - 16
model/data_waring.go

@@ -3,33 +3,172 @@ package model
 import (
 	"time"
 
+	"gitee.com/xuyiping_admin/pkg/xerr"
+
 	pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
 )
 
 const DefaultUserId = 0
 
 type DataWarning struct {
-	Id                int64                 `json:"id"`
-	PastureId         int64                 `json:"pastureId"`
-	UserId            int64                 `json:"userId"`
-	Kind              string                `json:"kind"`
-	Name              string                `json:"name"`
-	Description       string                `json:"description"`
-	DataValue         string                `json:"dataValue"`
-	DataUpdateAt      int64                 `json:"dataUpdateAt"`
-	ConditionUpdateAt int64                 `json:"conditionUpdateAt"`
-	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"`
+	Kind              pasturePb.DataWarningType_Kind `json:"kind"`
+	Name              string                         `json:"name"`
+	Description       string                         `json:"description"`
+	DataValue         string                         `json:"dataValue"`
+	DataUpdateAt      int64                          `json:"dataUpdateAt"`
+	ConditionUpdateAt int64                          `json:"conditionUpdateAt"`
+	IsShow            pasturePb.IsShow_Kind          `json:"isShow"`
+	CreatedAt         int64                          `json:"createdAt"`
+	UpdatedAt         int64                          `json:"updatedAt"`
 }
 
 func (d *DataWarning) TableName() string {
 	return "data_warning"
 }
 
-func NewDataWarningList(pastureId, userId int64, req []*pasturePb.WarningDataSet, warningMap map[string]*DataWarning) []*DataWarning {
+func (d *DataWarning) GetWarningColumn() (headers map[string]string, headerSort []string, err error) {
+	switch d.Kind {
+	case pasturePb.DataWarningType_Sale_Standard:
+		headers = map[string]string{
+			"cowId":              "编号",
+			"earNumber":          "牛号",
+			"penName":            "栏舍",
+			"admissionAge":       "在群天数",
+			"lastWeightAtFormat": "最后称重时间",
+			"currentWeight":      "当前体重(kg)",
+			"avgDailyWeight":     "日增重(kg)",
+		}
+		headerSort = []string{
+			"cowId",
+			"earNumber",
+			"penName",
+			"admissionAge",
+			"lastWeightAtFormat",
+			"currentWeight",
+			"avgDailyWeight",
+		}
+	case pasturePb.DataWarningType_UnPregnant_Mating_Time:
+		headers = map[string]string{
+			"cowId":              "编号",
+			"earNumber":          "牛号",
+			"penName":            "栏舍",
+			"birthAtFormat":      "出生日期",
+			"cowTypeName":        "牛只类型",
+			"dayAge":             "日龄",
+			"breedStatusName":    "繁殖状态",
+			"lastMatingAtFormat": "最近配种时间",
+			"matingTimes":        "配次",
+			"abortionTimes":      "流产次数",
+		}
+		headerSort = []string{
+			"cowId",
+			"earNumber",
+			"penName",
+			"birthAtFormat",
+			"cowTypeName",
+			"dayAge",
+			"breedStatusName",
+			"lastMatingAtFormat",
+			"matingTimes",
+			"abortionTimes",
+		}
+	case pasturePb.DataWarningType_Abortion_Time:
+		headers = map[string]string{
+			"cowId":                "编号",
+			"earNumber":            "牛号",
+			"penName":              "栏舍",
+			"lact":                 "胎次",
+			"matingAge":            "配后天数",
+			"breedStatusName":      "繁殖状态",
+			"abortionTimes":        "流产次数",
+			"lastAbortionAtFormat": "流产时间",
+			"matingTimes":          "配次",
+		}
+		headerSort = []string{
+			"cowId",
+			"earNumber",
+			"penName",
+			"lact",
+			"breedStatusName",
+			"matingAge",
+			"abortionTimes",
+			"lastAbortionAtFormat",
+			"matingTimes",
+		}
+
+	case pasturePb.DataWarningType_Over_Age_UnPaired_Young:
+		headers = map[string]string{
+			"cowId":         "编号",
+			"earNumber":     "牛号",
+			"penName":       "栏舍",
+			"birthAtFormat": "出生日期",
+			"dayAge":        "日龄",
+		}
+		headerSort = []string{
+			"cowId",
+			"earNumber",
+			"penName",
+			"birthAtFormat",
+			"dayAge",
+		}
+	case pasturePb.DataWarningType_Over_Age_UnPregnant_Young:
+		headers = map[string]string{
+			"cowId":              "编号",
+			"earNumber":          "牛号",
+			"penName":            "栏舍",
+			"birthAtFormat":      "出生日期",
+			"dayAge":             "日龄",
+			"breedStatusName":    "繁殖状态",
+			"lastMatingAtFormat": "配种日期",
+			"matingTimes":        "配次",
+			"abortionTimes":      "流产次数",
+		}
+		headerSort = []string{
+			"cowId",
+			"earNumber",
+			"penName",
+			"birthAtFormat",
+			"dayAge",
+			"breedStatusName",
+			"lastMatingAtFormat",
+			"matingTimes",
+			"abortionTimes",
+		}
+	case pasturePb.DataWarningType_Over_Month_UnSale:
+		headers = map[string]string{
+			"cowId":              "编号",
+			"earNumber":          "牛号",
+			"penName":            "栏舍",
+			"admissionAtFormat":  "入场日期",
+			"admissionAge":       "在群天数",
+			"avgDailyWeight":     "平均日增重",
+			"lastWeightAtFormat": "上次称重日期",
+			"currentWeight":      "上次体重",
+			"cowKindName":        "品种",
+		}
+		headerSort = []string{
+			"cowId",
+			"earNumber",
+			"penName",
+			"admissionAtFormat",
+			"admissionAge",
+			"avgDailyWeight",
+			"lastWeightAtFormat",
+			"currentWeight",
+			"cowKindName",
+		}
+	default:
+		return nil, nil, xerr.Custom("暂不支持该预警数据")
+	}
+	return headers, headerSort, nil
+}
+
+func NewDataWarningList(pastureId, userId int64, req []*pasturePb.WarningDataSet, warningMap map[pasturePb.DataWarningType_Kind]*DataWarning) []*DataWarning {
 	res := make([]*DataWarning, 0)
-	isAdd := make(map[string]bool)
+	isAdd := make(map[pasturePb.DataWarningType_Kind]bool)
 	for _, v := range req {
 		if _, ok := isAdd[v.Kind]; ok {
 			continue
@@ -41,7 +180,7 @@ func NewDataWarningList(pastureId, userId int64, req []*pasturePb.WarningDataSet
 	return res
 }
 
-func NewDataWarning(pastureId, userId int64, Kind string, isShow pasturePb.IsShow_Kind, defaultDataWarning *DataWarning) *DataWarning {
+func NewDataWarning(pastureId, userId int64, Kind pasturePb.DataWarningType_Kind, isShow pasturePb.IsShow_Kind, defaultDataWarning *DataWarning) *DataWarning {
 	return &DataWarning{
 		PastureId:   pastureId,
 		UserId:      userId,
@@ -57,7 +196,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 {
+		if warningData.IsShow != pasturePb.IsShow_Ok || warningData.DataValue == "0" {
 			continue
 		}
 		dataUpdateTimeFormat := ""
@@ -74,3 +213,20 @@ func (d DataWarningSlice) ToPB() []*pasturePb.WarningDataShow {
 	}
 	return res
 }
+
+type WarningDataPopResponse struct {
+	Code int32       `json:"code"`
+	Msg  string      `json:"msg"`
+	Data *WarningPop `json:"data"`
+}
+
+type WarningPop struct {
+	Title      string                         `json:"title"`
+	Kind       pasturePb.DataWarningType_Kind `json:"kind"`
+	Headers    map[string]string              `json:"headers"`
+	HeaderSort []string                       `json:"headerSort"`
+	Total      int32                          `json:"total"`
+	Page       int32                          `json:"page"`
+	PageSize   int32                          `json:"pageSize"`
+	DataList   interface{}                    `json:"list"`
+}

+ 2 - 1
model/data_warning_items.go

@@ -42,7 +42,8 @@ type DataWarningItemsSlice []*DataWarningItems
 func (d DataWarningItemsSlice) ToPB(dataWarning []*DataWarning) []*pasturePb.WarningDataSet {
 	res := make([]*pasturePb.WarningDataSet, len(d))
 	for i, v := range d {
-		name, kind := "", ""
+		name := ""
+		kind := pasturePb.DataWarningType_Invalid
 		for _, w := range dataWarning {
 			if w.Id == v.WarningId {
 				name = w.Name

+ 22 - 0
model/pen_behavior.go

@@ -98,6 +98,28 @@ func (p PenBehaviorSlice) ToPB() *pasturePb.BarnBehaviorCurveItem {
 		res.WeekAvgRumina = append(res.WeekAvgRumina, v.WeekRuminaRate)
 		res.WeekAvgIntake = append(res.WeekAvgIntake, v.WeekIntakeRate)
 		res.WeekAvgReset = append(res.WeekAvgReset, v.WeekRestRate)
+
+		ruminaMaxShadow := float32(v.WeekRuminaRate + v.RuminaStd) //- float32(v.WeekRuminaRate-v.RuminaStd)
+		ruminaMinShadow := float32(v.WeekRuminaRate - v.RuminaStd)
+		if ruminaMinShadow < 0 {
+			ruminaMinShadow = 0
+		}
+		res.WeekAvgRuminaMaxShadow = append(res.WeekAvgRuminaMaxShadow, ruminaMaxShadow)
+		res.WeekAvgRuminaMinShadow = append(res.WeekAvgRuminaMinShadow, ruminaMinShadow)
+		intakeMaxShadow := float32(v.WeekIntakeRate + v.IntakeStd) //- float32(v.WeekIntakeRate-v.IntakeStd)
+		intakeMinShadow := float32(v.WeekIntakeRate - v.IntakeStd)
+		if intakeMinShadow < 0 {
+			intakeMinShadow = 0
+		}
+		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)
+		if restMinShadow < 0 {
+			restMinShadow = 0
+		}
+		res.WeekAvgRestMaxShadow = append(res.WeekAvgRestMaxShadow, restMaxShadow)
+		res.WeekAvgRestMinShadow = append(res.WeekAvgRestMinShadow, restMinShadow)
 	}
 
 	return res

+ 1 - 1
module/backend/calendar.go

@@ -127,7 +127,7 @@ func (s *StoreEntry) CalendarToDoList(ctx context.Context, req *pasturePb.Calend
 	historyCount := make([]*model.CompletedData, 0)
 	todayStartTime := util.TimeParseLocalUnix(nowTime)
 	todayEndTime := util.TimeParseLocalEndUnix(nowTime)
-	whereSql := fmt.Sprintf(` WHERE pasture_id = %d AND end_day <= %d  AND (status = %d OR (status = %d AND reality_day BETWEEN %d AND %d ))`,
+	whereSql := fmt.Sprintf(` WHERE pasture_id = %d AND end_day > %d  AND (status = %d OR (status = %d AND reality_day BETWEEN %d AND %d ))`,
 		pastureId, todayEndTime, pasturePb.IsShow_No, pasturePb.IsShow_Ok, todayStartTime, todayEndTime)
 	historyCountSql := `SELECT a.count as count,a.calendar_type_kind as calendar_type_kind FROM (
 		SELECT count(cow_id) as count,1 as calendar_type_kind FROM event_immunization_plan ` + whereSql + `

+ 105 - 9
module/backend/dashboard.go

@@ -2,9 +2,11 @@ package backend
 
 import (
 	"context"
+	"errors"
 	"kpt-pasture/model"
 	"kpt-pasture/util"
 	"net/http"
+	"regexp"
 	"strconv"
 	"strings"
 	"time"
@@ -247,12 +249,6 @@ func (s *StoreEntry) DataWarningList(ctx context.Context) (*pasturePb.IndexDataW
 
 	var isExist bool // 判断是否存在自己的设置的数据
 	userDataWarning, _ := s.FindDataWarning(ctx, pastureId, userModel.SystemUser.Id)
-	zaplog.Info("defaultDataWarning",
-		zap.Any("defaultDataWarning", defaultDataWarning),
-		zap.Any("pastureId", pastureId),
-		zap.Any("userDataWarning", userDataWarning),
-		zap.Any("userId", userModel.SystemUser.Id),
-	)
 	if len(userDataWarning) == 0 {
 		// 如果用户没有配置自己的预警数据,则使用默认数据
 		isExist = true
@@ -294,17 +290,108 @@ func (s *StoreEntry) DataWarningList(ctx context.Context) (*pasturePb.IndexDataW
 	}, nil
 }
 
+func (s *StoreEntry) DataWarningPop(ctx context.Context, req *pasturePb.WarningDataListRequest, pagination *pasturePb.PaginationModel) (*model.WarningDataPopResponse, error) {
+	userModel, err := s.GetUserModel(ctx)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	if req.Kind <= pasturePb.DataWarningType_Invalid {
+		return nil, xerr.Custom("请选择预警数据")
+	}
+
+	pastureId := userModel.AppPasture.Id
+	dataWaringItem := &model.DataWarning{}
+	if err = s.DB.Model(new(model.DataWarning)).
+		Where("pasture_id = ?", pastureId).
+		Where("user_id = ?", userModel.SystemUser.Id).
+		Where("kind = ?", req.Kind).
+		First(dataWaringItem).Error; err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			if err = s.DB.Model(new(model.DataWarning)).
+				Where("pasture_id = ?", pastureId).
+				Where("user_id = ?", model.DefaultUserId).
+				Where("kind = ?", req.Kind).
+				First(dataWaringItem).Error; err != nil {
+				return nil, xerr.WithStack(err)
+			}
+		} else {
+			return nil, xerr.Custom("预警数据不存在,请联系管理员!")
+		}
+	}
+
+	headers, headerSort, err := dataWaringItem.GetWarningColumn()
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	resp := &model.WarningDataPopResponse{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &model.WarningPop{
+			DataList:   make([]interface{}, 0),
+			Title:      dataWaringItem.Name,
+			Kind:       dataWaringItem.Kind,
+			HeaderSort: headerSort,
+			Headers:    headers,
+			Page:       pagination.Page,
+			PageSize:   pagination.PageSize,
+			Total:      0,
+		},
+	}
+
+	query, params, err := s.BuildQuery(dataWaringItem.Id)
+	if err != nil {
+		zaplog.Error("UpdateWarningData", zap.Any("BuildQuery", err), zap.Any("warningId", dataWaringItem.Id))
+		return resp, nil
+	}
+	if len(query) == 0 || len(params) == 0 {
+		return resp, nil
+	}
+	var count int64
+	cowList := make([]*model.Cow, 0)
+	if err = s.DB.Model(new(model.Cow)).
+		Where("pasture_id = ?", pastureId).
+		Where(query, params...).
+		Count(&count).
+		Limit(int(pagination.PageSize)).
+		Offset(int(pagination.PageOffset)).
+		Find(&cowList).
+		Error; err != nil {
+		zaplog.Error("UpdateWarningData", zap.Any("err", err), zap.Any("query", query), zap.Any("params", params))
+	}
+
+	cowTypeMap := s.CowTypeMap()
+	breedStatusMap := s.CowBreedStatusMap()
+	cowKindMap := s.CowKindMap()
+	cowSourceMap := s.CowSourceMap()
+	admissionStatusMap := s.AdmissionStatusMap()
+	healthStatusMap := s.HealthStatusMap()
+	purposeMap := s.PurposeMap()
+	resp.Data.Total = int32(count)
+	systemBasic, err := s.GetSystemBasicByName(ctx, userModel.AppPasture.Id, model.PregnancyAge)
+	if err != nil {
+		return nil, xerr.Custom("请在基础参数配置妊娠天数")
+	}
+	resp.Data.DataList = model.CowSlice(cowList).ToPB(
+		cowTypeMap, breedStatusMap, cowKindMap,
+		cowSourceMap, admissionStatusMap, healthStatusMap,
+		purposeMap, systemBasic.MinValue,
+	)
+	return resp, nil
+}
+
 // 新增用户预警数据
 func (s *StoreEntry) addUserDataWarning(ctx context.Context, pastureId, userId int64, defaultDataWarning []*model.DataWarning, warningDataSet []*pasturePb.WarningDataSet) error {
 	// 将默认预警数据按 Kind 映射
-	defaultDataWarningMap := make(map[string]*model.DataWarning)
+	defaultDataWarningMap := make(map[pasturePb.DataWarningType_Kind]*model.DataWarning)
 	for _, v := range defaultDataWarning {
 		defaultDataWarningMap[v.Kind] = v
 	}
 
 	// 在事务中执行新增操作
 	return s.DB.Transaction(func(tx *gorm.DB) error {
-		addedKinds := make(map[string]bool) // 记录已添加的 Kind
+		addedKinds := make(map[pasturePb.DataWarningType_Kind]bool) // 记录已添加的 Kind
 		for _, set := range warningDataSet {
 			dataWarning := model.NewDataWarning(pastureId, userId, set.Kind, pasturePb.IsShow_Ok, defaultDataWarningMap[set.Kind])
 			// 如果该 Kind 已添加,跳过
@@ -360,9 +447,18 @@ func (s *StoreEntry) updateUserDataWarning(ctx context.Context, pastureId, userI
 		// 更新预警数据的 IsShow 字段
 		for _, warning := range userDataWarningList {
 			if isShow, 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)
+				}
 				if err = tx.Model(&model.DataWarning{}).
 					Where("id = ?", warning.Id).
-					Update("is_show", isShow).Error; err != nil {
+					Updates(map[string]interface{}{
+						"is_show":     isShow,
+						"name":        warning.Name,
+						"description": warning.Description,
+					}).Error; err != nil {
 					return xerr.WithStack(err)
 				}
 			}

+ 1 - 0
module/backend/interface.go

@@ -308,6 +308,7 @@ type DashboardService interface {
 	FocusIndicatorsSet(ctx context.Context, req *pasturePb.IndexFocusIndicatorsSetRequest) error
 	DataWarningSet(ctx context.Context, req *pasturePb.IndexDataWarningSetRequest) error
 	DataWarningList(ctx context.Context) (*pasturePb.IndexDataWarningResponse, error)
+	DataWarningPop(ctx context.Context, req *pasturePb.WarningDataListRequest, pagination *pasturePb.PaginationModel) (*model.WarningDataPopResponse, error)
 	CalendarToDoCount(ctx context.Context) (*pasturePb.TodoCountResponse, error)
 }
 

+ 1 - 1
module/crontab/cow_cron.go

@@ -144,7 +144,7 @@ func (e *Entry) UpdateCowInfoByPasture(pastureId int64) {
 		cow.EventUpdate(weeklyActive)
 		if err := e.DB.Model(new(model.Cow)).
 			Select("day_age", "calving_age", "pregnancy_age", "admission_age",
-				"abortion_age", "cow_type", "weekly_active", "lactation_age", "dry_milk_age").
+				"abortion_age", "cow_type", "weekly_active", "lactation_age", "dry_milk_age", "mating_age").
 			Where("id = ?", cow.Id).
 			Updates(cow).Error; err != nil {
 			zaplog.Error("Crontab", zap.Any("UpdateCowDayAge", err))