Przeglądaj źródła

event: death 增加死亡列表

Yi 1 tydzień temu
rodzic
commit
1f8f16aa07

+ 1 - 1
go.mod

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

+ 4 - 0
go.sum

@@ -142,6 +142,10 @@ gitee.com/xuyiping_admin/go_proto v0.0.0-20250408020446-8115565fe86e h1:wETx6NGE
 gitee.com/xuyiping_admin/go_proto v0.0.0-20250408020446-8115565fe86e/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
 gitee.com/xuyiping_admin/go_proto v0.0.0-20250408032224-57e63f411526 h1:oe8HolRGDYj30IltPmUMFq6i0+H8pJsj533WkjdIHSM=
 gitee.com/xuyiping_admin/go_proto v0.0.0-20250408032224-57e63f411526/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250408072509-1117ad78b23f h1:VWi6G/NWr/FxORMXpie4Sgg2+cmlh/ozVwJWdsyr4+g=
+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/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=

+ 22 - 1
http/handler/event/event_base.go

@@ -230,7 +230,7 @@ func WeightBatch(c *gin.Context) {
 	})
 }
 
-func Death(c *gin.Context) {
+func DeathBatch(c *gin.Context) {
 	var req pasturePb.EventDeathBatch
 	if err := ginutil.BindProto(c, &req); err != nil {
 		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
@@ -265,6 +265,27 @@ func Death(c *gin.Context) {
 	})
 }
 
+func DeathList(c *gin.Context) {
+	var req pasturePb.SearchEventRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	pagination := &pasturePb.PaginationModel{
+		Page:       int32(c.GetInt(middleware.Page)),
+		PageSize:   int32(c.GetInt(middleware.PageSize)),
+		PageOffset: int32(c.GetInt(middleware.PageOffset)),
+	}
+
+	res, err := middleware.Dependency(c).StoreEventHub.OpsService.DeathList(c, &req, pagination)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	ginutil.JSONResp(c, res)
+}
+
 func CowEarNumber(c *gin.Context) {
 	var req pasturePb.EventReplaceEarNumber
 	if err := ginutil.BindProto(c, &req); err != nil {

+ 2 - 1
http/route/event_api.go

@@ -58,7 +58,8 @@ func EventAPI(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
 		// 批量治愈
 		eventRoute.POST("/disease/curable/batch", event.CowDiseaseCurable)
 		// 死亡
-		eventRoute.POST("/death/batch", event.Death)
+		eventRoute.POST("/death/batch", event.DeathBatch)
+		eventRoute.POST("/death/list", event.DeathList)
 		// 更换耳标号
 		eventRoute.PUT("/cow/ear/number", event.CowEarNumber)
 		// 牛只销售

+ 24 - 24
model/app_pasture_list.go

@@ -3,30 +3,30 @@ package model
 import pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
 
 type AppPastureList struct {
-	Id                   int64                 `json:"id"`
-	FarmId               string                `json:"farmId"`
-	Name                 string                `json:"name"`
-	ShortName            string                `json:"shortName"`
-	GroupId              int64                 `json:"groupId"`
-	Province             string                `json:"province"`
-	City                 string                `json:"city"`
-	County               string                `json:"county"`
-	Address              string                `json:"address"`
-	LegalPersonName      string                `json:"legalPersonName"`
-	LegalPersonPhone     string                `json:"legalPersonPhone"`
-	FactoryDirectorName  string                `json:"factoryDirectorName"`
-	FactoryDirectorPhone string                `json:"factoryDirectorPhone"`
-	Category             int32                 `json:"category"`
-	CurrentScale         string                `json:"currentScale"`
-	PlanScale            string                `json:"planScale"`
-	AppId                string                `json:"appId"`
-	Status               int32                 `json:"status"`
-	IsShow               pasturePb.IsShow_Kind `json:"isShow"`
-	ProductionModel      int32                 `json:"productionModel"`
-	Remarks              string                `json:"remarks"`
-	CreatedName          string                `json:"createdName"`
-	CreatedAt            int64                 `json:"createdAt"`
-	UpdatedAt            string                `json:"updatedAt"`
+	Id                   int64                          `json:"id"`
+	FarmId               string                         `json:"farmId"`
+	Name                 string                         `json:"name"`
+	ShortName            string                         `json:"shortName"`
+	GroupId              int64                          `json:"groupId"`
+	Province             string                         `json:"province"`
+	City                 string                         `json:"city"`
+	County               string                         `json:"county"`
+	Address              string                         `json:"address"`
+	LegalPersonName      string                         `json:"legalPersonName"`
+	LegalPersonPhone     string                         `json:"legalPersonPhone"`
+	FactoryDirectorName  string                         `json:"factoryDirectorName"`
+	FactoryDirectorPhone string                         `json:"factoryDirectorPhone"`
+	Category             pasturePb.PastureCategory_Kind `json:"category"`
+	CurrentScale         string                         `json:"currentScale"`
+	PlanScale            string                         `json:"planScale"`
+	AppId                string                         `json:"appId"`
+	Status               int32                          `json:"status"`
+	IsShow               pasturePb.IsShow_Kind          `json:"isShow"`
+	ProductionModel      int32                          `json:"productionModel"`
+	Remarks              string                         `json:"remarks"`
+	CreatedName          string                         `json:"createdName"`
+	CreatedAt            int64                          `json:"createdAt"`
+	UpdatedAt            string                         `json:"updatedAt"`
 }
 
 func (a *AppPastureList) TableName() string {

+ 2 - 0
model/cow.go

@@ -406,6 +406,8 @@ func NewEnterCow(pastureId int64, req *pasturePb.EventEnterRequest, penMap map[i
 		LastWeightAt:        int64(req.EstrusAt),
 		CurrentWeight:       int64(req.Weight * 1000),
 		LastDryMilkAt:       int64(req.DryMilkAt),
+		MatingTimes:         req.MatingTimes,
+		LastCalvingAt:       int64(req.CalvingAt),
 	}
 }
 

+ 24 - 0
model/event_death.go

@@ -52,6 +52,30 @@ func NewEventDeath(pastureId int64, cow *Cow, req *pasturePb.EventDeath, current
 	}
 }
 
+type EventDeathSlice []*EventDeath
+
+func (e EventDeathSlice) ToPB() []*pasturePb.EventDeath {
+	res := make([]*pasturePb.EventDeath, len(e))
+	for i, v := range e {
+		res[i] = &pasturePb.EventDeath{
+			Id:                   int32(v.Id),
+			EarNumber:            v.EarNumber,
+			DeathReasonKind:      v.DeathReasonKind,
+			DeathReasonName:      v.DeathReasonName,
+			DeathAt:              int32(v.DeathAt),
+			OperationId:          int32(v.OperationId),
+			OperationName:        v.OperationName,
+			Remarks:              v.Remarks,
+			Weight:               float32(v.Weight / 1000),
+			DeathDestinationKind: v.DeathDestinationKind,
+			DeathDestinationName: v.DeathDestinationName,
+			CreatedAt:            int32(v.CreatedAt),
+			UpdatedAt:            int32(v.UpdatedAt),
+		}
+	}
+	return res
+}
+
 type EventDeathModel struct {
 	Cow        *Cow
 	EventDeath *EventDeath

+ 17 - 9
model/milk_hall.go

@@ -8,16 +8,24 @@ const (
 	AFI     = "afi"
 )
 
+const (
+	IsExtra0 = iota
+	IsExtra1
+	IsExtra2
+	IsExtra3
+)
+
 type MilkHall struct {
-	Id        int64                 `json:"id"`
-	PastureId int64                 `json:"pasture_id"`
-	Name      string                `json:"name"`
-	Brand     string                `json:"brand"`
-	Kind      string                `json:"kind"`
-	Field     int32                 `json:"field"`
-	IsShow    pasturePb.IsShow_Kind `json:"is_show"`
-	CreatedAt int64                 `json:"created_at"`
-	UpdatedAt int64                 `json:"updated_at"`
+	Id            int64                 `json:"id"`
+	PastureId     int64                 `json:"pasture_id"`
+	Name          string                `json:"name"`
+	Brand         string                `json:"brand"`
+	Kind          string                `json:"kind"`
+	Field         int32                 `json:"field"`
+	IsExtraUpdate int32                 `json:"isExtraUpdate"`
+	IsShow        pasturePb.IsShow_Kind `json:"is_show"`
+	CreatedAt     int64                 `json:"created_at"`
+	UpdatedAt     int64                 `json:"updated_at"`
 }
 
 func (m *MilkHall) TableName() string {

+ 6 - 0
model/milk_original.go

@@ -52,6 +52,12 @@ func (m *MilkOriginal) TableName() string {
 	return "milk_original"
 }
 
+func (m *MilkOriginal) UpdateCowInfo(cow *Cow) {
+	m.CowId = cow.Id
+	m.PenId = cow.PenId
+	m.PenName = cow.PenName
+}
+
 func NewAFIMilkOriginal(pastureId int64, milkHallNumber string, req *AFIMilkHallOriginal) *MilkOriginal {
 	return &MilkOriginal{
 		PastureId:        pastureId,

+ 26 - 0
module/backend/config_data_base.go

@@ -357,3 +357,29 @@ func (s *StoreEntry) CowOutReasonList(isAll string) []*pasturePb.ConfigOptionsLi
 	})
 	return configOptions
 }
+
+func (s *StoreEntry) CowDeathDestinationList(isAll string) []*pasturePb.ConfigOptionsList {
+	configOptions := make([]*pasturePb.ConfigOptionsList, 0)
+	if isAll == model.IsAllYes {
+		configOptions = append(configOptions,
+			&pasturePb.ConfigOptionsList{
+				Value:    int32(0),
+				Label:    "全部",
+				Disabled: true,
+			})
+	}
+	configOptions = append(configOptions, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.DeathDestination_NuisanceLess),
+		Label:    "无公害处理",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.DeathDestination_Slaughterhouse),
+		Label:    "屠宰场",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.DeathDestination_Other),
+		Label:    "其他",
+		Disabled: true,
+	})
+	return configOptions
+}

+ 54 - 19
module/backend/cow.go

@@ -303,31 +303,66 @@ func (s *StoreEntry) CowLactCurve(ctx context.Context, req *pasturePb.CowLactCur
 		return nil, xerr.Customf("错误的牛只信息: %d", req.CowId)
 	}
 
-	weightList := make([]*model.EventWeight, 0)
-	if err = s.DB.Table(new(model.EventWeight).TableName()).
-		Where("cow_id = ?", cowInfo.Id).
+	cowLactList := make([]*model.CowLact, 0)
+	if err = s.DB.Table(new(model.CowLact).TableName()).
+		Where("cow_id = ?", req.CowId).
 		Where("pasture_id = ?", userModel.AppPasture.Id).
-		Order("weight_at").
-		Find(&weightList).Error; err != nil {
+		Order("lact").
+		Find(&cowLactList).Error; err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	data := &pasturePb.CowLactCurveData{
+		DateTime:            make(map[int32]string),
+		WeekAvgMilk:         make([]float32, 0),
+		DayMilk:             make([]float32, 0),
+		DHI:                 make([]float32, 0),
+		MilkProductionTrend: make([]float32, 0),
+		DayHigh:             make([]int32, 0),
+		DayRumina:           make([]int32, 0),
+		DayIntake:           make([]int32, 0),
+		DayInactive:         make([]int32, 0),
+		DayChew:             make([]int32, 0),
+		DayImmobility:       make([]int32, 0),
+		EstrusWarning:       make(map[int32]int32),
+	}
+
+	if len(cowLactList) <= 0 {
+		return &pasturePb.CowLactCurveResponse{
+			Code: http.StatusOK,
+			Msg:  "ok",
+			Data: data,
+		}, nil
+	}
+	starTime := ""
+	endTime := ""
+	cowLactMap := make(map[int32]*model.CowLact)
+	for _, v := range cowLactList {
+		cowLactMap[v.Lact] = v
+		if v.Lact == req.Lact {
+			starTime = v.StartTime
+		}
+	}
+	if st, ok := cowLactMap[req.Lact+1]; ok {
+		et, _ := util.TimeParseLocal(model.LayoutDate2, st.StartTime)
+		endTime = et.AddDate(0, 0, -1).Format(model.LayoutDate2)
+	} else {
+		endTime = time.Now().Local().Format(model.LayoutDate2)
+	}
+
+	neckRingList := 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 ?", starTime, endTime).
+		Order("heat_date, frameid").Find(&neckRingList).Error; err != nil {
 		return nil, xerr.WithStack(err)
 	}
 
 	return &pasturePb.CowLactCurveResponse{
 		Code: http.StatusOK,
 		Msg:  "ok",
-		Data: &pasturePb.CowLactCurveData{
-			DateTime:            nil,
-			WeekAvgMilk:         nil,
-			DayMilk:             nil,
-			DHI:                 nil,
-			MilkProductionTrend: nil,
-			DayHigh:             nil,
-			DayRumina:           nil,
-			DayIntake:           nil,
-			DayInactive:         nil,
-			DayChew:             nil,
-			DayImmobility:       nil,
-			EstrusWarning:       nil,
-		},
+		Data: data,
 	}, nil
 }

+ 8 - 0
module/backend/enum_map.go

@@ -327,6 +327,14 @@ func (s *StoreEntry) CowOutReasonsMap() map[pasturePb.OutReasons_Kind]string {
 	return res
 }
 
+func (s *StoreEntry) CowDeathDestinationMap() map[pasturePb.DeathDestination_Kind]string {
+	res := make(map[pasturePb.DeathDestination_Kind]string)
+	for _, v := range s.CowDeathDestinationList("") {
+		res[pasturePb.DeathDestination_Kind(v.Value)] = v.Label
+	}
+	return res
+}
+
 func (s *StoreEntry) eventCategoryMap() map[pasturePb.EventType_Kind]pasturePb.EventCategory_Kind {
 	return map[pasturePb.EventType_Kind]pasturePb.EventCategory_Kind{
 		pasturePb.EventType_Enter:             pasturePb.EventCategory_Base,

+ 1 - 0
module/backend/enum_options.go

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

+ 45 - 0
module/backend/event_base_more.go

@@ -31,12 +31,19 @@ func (s *StoreEntry) DeathBatch(ctx context.Context, req *pasturePb.EventDeathBa
 			return xerr.Customf("获取牛只信息失败: %s", item.EarNumber)
 		}
 
+		if name, ok := s.DeadReasonMap()[item.DeathReasonKind]; ok {
+			item.DeathReasonName = name
+		}
+
 		operationUser, err := s.GetSystemUserById(ctx, int64(item.OperationId))
 		if err != nil {
 			zaplog.Error("DeathBatch", zap.Any("item", item), zap.Any("err", err))
 			return xerr.Customf("获取操作人员信息失败: %d", item.OperationId)
 		}
 
+		if name, ok := s.CowDeathDestinationMap()[item.DeathDestinationKind]; ok {
+			item.DeathDestinationName = name
+		}
 		newEventDeath := model.NewEventDeath(userModel.AppPasture.Id, cow, item, userModel.SystemUser, operationUser)
 		newEventDeathList = append(newEventDeathList, &model.EventDeathModel{
 			Cow:        cow,
@@ -73,6 +80,44 @@ func (s *StoreEntry) DeathBatch(ctx context.Context, req *pasturePb.EventDeathBa
 	return nil
 }
 
+func (s *StoreEntry) DeathList(ctx context.Context, req *pasturePb.SearchEventRequest, pagination *pasturePb.PaginationModel) (*pasturePb.EventDeathResponse, error) {
+	userModel, err := s.GetUserModel(ctx)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	eventDeathList := make([]*model.EventDeath, 0)
+	pref := s.DB.Model(new(model.EventDeath)).
+		Where("pasture_id = ?", userModel.AppPasture.Id)
+
+	if req.StartDayAt > 0 && req.EndDayAt > 0 && req.EndDayAt >= req.StartDayAt {
+		pref.Where("death_at BETWEEN ? AND ?", req.StartDayAt, req.EndDayAt)
+	}
+
+	if req.EarNumber != "" {
+		pref.Where("ear_number = ?", req.EarNumber)
+	}
+
+	var count int64
+	if err = pref.Order("id desc").
+		Count(&count).Limit(int(pagination.PageSize)).
+		Offset(int(pagination.PageOffset)).
+		Find(&eventDeathList).Error; err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	return &pasturePb.EventDeathResponse{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &pasturePb.EventDeathData{
+			List:     model.EventDeathSlice(eventDeathList).ToPB(),
+			Total:    int32(count),
+			PageSize: pagination.PageSize,
+			Page:     pagination.Page,
+		},
+	}, nil
+}
+
 func (s *StoreEntry) CowEarNumberUpdate(ctx context.Context, req *pasturePb.EventReplaceEarNumber) (err error) {
 	userModel, err := s.GetUserModel(ctx)
 	if err != nil {

+ 1 - 0
module/backend/interface.go

@@ -224,6 +224,7 @@ type EventService interface {
 	CowDiseaseCurable(ctx context.Context, req *pasturePb.EventCowCurableRequest) error
 	// DeathBatch 死亡
 	DeathBatch(ctx context.Context, req *pasturePb.EventDeathBatch) error
+	DeathList(ctx context.Context, req *pasturePb.SearchEventRequest, pagination *pasturePb.PaginationModel) (*pasturePb.EventDeathResponse, error)
 	// CowEarNumberUpdate 修改耳号
 	CowEarNumberUpdate(ctx context.Context, req *pasturePb.EventReplaceEarNumber) error
 	// SubmitEventLog 记录提交事件结果日志

+ 220 - 38
module/crontab/milk_original.go

@@ -9,6 +9,8 @@ import (
 	"strings"
 	"time"
 
+	"gorm.io/gorm"
+
 	"gitee.com/xuyiping_admin/pkg/logger/zaplog"
 	"go.uber.org/zap"
 )
@@ -32,21 +34,27 @@ func (e *Entry) ProcessMilkOriginal(pastureId int64) {
 		zaplog.Error("MilkOriginal", zap.Any("pastureId", pastureId), zap.Any("err", err))
 		return
 	}
+	shifts := make([]int32, 0)
 	milkClassConfig := &MilkClassConfig{}
 	for _, v := range milkConfigList {
 		switch v.Name {
 		case model.FirstClassMilkTime:
 			milkClassConfig.FirstClassMilkTime = v.Value
+			shifts = append(shifts, 1)
 		case model.SecondClassMilkTime:
 			milkClassConfig.SecondClassMilkTime = v.Value
+			shifts = append(shifts, 2)
 		case model.ThirdClassMilkTime:
 			milkClassConfig.ThirdClassMilkTime = v.Value
+			shifts = append(shifts, 3)
 		case model.FourthClassMilkTime:
 			milkClassConfig.FourthClassMilkTime = v.Value
+			shifts = append(shifts, 4)
 		case model.UpdateMilkOriginalMaxId:
 			maxId, _ := strconv.ParseInt(v.Value, 10, 64)
 			milkClassConfig.OldUpdateMaxId = maxId
 		}
+
 	}
 	xDBeg, xBeg1, xBeg2, xBeg3, xBeg4 := parseXBeg(milkClassConfig)
 	e.UpdateShifts(pastureId, xBeg1, xBeg2, xBeg3, xBeg4)
@@ -86,11 +94,13 @@ func (e *Entry) ProcessMilkOriginal(pastureId int64) {
 		return
 	}
 
+	milkHallList := e.FindMilkHallList(pastureId)
 	e.DeleteRepeatMilkData(pastureId, deleteModel, milkClassConfig, milkOriginalList)
-	e.MilkHallData(pastureId)
+	e.UpdateRecognitionTime(pastureId, milkHallList)
 	e.UpdateRepeatCupSet1(milkOriginalList)
-	e.UpdateMilkOriginCowInfo(milkOriginalList)
+	e.UpdateMilkOriginCowInfo(milkOriginalList, milkHallList)
 	e.UpdateRepeatCupSet2(milkOriginalList)
+	e.UpdateMilkOriginalInitialTimesAndAttachAdjustTime(shifts, milkOriginalList)
 }
 
 // UpdateShifts 更新班次
@@ -219,44 +229,38 @@ func (e *Entry) DeleteRepeatMilkData(pastureId int64, deleteModel *DeleteMilkOri
 	}
 }
 
-func (e *Entry) MilkHallData(pastureId int64) {
-	milkHallList := e.FindMilkHallList(pastureId)
+// 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 milkHallList {
-		e.UpdateRecognitionTime(pastureId, v)
-	}
-}
-
-// 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())
+		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))
+			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))
+				}
 			}
 		}
 	}
