Browse Source

estrus: 发情数据调整

Yi 1 month ago
parent
commit
97f2c9090b

+ 1 - 1
Dockerfile

@@ -15,7 +15,7 @@ RUN go env -w GO111MODULE=on && \
 
 FROM alpine:latest
 LABEL name="kpt-pasture" \
-description="pt service" \
+description="pasture service" \
 owner="yiping.xu"
 
 WORKDIR /app/kpt-pasture

+ 1 - 1
go.mod

@@ -3,7 +3,7 @@ module kpt-pasture
 go 1.17
 
 require (
-	gitee.com/xuyiping_admin/go_proto v0.0.0-20250211015100-dc46b810edc2
+	gitee.com/xuyiping_admin/go_proto v0.0.0-20250211104420-863ff8fc2535
 	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

+ 12 - 0
go.sum

@@ -167,6 +167,18 @@ gitee.com/xuyiping_admin/go_proto v0.0.0-20250211014633-a13f8eeb1990 h1:gjJmp0kM
 gitee.com/xuyiping_admin/go_proto v0.0.0-20250211014633-a13f8eeb1990/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
 gitee.com/xuyiping_admin/go_proto v0.0.0-20250211015100-dc46b810edc2 h1:g6uOnX0e5rCEj/3inaIuwmxcAo8tCR4FIEpUnF4GAS0=
 gitee.com/xuyiping_admin/go_proto v0.0.0-20250211015100-dc46b810edc2/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250211064756-0c54f3109753 h1:chRi9ULtdMteEQBh/jXemiifX4G1Joi/BbyB0Lru63o=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250211064756-0c54f3109753/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250211075436-87c7a5e75ebe h1:uIlkm9hPTbWMzloyShmiLJETgHyaQxAdPTT/sMLPhQ8=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250211075436-87c7a5e75ebe/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250211091408-ce100724cc83 h1:UIoLJ8lt5G8dBMSFiwz6rD/YCECIwZafvwQhKQmGMPA=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250211091408-ce100724cc83/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250211094203-6344b8109fc2 h1:8ViobF44MfGh7UDkqWnIlZfjSK7lCCPhiewoBZlZ1D0=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250211094203-6344b8109fc2/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250211104144-848fe88b8c1e h1:x+sO/2mrK1vcUPVnNuAuhvlD6PPsAxAc7K4EEfgefLs=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250211104144-848fe88b8c1e/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250211104420-863ff8fc2535 h1:fiM+bgvJRorAZCwgzMTXgolvF7AhEM7hx/MkmokhrOk=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250211104420-863ff8fc2535/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=

+ 2 - 2
http/handler/analysis/analysis.go

@@ -12,14 +12,14 @@ import (
 	"github.com/gin-gonic/gin"
 )
 
-func GrowthCurve(c *gin.Context) {
+func WeightScatterPlot(c *gin.Context) {
 	var req pasturePb.SearchGrowthCurvesRequest
 	if err := ginutil.BindProto(c, &req); err != nil {
 		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
 		return
 	}
 
-	res, err := middleware.Dependency(c).StoreEventHub.OpsService.GrowthCurve(c, &req)
+	res, err := middleware.Dependency(c).StoreEventHub.OpsService.WeightScatterPlot(c, &req)
 	if err != nil {
 		apierr.ClassifiedAbort(c, err)
 		return

+ 17 - 0
http/handler/dashboard/bar.go

@@ -2,6 +2,9 @@ package dashboard
 
 import (
 	"kpt-pasture/http/middleware"
+	"net/http"
+
+	"gitee.com/xuyiping_admin/pkg/valid"
 
 	"gitee.com/xuyiping_admin/pkg/apierr"
 	"gitee.com/xuyiping_admin/pkg/ginutil"
@@ -25,3 +28,17 @@ func NeckRingWarning(c *gin.Context) {
 	}
 	ginutil.JSONResp(c, res)
 }
+
+func FocusIndicators(c *gin.Context) {
+	dimension := c.Param("dimension")
+	if err := valid.Validate(dimension, valid.Required); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+	res, err := middleware.Dependency(c).StoreEventHub.OpsService.FocusIndicators(c, dimension)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	ginutil.JSONResp(c, res)
+}

+ 19 - 0
http/handler/test.go

@@ -32,6 +32,25 @@ func CowNeckRingBound(c *gin.Context) {
 	})
 }
 
+func CowNeckRingBound2(c *gin.Context) {
+	pagination := &pasturePb.PaginationModel{
+		Page:       int32(c.GetInt(middleware.Page)),
+		PageSize:   int32(c.GetInt(middleware.PageSize)),
+		PageOffset: int32(c.GetInt(middleware.PageOffset)),
+	}
+
+	if err := middleware.BackendOperation(c).OpsService.CowNeckRingNumberBound2(c, pagination); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
+}
+
 func UpdateCowPen(c *gin.Context) {
 	pagination := &pasturePb.PaginationModel{
 		Page:       int32(c.GetInt(middleware.Page)),

+ 1 - 1
http/route/analysis_api.go

@@ -13,7 +13,7 @@ func AnalysisAPI(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
 		}
 		// analysis API 组
 		analysisRoute := authRouteGroup(s, "/api/v1/analysis/")
-		analysisRoute.POST("/growth/curve", analysis.GrowthCurve)
+		analysisRoute.POST("/weight/scatter/plot", analysis.WeightScatterPlot) // 体重散点图
 		analysisRoute.POST("/weight/range", analysis.WeightRange)
 		analysisRoute.POST("/mating/timely", analysis.MatingTimeLy)
 		analysisRoute.POST("/pen/weight", analysis.PenWeight)

+ 1 - 0
http/route/dashboard_api.go

@@ -15,5 +15,6 @@ func DashboardApi(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
 		dashboardRoute := authRouteGroup(s, "/api/v1/dashboard/")
 		dashboardRoute.GET("/bar", dashboard.Bar)
 		dashboardRoute.GET("/neck_ring/warning", dashboard.NeckRingWarning)
+		dashboardRoute.GET("/focus/indicators/:dimension", dashboard.FocusIndicators)
 	}
 }

+ 1 - 0
http/route/test_api.go

@@ -14,6 +14,7 @@ func TestAPI(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
 		// test API 组  测试接口
 		testRoute := authRouteGroup(s, "/api/v1/test/")
 		testRoute.POST("/cow/neck_ring/bound", handler.CowNeckRingBound)
+		testRoute.POST("/cow/neck_ring/bound2", handler.CowNeckRingBound2)
 		testRoute.POST("/cow/pen/update", handler.UpdateCowPen)
 	}
 }

+ 15 - 3
model/cow.go

@@ -309,7 +309,7 @@ func (c CowSlice) ToPB2(penMap map[int32]*Pen, penWeightSlice PenWeightSlice) []
 			BirthWeight:              float32(v.BirthWeight) / 1000,
 			CurrentWeight:            float32(v.CurrentWeight) / 1000,
 			LastWeightAt:             int32(v.LastWeightAt),
-			AdmissionAge:             int32(v.AdmissionAge),
+			AdmissionAge:             v.AdmissionAge,
 			AdmissionWeight:          float32(v.AbortionAge) / 1000,
 			PreviousStageDailyWeight: previousStageDailyWeight,
 			PenAvgWeight:             penAvgWeight,
@@ -324,6 +324,16 @@ func NewCow(pastureId int64, req *pasturePb.EventEnterRequest, penMap map[int32]
 	if req.BreedStatus == pasturePb.BreedStatus_Pregnant {
 		isPregnant = pasturePb.IsShow_Ok
 	}
+
+	admissionAt := int64(0)
+	switch req.CowSource {
+	case pasturePb.CowSource_Calving:
+		admissionAt = int64(req.BirthAt)
+	case pasturePb.CowSource_Transfer_In:
+		admissionAt = int64(req.EnterAt)
+	case pasturePb.CowSource_Buy:
+		admissionAt = int64(req.EnterAt)
+	}
 	return &Cow{
 		PastureId:           pastureId,
 		Sex:                 req.Sex,
@@ -346,8 +356,10 @@ func NewCow(pastureId int64, req *pasturePb.EventEnterRequest, penMap map[int32]
 		FirstMatingAt:       int64(req.MatingAt),
 		LastMatingAt:        int64(req.MatingAt),
 		LastPregnantCheckAt: int64(req.PregnancyCheckAt),
-		AdmissionAt:         int64(req.BirthAt),
+		AdmissionAt:         admissionAt,
 		BirthWeight:         int64(req.Weight * 1000),
+		LastWeightAt:        int64(req.EstrusAt),
+		CurrentWeight:       int64(req.Weight * 1000),
 	}
 }
 
@@ -435,7 +447,7 @@ func (c *Cow) GetDayWeight() float64 {
 	if c.CurrentWeight-c.LastSecondWeight > 0 && c.LastWeightAt > c.LastSecondWeightAt {
 		days := int32(math.Floor(float64(c.LastWeightAt-c.LastSecondWeightAt) / 86400))
 		if days <= 0 {
-			return 0
+			return float64(c.CurrentWeight - c.LastSecondWeight)
 		}
 		dayWeight := math.Round(1.0 * float64(c.CurrentWeight-c.LastSecondWeight) / float64(days))
 		return dayWeight / 1000

+ 2 - 2
model/event_enter.go

@@ -68,7 +68,7 @@ func NewEventEnter(pastureId, cowId int64, req *pasturePb.EventEnterRequest) *Ev
 		EstrusAt:         int64(req.EstrusAt),
 		EnterAt:          int64(req.EnterAt),
 		Remarks:          req.Remarks,
-		Weight:           int64(req.Weight * 100),
+		Weight:           int64(req.Weight * 1000),
 		Price:            int64(req.Price * 100),
 		MessengerId:      int64(req.MessengerId),
 		MessengerName:    req.MessengerName,
@@ -117,7 +117,7 @@ func (e EventEnterSlice) ToPB(
 			WeaningAt:        int32(d.WeaningAt),
 			EstrusAt:         int32(d.EstrusAt),
 			EnterAt:          int32(d.EnterAt),
-			Weight:           float32(d.Weight) / 100,
+			Weight:           float32(d.Weight) / 1000,
 			Price:            float32(d.Price) / 100,
 			Remarks:          d.Remarks,
 			MessengerId:      int32(d.MessengerId),

+ 9 - 39
model/event_estrus.go

@@ -1,9 +1,6 @@
 package model
 
 import (
-	"fmt"
-	"kpt-pasture/store/kptstore"
-
 	pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
 )
 
@@ -17,7 +14,10 @@ type EventEstrus struct {
 	DayAge              int32                           `json:"dayAge"`
 	CalvingAge          int32                           `json:"calvingAge"`
 	ExposeEstrusType    pasturePb.ExposeEstrusType_Kind `json:"exposeEstrusType"`
-	LastEstrusDate      string                          `json:"lastEstrusDate"`
+	LastEstrusDay       int64                           `json:"lastEstrusDay"`
+	PlanDay             int64                           `json:"planDay"`
+	RealityDay          int64                           `json:"realityDay"`
+	EndDay              int64                           `json:"endDay"`
 	IsMating            pasturePb.IsShow_Kind           `json:"isMating"`
 	UnMatingReasonsKind pasturePb.UnMatingReasons_Kind  `json:"unMatingReasonsKind"`
 	UnMatingReasonsName string                          `json:"unMatingReasonsName"`
@@ -40,6 +40,8 @@ func NewEventEstrus(
 	cow *Cow,
 	exposeEstrusType pasturePb.ExposeEstrusType_Kind,
 	isShow pasturePb.IsShow_Kind,
+	isMating pasturePb.IsShow_Kind,
+	PlanDay int32,
 	operationUser, currentUser *SystemUser,
 ) *EventEstrus {
 	return &EventEstrus{
@@ -51,6 +53,9 @@ func NewEventEstrus(
 		NeckRingNumber:   cow.NeckRingNumber,
 		EarNumber:        cow.EarNumber,
 		ExposeEstrusType: exposeEstrusType,
+		PlanDay:          int64(PlanDay),
+		RealityDay:       int64(PlanDay),
+		IsMating:         isMating,
 		IsShow:           isShow,
 		OperationId:      operationUser.Id,
 		OperationName:    operationUser.Name,
@@ -58,38 +63,3 @@ func NewEventEstrus(
 		MessageName:      currentUser.Name,
 	}
 }
-
-type EstrusSlice []*EventEstrus
-
-func (e EstrusSlice) ToPB(
-	dB *kptstore.DB,
-	getCowInfo func(dB *kptstore.DB, cowId int64) *Cow,
-	getCowLastEvent func(DB *kptstore.DB, cowId int64, eventCategoryId pasturePb.EventCategory_Kind) *EventCowLog,
-) []*pasturePb.EstrusItems {
-	res := make([]*pasturePb.EstrusItems, len(e))
-	for i, v := range e {
-		cowInfo := getCowInfo(dB, v.CowId)
-		lastEventLog := getCowLastEvent(dB, v.CowId, pasturePb.EventCategory_Breed)
-		planDay, optimumMatingTime := "", ""
-		lastBreedEventDetails := ""
-		if lastEventLog != nil {
-			lastBreedEventDetails = fmt.Sprintf("%s %s", lastEventLog.EventTypeName, lastEventLog.EventDescription)
-		}
-		res[i] = &pasturePb.EstrusItems{
-			Id:                     int32(v.Id),
-			CowId:                  int32(v.CowId),
-			EarNumber:              v.EarNumber,
-			DayAge:                 v.DayAge,
-			Lact:                   v.Lact,
-			PenName:                cowInfo.PenName,
-			Status:                 v.IsShow,
-			CalvingAge:             int32(cowInfo.CalvingAge),
-			PlanDay:                planDay,
-			MatingTimes:            cowInfo.MatingTimes,
-			OptimumMatingStartTime: optimumMatingTime,
-			OptimumMatingEndTime:   optimumMatingTime,
-			LastBreedEventDetails:  lastBreedEventDetails,
-		}
-	}
-	return res
-}

+ 1 - 0
model/event_mating.go

@@ -110,6 +110,7 @@ func NewEventMating(pastureId int64, cow *Cow, planDay int64, exposeEstrusType p
 		PenName:          cow.PenName,
 		CowType:          cow.CowType,
 		CowKind:          cow.CowKind,
+		DayAge:           cow.DayAge,
 		CalvingAt:        cow.LastMatingAt,
 		PlanDay:          planDay,
 		EndDay:           planDay,

+ 43 - 25
model/indicators_data.go

@@ -1,6 +1,14 @@
 package model
 
-import pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
+import (
+	"fmt"
+	"sort"
+
+	"gitee.com/xuyiping_admin/pkg/logger/zaplog"
+	"go.uber.org/zap"
+
+	pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
+)
 
 type IndicatorsData struct {
 	Id           int64                        `json:"id"`
@@ -23,33 +31,44 @@ type IndicatorsDataSlice []*IndicatorsData
 func (i IndicatorsDataSlice) ToPB(detailsMap map[string]*IndicatorsDetails) *IndicatorComparison {
 	res := &IndicatorComparison{
 		Headers: make([]string, 0),
-		List:    make(map[string][]string),
-		Notes:   make(map[string]map[string]string),
+		List:    make([]map[string]string, 0),
 	}
-
-	dateMap := make(map[string]bool)
+	dateSet := make(map[string]struct{}) // 使用空结构体节省内存
+	for _, v := range i {
+		dateSet[v.Date] = struct{}{}
+	}
+	for date := range dateSet {
+		res.Headers = append(res.Headers, date)
+	}
+	sort.Strings(res.Headers) // 确保日期顺序一致
+	dataMap := make(map[string]map[string]string)
 	for _, v := range i {
-		if len(res.Headers) == 0 {
-			res.Headers = []string{"指标名称", "单位"}
-		}
-		if !dateMap[v.Date] {
-			dateMap[v.Date] = true
-			res.Headers = append(res.Headers, v.Date)
-		}
-
 		details, ok := detailsMap[v.Kind]
-		if ok {
-			if res.List[v.Kind] == nil {
-				res.List[v.Kind] = make([]string, 0)
-				res.List[v.Kind] = append(res.List[v.Kind], details.Name, details.Unit)
-			}
-			res.List[v.Kind] = append(res.List[v.Kind], v.Value)
-			if res.Notes[v.Kind] == nil {
-				res.Notes[v.Kind] = make(map[string]string)
+		if !ok {
+			zaplog.Error("detailsMap", zap.Any("Kind", v.Kind))
+			continue
+		}
+		if data, exists := dataMap[details.Kind]; exists {
+			data[v.Date] = v.Value
+		} else {
+			data = make(map[string]string)
+			for _, date := range res.Headers {
+				data[date] = "" // 初始化所有日期为空字符串
 			}
-			res.Notes[v.Kind]["zh"] = details.Zh
+			data[v.Date] = v.Value
+			data["name"] = details.Name
+			data["kind"] = details.Kind
+			data["unit"] = details.Unit
+			data["categoryType"] = fmt.Sprintf("%d", details.CategoryType)
+			data["categoryName"] = details.CategoryName
+			data["zh"] = details.Zh
+			dataMap[details.Kind] = data
 		}
 	}
+
+	for _, data := range dataMap {
+		res.List = append(res.List, data)
+	}
 	return res
 }
 
@@ -60,7 +79,6 @@ type IndicatorsComparisonResponse struct {
 }
 
 type IndicatorComparison struct {
-	Headers []string                     `json:"headers"`
-	List    map[string][]string          `json:"list"`
-	Notes   map[string]map[string]string `json:"notes"`
+	Headers []string            `json:"headers"`
+	List    []map[string]string `json:"list"`
 }

+ 6 - 3
model/neck_ring.go

@@ -12,6 +12,7 @@ type NeckRing struct {
 	PastureId      int64                         `json:"pastureId"`
 	NeckRingNumber string                        `json:"neckRingNumber"`
 	CowId          int64                         `json:"cowId"`
+	EarNumber      string                        `json:"earNumber"`
 	WearAt         int64                         `json:"wearAt"`
 	Status         pasturePb.NeckRingStatus_Kind `json:"status"`
 	ErrorReason    string                        `json:"errorReason"`
@@ -30,11 +31,12 @@ func (n *NeckRing) EventBindUpdate(cowId int64) {
 	n.WearAt = time.Now().Unix()
 }
 
-func NewNeckRing(pastureId int64, neckRingNumber string, cowId int64, operationUser *SystemUser) *NeckRing {
+func NewNeckRing(pastureId int64, cowInfo *Cow, operationUser *SystemUser) *NeckRing {
 	return &NeckRing{
 		PastureId:      pastureId,
-		NeckRingNumber: neckRingNumber,
-		CowId:          cowId,
+		NeckRingNumber: cowInfo.NeckRingNumber,
+		CowId:          cowInfo.Id,
+		EarNumber:      cowInfo.EarNumber,
 		WearAt:         time.Now().Unix(),
 		Status:         pasturePb.NeckRingStatus_Bind,
 		OperationId:    int32(operationUser.Id),
@@ -70,6 +72,7 @@ func (n NeckRingSlice) ToPB(
 		res[i] = &pasturePb.SearchNeckRingList{
 			Id:           int32(v.Id),
 			Number:       v.NeckRingNumber,
+			EarNumber:    v.EarNumber,
 			PenId:        penId,
 			PenName:      penName,
 			CowId:        int32(v.CowId),

+ 3 - 3
model/neck_ring_bind_log.go

@@ -19,12 +19,12 @@ func (n *NeckRingBindLog) TableName() string {
 	return "neck_ring_bind_log"
 }
 
-func NewNeckRingBindLog(pastureId int64, neckRingNumber string, cowId int64, operationUser *SystemUser) *NeckRingBindLog {
+func NewNeckRingBindLog(pastureId int64, cowInfo *Cow, operationUser *SystemUser) *NeckRingBindLog {
 	return &NeckRingBindLog{
 		PastureId:      pastureId,
-		NeckRingNumber: neckRingNumber,
+		NeckRingNumber: cowInfo.NeckRingNumber,
 		BindAt:         time.Now().Unix(),
-		CowId:          cowId,
+		CowId:          cowInfo.Id,
 		OperationId:    operationUser.Id,
 		OperationName:  operationUser.Name,
 	}

+ 41 - 1
model/neck_ring_estrus.go

@@ -1,6 +1,11 @@
 package model
 
-import pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
+import (
+	"fmt"
+	"kpt-pasture/store/kptstore"
+
+	pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
+)
 
 type NeckRingEstrus struct {
 	Id               int64                           `json:"id"`
@@ -48,3 +53,38 @@ func NewNeckRingEstrus(
 		CheckResult:      checkResult,
 	}
 }
+
+type NeckRingEstrusSlice []*NeckRingEstrus
+
+func (e NeckRingEstrusSlice) ToPB(
+	dB *kptstore.DB,
+	getCowInfo func(dB *kptstore.DB, cowId int64) *Cow,
+	getCowLastEvent func(DB *kptstore.DB, cowId int64, eventCategoryId pasturePb.EventCategory_Kind) *EventCowLog,
+) []*pasturePb.EstrusItems {
+	res := make([]*pasturePb.EstrusItems, len(e))
+	for i, v := range e {
+		cowInfo := getCowInfo(dB, v.CowId)
+		lastEventLog := getCowLastEvent(dB, v.CowId, pasturePb.EventCategory_Breed)
+		planDay, optimumMatingTime := "", ""
+		lastBreedEventDetails := ""
+		if lastEventLog != nil {
+			lastBreedEventDetails = fmt.Sprintf("%s %s", lastEventLog.EventTypeName, lastEventLog.EventDescription)
+		}
+		res[i] = &pasturePb.EstrusItems{
+			Id:                     int32(v.Id),
+			CowId:                  int32(v.CowId),
+			EarNumber:              v.EarNumber,
+			DayAge:                 cowInfo.DayAge,
+			Lact:                   v.Lact,
+			PenName:                cowInfo.PenName,
+			Status:                 v.IsShow,
+			CalvingAge:             cowInfo.CalvingAge,
+			PlanDay:                planDay,
+			MatingTimes:            cowInfo.MatingTimes,
+			OptimumMatingStartTime: optimumMatingTime,
+			OptimumMatingEndTime:   optimumMatingTime,
+			LastBreedEventDetails:  lastBreedEventDetails,
+		}
+	}
+	return res
+}

+ 11 - 10
module/backend/analysis.go

@@ -15,8 +15,8 @@ import (
 	"go.uber.org/zap"
 )
 
-// GrowthCurve 生长曲线 获取图表数据
-func (s *StoreEntry) GrowthCurve(ctx context.Context, req *pasturePb.SearchGrowthCurvesRequest) (*pasturePb.GrowthCurvesResponse, error) {
+// WeightScatterPlot 体重散点图 获取图表数据
+func (s *StoreEntry) WeightScatterPlot(ctx context.Context, req *pasturePb.SearchGrowthCurvesRequest) (*pasturePb.GrowthCurvesResponse, error) {
 	userModel, err := s.GetUserModel(ctx)
 	if err != nil {
 		return nil, xerr.Custom("当前用户信息错误,请退出重新登录")
@@ -46,13 +46,13 @@ func (s *StoreEntry) GrowthCurve(ctx context.Context, req *pasturePb.SearchGrowt
 	}
 	// 计算图表数据
 	chartsList := &pasturePb.Charts{
-		CowId:  make([]int32, 0),
-		Weight: make([]float32, 0),
-		DayAge: make([]int32, 0),
+		CowId:        make([]int32, 0),
+		Weight:       make([]float32, 0),
+		AdmissionAge: make([]int32, 0),
 	}
 	cowData := make([]*pasturePb.CowList, 0)
 	for _, cow := range cowList {
-		currentWeight := float32(cow.CurrentWeight) / 100
+		currentWeight := float32(cow.CurrentWeight) / 1000
 		penName := ""
 		for _, v := range penList {
 			if cow.PenId != v.Id {
@@ -67,15 +67,16 @@ func (s *StoreEntry) GrowthCurve(ctx context.Context, req *pasturePb.SearchGrowt
 			PenName:                penName,
 			CurrentWeight:          currentWeight,
 			BirthAt:                int32(cow.BirthAt),
-			BirthWeight:            float32(cow.BirthWeight) / 100,
+			BirthWeight:            float32(cow.BirthWeight) / 1000,
 			LastWeightAt:           int32(cow.LastWeightAt),
-			DailyWeightGain:        float32(cow.GetDayWeight() / 100),
-			AverageDailyWeightGain: float32(cow.GetAverageDailyWeight() / 100),
+			DailyWeightGain:        float32(cow.GetDayWeight() / 1000),
+			AverageDailyWeightGain: float32(cow.GetAverageDailyWeight() / 1000),
+			AdmissionAge:           cow.GetAdmissionAge(),
 		})
 
 		chartsList.CowId = append(chartsList.CowId, int32(cow.Id))
 		chartsList.Weight = append(chartsList.Weight, currentWeight)
-		chartsList.DayAge = append(chartsList.DayAge, cow.GetDayAge())
+		chartsList.AdmissionAge = append(chartsList.AdmissionAge, cow.GetAdmissionAge())
 	}
 
 	// 返回数据

+ 1 - 1
module/backend/cow.go

@@ -234,7 +234,7 @@ func (s *StoreEntry) BehaviorCurve(ctx context.Context, req *pasturePb.CowBehavi
 
 	// 发情数据
 	estrusList := make([]*model.NeckRingEstrus, 0)
-	if err = s.DB.Table(new(model.EventEstrus).TableName()).
+	if err = s.DB.Table(new(model.NeckRingEstrus).TableName()).
 		Where("cow_id = ?", cowInfo.Id).
 		Where("pasture_id = ?", userModel.AppPasture.Id).
 		Where("estrus_start_date >= ?", startDataTime).

+ 83 - 1
module/backend/dashboard.go

@@ -3,7 +3,11 @@ package backend
 import (
 	"context"
 	"kpt-pasture/model"
+	"kpt-pasture/util"
 	"net/http"
+	"strconv"
+	"strings"
+	"time"
 
 	"gitee.com/xuyiping_admin/pkg/logger/zaplog"
 	"go.uber.org/zap"
@@ -45,7 +49,11 @@ func (s *StoreEntry) NeckRingWarning(ctx context.Context) (*pasturePb.IndexNeckR
 	}
 
 	estrusWarningCowList := make([]*model.NeckRingEstrus, 0)
-	estrusWarningLevelItems := make(map[int32]int32)
+	estrusWarningLevelItems := map[int32]int32{
+		int32(pasturePb.EstrusLevel_Low):    0,
+		int32(pasturePb.EstrusLevel_Middle): 0,
+		int32(pasturePb.EstrusLevel_High):   0,
+	}
 	if err = s.DB.Model(new(model.EventEstrus)).
 		Where("pasture_id = ?", userModel.AppPasture.Id).
 		Where("is_show = ?", pasturePb.IsShow_Ok).
@@ -79,3 +87,77 @@ func (s *StoreEntry) NeckRingWarning(ctx context.Context) (*pasturePb.IndexNeckR
 	}, nil
 
 }
+
+func (s *StoreEntry) FocusIndicators(ctx context.Context, dimension string) (*pasturePb.IndexFocusIndicatorsResponse, error) {
+	userModel, err := s.GetUserModel(ctx)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	userFocusIndicators := userModel.SystemUser.IndicatorsKinds
+	if len(userFocusIndicators) <= 0 {
+		userFocusIndicators = "all_cow,output_number,input_number,fatten_cattle_number,sales_volume"
+	}
+	userFocusIndicatorsList := strings.Split(userFocusIndicators, ",")
+	indicatorsDataList := make([]*model.IndicatorsData, 0)
+	pref := s.DB.Model(new(model.IndicatorsData)).
+		Where("pasture_id = ?", userModel.AppPasture.Id).
+		Where("kind in (?)", userFocusIndicatorsList)
+
+	/*if dimension == "Year" {
+		pref.Where("date = ?", time.Now().Format(model.LayoutMonth))
+	}*/
+
+	nowTime := time.Now()
+	if dimension == "Month" {
+		pref.Where("date = ?", nowTime.Format(model.LayoutMonth))
+	}
+
+	if err = pref.Find(&indicatorsDataList).Error; err != nil {
+		zaplog.Error("FocusIndicators", zap.Any("err", err))
+	}
+
+	indicatorsDetailsMap, err := s.GetIndicatorsDetailsMap(ctx)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	data := make([]*pasturePb.FocusData, 0)
+	for _, v := range indicatorsDataList {
+		indicatorsDetails, ok := indicatorsDetailsMap[v.Kind]
+		if !ok {
+			continue
+		}
+		onYear, onMonth := "", ""
+		if dimension == "Year" {
+			return nil, xerr.Custom("暂不支持该维度")
+		}
+
+		if dimension == "Month" {
+			lastMonth := nowTime.AddDate(0, -1, 0).Format(model.LayoutMonth)
+			oldIndicators, _ := s.GetIndicatorsDataByDate(userModel.AppPasture.Id, v.Kind, lastMonth)
+			if oldIndicators != nil {
+				if oldIndicators.Value != "" && oldIndicators.Value != "0" {
+					oldValue, _ := strconv.ParseFloat(oldIndicators.Value, 64)
+					currValue, _ := strconv.ParseFloat(v.Value, 64)
+					onMonthValue := (oldValue - currValue) / oldValue * 100
+					omv := util.RoundToTwoDecimals(onMonthValue)
+					onMonth = strconv.FormatFloat(omv, 'f', 2, 64) + "%"
+				}
+			}
+		}
+		data = append(data, &pasturePb.FocusData{
+			Kind:     indicatorsDetails.Kind,
+			Name:     indicatorsDetails.Name,
+			Value:    v.Value,
+			Describe: indicatorsDetails.Zh,
+			UnitName: indicatorsDetails.Unit,
+			OnMonth:  onMonth,
+			OnYear:   onYear,
+		})
+	}
+	return &pasturePb.IndexFocusIndicatorsResponse{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: data,
+	}, err
+}

+ 25 - 0
module/backend/enum_map.go

@@ -310,3 +310,28 @@ func (s *StoreEntry) UnMatingReasonsMap() map[pasturePb.UnMatingReasons_Kind]str
 	}
 	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,
+		pasturePb.EventType_Transfer_Ben:     pasturePb.EventCategory_Base,
+		pasturePb.EventType_Body_Score:       pasturePb.EventCategory_Base,
+		pasturePb.EventType_Pregnancy_Check:  pasturePb.EventCategory_Base,
+		pasturePb.EventType_Weight:           pasturePb.EventCategory_Base,
+		pasturePb.EventType_Death:            pasturePb.EventCategory_Base,
+		pasturePb.EventType_Transfer_Out:     pasturePb.EventCategory_Base,
+		pasturePb.EventType_Transfer_In:      pasturePb.EventCategory_Base,
+		pasturePb.EventType_Out:              pasturePb.EventCategory_Base,
+		pasturePb.EventType_Estrus:           pasturePb.EventCategory_Breed,
+		pasturePb.EventType_Calving:          pasturePb.EventCategory_Breed,
+		pasturePb.EventType_Seme_Time:        pasturePb.EventCategory_Breed,
+		pasturePb.EventType_Mating:           pasturePb.EventCategory_Breed,
+		pasturePb.EventType_Birth:            pasturePb.EventCategory_Breed,
+		pasturePb.EventType_Immunication:     pasturePb.EventCategory_Health,
+		pasturePb.EventType_Castrated:        pasturePb.EventCategory_Health,
+		pasturePb.EventType_Insect_Repellent: pasturePb.EventCategory_Health,
+		pasturePb.EventType_Weaning:          pasturePb.EventCategory_Breed,
+		pasturePb.EventType_Sale:             pasturePb.EventCategory_Other,
+		pasturePb.EventType_Abort:            pasturePb.EventCategory_Other,
+	}
+}

+ 2 - 2
module/backend/event_base.go

@@ -283,7 +283,7 @@ func (s *StoreEntry) WeightList(ctx context.Context, req *pasturePb.SearchEventR
 	weightList := make([]*pasturePb.SearchWeightList, 0)
 	var count int64 = 0
 	pref := s.DB.Table(fmt.Sprintf("%s as a", new(model.EventWeight).TableName())).
-		Select(`a.id,a.cow_id,a.ear_number,a.weight / 100 as weight,a.lact,a.day_age,a.weight_at,a.remarks,a.created_at,
+		Select(`a.id,a.cow_id,a.ear_number,ROUND(a.weight/1000, 2) as weight,a.lact,a.day_age,a.weight_at,a.remarks,a.created_at,
 		a.updated_at,a.message_id,a.operation_id,a.message_name,a.operation_name`)
 	if len(req.CowId) > 0 {
 		cowIds := strings.Split(req.CowId, ",")
@@ -337,7 +337,7 @@ func (s *StoreEntry) WeightBatch(ctx context.Context, req *pasturePb.BatchEventW
 		}
 
 		// 更新牛只信息
-		cow.EventWeightUpdate(int64(item.Weight), int64(item.WeightAt))
+		cow.EventWeightUpdate(int64(item.Weight*1000), int64(item.WeightAt))
 		if err = s.DB.Model(new(model.Cow)).
 			Select("last_second_weight_at", "last_second_weight", "last_weight_at", "current_weight").
 			Where("id = ?", cow.Id).

+ 22 - 15
module/backend/event_breed.go

@@ -318,9 +318,9 @@ func (s *StoreEntry) EstrusList(ctx context.Context, req *pasturePb.EstrusItemsR
 		return nil, xerr.WithStack(err)
 	}
 
-	estrusList := make([]*model.EventEstrus, 0)
+	estrusList := make([]*model.NeckRingEstrus, 0)
 	var count int64 = 0
-	pref := s.DB.Table(new(model.EventEstrus).TableName()).
+	pref := s.DB.Table(new(model.NeckRingEstrus).TableName()).
 		Where("is_show = ?", pasturePb.IsShow_Ok).
 		Where("pasture_id = ?", userModel.AppPasture.Id).
 		Where("expose_estrus_type = ?", pasturePb.ExposeEstrusType_Natural_Estrus)
@@ -351,7 +351,7 @@ func (s *StoreEntry) EstrusList(ctx context.Context, req *pasturePb.EstrusItemsR
 		Code: http.StatusOK,
 		Msg:  "ok",
 		Data: &pasturePb.EstrusItemsData{
-			List:     model.EstrusSlice(estrusList).ToPB(s.DB, getCowInfoByCowId, getCowLastEvent),
+			List:     model.NeckRingEstrusSlice(estrusList).ToPB(s.DB, getCowInfoByCowId, getCowLastEvent),
 			Total:    int32(count),
 			PageSize: pagination.PageSize,
 			Page:     pagination.Page,
@@ -384,41 +384,48 @@ func (s *StoreEntry) EstrusBatchMating(ctx context.Context, req *pasturePb.Event
 		}
 
 		// 如果存在,并且是脖环揭发则跳过
-		if isExists && existsEventEstrus.ExposeEstrusType == pasturePb.ExposeEstrusType_Neck_Ring {
-			zaplog.Info("EventNaturalEstrusBatch", zap.Any("existsEventEstrus", existsEventEstrus), zap.Any("item", item))
-			continue
+		if isExists {
+			if existsEventEstrus.ExposeEstrusType == pasturePb.ExposeEstrusType_Neck_Ring {
+				zaplog.Info("EventNaturalEstrusBatch", zap.Any("existsEventEstrus", existsEventEstrus), zap.Any("item", item))
+				continue
+			}
+			realityDay := time.Unix(existsEventEstrus.RealityDay, 0).Format(model.LayoutDate2)
+			planDay := time.Unix(int64(item.EstrusAt), 0).Format(model.LayoutDate2)
+			if realityDay == planDay {
+				return xerr.Customf("该牛只:%d,今天已经提交过发情数据", item.CowId)
+			}
 		}
 
 		operationUser, _ := s.GetSystemUserById(ctx, userModel.SystemUser.Id)
+		item.OperationName = operationUser.Name
 		newEventEstrus := model.NewEventEstrus(
 			userModel.AppPasture.Id,
 			cowInfo,
 			pasturePb.ExposeEstrusType_Natural_Estrus,
 			pasturePb.IsShow_Ok,
+			item.IsMating,
+			item.EstrusAt,
 			operationUser,
 			userModel.SystemUser,
 		)
 		if item.IsMating == pasturePb.IsShow_Ok {
-			newEventEstrus.IsMating = pasturePb.IsShow_Ok
 			newEventMating := model.NewEventMating(userModel.AppPasture.Id, cowInfo, time.Now().Unix(), pasturePb.ExposeEstrusType_Natural_Estrus)
 			eventMatingList = append(eventMatingList, newEventMating)
 		} else {
-			newEventEstrus.IsMating = pasturePb.IsShow_No
 			newEventEstrus.UnMatingReasonsKind = item.UnMatingReasonsKind
 			newEventEstrus.UnMatingReasonsName = unMatingReasonsMap[item.UnMatingReasonsKind]
 		}
+		newEventEstrus.Remarks = item.Remarks
 		eventEstrusList = append(eventEstrusList, newEventEstrus)
 	}
 
 	// 记录事件日志
 	defer func() {
-		if err == nil {
-			if len(eventEstrusList) > 0 {
-				for _, v := range eventEstrusList {
-					cow, _ := s.GetCowInfoByCowId(ctx, userModel.AppPasture.Id, v.CowId)
-					cowLogs := s.SubmitEventLog(ctx, userModel.AppPasture.Id, cow, pasturePb.EventType_Estrus, v.ExposeEstrusType, v)
-					s.DB.Table(cowLogs.TableName()).Create(cowLogs)
-				}
+		if err == nil && len(eventEstrusList) > 0 {
+			for _, v := range eventEstrusList {
+				cow, _ := s.GetCowInfoByCowId(ctx, userModel.AppPasture.Id, v.CowId)
+				cowLogs := s.SubmitEventLog(ctx, userModel.AppPasture.Id, cow, pasturePb.EventType_Estrus, v.ExposeEstrusType, v)
+				s.DB.Table(cowLogs.TableName()).Create(cowLogs)
 			}
 		}
 	}()

+ 11 - 32
module/backend/event_cow_log.go

@@ -21,13 +21,13 @@ func (s *StoreEntry) SubmitEventLog(
 	req interface{},
 ) *model.EventCowLog {
 	var (
-		desc, remarks   = "", ""
-		eventTypeName   = s.EventTypeMap()[eventType]
-		eventAt         = int64(0)
-		eventCategoryId = pasturePb.EventCategory_Invalid
-		penMap          = s.PenMap(ctx, pastureId)
-		cowTypeMap      = s.CowTypeMap()
-		operationUser   = &model.SystemUser{}
+		desc, remarks    = "", ""
+		eventTypeName    = s.EventTypeMap()[eventType]
+		eventAt          = int64(0)
+		eventCategoryMap = s.eventCategoryMap()
+		penMap           = s.PenMap(ctx, pastureId)
+		cowTypeMap       = s.CowTypeMap()
+		operationUser    = &model.SystemUser{}
 	)
 
 	switch eventType {
@@ -35,7 +35,6 @@ func (s *StoreEntry) SubmitEventLog(
 		data := req.(*pasturePb.EventEnterRequest)
 		eventAt = int64(data.EnterAt)
 		remarks = data.Remarks
-		eventCategoryId = pasturePb.EventCategory_Base
 		sourceMap := s.CowSourceMap()
 		sex := "公"
 		if data.Sex == pasturePb.Genders_Female {
@@ -56,10 +55,8 @@ func (s *StoreEntry) SubmitEventLog(
 		remarks = data.Remarks
 		operationUser.Id = data.OperationId
 		operationUser.Name = data.OperationName
-		eventCategoryId = pasturePb.EventCategory_Base
 		desc = fmt.Sprintf("转出栏舍: %s; 转入栏舍: %s", penMap[data.PenOutId].Name, penMap[data.PenInId].Name)
 	case pasturePb.EventType_Body_Score:
-		eventCategoryId = pasturePb.EventCategory_Other
 	case pasturePb.EventType_Pregnancy_Check:
 		data := req.(*pasturePb.EventPregnantCheckBatch)
 		for _, v := range data.Item {
@@ -67,7 +64,6 @@ func (s *StoreEntry) SubmitEventLog(
 				continue
 			}
 			eventAt = int64(v.PregnantCheckAt)
-			eventCategoryId = pasturePb.EventCategory_Breed
 			if v.PregnantCheckResult == pasturePb.PregnantCheckResult_Pregnant {
 				desc += fmt.Sprintf("孕检方式: %s; 孕检结果: %s", s.PregnantCheckMethodMap()[v.PregnantCheckMethod], s.MatingResultMap()[pasturePb.MatingResult_Pregnant])
 			}
@@ -85,9 +81,8 @@ func (s *StoreEntry) SubmitEventLog(
 			remarks = v.Remarks
 		}
 	case pasturePb.EventType_Estrus:
-		eventType = pasturePb.EventType_Mating // 发情配种批量提交
-		data := req.(*pasturePb.EventNaturalEstrusItems)
-		eventAt = int64(data.EstrusAt)
+		data := req.(*model.EventEstrus)
+		eventAt = data.PlanDay
 		isMating := "是"
 		if data.IsMating == pasturePb.IsShow_Ok {
 			isMating = "否"
@@ -96,7 +91,6 @@ func (s *StoreEntry) SubmitEventLog(
 		operationUser.Id = int64(data.OperationId)
 		operationUser.Name = data.OperationName
 		remarks = data.Remarks
-		eventCategoryId = pasturePb.EventCategory_Breed
 	case pasturePb.EventType_Calving:
 		data := req.(*pasturePb.EventCalving)
 		eventAt = int64(data.CalvingAt)
@@ -112,7 +106,6 @@ func (s *StoreEntry) SubmitEventLog(
 		operationUser.Id = int64(data.OperationId)
 		operationUser.Name = data.OperationName
 		remarks = data.Remarks
-		eventCategoryId = pasturePb.EventCategory_Breed
 	case pasturePb.EventType_Seme_Time:
 		data := req.(*pasturePb.EventSameTime)
 		eventAt = int64(data.SameTimeAt)
@@ -120,7 +113,6 @@ func (s *StoreEntry) SubmitEventLog(
 		operationUser.Id = int64(data.OperationId)
 		operationUser.Name = data.OperationName
 		remarks = data.Remarks
-		eventCategoryId = pasturePb.EventCategory_Breed
 	case pasturePb.EventType_Mating:
 		data := req.(*pasturePb.EventMating)
 		eventAt = int64(data.MatingAt)
@@ -128,31 +120,24 @@ func (s *StoreEntry) SubmitEventLog(
 		operationUser.Id = int64(data.OperationId)
 		operationUser.Name = data.OperationName
 		remarks = data.Remarks
-		eventCategoryId = pasturePb.EventCategory_Breed
 	case pasturePb.EventType_Birth:
 		eventAt = cow.BirthAt
-		desc = fmt.Sprintf("出生体重: %fKG;母号:%s;父号: %s", float32(cow.BirthWeight)/100, cow.MotherNumber, cow.LastBullNumber)
-		eventCategoryId = pasturePb.EventCategory_Breed
+		desc = fmt.Sprintf("出生体重: %fKG;母号:%s;父号: %s", float32(cow.BirthWeight)/1000, cow.MotherNumber, cow.LastBullNumber)
 	case pasturePb.EventType_Death:
 		data := req.(*model.EventDeparture)
 		eventAt = data.DepartureAt
 		desc = fmt.Sprintf("死亡原因: %s", data.ReasonName)
 		operationUser.Id = data.OperationId
 		operationUser.Name = data.OperationName
-		eventCategoryId = pasturePb.EventCategory_Base
 	case pasturePb.EventType_Transfer_Out:
-		eventCategoryId = pasturePb.EventCategory_Base
 	case pasturePb.EventType_Transfer_In:
-		eventCategoryId = pasturePb.EventCategory_Base
 	case pasturePb.EventType_Out:
 		data := req.(*model.EventDeparture)
 		eventAt = data.DepartureAt
 		desc = fmt.Sprintf("淘汰原因: %s", data.ReasonName)
 		operationUser.Id = data.OperationId
 		operationUser.Name = data.OperationName
-		eventCategoryId = pasturePb.EventCategory_Base
 	case pasturePb.EventType_Immunication:
-		eventCategoryId = pasturePb.EventCategory_Health
 	case pasturePb.EventType_Weaning:
 		data := req.(*pasturePb.EventWeaningBatchRequest)
 		eventAt = int64(data.WeaningAt)
@@ -165,9 +150,7 @@ func (s *StoreEntry) SubmitEventLog(
 				break
 			}
 		}
-		eventCategoryId = pasturePb.EventCategory_Other
 	case pasturePb.EventType_Sale:
-		eventCategoryId = pasturePb.EventCategory_Other
 	case pasturePb.EventType_Abort:
 		data := req.(*pasturePb.EventAbortionRequest)
 		eventAt = int64(data.AbortionAt)
@@ -175,7 +158,6 @@ func (s *StoreEntry) SubmitEventLog(
 		operationUser.Name = data.OperationName
 		remarks = data.Remarks
 		desc = fmt.Sprintf("流产原因: %s", s.AbortionReasonsMap()[data.AbortionReasons])
-		eventCategoryId = pasturePb.EventCategory_Base
 	case pasturePb.EventType_Weight:
 		data := req.(*pasturePb.EventWeight)
 		eventAt = int64(data.WeightAt)
@@ -183,17 +165,14 @@ func (s *StoreEntry) SubmitEventLog(
 		operationUser.Name = data.OperationName
 		remarks = data.Remarks
 		desc = fmt.Sprintf("日龄: %d;具体体重: %f kg", cow.DayAge, data.Weight)
-		eventCategoryId = pasturePb.EventCategory_Base
 	case pasturePb.EventType_Castrated:
-		eventCategoryId = pasturePb.EventCategory_Health
 	case pasturePb.EventType_Insect_Repellent:
-		eventCategoryId = pasturePb.EventCategory_Health
 	}
 	newEventCowLogModel := &model.EventCowLogModel{
 		Cow:               cow,
 		CowTypeName:       cowTypeMap[cow.CowType],
 		OperationUser:     operationUser,
-		EventCategoryKind: eventCategoryId,
+		EventCategoryKind: eventCategoryMap[eventType],
 		EventAt:           eventAt,
 		EventType:         eventType,
 		EventTypeName:     eventTypeName,

+ 13 - 5
module/backend/goods.go

@@ -143,13 +143,13 @@ func (s *StoreEntry) NeckRingCreateOrUpdate(ctx context.Context, req *pasturePb.
 			switch req.Status {
 			// 绑定
 			case pasturePb.NeckRingOperationStatus_Bind:
-				_, err = s.GetCowInfoByCowId(ctx, userModel.AppPasture.Id, int64(v.CowId))
+				cowInfo, err := s.GetCowInfoByCowId(ctx, userModel.AppPasture.Id, int64(v.CowId))
 				if err != nil {
 					return xerr.Customf("该牛不存在")
 				}
 				neckRing, ok := s.NeckRingIsExist(ctx, userModel.AppPasture.Id, v.Number)
 				if !ok {
-					newNeckRing := model.NewNeckRing(userModel.AppPasture.Id, v.Number, int64(v.CowId), userModel.SystemUser)
+					newNeckRing := model.NewNeckRing(userModel.AppPasture.Id, cowInfo, userModel.SystemUser)
 					if err = tx.Create(newNeckRing).Error; err != nil {
 						return xerr.WithStack(err)
 					}
@@ -164,7 +164,7 @@ func (s *StoreEntry) NeckRingCreateOrUpdate(ctx context.Context, req *pasturePb.
 					}
 				}
 
-				newNeckRingLog := model.NewNeckRingBindLog(userModel.AppPasture.Id, v.Number, int64(v.CowId), userModel.SystemUser)
+				newNeckRingLog := model.NewNeckRingBindLog(userModel.AppPasture.Id, cowInfo, userModel.SystemUser)
 				if err = tx.Create(newNeckRingLog).Error; err != nil {
 					return xerr.WithStack(err)
 				}
@@ -201,9 +201,17 @@ func (s *StoreEntry) NeckRingCreateOrUpdate(ctx context.Context, req *pasturePb.
 				}
 				// 编辑
 			case pasturePb.NeckRingOperationStatus_Edit:
+				cowInfo, err := s.GetCowInfoByCowId(ctx, userModel.AppPasture.Id, int64(v.CowId))
+				if err != nil {
+					return xerr.Customf("该牛不存在")
+				}
 				if err = tx.Model(new(model.NeckRing)).
-					Where("cow_id = ?", v.CowId).
-					Update("neck_ring_number", v.Number).Error; err != nil {
+					Where("neck_ring_number = ?", v.Number).
+					Updates(map[string]interface{}{
+						"cow_id":     cowInfo.Id,
+						"ear_number": cowInfo.EarNumber,
+						"status":     pasturePb.NeckRingStatus_Bind,
+					}).Error; err != nil {
 					return xerr.WithStack(err)
 				}
 

+ 24 - 5
module/backend/indicators.go

@@ -31,6 +31,18 @@ func (s *StoreEntry) IndicatorsComparison(ctx context.Context, req *pasturePb.In
 		return nil, xerr.WithStack(err)
 	}
 
+	indicatorsDetailsMap, err := s.GetIndicatorsDetailsMap(ctx)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	return &model.IndicatorsComparisonResponse{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: model.IndicatorsDataSlice(indicatorsDataList).ToPB(indicatorsDetailsMap),
+	}, nil
+}
+
+func (s *StoreEntry) GetIndicatorsDetailsMap(ctx context.Context) (map[string]*model.IndicatorsDetails, error) {
 	indicatorsDetails := make(map[string]*model.IndicatorsDetails)
 	indicatorsDetailsList, err := s.FindIndicatorsDetailsList(ctx)
 	if err != nil {
@@ -39,10 +51,17 @@ func (s *StoreEntry) IndicatorsComparison(ctx context.Context, req *pasturePb.In
 	for _, v := range indicatorsDetailsList {
 		indicatorsDetails[v.Kind] = v
 	}
+	return indicatorsDetails, err
+}
 
-	return &model.IndicatorsComparisonResponse{
-		Code: http.StatusOK,
-		Msg:  "ok",
-		Data: model.IndicatorsDataSlice(indicatorsDataList).ToPB(indicatorsDetails),
-	}, nil
+func (s *StoreEntry) GetIndicatorsDataByDate(pastureId int64, Kind, date string) (*model.IndicatorsData, error) {
+	res := &model.IndicatorsData{}
+	if err := s.DB.Model(new(model.IndicatorsData)).
+		Where("pasture_id = ?", pastureId).
+		Where("kind = ?", Kind).
+		Where("date = ?", date).
+		First(&res).Error; err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	return res, nil
 }

+ 3 - 1
module/backend/interface.go

@@ -243,7 +243,7 @@ type GoodsService interface {
 
 //go:generate mockgen -destination mock/AnalyseService.go -package kptservicemock kpt-pasture/module/backend AnalyseService
 type AnalyseService interface {
-	GrowthCurve(ctx context.Context, req *pasturePb.SearchGrowthCurvesRequest) (*pasturePb.GrowthCurvesResponse, error)
+	WeightScatterPlot(ctx context.Context, req *pasturePb.SearchGrowthCurvesRequest) (*pasturePb.GrowthCurvesResponse, error)
 	WeightRange(ctx context.Context, req *pasturePb.WeightRangeRequest) (*pasturePb.WeightRangeResponse, error)
 	MatingTimely(ctx context.Context, req *pasturePb.MatingTimelyRequest) (*model.MatingTimelyResponse, error)
 	PenWeight(ctx context.Context, req *pasturePb.PenWeightRequest, pagination *pasturePb.PaginationModel) (*pasturePb.PenWeightResponse, error)
@@ -261,6 +261,7 @@ type AnalyseService interface {
 type DashboardService interface {
 	Bar(ctx context.Context) (*pasturePb.BarCowStructResponse, error)
 	NeckRingWarning(ctx context.Context) (*pasturePb.IndexNeckRingResponse, error)
+	FocusIndicators(ctx context.Context, dimension string) (*pasturePb.IndexFocusIndicatorsResponse, error)
 }
 
 //go:generate mockgen -destination mock/WorkService.go -package kptservicemock kpt-pasture/module/backend WorkService
@@ -288,5 +289,6 @@ type WorkService interface {
 
 type TestService interface {
 	CowNeckRingNumberBound(ctx context.Context, pagination *pasturePb.PaginationModel) error
+	CowNeckRingNumberBound2(ctx context.Context, pagination *pasturePb.PaginationModel) error
 	UpdateCowPen(ctx context.Context, pagination *pasturePb.PaginationModel) error
 }

+ 1 - 1
module/backend/system_service.go

@@ -28,7 +28,7 @@ const (
 func (s *StoreEntry) Login(ctx context.Context, req *pasturePb.SearchUserRequest) (*pasturePb.SystemUserResponse, error) {
 	systemUser := &model.SystemUser{}
 	if err := s.DB.Where("name = ?", req.Name).
-		Find(systemUser).Error; err != nil {
+		First(systemUser).Error; err != nil {
 		return nil, xerr.WithStack(err)
 	}
 

+ 31 - 27
module/backend/test_service.go

@@ -2,8 +2,11 @@ package backend
 
 import (
 	"context"
+	"errors"
 	"kpt-pasture/model"
 
+	"gorm.io/gorm"
+
 	pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
 
 	"gitee.com/xuyiping_admin/pkg/logger/zaplog"
@@ -36,10 +39,10 @@ func (s *StoreEntry) CowNeckRingNumberBound(ctx context.Context, pagination *pas
 
 	newNeckRingList := make([]*model.NeckRing, 0)
 	newNeckRingBindLogList := make([]*model.NeckRingBindLog, 0)
-	for _, v := range cowList {
-		newNeckRing := model.NewNeckRing(userModel.AppPasture.Id, v.NeckRingNumber, v.Id, userModel.SystemUser)
+	for _, cow := range cowList {
+		newNeckRing := model.NewNeckRing(userModel.AppPasture.Id, cow, userModel.SystemUser)
 		newNeckRingList = append(newNeckRingList, newNeckRing)
-		newNeckRingBindLog := model.NewNeckRingBindLog(userModel.AppPasture.Id, v.NeckRingNumber, v.Id, userModel.SystemUser)
+		newNeckRingBindLog := model.NewNeckRingBindLog(userModel.AppPasture.Id, cow, userModel.SystemUser)
 		newNeckRingBindLogList = append(newNeckRingBindLogList, newNeckRingBindLog)
 	}
 
@@ -61,13 +64,6 @@ func (s *StoreEntry) CowNeckRingNumberBound2(ctx context.Context, pagination *pa
 		return xerr.WithStack(err)
 	}
 
-	lastNeckRing := &model.NeckRing{}
-	if err = s.DB.Model(new(model.NeckRing)).
-		Order("cow_id desc").
-		First(lastNeckRing).Error; err != nil {
-		return xerr.WithStack(err)
-	}
-
 	cowList := make([]*model.Cow, 0)
 	if err = s.DB.Model(new(model.Cow)).
 		Where("neck_ring_number != ?", "").
@@ -77,23 +73,31 @@ func (s *StoreEntry) CowNeckRingNumberBound2(ctx context.Context, pagination *pa
 		return xerr.WithStack(err)
 	}
 
-	newNeckRingList := make([]*model.NeckRing, 0)
-	newNeckRingBindLogList := make([]*model.NeckRingBindLog, 0)
-	for _, v := range cowList {
-		newNeckRing := model.NewNeckRing(userModel.AppPasture.Id, v.NeckRingNumber, v.Id, userModel.SystemUser)
-		newNeckRingList = append(newNeckRingList, newNeckRing)
-		newNeckRingBindLog := model.NewNeckRingBindLog(userModel.AppPasture.Id, v.NeckRingNumber, v.Id, userModel.SystemUser)
-		newNeckRingBindLogList = append(newNeckRingBindLogList, newNeckRingBindLog)
-	}
-
-	if err = s.DB.Model(new(model.NeckRing)).
-		Create(newNeckRingList).Error; err != nil {
-		zaplog.Error("CowNeckRingNumberBound-NewNeckRing", zap.Any("error", err))
-	}
-
-	if err = s.DB.Model(new(model.NeckRingBindLog)).
-		Create(newNeckRingBindLogList).Error; err != nil {
-		zaplog.Error("CowNeckRingNumberBound-NeckRingBindLog", zap.Any("error", err))
+	for _, cow := range cowList {
+		newNeckRing := model.NewNeckRing(userModel.AppPasture.Id, cow, userModel.SystemUser)
+		oldNeckRing := &model.NeckRing{}
+		if err = s.DB.Model(new(model.NeckRing)).
+			Where("pasture_id = ?", userModel.AppPasture.Id).
+			Where("neck_ring_number = ?", cow.NeckRingNumber).
+			First(oldNeckRing).Error; err != nil {
+			if errors.Is(err, gorm.ErrRecordNotFound) {
+				if err = s.DB.Model(new(model.NeckRing)).Create(newNeckRing).Error; err != nil {
+					zaplog.Error("CowNeckRingNumberBound2-NewNeckRing", zap.Any("error", err))
+				}
+			} else {
+				continue
+			}
+		}
+		if oldNeckRing.Id > 0 {
+			if err = s.DB.Model(new(model.NeckRing)).
+				Where("id = ?", oldNeckRing.Id).Updates(map[string]interface{}{
+				"cow_id":     cow.Id,
+				"ear_number": cow.EarNumber,
+				"status":     pasturePb.NeckRingStatus_Bind,
+			}).Error; err != nil {
+				zaplog.Error("CowNeckRingNumberBound2-OldNeckRing", zap.Any("error", err))
+			}
+		}
 	}
 	return nil
 }

+ 2 - 2
module/crontab/neck_ring_estrus.go

@@ -56,7 +56,7 @@ func (e *Entry) EntryCowEstrus(pastureId int64) (err error) {
 	}
 
 	// 将历史发情预警数据更新为已过期
-	if err = e.DB.Model(new(model.EventEstrus)).
+	if err = e.DB.Model(new(model.NeckRingEstrus)).
 		Where("estrus_start_date <= ?", time.Now().AddDate(0, 0, -4).Format(model.LayoutTime)).
 		Update("is_show", pasturePb.IsShow_No).Error; err != nil {
 		zaplog.Error("EntryCowEstrus", zap.Any("UpdateEventEstrus", err))
@@ -241,7 +241,7 @@ func calculateLevel(cft float32, cowEstrus *CowEstrus, xToday *XToday) pasturePb
 }
 
 // getResult 根据b3数据计算结果 0 1 2 3 -1 -2
-func getResult(b3 *model.EventEstrus, cft float32, cowEstrus *CowEstrus) pasturePb.CheckResult_Kind {
+func getResult(b3 *model.NeckRingEstrus, cft float32, cowEstrus *CowEstrus) pasturePb.CheckResult_Kind {
 	result := pasturePb.CheckResult_Invalid
 	if b3.CheckResult == pasturePb.CheckResult_Fail && b3.DayHigh > int32(cft)+cowEstrus.HadJust {
 		result = pasturePb.CheckResult_Fail

+ 6 - 6
module/crontab/sql.go

@@ -48,17 +48,17 @@ func (e *Entry) GetPenMapList() (map[int32]*model.Pen, error) {
 }
 
 // GetBeforeThreeDaysCowEstrus 获取值得时间之前三天内最大发情记录
-func (e *Entry) GetBeforeThreeDaysCowEstrus(cowId int64, activeTime string) *model.EventEstrus {
-	eventEstrus := &model.EventEstrus{}
-	if err := e.DB.Model(new(model.EventEstrus)).
+func (e *Entry) GetBeforeThreeDaysCowEstrus(cowId int64, activeTime string) *model.NeckRingEstrus {
+	neckRingEstrus := &model.NeckRingEstrus{}
+	if err := e.DB.Model(new(model.NeckRingEstrus)).
 		Select("MAX(max_high) as max_high,cow_id,MAX(day_high) as day_high").
 		Select("MAX(IF(check_result=1,3,check_result)) AS check_result").
 		Where("cow_id = ?", cowId).
 		Where("active_date >= ?", activeTime).
-		First(eventEstrus).Error; err != nil {
-		return eventEstrus
+		First(neckRingEstrus).Error; err != nil {
+		return neckRingEstrus
 	}
-	return eventEstrus
+	return neckRingEstrus
 }
 
 // GetTwoEstrus 判断最近50天内是否存在发情记录(发情等级>=2),如果18~25天@xadjust21,如果36~50天@xadjust42

+ 4 - 6
util/util_test.go

@@ -507,10 +507,8 @@ func TestGetNeckRingActiveTimer(t *testing.T) {
 }
 
 func Test_demo(t *testing.T) {
-
-	a := make(map[string]int32)
-	a["1"] = 110
-	for pastureId, value := range a {
-		fmt.Println(pastureId, value)
-	}
+	oldValue := float64(850)
+	currValue := float64(364)
+	a := RoundToTwoDecimals((oldValue - currValue) / oldValue * 100)
+	fmt.Println(a)
 }