Ver Fonte

cow: behaviorRate 行为占比

Yi há 1 semana atrás
pai
commit
391954dc15

+ 1 - 1
go.mod

@@ -3,7 +3,7 @@ module kpt-pasture
 go 1.17
 
 require (
-	gitee.com/xuyiping_admin/go_proto v0.0.0-20250408075038-dd76bfdd8a73
+	gitee.com/xuyiping_admin/go_proto v0.0.0-20250409095220-1fc7ef026108
 	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

@@ -146,6 +146,18 @@ gitee.com/xuyiping_admin/go_proto v0.0.0-20250408072509-1117ad78b23f h1:VWi6G/NW
 gitee.com/xuyiping_admin/go_proto v0.0.0-20250408072509-1117ad78b23f/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
 gitee.com/xuyiping_admin/go_proto v0.0.0-20250408075038-dd76bfdd8a73 h1:QNo+OSvJtuCgxvYreVcqZJXHFafnaD3/NTzzQbWNigo=
 gitee.com/xuyiping_admin/go_proto v0.0.0-20250408075038-dd76bfdd8a73/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250409012212-55a38ade5fbe h1:dUQ3PYq07bX0q8E3ZF/sq6TdS12IfE4ogLzCmK1Im5U=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250409012212-55a38ade5fbe/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250409054106-c044078978f9 h1:HjAO7MAZz/Vu3dRE6GI7kxx31RBHH0lQ5WXaWvtwz3k=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250409054106-c044078978f9/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250409060243-3b624c8d5ece h1:tHo+IVad4bQ9eQDSdVEGfQZ3hbGr3w6c/vi4ibQzt08=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250409060243-3b624c8d5ece/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250409093335-d8b013bd5bab h1:9nfap+BBJeP17KlVGViBUXDf1u84YKGN1Qliz/2VvBo=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250409093335-d8b013bd5bab/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250409093847-cb281b5a01b2 h1:KPl+ZJuQKgGQfgxfJn/RtX1+YwzFI5x0iEDeAvHV6Cg=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250409093847-cb281b5a01b2/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250409095220-1fc7ef026108 h1:Sz/paz5RJjGg2C50xE3xuFl2aVkQs31zRstVep+xejQ=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250409095220-1fc7ef026108/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=

+ 24 - 0
http/handler/cow/cow.go

@@ -149,6 +149,30 @@ func LactCurve(c *gin.Context) {
 	ginutil.JSONResp(c, res)
 }
 
+func BehaviorRate(c *gin.Context) {
+	var req pasturePb.CowBehaviorRateRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.CowId, valid.Required),
+		valid.Field(&req.StartTime, valid.Required),
+		valid.Field(&req.EndTime, valid.Required),
+	); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	res, err := middleware.Dependency(c).StoreEventHub.OpsService.BehaviorRate(c, &req)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	ginutil.JSONResp(c, res)
+}
+
 // IndicatorsComparison 指标对比
 func IndicatorsComparison(c *gin.Context) {
 	var req pasturePb.IndicatorsComparisonRequest

+ 1 - 0
http/route/cow_api.go

@@ -19,6 +19,7 @@ func CowAPI(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
 		cowRoute.POST("/behavior/curve", cow.BehaviorCurve)
 		cowRoute.POST("/growth/curve", cow.GrowthCurve)
 		cowRoute.POST("/lact/curve", cow.LactCurve)
+		cowRoute.POST("/behavior/rate", cow.BehaviorRate)
 
 		searchRoute := authRouteGroup(s, "/api/v1/search/")
 		searchRoute.POST("/indicators/comparison", cow.IndicatorsComparison)

+ 2 - 7
model/cow.go

@@ -204,7 +204,6 @@ func (c *Cow) UnForbiddenMatingUpdate() {
 type CowSlice []*Cow
 
 func (c CowSlice) ToPB(
-	penMap map[int32]*Pen,
 	cowTypeMap map[pasturePb.CowType_Kind]string,
 	breedStatusMap map[pasturePb.BreedStatus_Kind]string,
 	cowKindMap map[pasturePb.CowKind_Kind]string,
@@ -214,11 +213,6 @@ func (c CowSlice) ToPB(
 ) []*pasturePb.CowDetails {
 	res := make([]*pasturePb.CowDetails, len(c))
 	for i, v := range c {
-		penName := ""
-		if pen, ok := penMap[v.PenId]; ok {
-			penName = pen.Name
-		}
-
 		sex := "公"
 		if v.Sex == pasturePb.Genders_Female {
 			sex = "母"
@@ -284,7 +278,7 @@ func (c CowSlice) ToPB(
 			CowId:                     int32(v.Id),
 			Sex:                       sex,
 			NeckRingNumber:            v.NeckRingNumber,
-			PenName:                   penName,
+			PenName:                   v.PenName,
 			Lact:                      v.Lact,
 			CowTypeName:               cowTypeMap[v.CowType],
 			CowType:                   v.CowType,
@@ -296,6 +290,7 @@ func (c CowSlice) ToPB(
 			CurrentWeight:             float32(v.CurrentWeight) / 1000,
 			CurrentHeight:             int32(v.CurrentHeight),
 			DayAge:                    v.DayAge,
+			AdmissionAge:              v.AdmissionAge,
 			SourceName:                cowSourceMap[v.SourceKind],
 			MotherNumber:              v.MotherNumber,
 			FatherNumber:              v.FatherNumber,

+ 1 - 0
model/event_sale.go

@@ -97,6 +97,7 @@ func (e EventSaleSlice) ToPB(eventSaleCarMap map[int64][]*EventSaleCar, eventSal
 			SaleTicket:       strings.Split(v.SaleTicker, ","),
 			QuarantineReport: strings.Split(v.QuarantineReport, ","),
 			SaleVehicleItems: saleVehicleItems,
+			SaleCount:        int32(len(eventSaleCowList)),
 		}
 	}
 	return res

+ 31 - 6
model/milk_original.go

@@ -15,9 +15,9 @@ type MilkOriginal struct {
 	InitialTime      string  `json:"initialTime"`
 	AttachTime       string  `json:"attachTime"`
 	AttachAdjustTime string  `json:"attachAdjustTime"`
-	DetacherTime     string  `json:"detacherTime"`
+	DetachedTime     string  `json:"detachedTime"`
 	EndTime          string  `json:"endTime"`
-	DetacherAddress  int64   `json:"detacherAddress"`
+	DetachedAddress  int64   `json:"detachedAddress"`
 	Conductivity     int32   `json:"conductivity"`
 	CowActivity      int32   `json:"cowActivity"`
 	Source           int8    `json:"source"`
@@ -73,9 +73,9 @@ func NewAFIMilkOriginal(pastureId int64, milkHallNumber string, req *AFIMilkHall
 		InitialTime:      "",
 		AttachTime:       "",
 		AttachAdjustTime: "",
-		DetacherTime:     "",
+		DetachedTime:     "",
 		EndTime:          "",
-		DetacherAddress:  req.StallNumber,
+		DetachedAddress:  req.StallNumber,
 		Conductivity:     req.Amt1,
 		CowActivity:      0,
 		Source:           0,
@@ -123,9 +123,9 @@ func NewGEAMilkOriginal(
 		InitialTime:      "",
 		AttachTime:       attachTime,
 		AttachAdjustTime: "",
-		DetacherTime:     detachTime,
+		DetachedTime:     detachTime,
 		EndTime:          "",
-		DetacherAddress:  detacherAddress,
+		DetachedAddress:  detacherAddress,
 		Conductivity:     conductivity,
 		CowActivity:      0,
 		Source:           0,
@@ -154,3 +154,28 @@ func NewGEAMilkOriginal(
 		Flow60to120:      f60t120,
 	}
 }
+
+type MinMilkOriginalRecords struct {
+	MilkDate        string
+	Shifts          string
+	DetachedAddress string
+	CowId           int64
+	MinId           int64
+	Count           int64
+	MinAttachTime   string
+}
+
+type BaseRecords struct {
+	Id              int64
+	AttachAdjust    string
+	DetachedAddress int
+	Shifts          int
+	EarNumber       string
+	MilkDate        string
+	RecognitionTime string
+}
+
+type UpdateLoadRecord struct {
+	Id   int64
+	Load int32
+}

+ 10 - 0
model/neck_active_habit.go

@@ -219,6 +219,16 @@ func (n NeckActiveHabitSlice) ToPB(curveName string) *CowBehaviorCurveData {
 	return res
 }
 
+func (n NeckActiveHabitSlice) ToPB2(dataBetween []string) *pasturePb.CowBehaviorRateData {
+	return &pasturePb.CowBehaviorRateData{
+		DateTime:     dataBetween,
+		RuminaRate:   make([]float32, 0),
+		IntakeRate:   make([]float32, 0),
+		InactiveRate: make([]float32, 0),
+		OtherRate:    make([]float32, 0),
+	}
+}
+
 type MaxHabitIdModel struct {
 	Id int64 `json:"id"`
 }

+ 7 - 4
module/backend/analysis.go

@@ -29,11 +29,14 @@ func (s *StoreEntry) WeightScatterPlot(ctx context.Context, req *pasturePb.Searc
 		pref.Where("ear_number = ?", req.EarNumber)
 	}
 
-	if len(req.BirthDate) == 2 && req.BirthDate[0] != "" && req.BirthDate[1] != "" {
-		t0, _ := util.TimeParseLocal(model.LayoutDate2, req.BirthDate[0])
-		t1, _ := util.TimeParseLocal(model.LayoutDate2, req.BirthDate[1])
+	if len(req.PenIds) > 0 {
+		pref.Where("pen_id IN (?)", req.PenIds)
+	}
 
-		pref.Where("birth_at BETWEEN ? AND ?", t0.Unix(), t1.Unix()+86399)
+	if len(req.AdmissionDate) == 2 {
+		t0, _ := util.TimeParseLocal(model.LayoutDate2, req.AdmissionDate[0])
+		t1, _ := util.TimeParseLocal(model.LayoutDate2, req.AdmissionDate[1])
+		pref.Where("admission_at BETWEEN ? AND ?", t0.Unix(), t1.Unix()+86399)
 	}
 
 	var count int64

+ 20 - 11
module/backend/calendar.go

@@ -250,9 +250,10 @@ func (s *StoreEntry) ImmunisationCowList(ctx context.Context, req *pasturePb.Ite
 		Code: http.StatusOK,
 		Msg:  "ok",
 		Data: &pasturePb.ImmunizationItemsData{
-			Total:    int32(count),
-			Page:     pagination.Page,
-			PageSize: pagination.PageSize,
+			Total:      int32(count),
+			Page:       pagination.Page,
+			PageSize:   pagination.PageSize,
+			HeaderSort: []string{"id", "cowId", "planDay", "planName", "penName", "dayAge", "earNumber", "planId"},
 			Header: map[string]string{
 				"id":        "编号",
 				"cowId":     "牛号",
@@ -317,6 +318,9 @@ func (s *StoreEntry) SameTimeCowList(ctx context.Context, req *pasturePb.ItemsRe
 			Total:    int32(count),
 			Page:     pagination.Page,
 			PageSize: pagination.PageSize,
+			HeaderSort: []string{"id", "cowId", "earNumber", "breedStatusName", "cowTypeName", "planDayAtFormat", "penName",
+				"lact", "calvingAge", "abortionAge", "dayAge", "status", "sameTimeTypeName", "matingTimes", "calvingAtFormat",
+				"abortionAtFormat", "sameTimeName"},
 			Header: map[string]string{
 				"id":               "编号",
 				"cowId":            "牛号",
@@ -393,6 +397,8 @@ func (s *StoreEntry) PregnancyCheckCowList(ctx context.Context, req *pasturePb.I
 			Total:    int32(count),
 			Page:     pagination.Page,
 			PageSize: pagination.PageSize,
+			HeaderSort: []string{"id", "cowId", "earNumber", "cowTypeName", "penName", "lact", "dayAge", "planDay",
+				"checkTypeName", "status", "matingTimes", "calvingAtFormat", "matingAtFormat", "matingAge", "bullId", "pregnancyAge"},
 			Header: map[string]string{
 				"id":              "编号",
 				"cowId":           "牛号",
@@ -425,8 +431,9 @@ func (s *StoreEntry) WeaningCowList(ctx context.Context, req *pasturePb.ItemsReq
 	weaningItems := make([]*pasturePb.WeaningItems, 0)
 	count := int64(0)
 	pref := s.DB.Table(fmt.Sprintf("%s as a", new(model.EventWeaning).TableName())).
-		Select(`a.id,a.cow_id,ROUND(b.current_weight / 1000,2) as current_weight,DATE_FORMAT(FROM_UNIXTIME(a.plan_day), '%Y-%m-%d') AS plan_day_format,
-			b.day_age,b.pen_name,b.ear_number,DATE_FORMAT(FROM_UNIXTIME(b.birth_at), '%Y-%m-%d') AS birth_at_format`).
+		Select(`a.id,a.cow_id,ROUND(b.current_weight / 1000,2) as current_weight,
+		DATE_FORMAT(FROM_UNIXTIME(a.plan_day), '%Y-%m-%d') AS plan_day_format,b.day_age,b.pen_name,
+		b.ear_number,DATE_FORMAT(FROM_UNIXTIME(b.birth_at), '%Y-%m-%d') AS birth_at_format`).
 		Joins("left join cow as b on a.cow_id = b.id").
 		Where("b.admission_status = ?", pasturePb.AdmissionStatus_Admission).
 		Where("a.status = ?", pasturePb.IsShow_No).
@@ -448,9 +455,10 @@ func (s *StoreEntry) WeaningCowList(ctx context.Context, req *pasturePb.ItemsReq
 		Code: http.StatusOK,
 		Msg:  "ok",
 		Data: &pasturePb.WeaningItemsData{
-			Total:    int32(count),
-			Page:     pagination.Page,
-			PageSize: pagination.PageSize,
+			Total:      int32(count),
+			Page:       pagination.Page,
+			PageSize:   pagination.PageSize,
+			HeaderSort: []string{"id", "cowId", "earNumber", "penName", "dayAge", "planDayFormat", "birthAtFormat", "currentWeight"},
 			Header: map[string]string{
 				"id":            "编号",
 				"cowId":         "牛号",
@@ -516,9 +524,10 @@ func (s *StoreEntry) MatingCowList(ctx context.Context, req *pasturePb.ItemsRequ
 		Code: http.StatusOK,
 		Msg:  "ok",
 		Data: &pasturePb.MatingItemsData{
-			Total:    int32(count),
-			Page:     pagination.Page,
-			PageSize: pagination.PageSize,
+			Total:      int32(count),
+			Page:       pagination.Page,
+			PageSize:   pagination.PageSize,
+			HeaderSort: []string{"id", "cowId", "earNumber", "dayAge", "lact", "penName", "breedStatusName", "cowTypeName", "calvingAge", "abortionAge", "exposeEstrusTypeName", "lastCalvingAtFormat"},
 			Header: map[string]string{
 				"id":                   "编号",
 				"cowId":                "牛号",

+ 18 - 8
module/backend/calendar_more.go

@@ -22,8 +22,9 @@ func (s *StoreEntry) CalvingCowList(ctx context.Context, req *pasturePb.ItemsReq
 	calvingItems := make([]*pasturePb.CalvingItems, 0)
 	count := int64(0)
 	pref := s.DB.Table(fmt.Sprintf("%s as a", new(model.EventCalving).TableName())).
-		Select(`a.id,a.cow_id,a.ear_number,a.status,b.breed_status,b.pen_id,ROUND(b.current_weight/100,2) as current_weight,DATE_FORMAT(FROM_UNIXTIME(last_mating_at), '%Y-%m-%d') AS mating_at_format,
-		b.day_age,b.last_bull_number as bull_id,b.pen_name,DATEDIFF(NOW(),FROM_UNIXTIME(last_mating_at)) AS mating_age,DATE_FORMAT(FROM_UNIXTIME(a.plan_day), '%Y-%m-%d') AS plan_day`).
+		Select(`a.id,a.cow_id,a.ear_number,a.status,b.breed_status,b.pen_id,ROUND(b.current_weight/1000,2) as current_weight,
+		DATE_FORMAT(FROM_UNIXTIME(last_mating_at), '%Y-%m-%d') AS mating_at_format,b.day_age,b.last_bull_number as bull_id,
+		b.pen_name,DATEDIFF(NOW(),FROM_UNIXTIME(last_mating_at)) AS mating_age,DATE_FORMAT(FROM_UNIXTIME(a.plan_day), '%Y-%m-%d') AS plan_day`).
 		Joins("left join cow as b on a.cow_id = b.id").
 		Where("a.status = ?", pasturePb.IsShow_No).
 		Where("b.admission_status = ?", pasturePb.AdmissionStatus_Admission).
@@ -62,6 +63,10 @@ func (s *StoreEntry) CalvingCowList(ctx context.Context, req *pasturePb.ItemsReq
 			Total:    int32(count),
 			Page:     pagination.Page,
 			PageSize: pagination.PageSize,
+			HeaderSort: []string{
+				"id", "cowId", "earNumber", "penName", "lact", "breedStatusName", "matingAge", "dayAge", "status",
+				"bullId", "planDay", "matingAtFormat", "currentWeight",
+			},
 			Header: map[string]string{
 				"id":              "编号",
 				"cowId":           "牛号",
@@ -90,8 +95,9 @@ func (s *StoreEntry) DryMilkCowList(ctx context.Context, req *pasturePb.ItemsReq
 	dryMilkItems := make([]*pasturePb.DruMilkItems, 0)
 	count := int64(0)
 	pref := s.DB.Table(fmt.Sprintf("%s as a", new(model.EventDryMilk).TableName())).
-		Select(`a.id,a.cow_id,a.ear_number,a.status,b.breed_status,b.pen_id,b.day_age,b.last_bull_number as bull_number,
-b.pen_name,DATEDIFF(NOW(),FROM_UNIXTIME(last_mating_at)) AS mating_age,DATE_FORMAT(FROM_UNIXTIME(a.plan_day), '%Y-%m-%d') AS plan_day`).
+		Select(`a.id,a.cow_id,a.ear_number,a.status,b.breed_status,b.pen_id,b.day_age,
+		b.last_bull_number as bull_number,b.pen_name,DATEDIFF(NOW(),FROM_UNIXTIME(last_mating_at)) AS mating_age,
+		DATE_FORMAT(FROM_UNIXTIME(a.plan_day), '%Y-%m-%d') AS plan_day`).
 		Joins("left join cow as b on a.cow_id = b.id").
 		Where("a.status = ?", pasturePb.IsShow_No).
 		Where("b.admission_status = ?", pasturePb.AdmissionStatus_Admission).
@@ -136,6 +142,9 @@ b.pen_name,DATEDIFF(NOW(),FROM_UNIXTIME(last_mating_at)) AS mating_age,DATE_FORM
 			Total:    int32(count),
 			Page:     pagination.Page,
 			PageSize: pagination.PageSize,
+			HeaderSort: []string{
+				"id", "cowId", "earNumber", "dayAge", "penName", "lact", "pregnancyAge", "status", "bullNumber", "planDay", "calvingAtFormat",
+			},
 			Header: map[string]string{
 				"id":              "编号",
 				"cowId":           "牛号",
@@ -186,10 +195,11 @@ func (s *StoreEntry) TreatmentCowList(ctx context.Context, req *pasturePb.ItemsR
 		Code: http.StatusOK,
 		Msg:  "ok",
 		Data: &pasturePb.EventCowDiseaseData{
-			List:     model.EventCowDiseaseSlice(diseaseItems).ToPB(s.HealthStatusMap()),
-			Total:    int32(count),
-			PageSize: pagination.PageSize,
-			Page:     pagination.Page,
+			List:       model.EventCowDiseaseSlice(diseaseItems).ToPB(s.HealthStatusMap()),
+			Total:      int32(count),
+			PageSize:   pagination.PageSize,
+			Page:       pagination.Page,
+			HeaderSort: []string{"id", "cowId", "earNumber", "penName", "diagnoseName", "healthStatus", "lastPrescriptionName", "treatmentDays", "onsetDays"},
 			Header: map[string]string{
 				"id":                   "编号",
 				"cowId":                "牛号",

+ 40 - 8
module/backend/cow.go

@@ -41,7 +41,6 @@ func (s *StoreEntry) Detail(ctx context.Context, req *pasturePb.SearchEventReque
 		}
 	}
 
-	penMap := s.PenMap(ctx, userModel.AppPasture.Id)
 	cowTypeMap := s.CowTypeMap()
 	breedStatusMap := s.CowBreedStatusMap()
 	cowKindMap := s.CowKindMap()
@@ -52,7 +51,7 @@ func (s *StoreEntry) Detail(ctx context.Context, req *pasturePb.SearchEventReque
 	return &pasturePb.CowInfoResponse{
 		Code: http.StatusOK,
 		Msg:  "ok",
-		Data: model.CowSlice([]*model.Cow{cowInfo}).ToPB(penMap, cowTypeMap, breedStatusMap, cowKindMap, cowSourceMap, admissionStatusMap, healthStatusMap)[0],
+		Data: model.CowSlice([]*model.Cow{cowInfo}).ToPB(cowTypeMap, breedStatusMap, cowKindMap, cowSourceMap, admissionStatusMap, healthStatusMap)[0],
 	}, nil
 }
 
@@ -73,7 +72,7 @@ func (s *StoreEntry) List(ctx context.Context, req *pasturePb.SearchEventRequest
 	}
 
 	if req.EarNumber != "" {
-		pref.Where("ear_number = ?", req.EarNumber)
+		pref.Where("ear_number like ?", fmt.Sprintf("%s%s%s", "%", req.EarNumber, "%"))
 	}
 
 	if req.Id > 0 {
@@ -120,7 +119,6 @@ func (s *StoreEntry) List(ctx context.Context, req *pasturePb.SearchEventRequest
 		return nil, xerr.WithStack(err)
 	}
 
-	penMap := s.PenMap(ctx, userModel.AppPasture.Id)
 	cowTypeMap := s.CowTypeMap()
 	breedStatusMap := s.CowBreedStatusMap()
 	cowKindMap := s.CowKindMap()
@@ -131,10 +129,7 @@ func (s *StoreEntry) List(ctx context.Context, req *pasturePb.SearchEventRequest
 		Code: http.StatusOK,
 		Msg:  "ok",
 		Data: &pasturePb.SearchCowData{
-			List: model.CowSlice(cowList).ToPB(
-				penMap, cowTypeMap, breedStatusMap, cowKindMap,
-				cowSourceMap, admissionStatusMap, healthStatusMap,
-			),
+			List:     model.CowSlice(cowList).ToPB(cowTypeMap, breedStatusMap, cowKindMap, cowSourceMap, admissionStatusMap, healthStatusMap),
 			Total:    int32(count),
 			PageSize: pagination.PageSize,
 			Page:     pagination.Page,
@@ -366,3 +361,40 @@ func (s *StoreEntry) CowLactCurve(ctx context.Context, req *pasturePb.CowLactCur
 		Data: data,
 	}, nil
 }
+
+func (s *StoreEntry) BehaviorRate(ctx context.Context, req *pasturePb.CowBehaviorRateRequest) (*pasturePb.CowBehaviorRateResponse, error) {
+	userModel, err := s.GetUserModel(ctx)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	cowInfo, err := s.GetCowInfoByCowId(ctx, userModel.AppPasture.Id, int64(req.CowId))
+	if err != nil {
+		return nil, xerr.Customf("错误的牛只信息: %d", req.CowId)
+	}
+
+	if cowInfo.NeckRingNumber == "" {
+		return nil, xerr.Customf("该牛只未佩戴脖环: %s", req.EarNumber)
+	}
+
+	neckActiveHabitList := make([]*model.NeckActiveHabit, 0)
+	if err = s.DB.Model(new(model.NeckActiveHabit)).
+		Where("neck_ring_number = ?", cowInfo.NeckRingNumber).
+		Where("pasture_id = ?", userModel.AppPasture.Id).
+		Where("cow_id > ?", 0).
+		Where("heat_date BETWEEN ? AND ?", req.StartTime, req.EndTime).
+		Order("heat_date, frameid").
+		Find(&neckActiveHabitList).Error; err != nil {
+	}
+
+	dataBetween, err := util.GetDaysBetween(req.StartTime, req.EndTime)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	return &pasturePb.CowBehaviorRateResponse{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: model.NeckActiveHabitSlice(neckActiveHabitList).ToPB2(dataBetween),
+	}, nil
+}

+ 24 - 4
module/backend/dashboard.go

@@ -2,7 +2,6 @@ package backend
 
 import (
 	"context"
-	"fmt"
 	"kpt-pasture/model"
 	"kpt-pasture/util"
 	"net/http"
@@ -32,20 +31,41 @@ func (s *StoreEntry) NeckRingWarning(ctx context.Context) (*pasturePb.IndexNeckR
 		int32(pasturePb.EstrusLevel_High):   0,
 	}
 
-	if err = s.DB.Table(fmt.Sprintf("%s as a", new(model.NeckRingEstrusWarning).TableName())).
+	pref, err := s.EstrusWarningQuery(ctx, userModel.AppPasture.Id)
+	if err != nil {
+		return nil, xerr.Customf("系统错误!")
+	}
+	if err = pref.Order("a.level DESC").
+		Select("a.level, count(a.level) as count").
+		Group("a.level").
+		Find(&estrusWarningCowList).Error; err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	/*if err = s.DB.Table(fmt.Sprintf("%s as a", new(model.NeckRingEstrusWarning).TableName())).
 		Select("a.level, count(a.level) as count").
 		Where("a.pasture_id = ?", userModel.AppPasture.Id).
 		Where("a.is_show = ?", pasturePb.IsShow_Ok).
 		Group("a.level").
 		Find(&estrusWarningCowList).Error; err != nil {
 		zaplog.Error("NeckRingWarning", zap.Any("estrusWarningNumber", err))
-	}
+	}*/
 	countEstrusWarning := 0
 	for _, v := range estrusWarningCowList {
 		estrusWarningLevelItems[int32(v.Level)] = estrusWarningLevelItems[v.Count]
 		countEstrusWarning += int(v.Count)
 	}
 
+	abortionCount := int64(0)
+	pref, err = s.AbortionWarningQuery(ctx, userModel.AppPasture.Id)
+	if err != nil {
+		return nil, xerr.Customf("系统错误!")
+	}
+
+	if err = pref.Group("cow_id").Count(&abortionCount).Error; err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
 	healthWarningNumber := int64(0)
 	if err = s.DB.Model(new(model.NeckRingHealth)).
 		Where("pasture_id = ?", userModel.AppPasture.Id).
@@ -60,7 +80,7 @@ func (s *StoreEntry) NeckRingWarning(ctx context.Context) (*pasturePb.IndexNeckR
 		Data: &pasturePb.NeckRingData{
 			EstrusWarningNumber:     int32(countEstrusWarning),
 			HealthWarningNumber:     int32(healthWarningNumber),
-			AbortionWarningNumber:   0,
+			AbortionWarningNumber:   int32(abortionCount),
 			StressWarningNumber:     0,
 			EstrusWarningLevelItems: estrusWarningLevelItems,
 		},

+ 3 - 0
module/backend/event_base.go

@@ -106,6 +106,9 @@ func (s *StoreEntry) CreateEnter(ctx context.Context, req *pasturePb.EventEnterR
 	}
 
 	newCow := model.NewEnterCow(userModel.AppPasture.Id, req, penMap)
+	if req.BirthAt > 0 {
+		newCow.DayAge = newCow.GetDayAge()
+	}
 	if err = s.DB.Transaction(func(tx *gorm.DB) error {
 		// 新增牛只信息
 		if err = tx.Model(new(model.Cow)).Create(newCow).Error; err != nil {

+ 2 - 2
module/backend/event_base_more.go

@@ -252,7 +252,7 @@ func (s *StoreEntry) CowSaleList(ctx context.Context, req *pasturePb.EventCowSal
 		pref.Where("dealer_id = ?", req.DealerId)
 	}
 
-	if err = pref.Order("a.id desc").
+	if err = pref.Order("id desc").
 		Count(&count).Limit(int(pagination.PageSize)).
 		Offset(int(pagination.PageOffset)).
 		Find(&eventSale).Error; err != nil {
@@ -297,7 +297,7 @@ func (s *StoreEntry) CowSaleList(ctx context.Context, req *pasturePb.EventCowSal
 			PageSize: pagination.PageSize,
 			Page:     pagination.Page,
 		},
-	}, err
+	}, nil
 }
 
 func (s *StoreEntry) ImmunizationList(ctx context.Context, req *pasturePb.SearchEventImmunizationRequest, pagination *pasturePb.PaginationModel) (*pasturePb.SearchEventImmunizationResponse, error) {

+ 1 - 0
module/backend/interface.go

@@ -247,6 +247,7 @@ type CowService interface {
 	BehaviorCurve(ctx context.Context, req *pasturePb.CowBehaviorCurveRequest) (*model.CowBehaviorCurveResponse, error)
 	CowGrowthCurve(ctx context.Context, req *pasturePb.CowGrowthCurveRequest) (*pasturePb.CowGrowthCurveResponse, error)
 	CowLactCurve(ctx context.Context, req *pasturePb.CowLactCurveRequest) (*pasturePb.CowLactCurveResponse, error)
+	BehaviorRate(ctx context.Context, req *pasturePb.CowBehaviorRateRequest) (*pasturePb.CowBehaviorRateResponse, error)
 
 	IndicatorsComparison(ctx context.Context, req *pasturePb.IndicatorsComparisonRequest) (*model.IndicatorsComparisonResponse, error)
 	LongTermInfertility(ctx context.Context, req *pasturePb.LongTermInfertilityRequest, pagination *pasturePb.PaginationModel) (*pasturePb.LongTermInfertilityResponse, error)

+ 40 - 26
module/backend/neck_ring_warning.go

@@ -26,36 +26,15 @@ func (s *StoreEntry) EstrusOrAbortionCowList(ctx context.Context, req *pasturePb
 	var pref *gorm.DB
 	switch req.Kind {
 	case "abortion":
-		pref = s.DB.Table(fmt.Sprintf("%s as a", new(model.NeckRingEstrusWarning).TableName())).
-			Joins(fmt.Sprintf("JOIN %s AS b on a.cow_id = b.id", new(model.Cow).TableName())).
-			Where("b.pregnancy_age BETWEEN ? AND ?", 1, 260).
-			Where("b.admission_status = ?", pasturePb.AdmissionStatus_Admission).
-			Where("b.is_pregnant = ?", pasturePb.IsShow_Ok).
-			Where("a.level >= ?", pasturePb.EstrusLevel_Middle).
-			Where("a.pasture_id = ?", userModel.AppPasture.Id).
-			Where("a.is_show = ?", pasturePb.IsShow_Ok)
+		pref, err = s.AbortionWarningQuery(ctx, userModel.AppPasture.Id)
+		if err != nil {
+			return nil, xerr.WithStack(err)
+		}
 	default:
-		nowTime := time.Now()
-		startTime := time.Unix(util.TimeParseLocalUnix(nowTime.Format(model.LayoutDate2)), 0).Format(model.LayoutTime)
-		entTime := time.Unix(util.TimeParseLocalEndUnix(nowTime.AddDate(0, 0, 1).Format(model.LayoutDate2)), 0).Format(model.LayoutTime)
-		systemBasic, err := s.FindSystemBasic(ctx, userModel.AppPasture.Id, model.EstrusWaringDays)
+		pref, err = s.EstrusWarningQuery(ctx, userModel.AppPasture.Id)
 		if err != nil {
 			return nil, xerr.WithStack(err)
 		}
-
-		pref = s.DB.Table(fmt.Sprintf("%s as a", new(model.NeckRingEstrusWarning).TableName())).
-			Joins(fmt.Sprintf("JOIN %s AS b on a.cow_id = b.id", new(model.Cow).TableName())).
-			Where("b.last_mating_at < UNIX_TIMESTAMP(a.date_time)").
-			Where("b.admission_status = ?", pasturePb.AdmissionStatus_Admission).
-			Where("b.is_forbidden_mating = ?", pasturePb.IsShow_No).
-			Where("a.level >= ?", pasturePb.EstrusLevel_Low).
-			Where("a.pasture_id = ?", userModel.AppPasture.Id).
-			Where("a.date_time BETWEEN ? AND ?", startTime, entTime).
-			Where(s.DB.Where("b.last_mating_at < UNIX_TIMESTAMP(a.first_time)").
-				Or(s.DB.Where("b.last_mating_at = ?", 0).
-					Where("b.calving_age > ?", systemBasic.MinValue).
-					Or("b.lact = ?", 0))).
-			Where("a.is_show = ?", pasturePb.IsShow_Ok)
 	}
 
 	if len(req.EarNumber) > 0 {
@@ -108,3 +87,38 @@ func (s *StoreEntry) EstrusOrAbortionCowList(ctx context.Context, req *pasturePb
 		},
 	}, nil
 }
+
+func (s *StoreEntry) EstrusWarningQuery(ctx context.Context, pastureId int64) (*gorm.DB, error) {
+	nowTime := time.Now()
+	startTime := time.Unix(util.TimeParseLocalUnix(nowTime.Format(model.LayoutDate2)), 0).Format(model.LayoutTime)
+	entTime := time.Unix(util.TimeParseLocalEndUnix(nowTime.AddDate(0, 0, 1).Format(model.LayoutDate2)), 0).Format(model.LayoutTime)
+	systemBasic, err := s.FindSystemBasic(ctx, pastureId, model.EstrusWaringDays)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	return s.DB.Table(fmt.Sprintf("%s as a", new(model.NeckRingEstrusWarning).TableName())).
+		Joins(fmt.Sprintf("JOIN %s AS b on a.cow_id = b.id", new(model.Cow).TableName())).
+		Where("b.last_mating_at < UNIX_TIMESTAMP(a.date_time)").
+		Where("b.admission_status = ?", pasturePb.AdmissionStatus_Admission).
+		Where("b.is_forbidden_mating = ?", pasturePb.IsShow_No).
+		Where("a.level >= ?", pasturePb.EstrusLevel_Low).
+		Where("a.pasture_id = ?", pastureId).
+		Where("a.date_time BETWEEN ? AND ?", startTime, entTime).
+		Where(s.DB.Where("b.last_mating_at < UNIX_TIMESTAMP(a.first_time)").
+			Or(s.DB.Where("b.last_mating_at = ?", 0).
+				Where("b.calving_age > ?", systemBasic.MinValue).
+				Or("b.lact = ?", 0))).
+		Where("a.is_show = ?", pasturePb.IsShow_Ok), nil
+}
+
+func (s *StoreEntry) AbortionWarningQuery(ctx context.Context, pastureId int64) (*gorm.DB, error) {
+	return s.DB.Table(fmt.Sprintf("%s as a", new(model.NeckRingEstrusWarning).TableName())).
+		Joins(fmt.Sprintf("JOIN %s AS b on a.cow_id = b.id", new(model.Cow).TableName())).
+		Where("b.pregnancy_age BETWEEN ? AND ?", 1, 260).
+		Where("b.admission_status = ?", pasturePb.AdmissionStatus_Admission).
+		Where("b.is_pregnant = ?", pasturePb.IsShow_Ok).
+		Where("a.level >= ?", pasturePb.EstrusLevel_Middle).
+		Where("a.pasture_id = ?", pastureId).
+		Where("a.is_show = ?", pasturePb.IsShow_Ok), nil
+}

+ 27 - 299
module/crontab/milk_original.go → module/crontab/milk_original_update.go

@@ -4,13 +4,10 @@ import (
 	"fmt"
 	"kpt-pasture/model"
 	"kpt-pasture/util"
-	"sort"
 	"strconv"
 	"strings"
 	"time"
 
-	"gorm.io/gorm"
-
 	"gitee.com/xuyiping_admin/pkg/logger/zaplog"
 	"go.uber.org/zap"
 )
@@ -93,14 +90,19 @@ func (e *Entry) ProcessMilkOriginal(pastureId int64) {
 		zaplog.Error("DeleteRepeatMilkData", zap.Any("pastureId", pastureId), zap.Any("err", err))
 		return
 	}
-
-	milkHallList := e.FindMilkHallList(pastureId)
 	e.DeleteRepeatMilkData(pastureId, deleteModel, milkClassConfig, milkOriginalList)
-	e.UpdateRecognitionTime(pastureId, milkHallList)
-	e.UpdateRepeatCupSet1(milkOriginalList)
-	e.UpdateMilkOriginCowInfo(milkOriginalList, milkHallList)
-	e.UpdateRepeatCupSet2(milkOriginalList)
-	e.UpdateMilkOriginalInitialTimesAndAttachAdjustTime(shifts, milkOriginalList)
+	milkHallList := e.FindMilkHallList(pastureId)
+	for _, hall := range milkHallList {
+		e.UpdateRecognitionTime(pastureId, hall)
+		e.UpdateRepeatCupSet1(milkOriginalList)
+		e.UpdateMilkOriginCowInfo(milkOriginalList, hall)
+		e.UpdateRepeatCupSet2(milkOriginalList)
+		e.UpdateMilkOriginalInitialTimesAndAttachAdjustTime(shifts, milkOriginalList)
+		e.UpdateMilkNattach(pastureId, milkClassConfig, hall)
+		e.UpdateMilkNoCowId(pastureId, milkClassConfig, hall)
+		e.UpdateMilkLoad(pastureId, milkClassConfig, hall)
+		e.UpdateMilkLoad2(pastureId, milkClassConfig, hall)
+	}
 }
 
 // UpdateShifts 更新班次
@@ -114,8 +116,8 @@ func (e *Entry) UpdateShifts(pastureId int64, xBeg1, xBeg2, xBeg3, xBeg4 int) {
 	}
 
 	for _, v := range milkOriginalList {
-		subDetachTime1 := util.Substr(v.DetacherTime, 11, 2)
-		subDetachTime2 := util.Substr(v.DetacherTime, 14, 2)
+		subDetachTime1 := util.Substr(v.DetachedTime, 11, 2)
+		subDetachTime2 := util.Substr(v.DetachedTime, 14, 2)
 
 		subDetachTime1Int, _ := strconv.ParseInt(subDetachTime1, 10, 64)
 		subDetachTime2Int, _ := strconv.ParseInt(subDetachTime2, 10, 64)
@@ -189,9 +191,9 @@ func (e *Entry) UpdateMilkDate(pastureId int64, xDBeg int) {
 		}
 
 		// 比较挤奶时间和结束时间,取较晚的时间
-		detacherTime, _ := util.TimeParseLocal(model.LayoutTime, v.DetacherTime)
-		latestTime := detacherTime
-		if endTime.After(detacherTime) {
+		detachedTime, _ := util.TimeParseLocal(model.LayoutTime, v.DetachedTime)
+		latestTime := detachedTime
+		if endTime.After(detachedTime) {
 			latestTime = endTime
 		}
 
@@ -225,298 +227,24 @@ func (e *Entry) DeleteRepeatMilkData(pastureId int64, deleteModel *DeleteMilkOri
 		e.delete1(v, deleteModel.XMind, cfg)
 		e.delete2(v, deleteModel.XMind, cfg)
 		e.delete3(v, deleteModel.XMind, cfg)
-		e.delete4(v, deleteModel.XMind, cfg)
-	}
-}
-
-// UpdateRecognitionTime 识别时间超过40分钟未套杯牛只,识别改为未识别
-func (e *Entry) UpdateRecognitionTime(pastureId int64, milkHallList []*model.MilkHall) {
-	if len(milkHallList) == 0 {
-		return
-	}
-	for _, hall := range milkHallList {
-		milkOriginalList := make([]*model.MilkOriginal, 0)
-		if err := e.DB.Model(new(model.MilkOriginal)).
-			Where("pasture_id = ?", pastureId).
-			Where("milk_hall_number = ?", hall.Name).
-			Where("milk_hall_brand = ?", hall.Brand).
-			Where("load = ?", 0).
-			Find(&milkOriginalList).Error; err != nil {
-			zaplog.Error("MilkHallData", zap.Any("err", err))
-		}
-
-		for _, v := range milkOriginalList {
-			t1, _ := util.TimeParseLocal(model.LayoutTime, v.AttachTime)
-			t2, _ := util.TimeParseLocal(model.LayoutTime, v.RecognitionTime)
-			diff := t1.Sub(t2)
-			minute := int(diff.Minutes())
-
-			if util.Substr(v.RecognitionTime, -1, 8) != "00:00:00" && minute > 40 {
-				if err := e.DB.Model(new(model.MilkOriginal)).
-					Where("id = ?", v.Id).
-					Updates(map[string]interface{}{
-						"cow_id":           0,
-						"ele_ear_number":   "",
-						"recognition_time": fmt.Sprintf("%s 00:00:00", util.Substr(v.RecognitionTime, 0, 10)),
-					}).Error; err != nil {
-					zaplog.Error("MilkHallData", zap.Any("err", err))
-				}
-			}
-		}
-	}
-}
-
-// UpdateRepeatCupSet1 更新重复套杯1, 识别时间相同,且不为0为重复套杯
-func (e *Entry) UpdateRepeatCupSet1(milkOriginalList []*model.MilkOriginal) {
-	if len(milkOriginalList) == 0 {
-		return
-	}
-
-	milkOriginalMap := make(map[string][]*model.MilkOriginal)
-	for _, v := range milkOriginalList {
-		if strings.HasSuffix(v.RecognitionTime, "00:00:00") {
-			continue
-		}
-		key := fmt.Sprintf("%s_%d_%d_%s", v.MilkDate, v.Shifts, v.DetacherAddress, v.RecognitionTime)
-		milkOriginalMap[key] = append(milkOriginalMap[key], v)
-	}
-
-	for _, originalList := range milkOriginalMap {
-		if len(originalList) >= 2 {
-			// 按照Id升序排序(保留第一条)
-			sort.Slice(originalList, func(i, j int) bool {
-				return originalList[i].Id < originalList[j].Id
-			})
-
-			for i, v := range originalList {
-				if i == 0 {
-					continue
-				}
-				if err := e.DB.Model(new(model.MilkOriginal)).
-					Select("").Where("id = ?", v.Id).
-					Update("nattach", 2).Error; err != nil {
-					zaplog.Error("UpdateRepeatCupSet1", zap.Any("err", err))
-				}
-			}
-		}
-	}
-}
-
-// UpdateMilkOriginCowInfo 更新牛只信息
-func (e *Entry) UpdateMilkOriginCowInfo(milkOriginalList []*model.MilkOriginal, milkHallList []*model.MilkHall) {
-	milkHallMap := make(map[string][]*model.MilkOriginal)
-	for _, v := range milkOriginalList {
-		key := fmt.Sprintf("%s", v.MilkHallNumber)
-		milkHallMap[key] = append(milkHallMap[key], v)
-	}
-
-	for _, v := range milkHallList {
-		dataList, ok := milkHallMap[v.Name]
-		if !ok {
-			continue
-		}
-		switch v.IsExtraUpdate {
-		case model.IsExtra0:
-
-		case model.IsExtra1, model.IsExtra3:
-			for _, d := range dataList {
-				if d.EarNumber == "" {
-					continue
-				}
-
-				cowInfo, err := e.GetCowByEarNumber(d.PastureId, d.EarNumber)
-				if err != nil {
-					zaplog.Error("UpdateMilkOriginCowInfo", zap.Any("err", err), zap.Any("data", d))
-					continue
-				}
-				// 更新牛只信息
-				d.UpdateCowInfo(cowInfo)
-				if err = e.DB.Model(new(model.MilkOriginal)).
-					Select("cow_id", "pen_id", "pen_name").
-					Where("id = ?", d.Id).Updates(d).Error; err != nil {
-					zaplog.Error("UpdateMilkOriginCowInfo", zap.Any("err", err), zap.Any("data", d))
-				}
-			}
-		case model.IsExtra2:
-		default:
-		}
-	}
-}
-
-func (e *Entry) UpdateMilkOriginalInitialTimesAndAttachAdjustTime(shifts []int32, milkOriginalList []*model.MilkOriginal) {
-	for _, shift := range shifts {
-		shiftMinDetachTimes := ""
-		// 按脱杯地址分组处理
-		addressMap := make(map[int64][]*model.MilkOriginal)
-		for _, m := range milkOriginalList {
-			if m.Shifts != shift || m.DetacherTime == "" {
-				continue
-			}
-			if shiftMinDetachTimes == "" {
-				shiftMinDetachTimes = m.DetacherTime
-			} else {
-				t1, _ := util.TimeParseLocal(model.LayoutTime, m.DetacherTime)
-				t2, _ := util.TimeParseLocal(model.LayoutTime, shiftMinDetachTimes)
-				if t2.Before(t1) {
-					shiftMinDetachTimes = m.DetacherTime
-				}
-			}
-			addressMap[m.DetacherAddress] = append(addressMap[m.DetacherAddress], m)
-		}
-
-		if shiftMinDetachTimes == "" {
-			continue
-		}
-
-		bt, _ := util.TimeParseLocal(model.LayoutTime, shiftMinDetachTimes)
-		b5 := bt.Add(-5*time.Minute).Format(model.LayoutHour) + "00:00"
-
-		for _, list := range addressMap {
-			// 对当前地址的记录按时间排序
-			sort.Slice(list, func(i, j int) bool {
-				if list[i].MilkDate != list[j].MilkDate {
-					return list[i].MilkDate < list[j].MilkDate
-				}
-
-				if list[i].Shifts != list[j].Shifts {
-					return list[i].Shifts < list[j].Shifts
-				}
-				if list[i].DetacherAddress != list[j].DetacherAddress {
-					return list[i].DetacherAddress < list[j].DetacherAddress
-				}
-				return list[i].Id < list[j].Id
-			})
-
-			// 初始化变量,模拟SQL中的@address和@det
-			var lastAddress int64 = 0
-			var lastDetachTime string = "2001-01-01 06:00:00" // 默认初始值
-			// 批量更新参数
-			var updateParams []struct {
-				ID           int64
-				InitialTimes string
-				AttachAdjust string
-			}
-
-			for _, m := range list {
-				var initialTimeStr string
-				var attachAdjust string
-				// 如果当前记录的脱杯地址与上一条不同,则使用基准时间b5
-				if m.DetacherAddress != lastAddress {
-					initialTimeStr = b5
-				} else {
-					// 否则使用上一条记录的脱杯时间
-					initialTimeStr = lastDetachTime
-				}
-
-				// 更新最后记录的地址和时间
-				lastAddress = m.DetacherAddress
-				lastDetachTime = m.DetacherTime
-
-				// 只有当initialTime不为空且与原有值不同时才需要更新
-				if initialTimeStr != "" {
-					initialTime, _ := util.TimeParseLocal(model.LayoutTime, initialTimeStr)
-					attachTime, _ := util.TimeParseLocal(model.LayoutTime, m.AttachTime)
-					detachTime, _ := util.TimeParseLocal(model.LayoutTime, m.DetacherTime)
-
-					// 条件1:attachtimes以'00:00:00'结尾或attachtimes <= initialtimes
-					if strings.HasSuffix(m.AttachTime, "00:00:00") || attachTime.Before(initialTime) || attachTime.Equal(initialTime) {
-						// 计算 detachtimes - (1.5 + duration)*60 秒
-						adjustTime1 := detachTime.Add(-time.Duration((90 + m.Duration*60)) * time.Second)
-
-						// 取三者中的最大值
-						maxTime := util.FindMaxTime(attachTime, initialTime, adjustTime1)
-						attachAdjust = maxTime.Format(model.LayoutTime)
-					} else {
-						// 计算 detachtimes - duration*60 秒
-						adjustTime2 := detachTime.Add(-time.Duration(m.Duration*60) * time.Second)
-
-						// 取 attachtimes 和 adjustTime2 中的较小值
-						minTime := util.FindMinTime(attachTime, adjustTime2)
-
-						// 再与 initialtimes 取较大值
-						maxTime := util.FindMaxTime(minTime, initialTime)
-						attachAdjust = maxTime.Format(model.LayoutTime)
-					}
-
-					// 记录需要更新的字段
-					updateParams = append(updateParams, struct {
-						ID           int64
-						InitialTimes string
-						AttachAdjust string
-					}{
-						ID:           m.Id,
-						InitialTimes: initialTimeStr,
-						AttachAdjust: attachAdjust,
-					})
-
-					/*if err := e.DB.Model(new(model.MilkOriginal)).
-						Select("initial_time").
-						Where("id = ?", m.Id).
-						Update("initial_time", initialTime).Error; err != nil {
-						zaplog.Error("UpdateMilkOriginalInitialTimesAndAttachAdjustTime", zap.Any("err", err))
-					}*/
-				}
-			}
-
-			if len(updateParams) > 0 {
-				// 批量更新数据库
-				if err := e.DB.Transaction(func(tx *gorm.DB) error {
-					for _, param := range updateParams {
-						updates := map[string]interface{}{
-							"initial_times":      param.InitialTimes,
-							"attach_adjust_time": param.AttachAdjust,
-						}
-
-						if err := tx.Model(new(model.MilkOriginal)).
-							Select("initial_time", "attach_adjust_time").
-							Where("id = ? ", param.ID).
-							Updates(updates).Error; err != nil {
-							return err
-						}
-					}
-					return nil
-				}); err != nil {
-					zaplog.Error("UpdateMilkOriginalInitialTimesAndAttachAdjustTime", zap.Any("err", err))
-				}
-			}
-
-		}
-	}
-}
-
-// UpdateRepeatCupSet2  非标准重复套杯
-func (e *Entry) UpdateRepeatCupSet2(milkOriginalList []*model.MilkOriginal) {
-	for _, v := range milkOriginalList {
-		if v.AttachTime == "" || v.InitialTime == "" {
-			continue
-		}
-		nattchTime, _ := util.TimeParseLocal(model.LayoutTime, v.AttachTime)
-		initialTime, _ := util.TimeParseLocal(model.LayoutTime, v.InitialTime)
-		if util.Substr(v.InitialTime, -1, 5) != "00:00" && v.Nattach == 0 && nattchTime.Sub(initialTime).Minutes() <= 1 {
-			if err := e.DB.Model(new(model.MilkOriginal)).
-				Select("nattach").
-				Where("id = ?", v.Id).
-				Update("nattach", 2).Error; err != nil {
-				zaplog.Error("UpdateRepeatCupSet2", zap.Any("err", err))
-			}
-		}
+		e.delete4(v)
 	}
 }
 
 func (e *Entry) delete1(data *model.MilkOriginal, xMinD string, cfg *MilkClassConfig) {
 	// 1. 删除attach_time为00:00:00的记录
-	acctchStr := util.Substr(data.AttachTime, -1, 8)
-	if data.MilkDate < xMinD || acctchStr != "00:00:00" {
+	actchStr := util.Substr(data.AttachTime, -1, 8)
+	if data.MilkDate < xMinD || actchStr != "00:00:00" {
 		return
 	}
 
 	// 2. 检查是否存在符合条件的m2记录
 	var count int64
 	if err := e.DB.Model(new(model.MilkOriginal)).
-		Where("wid BETWEEN ? AND ?", cfg.OldUpdateMaxId+1, cfg.CurrentMaxId).
+		Where("id BETWEEN ? AND ?", cfg.OldUpdateMaxId+1, cfg.CurrentMaxId).
 		Where("milk_date = ?", data.MilkDate).
-		Where("detacher_address = ?", data.DetacherAddress).
-		Where("ABS(TIMESTAMPDIFF(SECOND, detach_time, ?)) < 10", data.DetacherTime).
+		Where("detached_address = ?", data.DetachedAddress).
+		Where("ABS(TIMESTAMPDIFF(SECOND, detach_time, ?)) < 10", data.DetachedTime).
 		Where("milk_weight = ?", data.MilkWeight).
 		Where("pasture_id = ?", data.PastureId).
 		Where("RIGHT(attach_time, 8) != '00:00:00'").
@@ -546,7 +274,7 @@ func (e *Entry) delete2(data *model.MilkOriginal, xMinD string, cfg *MilkClassCo
 		Where("id BETWEEN ? AND ?", cfg.OldUpdateMaxId+1, cfg.CurrentMaxId).
 		Where("milk_date = ?", data.MilkDate).
 		Where("shifts = ?", data.Shifts).
-		Where("detacher_address = ?", data.DetacherAddress).
+		Where("detached_address = ?", data.DetachedAddress).
 		Where("attach_time = ?", data.AttachTime).
 		Where("milk_weight = ?", data.MilkWeight).
 		Where("pasture_id = ?", data.PastureId).
@@ -603,7 +331,7 @@ func (e *Entry) delete3(data *model.MilkOriginal, xMinD string, cfg *MilkClassCo
 	}
 }
 
-func (e *Entry) delete4(data *model.MilkOriginal, xMinD string, cfg *MilkClassConfig) {
+func (e *Entry) delete4(data *model.MilkOriginal) {
 	// 1. 检查记录是否在时间范围内
 	if data.MilkDate < "2020-10-01" {
 		return
@@ -614,8 +342,8 @@ func (e *Entry) delete4(data *model.MilkOriginal, xMinD string, cfg *MilkClassCo
 	if err := e.DB.Model(new(model.MilkOriginal)).
 		Where("id = ?", data.Id).
 		Where("milk_date >= ?", "2020-10-01").
-		Where("recognition_time > detacher_time").
-		Where("attach_time > detacher_time").
+		Where("recognition_time > detached_time").
+		Where("attach_time > detached_time").
 		Where("SUBSTRING(attach_time, 12, 2) = ?", "23").
 		Where("pasture_id = ?", data.PastureId).
 		Select("1").

+ 493 - 0
module/crontab/milk_original_update_gea.go

@@ -0,0 +1,493 @@
+package crontab
+
+import (
+	"fmt"
+	"kpt-pasture/model"
+	"kpt-pasture/util"
+	"sort"
+	"strings"
+	"time"
+
+	"gorm.io/gorm"
+
+	"gitee.com/xuyiping_admin/pkg/logger/zaplog"
+	"go.uber.org/zap"
+)
+
+// UpdateRecognitionTime 识别时间超过40分钟未套杯牛只,识别改为未识别
+func (e *Entry) UpdateRecognitionTime(pastureId int64, hall *model.MilkHall) {
+	milkOriginalList := make([]*model.MilkOriginal, 0)
+	if err := e.DB.Model(new(model.MilkOriginal)).
+		Where("pasture_id = ?", pastureId).
+		Where("milk_hall_number = ?", hall.Name).
+		Where("milk_hall_brand = ?", hall.Brand).
+		Where("load = ?", 0).
+		Find(&milkOriginalList).Error; err != nil {
+		zaplog.Error("MilkHallData", zap.Any("err", err))
+	}
+
+	for _, v := range milkOriginalList {
+		t1, _ := util.TimeParseLocal(model.LayoutTime, v.AttachTime)
+		t2, _ := util.TimeParseLocal(model.LayoutTime, v.RecognitionTime)
+		diff := t1.Sub(t2)
+		minute := int(diff.Minutes())
+
+		if util.Substr(v.RecognitionTime, -1, 8) != "00:00:00" && minute > 40 {
+			if err := e.DB.Model(new(model.MilkOriginal)).
+				Where("id = ?", v.Id).
+				Updates(map[string]interface{}{
+					"cow_id":           0,
+					"ele_ear_number":   "",
+					"recognition_time": fmt.Sprintf("%s 00:00:00", util.Substr(v.RecognitionTime, 0, 10)),
+				}).Error; err != nil {
+				zaplog.Error("MilkHallData", zap.Any("err", err))
+			}
+		}
+	}
+}
+
+// UpdateRepeatCupSet1 更新重复套杯1, 识别时间相同,且不为0为重复套杯
+func (e *Entry) UpdateRepeatCupSet1(milkOriginalList []*model.MilkOriginal) {
+	if len(milkOriginalList) == 0 {
+		return
+	}
+
+	milkOriginalMap := make(map[string][]*model.MilkOriginal)
+	for _, v := range milkOriginalList {
+		if strings.HasSuffix(v.RecognitionTime, "00:00:00") {
+			continue
+		}
+		key := fmt.Sprintf("%s_%d_%d_%s", v.MilkDate, v.Shifts, v.DetachedAddress, v.RecognitionTime)
+		milkOriginalMap[key] = append(milkOriginalMap[key], v)
+	}
+
+	for _, originalList := range milkOriginalMap {
+		if len(originalList) >= 2 {
+			// 按照Id升序排序(保留第一条)
+			sort.Slice(originalList, func(i, j int) bool {
+				return originalList[i].Id < originalList[j].Id
+			})
+
+			for i, v := range originalList {
+				if i == 0 {
+					continue
+				}
+				if err := e.DB.Model(new(model.MilkOriginal)).
+					Select("").Where("id = ?", v.Id).
+					Update("nattach", 2).Error; err != nil {
+					zaplog.Error("UpdateRepeatCupSet1", zap.Any("err", err))
+				}
+			}
+		}
+	}
+}
+
+// UpdateMilkOriginCowInfo 更新牛只信息
+func (e *Entry) UpdateMilkOriginCowInfo(milkOriginalList []*model.MilkOriginal, hall *model.MilkHall) {
+	milkHallMap := make(map[string][]*model.MilkOriginal)
+	for _, v := range milkOriginalList {
+		key := fmt.Sprintf("%s", v.MilkHallNumber)
+		milkHallMap[key] = append(milkHallMap[key], v)
+	}
+
+	dataList, ok := milkHallMap[hall.Name]
+	if !ok {
+		return
+	}
+	switch hall.IsExtraUpdate {
+	case model.IsExtra0:
+
+	case model.IsExtra1, model.IsExtra3:
+		for _, d := range dataList {
+			if d.EarNumber == "" {
+				continue
+			}
+
+			cowInfo, err := e.GetCowByEarNumber(d.PastureId, d.EarNumber)
+			if err != nil {
+				zaplog.Error("UpdateMilkOriginCowInfo", zap.Any("err", err), zap.Any("data", d))
+				continue
+			}
+			// 更新牛只信息
+			d.UpdateCowInfo(cowInfo)
+			if err = e.DB.Model(new(model.MilkOriginal)).
+				Select("cow_id", "pen_id", "pen_name").
+				Where("id = ?", d.Id).Updates(d).Error; err != nil {
+				zaplog.Error("UpdateMilkOriginCowInfo", zap.Any("err", err), zap.Any("data", d))
+			}
+		}
+	case model.IsExtra2:
+	default:
+	}
+
+}
+
+func (e *Entry) UpdateMilkOriginalInitialTimesAndAttachAdjustTime(shifts []int32, milkOriginalList []*model.MilkOriginal) {
+	for _, shift := range shifts {
+		shiftMinDetachTimes := ""
+		// 按脱杯地址分组处理
+		addressMap := make(map[int64][]*model.MilkOriginal)
+		for _, m := range milkOriginalList {
+			if m.Shifts != shift || m.DetachedTime == "" {
+				continue
+			}
+			if shiftMinDetachTimes == "" {
+				shiftMinDetachTimes = m.DetachedTime
+			} else {
+				t1, _ := util.TimeParseLocal(model.LayoutTime, m.DetachedTime)
+				t2, _ := util.TimeParseLocal(model.LayoutTime, shiftMinDetachTimes)
+				if t2.Before(t1) {
+					shiftMinDetachTimes = m.DetachedTime
+				}
+			}
+			addressMap[m.DetachedAddress] = append(addressMap[m.DetachedAddress], m)
+		}
+
+		if shiftMinDetachTimes == "" {
+			continue
+		}
+
+		bt, _ := util.TimeParseLocal(model.LayoutTime, shiftMinDetachTimes)
+		b5 := bt.Add(-5*time.Minute).Format(model.LayoutHour) + "00:00"
+
+		for _, list := range addressMap {
+			// 对当前地址的记录按时间排序
+			sort.Slice(list, func(i, j int) bool {
+				if list[i].MilkDate != list[j].MilkDate {
+					return list[i].MilkDate < list[j].MilkDate
+				}
+
+				if list[i].Shifts != list[j].Shifts {
+					return list[i].Shifts < list[j].Shifts
+				}
+				if list[i].DetachedAddress != list[j].DetachedAddress {
+					return list[i].DetachedAddress < list[j].DetachedAddress
+				}
+				return list[i].Id < list[j].Id
+			})
+
+			// 初始化变量,模拟SQL中的@address和@det
+			var lastAddress int64 = 0
+			var lastDetachTime string = "2001-01-01 06:00:00" // 默认初始值
+			// 批量更新参数
+			var updateParams []struct {
+				ID           int64
+				InitialTimes string
+				AttachAdjust string
+			}
+
+			for _, m := range list {
+				var initialTimeStr string
+				var attachAdjust string
+				// 如果当前记录的脱杯地址与上一条不同,则使用基准时间b5
+				if m.DetachedAddress != lastAddress {
+					initialTimeStr = b5
+				} else {
+					// 否则使用上一条记录的脱杯时间
+					initialTimeStr = lastDetachTime
+				}
+
+				// 更新最后记录的地址和时间
+				lastAddress = m.DetachedAddress
+				lastDetachTime = m.DetachedTime
+
+				// 只有当initialTime不为空且与原有值不同时才需要更新
+				if initialTimeStr != "" {
+					initialTime, _ := util.TimeParseLocal(model.LayoutTime, initialTimeStr)
+					attachTime, _ := util.TimeParseLocal(model.LayoutTime, m.AttachTime)
+					detachTime, _ := util.TimeParseLocal(model.LayoutTime, m.DetachedTime)
+
+					// 条件1:attachtimes以'00:00:00'结尾或attachtimes <= initialtimes
+					if strings.HasSuffix(m.AttachTime, "00:00:00") || attachTime.Before(initialTime) || attachTime.Equal(initialTime) {
+						// 计算 detachtimes - (1.5 + duration)*60 秒
+						adjustTime1 := detachTime.Add(-time.Duration((90 + m.Duration*60)) * time.Second)
+
+						// 取三者中的最大值
+						maxTime := util.FindMaxTime(attachTime, initialTime, adjustTime1)
+						attachAdjust = maxTime.Format(model.LayoutTime)
+					} else {
+						// 计算 detachtimes - duration*60 秒
+						adjustTime2 := detachTime.Add(-time.Duration(m.Duration*60) * time.Second)
+
+						// 取 attachtimes 和 adjustTime2 中的较小值
+						minTime := util.FindMinTime(attachTime, adjustTime2)
+
+						// 再与 initialtimes 取较大值
+						maxTime := util.FindMaxTime(minTime, initialTime)
+						attachAdjust = maxTime.Format(model.LayoutTime)
+					}
+
+					// 记录需要更新的字段
+					updateParams = append(updateParams, struct {
+						ID           int64
+						InitialTimes string
+						AttachAdjust string
+					}{
+						ID:           m.Id,
+						InitialTimes: initialTimeStr,
+						AttachAdjust: attachAdjust,
+					})
+
+					/*if err := e.DB.Model(new(model.MilkOriginal)).
+						Select("initial_time").
+						Where("id = ?", m.Id).
+						Update("initial_time", initialTime).Error; err != nil {
+						zaplog.Error("UpdateMilkOriginalInitialTimesAndAttachAdjustTime", zap.Any("err", err))
+					}*/
+				}
+			}
+
+			if len(updateParams) > 0 {
+				// 批量更新数据库
+				if err := e.DB.Transaction(func(tx *gorm.DB) error {
+					for _, param := range updateParams {
+						updates := map[string]interface{}{
+							"initial_times":      param.InitialTimes,
+							"attach_adjust_time": param.AttachAdjust,
+						}
+
+						if err := tx.Model(new(model.MilkOriginal)).
+							Select("initial_time", "attach_adjust_time").
+							Where("id = ? ", param.ID).
+							Updates(updates).Error; err != nil {
+							return err
+						}
+					}
+					return nil
+				}); err != nil {
+					zaplog.Error("UpdateMilkOriginalInitialTimesAndAttachAdjustTime", zap.Any("err", err))
+				}
+			}
+
+		}
+	}
+}
+
+// UpdateRepeatCupSet2  非标准重复套杯
+func (e *Entry) UpdateRepeatCupSet2(milkOriginalList []*model.MilkOriginal) {
+	for _, v := range milkOriginalList {
+		if v.AttachTime == "" || v.InitialTime == "" {
+			continue
+		}
+		nattchTime, _ := util.TimeParseLocal(model.LayoutTime, v.AttachTime)
+		initialTime, _ := util.TimeParseLocal(model.LayoutTime, v.InitialTime)
+		if util.Substr(v.InitialTime, -1, 5) != "00:00" && v.Nattach == 0 && nattchTime.Sub(initialTime).Minutes() <= 1 {
+			if err := e.DB.Model(new(model.MilkOriginal)).
+				Select("nattach").
+				Where("id = ?", v.Id).
+				Update("nattach", 2).Error; err != nil {
+				zaplog.Error("UpdateRepeatCupSet2", zap.Any("err", err))
+			}
+		}
+	}
+}
+
+// UpdateMilkNattach 非标准重复套杯
+func (e *Entry) UpdateMilkNattach(pastureId int64, milkClassConfig *MilkClassConfig, hall *model.MilkHall) {
+	milkOriginalList := make([]*model.MilkOriginal, 0)
+	if err := e.DB.Model(new(model.MilkOriginal)).
+		Where("pasture_id = ?", pastureId).
+		Where("id BETWEEN ? AND ?", milkClassConfig.OldUpdateMaxId+1, milkClassConfig.CurrentMaxId).
+		Find(&milkOriginalList).Error; err != nil {
+		zaplog.Error("DeleteRepeatMilkData", zap.Any("pastureId", pastureId), zap.Any("err", err))
+		return
+	}
+	for _, v := range milkOriginalList {
+		if v.InitialTime == "" || v.AttachTime == "" {
+			continue
+		}
+		attachTime, _ := util.TimeParseLocal(model.LayoutTime, v.AttachTime)
+		initialTime, _ := util.TimeParseLocal(model.LayoutTime, v.InitialTime)
+		initialTimePlus1Min := initialTime.Add(time.Minute)
+
+		if hall.Brand == v.MilkHallBrand && hall.Name == v.MilkHallNumber && v.Nattach == 0 &&
+			!strings.HasSuffix(v.InitialTime, "00:00") && (attachTime.Before(initialTimePlus1Min) || attachTime.Equal(initialTimePlus1Min)) {
+			if err := e.DB.Model(new(model.MilkOriginal)).
+				Select("nattach").
+				Where("id = ?", v.Id).
+				Update("nattach", 2).Error; err != nil {
+				zaplog.Error("UpdateMilkNattach", zap.Any("err", err))
+			}
+		}
+	}
+}
+
+// UpdateMilkNoCowId 清理无牛号牛只:重复超过2圈牛号,套杯时间间隔大于17分钟
+func (e *Entry) UpdateMilkNoCowId(pastureId int64, milkClassConfig *MilkClassConfig, hall *model.MilkHall) {
+	//  SELECT 	m2.wid
+	//	FROM (SELECT m0.milkdate, m0.shifts, m0.detacher_address, m0.cow_id, MIN(m0.wid) wid, COUNT(0) nb, MIN(m0.attachtimes) attachtimes
+	//	FROM milkweight m0 WHERE m0.wid BETWEEN xdminwid AND xdmaxwid AND m0.milkdate=xcurdate AND m0.cow_id>0 AND m0.station=xvarName
+	//	GROUP BY m0.shifts, m0.detacher_address, m0.cow_id HAVING nb>1) m1
+	//	JOIN milkweight m2 ON m1.milkdate=m2.milkdate AND m1.shifts=m2.shifts AND m2.station=xvarName AND m1.detacher_address=m2.detacher_address
+	//	AND m1.cow_id=m2.cow_id AND m1.wid<m2.wid WHERE m2.wid BETWEEN xdminwid AND xdmaxwid AND m2.milkdate=xcurdate AND m2.cow_id>0
+	//	AND (m1.attachtimes + INTERVAL 17 MINUTE) < m2.attachtimes ;
+
+	minMilkOriginalRecordsList := make([]*model.MinMilkOriginalRecords, 0)
+	if err := e.DB.Model(new(model.MilkOriginal)).
+		Select("MIN(id) AS min_id, COUNT(0) AS count, cow_id, detached_address, milk_date, MIN(attach_time) AS min_attach_time").
+		Where("pasture_id = ?", pastureId).
+		Where("id BETWEEN ? AND ?", milkClassConfig.OldUpdateMaxId+1, milkClassConfig.CurrentMaxId).
+		Where("cow_id > ?", 0).
+		Where("milk_hall_number = ?", hall.Name).
+		Group("shifts, detached_address, cow_id").
+		Having("count > ? AND min_attach_time != ?", 1, "").
+		Find(&minMilkOriginalRecordsList).Error; err != nil {
+		zaplog.Error("UpdateMilkNoCowId", zap.Any("err", err))
+		return
+	}
+
+	// 2. 收集需要更新的 id 列表
+	idsToUpdate := make([]int64, 0)
+	for _, v := range minMilkOriginalRecordsList {
+		var laterRecords []struct {
+			Id int64
+		}
+
+		attachTime, _ := util.TimeParseLocal(model.LayoutTime, v.MinAttachTime)
+		attachTimeAdd17 := attachTime.Add(time.Minute * 17).Format(model.LayoutTime)
+		if err := e.DB.Model(new(model.MilkOriginal)).
+			Select("id").
+			Where("pasture_id = ?", pastureId).
+			Where("id BETWEEN ? AND ?", milkClassConfig.OldUpdateMaxId+1, milkClassConfig.CurrentMaxId).
+			Where("cow_id = ?", v.CowId).
+			Where("detached_address = ?", v.DetachedAddress).
+			Where("milk_date = ?", v.MilkDate).
+			Where("attach_time > ?", attachTimeAdd17).Find(&laterRecords).Error; err != nil {
+			zaplog.Error("UpdateMilkNoCowId", zap.Any("err", err))
+			continue
+		}
+		if len(laterRecords) > 0 {
+			idsToUpdate = append(idsToUpdate, v.MinId)
+		}
+	}
+
+	if len(idsToUpdate) > 0 {
+		if err := e.DB.Model(new(model.MilkOriginal)).
+			Where("id IN ?", idsToUpdate).
+			Updates(map[string]interface{}{
+				"cow_id":     0,
+				"ear_number": "",
+				"pen_id":     0,
+				"pen_name":   "",
+			}).Error; err != nil {
+			zaplog.Error("UpdateMilkNoCowId", zap.Any("err", err))
+		}
+	}
+}
+
+// UpdateMilkLoad 设置批次 圈数
+func (e *Entry) UpdateMilkLoad(pastureId int64, milkClassConfig *MilkClassConfig, hall *model.MilkHall) {
+	var (
+		xAddress = 999
+		//xShift          = 6
+		xLoad           = int32(0)
+		recognitionTime = time.Date(2001, 1, 1, 1, 1, 1, 0, time.Local)
+	)
+
+	baseRecords := make([]*model.BaseRecords, 0)
+	if err := e.DB.Model(new(model.MilkOriginal)).
+		Select("id,attach_adjust,detached_address,shifts,ear_number,milk_date,recognition_time").
+		Where("pasture_id = ?", pastureId).
+		Where("id BETWEEN ? AND ?", milkClassConfig.OldUpdateMaxId+1, milkClassConfig.CurrentMaxId).
+		Where("nattach = ?", 0).Where("milk_hall_number = ?", hall.Name).
+		Order("attach_adjust,detached_address").
+		Limit(99).Find(&baseRecords).Error; err != nil {
+		zaplog.Error("UpdateMilkLoad", zap.Any("err", err))
+		return
+	}
+
+	// 3. 处理每条记录并计算 load 值
+	recordsToUpdate := make([]*model.UpdateLoadRecord, 0)
+	for _, record := range baseRecords {
+		if record.RecognitionTime != "" && record.AttachAdjust != "" {
+			r, _ := util.TimeParseLocal(model.LayoutTime, record.RecognitionTime)
+			a, _ := util.TimeParseLocal(model.LayoutTime, record.AttachAdjust)
+			diff := int32(a.Sub(r).Minutes())
+			if !(diff >= 0 && diff <= 15 && !strings.HasSuffix(record.RecognitionTime, "00:00:00")) {
+				record.RecognitionTime = record.AttachAdjust
+			}
+		}
+		r, _ := util.TimeParseLocal(model.LayoutTime, record.RecognitionTime)
+		/*var maxLoad int32
+		if (record.DetachedAddress < xAddress-27 && r.After(recognitionTime.Add(-5*time.Minute))) || record.Shifts > xShift || r.After(recognitionTime.Add(6*time.Minute)) {
+			xLoad++
+			maxLoad = xLoad
+		} else {
+			maxLoad = xLoad
+		}*/
+
+		// 计算 currentLoad
+		var currentLoad int32
+		if (xAddress+60-record.DetachedAddress <= 27) &&
+			r.Before(recognitionTime.Add(3*time.Minute)) {
+			currentLoad = maxInt(xLoad-1, 1)
+		} else {
+			currentLoad = xLoad
+		}
+
+		// 更新地址
+		if (xAddress+60-record.DetachedAddress <= 27) &&
+			r.Before(recognitionTime.Add(3*time.Minute)) {
+			// 保持原地址
+		} else {
+			xAddress = record.DetachedAddress
+		}
+
+		// 更新shift和时间
+		//xShift = record.Shifts
+		if r.After(recognitionTime) {
+			recognitionTime = r
+		}
+
+		// 记录需要更新的数据
+		recordsToUpdate = append(recordsToUpdate, &model.UpdateLoadRecord{
+			Id:   record.Id,
+			Load: currentLoad,
+		})
+	}
+	if len(recordsToUpdate) > 0 {
+		batchSize := 100 // 每批更新100条
+		for i := 0; i < len(recordsToUpdate); i += batchSize {
+			end := i + batchSize
+			if end > len(recordsToUpdate) {
+				end = len(recordsToUpdate)
+			}
+			batch := recordsToUpdate[i:end]
+			if err := e.DB.Model(new(model.MilkOriginal)).
+				Where("id IN (?)", getWIDsFromUpdateRecords(batch)).
+				Updates(map[string]interface{}{
+					"load": gorm.Expr("CASE id " + buildCaseWhen(batch) + " END"),
+				}).Error; err != nil {
+				zaplog.Error("UpdateMilkLoad", zap.Any("err", err))
+				continue
+			}
+		}
+	}
+}
+
+// 辅助函数:从更新记录中提取WID列表
+func getWIDsFromUpdateRecords(records []*model.UpdateLoadRecord) []int64 {
+	ids := make([]int64, len(records))
+	for i, r := range records {
+		ids[i] = r.Id
+	}
+	return ids
+}
+
+// 辅助函数:构建CASE WHEN语句
+func buildCaseWhen(records []*model.UpdateLoadRecord) string {
+	var builder strings.Builder
+	for _, r := range records {
+		builder.WriteString(fmt.Sprintf("WHEN %d THEN %d ", r.Id, r.Load))
+	}
+	builder.WriteString("ELSE load")
+	return builder.String()
+}
+
+// 辅助函数:返回两个整数中的最大值
+func maxInt(a, b int32) int32 {
+	if a > b {
+		return a
+	}
+	return b
+}

+ 54 - 0
module/crontab/milk_original_update_gea_more.go

@@ -0,0 +1,54 @@
+package crontab
+
+import (
+	"kpt-pasture/model"
+
+	"gitee.com/xuyiping_admin/pkg/logger/zaplog"
+	"go.uber.org/zap"
+)
+
+// UpdateMilkLoad2 (UPDATE milkweight m SET m.nattach=1 WHERE m.wid BETWEEN xdminwid AND xdmaxwid AND m.milkdate=xcurdate AND m.nattach=0 AND m.station=xvarName;)
+func (e *Entry) UpdateMilkLoad2(pastureId int64, milkClassConfig *MilkClassConfig, hall *model.MilkHall) {
+	milkOriginalList := make([]*model.MilkOriginal, 0)
+	if err := e.DB.Model(new(model.MilkOriginal)).
+		Where("pasture_id = ?", pastureId).
+		Where("nattach = ?", 0).
+		Where("milk_hall_number = ?", hall.Name).
+		Where("id BETWEEN ? AND ?", milkClassConfig.OldUpdateMaxId+1, milkClassConfig.CurrentMaxId).
+		Find(&milkOriginalList).Error; err != nil {
+		return
+	}
+	for _, v := range milkOriginalList {
+		if err := e.DB.Model(new(model.MilkOriginal)).
+			Where("id = ?", v.Id).
+			Update("nattach", 1).Error; err != nil {
+			zaplog.Error("UpdateMilkLoad2", zap.Any("err", err))
+			return
+		}
+	}
+}
+
+// UpdateMilkLoad3 更新重复套杯牛只圈数
+//
+//	 SELECT aa.wid, aa.`load` FROM (
+//		  SELECT m0.milkdate, m0.shifts, m0.detacher_address, m0.wid, IF(m0.nattach=1 OR @address!=m0.detacher_address, @nload:= m0.`load`, @nload)	`load`,
+//		  @attachtimes:=attachtimes, @detachtimes:=detachtimes, @shifts:=m0.shifts, @address:=m0.detacher_address, m0.nattach
+//		FROM (
+//			SELECT m00.milkdate, m00.shifts, m00.detacher_address, m00.wid, m00.nattach, m00.`load`, m00.attachtimes, m00.detachtimes
+//			FROM milkweight m00 WHERE m00.wid BETWEEN xdminwid AND xdmaxwid AND m00.milkdate=xcurdate AND m00.station=xvarName
+//			ORDER BY m00.shifts, m00.detacher_address, m00.wid LIMIT 99999
+//			) m0, (SELECT @shifts:=6, @address:=0, @nload:=1) mm ORDER BY m0.shifts, m0.detacher_address, m0.wid LIMIT 9999
+//		) aa WHERE aa.nattach=2 ORDER BY aa.wid;
+//
+//		UPDATE milkweight m5 JOIN tmp_milkload1 m6 ON m5.Wid=m6.wid SET m5.`load`=m6.`load`;
+func (e *Entry) UpdateMilkLoad3(pastureId int64, milkClassConfig *MilkClassConfig, hall *model.MilkHall) {
+	milkOriginalList := make([]*model.MilkOriginal, 0)
+	if err := e.DB.Model(new(model.MilkOriginal)).
+		Where("pasture_id = ?", pastureId).
+		Where("milk_hall_number = ?", hall.Name).
+		Where("id BETWEEN ? AND ?", milkClassConfig.OldUpdateMaxId+1, milkClassConfig.CurrentMaxId).
+		Order("shifts,detached_address,id").
+		Find(&milkOriginalList).Error; err != nil {
+		return
+	}
+}