@@ -298,8 +302,186 @@ func (e *Entry) UpdateRepeatCupSet1(milkOriginalList []*model.MilkOriginal) {
 	}
 }
 
-func (e *Entry) UpdateMilkOriginCowInfo(milkOriginalList []*model.MilkOriginal) {
+// 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  非标准重复套杯
@@ -308,9 +490,9 @@ func (e *Entry) UpdateRepeatCupSet2(milkOriginalList []*model.MilkOriginal) {
 		if v.AttachTime == "" || v.InitialTime == "" {
 			continue
 		}
-		attchTime, _ := util.TimeParseLocal(model.LayoutTime, v.AttachTime)
+		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 && attchTime.Sub(initialTime).Minutes() <= 1 {
+		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).

+ 12 - 0
module/crontab/sql.go

@@ -49,6 +49,18 @@ func (e *Entry) GetCowByIds(pastureId int64, cowIds []int64) ([]*model.Cow, erro
 	return cowInfoList, nil
 }
 
+func (e *Entry) GetCowByEarNumber(pastureId int64, earNumber string) (*model.Cow, error) {
+	cowInfo := &model.Cow{}
+	if err := e.DB.Model(new(model.Cow)).
+		Where("ear_number = ?", earNumber).
+		Where("pasture_id = ?", pastureId).
+		Where("admission_status = ?", pasturePb.AdmissionStatus_Admission).
+		First(cowInfo).Error; err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	return cowInfo, nil
+}
+
 func (e *Entry) GetCowByNeckRingNumbers(pastureId int64, neckRingNumbers []string) ([]*model.Cow, error) {
 	cowInfoList := make([]*model.Cow, 0)
 	if err := e.DB.Model(new(model.Cow)).

+ 22 - 0
util/util_more.go

@@ -100,3 +100,25 @@ func Substr(s string, start, length int) string {
 	}
 	return string(runes[start:l])
 }
+
+// FindMaxTime 辅助函数:获取多个时间中的最大值
+func FindMaxTime(times ...time.Time) time.Time {
+	max := times[0]
+	for _, t := range times[1:] {
+		if t.After(max) {
+			max = t
+		}
+	}
+	return max
+}
+
+// FindMinTime 辅助函数:获取多个时间中的最小值
+func FindMinTime(times ...time.Time) time.Time {
+	min := times[0]
+	for _, t := range times[1:] {
+		if t.Before(min) {
+			min = t
+		}
+	}
+	return min
+}