Browse Source

event: 增加干奶事件

Yi 1 week ago
parent
commit
0f99adb867

+ 3 - 0
README.md

@@ -35,6 +35,9 @@ lint:
 1. data_warning
 2. data_warning_items
 3. system_basic
+4. neck_ring_config
+5. app_pasture_list
+6. app_pasture_receiver
 
 
 todo列表:

+ 1 - 1
go.mod

@@ -3,7 +3,7 @@ module kpt-pasture
 go 1.17
 
 require (
-	gitee.com/xuyiping_admin/go_proto v0.0.0-20250320032539-3a62ca0c9a40
+	gitee.com/xuyiping_admin/go_proto v0.0.0-20250320095321-6b7069667e91
 	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

+ 14 - 0
go.sum

@@ -52,6 +52,20 @@ gitee.com/xuyiping_admin/go_proto v0.0.0-20250320024624-258ecc3b44f0 h1:Mrc0nMlN
 gitee.com/xuyiping_admin/go_proto v0.0.0-20250320024624-258ecc3b44f0/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
 gitee.com/xuyiping_admin/go_proto v0.0.0-20250320032539-3a62ca0c9a40 h1:B6D9VigfBV0MX0tB+8ga8GduOSC22ZBcGOomj2jAtaw=
 gitee.com/xuyiping_admin/go_proto v0.0.0-20250320032539-3a62ca0c9a40/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250320061610-ddbb79e7b9d3 h1:ZiwSIB+CkDyvPtQRT93EXljd3hem0YExL4KQvlQdhWw=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250320061610-ddbb79e7b9d3/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250320071242-e07b97a9838c h1:Hlx3n1bi+hwO+Z13CMxn8MHpcj5vIiCtntmtiyi4AEA=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250320071242-e07b97a9838c/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250320073403-e107b8471f5f h1:CBWuJ1ZS64rckGk/b4SWBIEviXlAXuSN6uwPaQksh7U=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250320073403-e107b8471f5f/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250320080904-b83f36355fdd h1:yklG8s9PxBhNhPZf3W70FXfmWMLqkXsz/keZWqcoPSk=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250320080904-b83f36355fdd/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250320085006-505f81257a63 h1:hnLXdz5JvaIGD6/HE/k72dm756th4a/392xhbMLdD/o=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250320085006-505f81257a63/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250320093538-d89d5835caf8 h1:af0uUTZDRxknoYZCBVJYycTgVOyfz0GiqCeLKgrTmQw=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250320093538-d89d5835caf8/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250320095321-6b7069667e91 h1:GBpZpPJV/5FPsv3pUZ6AYEEMjoYrVj4clyFvzjC1t0Y=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250320095321-6b7069667e91/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=

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

@@ -406,3 +406,59 @@ func WeaningCreateBatch(c *gin.Context) {
 		Data: &operationPb.Success{Success: true},
 	})
 }
+
+func DryMilkBatch(c *gin.Context) {
+	var req pasturePb.EventMilkBatch
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.Items, valid.Required, valid.Each(valid.By(func(value interface{}) error {
+			item := value.(pasturePb.EventMilkItem)
+			return valid.ValidateStruct(&item,
+				valid.Field(&item.CowId, valid.Required),
+				valid.Field(&item.DryMilkAt, valid.Required),
+				valid.Field(&item.PenId, valid.Required),
+				valid.Field(&item.OperationId, valid.Required),
+			)
+		}))),
+	); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := middleware.Dependency(c).StoreEventHub.OpsService.DryMilkBatch(c, &req); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
+}
+
+func DryMilkList(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.DryMilkList(c, &req, pagination)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	ginutil.JSONResp(c, res)
+}

+ 4 - 0
http/route/event_api.go

@@ -67,5 +67,9 @@ func EventAPI(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
 		// 免疫
 		eventRoute.POST("/immunization/batch", event.ImmunizationBatch)
 		eventRoute.POST("/immunization/list", event.ImmunizationList)
+
+		// 干奶
+		eventRoute.POST("/dry/milk/batch", event.DryMilkBatch)
+		eventRoute.POST("/dry/milk/list", event.DryMilkList)
 	}
 }

+ 1 - 0
http/route/work_api.go

@@ -19,6 +19,7 @@ func WorkOrderAPI(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
 		workRoute.PUT("/order/is_show/:id", work.OrderIsShow)
 		workRoute.GET("/user/order/list/:status", work.UserOrderList)
 
+		// 日历
 		workRoute.POST("/calendar/list", work.CalendarList)
 		workRoute.POST("/calendar/todo/list", work.CalendarToDoList)
 		workRoute.POST("/calendar/detail", work.CalendarTableDetail)

+ 3 - 1
model/calendar.go

@@ -46,9 +46,10 @@ var CalendarTypeColorMap = map[pasturePb.CalendarType_Kind]string{
 	pasturePb.CalendarType_Calving:         "#F5319D",
 	pasturePb.CalendarType_Weaning:         "#F5319D",
 	pasturePb.CalendarType_Mating:          "#F5319D",
+	pasturePb.CalendarType_DryMilk:         "#165DFF",
 }
 
-var CalendarTypeCycleMap = map[pasturePb.CalendarType_Kind]int{
+var CalendarTypeEndDaysMap = map[pasturePb.CalendarType_Kind]int{
 	pasturePb.CalendarType_Immunisation:    6,
 	pasturePb.CalendarType_PG:              0,
 	pasturePb.CalendarType_RnGH:            0,
@@ -58,6 +59,7 @@ var CalendarTypeCycleMap = map[pasturePb.CalendarType_Kind]int{
 	pasturePb.CalendarType_Calving:         14,
 	pasturePb.CalendarType_Weaning:         6,
 	pasturePb.CalendarType_Mating:          0,
+	pasturePb.CalendarType_DryMilk:         14,
 }
 
 type CalendarSlice []*Calendar

+ 33 - 22
model/cow.go

@@ -10,28 +10,30 @@ import (
 
 type Cow struct {
 	Id                  int64                          `json:"id"`
-	PastureId           int64                          `json:"pastureId"`       // 牧场id
-	Sex                 pasturePb.Genders_Kind         `json:"sex"`             // 性别
-	NeckRingNumber      string                         `json:"neckRingNumber"`  // 脖环号
-	EarNumber           string                         `json:"earNumber"`       // 耳标号
-	EleEarNumber        string                         `json:"eleEarNumber"`    // 电子耳标号
-	EarOldNumber        string                         `json:"earOldNumber"`    // 旧耳标号
-	PenId               int32                          `json:"penId"`           // 栏舍id
-	PenName             string                         `json:"penName"`         // 栏舍名称
-	Lact                int32                          `json:"lact"`            // 胎次
-	DayAge              int32                          `json:"dayAge"`          // 日龄
-	CalvingAge          int32                          `json:"calvingAge"`      // 产后天使
-	PregnancyAge        int32                          `json:"pregnancyAge"`    // 怀孕天数 孕检结果有阳性更新,产犊后至0
-	AdmissionAge        int32                          `json:"admissionAge"`    // 入场日龄
-	AbortionAge         int32                          `json:"abortionAge"`     // 流产天数
-	CowType             pasturePb.CowType_Kind         `json:"cowType"`         // 牛只类型
-	BreedStatus         pasturePb.BreedStatus_Kind     `json:"breedStatus"`     // 繁殖状态
-	CowKind             pasturePb.CowKind_Kind         `json:"cowKind"`         // 牛只品种
-	BirthWeight         int64                          `json:"birthWeight"`     // 出生体重
-	CurrentWeight       int64                          `json:"currentWeight"`   // 当前体重
-	AdmissionWeight     int64                          `json:"admissionWeight"` // 入场体重
-	SourceKind          pasturePb.CowSource_Kind       `json:"sourceKind"`      // 来源哪里
-	PurposeKind         pasturePb.Purpose_Kind         `json:"purposeKind"`
+	PastureId           int64                          `json:"pastureId"`           // 牧场id
+	Sex                 pasturePb.Genders_Kind         `json:"sex"`                 // 性别
+	NeckRingNumber      string                         `json:"neckRingNumber"`      // 脖环号
+	EarNumber           string                         `json:"earNumber"`           // 耳标号
+	EleEarNumber        string                         `json:"eleEarNumber"`        // 电子耳标号
+	EarOldNumber        string                         `json:"earOldNumber"`        // 旧耳标号
+	PenId               int32                          `json:"penId"`               // 栏舍id
+	PenName             string                         `json:"penName"`             // 栏舍名称
+	Lact                int32                          `json:"lact"`                // 胎次
+	DayAge              int32                          `json:"dayAge"`              // 日龄
+	CalvingAge          int32                          `json:"calvingAge"`          // 产后天使
+	PregnancyAge        int32                          `json:"pregnancyAge"`        // 怀孕天数 孕检结果有阳性更新,产犊后至0
+	AdmissionAge        int32                          `json:"admissionAge"`        // 入场日龄
+	AbortionAge         int32                          `json:"abortionAge"`         // 流产天数
+	LactationAge        int32                          `json:"lactationAge"`        // 泌乳天数
+	CowType             pasturePb.CowType_Kind         `json:"cowType"`             // 牛只类型
+	MilkKind            pasturePb.CowMilk_Kind         `json:"milkKind"`            // 牛只奶属性
+	BreedStatus         pasturePb.BreedStatus_Kind     `json:"breedStatus"`         // 繁殖状态
+	CowKind             pasturePb.CowKind_Kind         `json:"cowKind"`             // 牛只品种
+	BirthWeight         int64                          `json:"birthWeight"`         // 出生体重
+	CurrentWeight       int64                          `json:"currentWeight"`       // 当前体重
+	AdmissionWeight     int64                          `json:"admissionWeight"`     // 入场体重
+	SourceKind          pasturePb.CowSource_Kind       `json:"sourceKind"`          // 来源哪里
+	PurposeKind         pasturePb.Purpose_Kind         `json:"purposeKind"`         // 用途
 	FatherNumber        string                         `json:"fatherNumber"`        // 父号
 	MotherNumber        string                         `json:"motherNumber"`        // 母号
 	AdmissionStatus     pasturePb.AdmissionStatus_Kind `json:"admissionStatus"`     // 在场状态
@@ -87,6 +89,7 @@ func (c *Cow) EventCalvingUpdate(calvingAt int64) {
 	c.LastCalvingAt = calvingAt
 	c.CalvingAge = c.GetCalvingAge()
 	c.CowType = pasturePb.CowType_Breeding_Calf
+	c.MilkKind = pasturePb.CowMilk_Lactation
 }
 
 // EventWeaningUpdate 断奶更新
@@ -171,10 +174,18 @@ func (c *Cow) EventMatingUpdate(matingAt int64, bullNumber string, isReMating bo
 	}
 }
 
+// EstrusUpdate 发情更新
 func (c *Cow) EstrusUpdate(estrusAt int64) {
 	c.LastEstrusAt = estrusAt
 }
 
+func (c *Cow) EventDryMilkUpdate(dryMilkAt int64, pen *Pen) {
+	c.LastDryMilkAt = dryMilkAt
+	c.MilkKind = pasturePb.CowMilk_Dry_Milk
+	c.PenId = pen.Id
+	c.PenName = pen.Name
+}
+
 type CowSlice []*Cow
 
 func (c CowSlice) ToPB(

+ 84 - 0
model/event_dry_milk.go

@@ -0,0 +1,84 @@
+package model
+
+import (
+	"kpt-pasture/util"
+
+	pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
+)
+
+type EventDryMilk struct {
+	Id            int64                 `json:"id"`
+	PastureId     int64                 `json:"pastureId"`
+	CowId         int64                 `json:"cowId"`
+	EarNumber     string                `json:"earNumber"`
+	Lact          int32                 `json:"lact"`
+	PenId         int32                 `json:"penId"`
+	PenName       string                `json:"penName"`
+	PlanDay       int64                 `json:"planDay"`
+	RealityDay    int64                 `json:"realityDay"`
+	EndDay        int64                 `json:"endDay"`
+	IsShow        pasturePb.IsShow_Kind `json:"isShow"`
+	Remarks       string                `json:"remarks"`
+	OperationId   int64                 `json:"operationId"`
+	OperationName string                `json:"operationName"`
+	MessageId     int64                 `json:"messageId"`
+	MessageName   string                `json:"messageName"`
+	CreatedAt     int64                 `json:"createdAt"`
+	UpdatedAt     int64                 `json:"updatedAt"`
+}
+
+func (e *EventDryMilk) TableName() string {
+	return "event_dry_milk"
+}
+
+func (e *EventDryMilk) EventDryMilkUpdate(cow *Cow, dryMilkAt int64, pen *Pen, operation, message *SystemUser, remarks string) {
+	e.Lact = cow.Lact
+	e.RealityDay = dryMilkAt
+	e.Remarks = remarks
+	e.IsShow = pasturePb.IsShow_Ok
+	e.OperationId = operation.Id
+	e.OperationName = operation.Name
+	e.MessageId = message.Id
+	e.MessageName = message.Name
+	e.PenId = pen.Id
+	e.PenName = pen.Name
+}
+
+func NewEventDryMilk(pastureId int64, cow *Cow, startDay, endDay string) *EventDryMilk {
+	return &EventDryMilk{
+		PastureId: pastureId,
+		CowId:     cow.Id,
+		EarNumber: cow.EarNumber,
+		Lact:      cow.Lact,
+		PlanDay:   util.TimeParseLocalUnix(startDay),
+		EndDay:    util.TimeParseLocalUnix(endDay),
+		IsShow:    pasturePb.IsShow_No,
+	}
+}
+
+func NewEventDryMilkList(pastureId int64, cowList []*Cow, startDay, endDay string) []*EventDryMilk {
+	eventDryMilkList := make([]*EventDryMilk, 0)
+	for _, cow := range cowList {
+		eventDryMilkList = append(eventDryMilkList, NewEventDryMilk(pastureId, cow, startDay, endDay))
+	}
+	return eventDryMilkList
+}
+
+type EventDryMilkSlice []*EventDryMilk
+
+func (e EventDryMilkSlice) ToPB() []*pasturePb.EventMilkItem {
+	res := make([]*pasturePb.EventMilkItem, len(e))
+	for i, v := range e {
+		res[i] = &pasturePb.EventMilkItem{
+			Id:            int32(v.Id),
+			CowId:         int32(v.CowId),
+			EarNumber:     v.EarNumber,
+			Lact:          v.Lact,
+			PenName:       v.PenName,
+			DryMilkAt:     int32(v.RealityDay),
+			OperationName: v.OperationName,
+			Remarks:       v.Remarks,
+		}
+	}
+	return res
+}

+ 16 - 0
model/indicators_data.go

@@ -7,6 +7,22 @@ import (
 	pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
 )
 
+const (
+	AllCow            = "all_cow"
+	CalvingInterval   = "calving_interval"
+	OutputNumber      = "output_number"
+	InputNumber       = "input_number"
+	SalesVolume       = "sales_volume"
+	CalvingNumber     = "calving_number"
+	AdultAbortionRate = "adult_abortion_rate"
+	YouthAbortionRate = "youth_abortion_rate"
+	AllDieNumber      = "all_die_number"
+	DiseaseNumber     = "disease_number"
+	CureNumber        = "cure_number"
+	OutNumber         = "out_number"
+	CalfDieNumber     = "calf_die_number"
+)
+
 type IndicatorsData struct {
 	Id           int64                        `json:"id"`
 	PastureId    int64                        `json:"pastureId"`

+ 48 - 0
model/pen_behavior.go

@@ -0,0 +1,48 @@
+package model
+
+type PenBehavior struct {
+	Id             int64  `json:"id"`
+	PastureId      int64  `json:"pastureId"`
+	HeatDate       string `json:"heatDate"`
+	ActiveTime     string `json:"activeTime"`
+	PenId          int64  `json:"penId"`
+	PenName        string `json:"penName"`
+	CowCount       int32  `json:"cowCount"`
+	AvgHigh        int32  `json:"avgHigh"`
+	SumRumina      int32  `json:"sumRumina"`
+	SumIntake      int32  `json:"sumIntake"`
+	SumRest        int32  `json:"sumRest"`
+	SumGasp        int32  `json:"sumGasp"`
+	RuminaRate     int32  `json:"ruminaRate"`
+	IntakeRate     int32  `json:"intakeRate"`
+	RestRate       int32  `json:"restRate"`
+	GaspRate       int32  `json:"gaspRate"`
+	WeekRuminaRate int32  `json:"weekRuminaRate"`
+	RuminaStd      int32  `json:"ruminaStd"`
+	WeekIntakeRate int32  `json:"weekIntakeRate"`
+	IntakeStd      int32  `json:"intakeStd"`
+	WeekRestRate   int32  `json:"weekRestRate"`
+	RestStd        int32  `json:"restStd"`
+	WeekGaspRate   int32  `json:"weekGaspRate"`
+	GaspStd        int32  `json:"gaspStd"`
+	CreatedAt      int64  `json:"createdAt"`
+	UpdatedAt      int64  `json:"updatedAt"`
+}
+
+func (p *PenBehavior) TableName() string {
+	return "pen_behavior"
+}
+
+type PenBehaviorData struct {
+	PastureId int64  `json:"pastureId"`
+	PenId     int32  `json:"penId"`
+	PenName   string `json:"penName"`
+	HeatDate  string `json:"heatDate"`
+	Frameid   int32  `json:"frameid"`
+	CowCount  int32  `json:"cowCount"`
+	AvgHigh   int32  `json:"avgHigh"`
+	SumRumina int32  `json:"sumRumina"`
+	SumIntake int32  `json:"sumIntake"`
+	SumRest   int32  `json:"sumRest"`
+	SumGasp   int32  `json:"sumGasp"`
+}

+ 1 - 0
model/system_basic.go

@@ -10,6 +10,7 @@ const (
 	PregnancyAge                     = "pregnancy_age"                        // 怀孕天数
 	WeaningAge                       = "weaning_age"                          // 断奶天数
 	EstrusWaringDays                 = "estrus_waring_days"                   // 发情预警产后天数
+	DryMilkAge                       = "dry_milk_age"                         // 干奶
 
 	ValueTypeFixed = 1 // 固定值
 	ValueTypeRange = 2 // 范围值

+ 1 - 1
module/backend/analysis.go

@@ -472,7 +472,7 @@ func (s *StoreEntry) TwentyOnePregnantRate(ctx context.Context, req *pasturePb.T
 		return nil, xerr.Customf("不支持的牛只类型: %d", req.CowType)
 	}
 
-	systemBasic, err := s.GetSystemBasicByName(ctx, systemBasicName)
+	systemBasic, err := s.GetSystemBasicByName(ctx, userModel.AppPasture.Id, systemBasicName)
 	if err != nil {
 		return nil, xerr.WithStack(err)
 	}

+ 2 - 203
module/backend/calendar.go

@@ -6,8 +6,6 @@ import (
 	"kpt-pasture/model"
 	"kpt-pasture/util"
 	"net/http"
-	"regexp"
-	"strconv"
 	"time"
 
 	pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
@@ -152,6 +150,8 @@ func (s *StoreEntry) getCalendarCowList(
 		return s.MatingCowList(ctx, req, pagination)
 	case pasturePb.CalendarType_Calving: // 产犊
 		return s.CalvingCowList(ctx, req, pagination)
+	case pasturePb.CalendarType_DryMilk: // 干奶
+		return s.DryMilkCowList(ctx, req, pagination)
 	default:
 		return nil, xerr.New("不支持的日历类型")
 	}
@@ -483,204 +483,3 @@ func (s *StoreEntry) MatingCowList(ctx context.Context, req *pasturePb.ItemsRequ
 		},
 	}, nil
 }
-
-func (s *StoreEntry) CalvingCowList(ctx context.Context, req *pasturePb.ItemsRequest, pagination *pasturePb.PaginationModel) (*pasturePb.CalvingItemsResponse, error) {
-	userModel, err := s.GetUserModel(ctx)
-	if err != nil {
-		return nil, xerr.WithStack(err)
-	}
-	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`).
-		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).
-		Where("a.pasture_id = ?", userModel.AppPasture.Id)
-
-	if req.EndDay != "" {
-		dateTime := util.TimeParseLocalEndUnix(req.EndDay)
-		pref.Where("a.plan_day <= ?", dateTime)
-	}
-
-	if req.PenId > 0 {
-		pref.Where("b.pen_id = ?", req.PenId)
-	}
-
-	if err = pref.Order("a.plan_day DESC").
-		Count(&count).
-		Limit(int(pagination.PageSize)).
-		Offset(int(pagination.PageOffset)).
-		Find(&calvingItems).Error; err != nil {
-		return nil, xerr.WithStack(err)
-	}
-
-	breedStatusMap := s.CowBreedStatusMap()
-	for _, v := range calvingItems {
-		breedStatusName := ""
-		if breedStatus, ok := breedStatusMap[v.BreedStatus]; ok {
-			breedStatusName = breedStatus
-		}
-		v.BreedStatusName = breedStatusName
-	}
-
-	return &pasturePb.CalvingItemsResponse{
-		Code: http.StatusOK,
-		Msg:  "ok",
-		Data: &pasturePb.CalvingItemsData{
-			Total:    int32(count),
-			Page:     pagination.Page,
-			PageSize: pagination.PageSize,
-			Header: map[string]string{
-				"id":              "编号",
-				"cowId":           "牛号",
-				"earNumber":       "耳标号",
-				"breedStatusName": "繁殖状态",
-				"penName":         "栏舍",
-				"lact":            "胎次",
-				"matingAge":       "配后天数",
-				"dayAge":          "日龄",
-				"status":          "是否完成",
-				"bullId":          "配种公牛号",
-				"planDay":         "预产时间",
-				"matingAtFormat":  "配种时间",
-				"currentWeight":   "体重",
-			},
-			List: calvingItems,
-		},
-	}, nil
-}
-
-// TreatmentCowList 治疗清单
-func (s *StoreEntry) TreatmentCowList(ctx context.Context, req *pasturePb.ItemsRequest, pagination *pasturePb.PaginationModel) (interface{}, error) {
-	userModel, err := s.GetUserModel(ctx)
-	if err != nil {
-		return nil, xerr.WithStack(err)
-	}
-
-	diseaseItems := make([]*model.EventCowDisease, 0)
-	count := int64(0)
-
-	pref := s.DB.Table(fmt.Sprintf("%s as a", new(model.EventCowDisease).TableName())).
-		Joins(fmt.Sprintf("JOIN %s AS b on a.cow_id = b.id", new(model.Cow).TableName())).
-		Where("a.pasture_id = ?", userModel.AppPasture.Id).
-		Where("a.health_status IN (?)", []pasturePb.HealthStatus_Kind{pasturePb.HealthStatus_Disease, pasturePb.HealthStatus_Treatment})
-
-	if req.PenId > 0 {
-		pref.Where("b.pen_id = ?", req.PenId)
-	}
-
-	if err = pref.Order("a.disease_at DESC").
-		Count(&count).
-		Limit(int(pagination.PageSize)).
-		Offset(int(pagination.PageOffset)).
-		Find(&diseaseItems).Error; err != nil {
-		return nil, xerr.WithStack(err)
-	}
-
-	return &pasturePb.EventCowDiseaseResponse{
-		Code: http.StatusOK,
-		Msg:  "ok",
-		Data: &pasturePb.EventCowDiseaseData{
-			List:     model.EventCowDiseaseSlice(diseaseItems).ToPB(s.HealthStatusMap()),
-			Total:    int32(count),
-			PageSize: pagination.PageSize,
-			Page:     pagination.Page,
-			Header: map[string]string{
-				"id":                   "编号",
-				"cowId":                "牛号",
-				"earNumber":            "耳标",
-				"diagnoseName":         "疾病名称",
-				"healthStatus":         "健康状态",
-				"lastPrescriptionName": "处方名称",
-				"treatmentDays":        "治疗天数",
-				"onsetDays":            "发病天数",
-				"penName":              "栏舍名称",
-			},
-		},
-	}, nil
-}
-
-// WorkOrderCowList 暂时不处理工单业务
-func (s *StoreEntry) WorkOrderCowList(ctx context.Context, req *pasturePb.ItemsRequest, pagination *pasturePb.PaginationModel) (interface{}, error) {
-	return nil, nil
-}
-
-// Paginate 函数用于对切片进行分页
-func Paginate(slice []*pasturePb.CalendarToDoList, req *pasturePb.CalendarToDoRequest, pagination *pasturePb.PaginationModel) ([]*pasturePb.CalendarToDoList, int32) {
-	newSlice := make([]*pasturePb.CalendarToDoList, 0)
-
-	if req.CalendarType > 0 {
-		calendarTypeName := CalendarTypeMap()[req.CalendarType]
-		if len(calendarTypeName) > 0 {
-			re := regexp.MustCompile(`[a-zA-Z]`) // 使用正则表达式替换匹配的字母为空字符串
-			calendarTypeName = re.ReplaceAllString(calendarTypeName, "")
-			for _, v := range slice {
-				if v.CalendarTypeName != calendarTypeName {
-					continue
-				}
-				newSlice = append(newSlice, v)
-			}
-		}
-	} else {
-		newSlice = append(newSlice, slice...)
-	}
-
-	if req.CowId > 0 {
-		filteredSlice := make([]*pasturePb.CalendarToDoList, 0)
-		for _, v := range newSlice {
-			if v.CowId != req.CowId {
-				continue
-			}
-			filteredSlice = append(filteredSlice, v)
-		}
-		newSlice = filteredSlice
-	}
-	total := int32(len(newSlice))
-	// 计算起始索引
-	start := (pagination.Page - 1) * pagination.PageSize
-
-	// 如果起始索引超出切片长度,返回空切片
-	if start >= int32(len(newSlice)) {
-		return []*pasturePb.CalendarToDoList{}, total
-	}
-
-	// 计算结束索引
-	end := start + pagination.PageSize
-
-	// 如果结束索引超出切片长度,调整到切片末尾
-	if end > int32(len(newSlice)) {
-		end = int32(len(newSlice))
-	}
-
-	// 返回分页后的切片
-	return newSlice[start:end], total
-}
-
-func ProgressList(dataList []*pasturePb.CalendarToDoList, toDayCompletedCountMap map[string]int32) map[string]*pasturePb.ProgressList {
-	res := make(map[string]*pasturePb.ProgressList)
-	for cn, cc := range toDayCompletedCountMap {
-		res[cn] = &pasturePb.ProgressList{
-			CalendarName:   cn,
-			CompletedCount: cc,
-		}
-
-		for _, d := range dataList {
-			calendarName := CalendarTypeMap()[d.CalendarType]
-			if calendarName != cn {
-				continue
-			}
-			res[cn].CalendarTypeKind = d.CalendarType
-			res[cn].IncompleteTotal += 1
-		}
-
-		if res[cn].IncompleteTotal > 0 && res[cn].CompletedCount > 0 {
-			res[cn].Progress = strconv.FormatFloat(float64(res[cn].CompletedCount)/float64(res[cn].IncompleteTotal)*100, 'f', 2, 64)
-		} else {
-			res[cn].Progress = "0%"
-		}
-	}
-
-	return res
-}

+ 289 - 0
module/backend/calendar_more.go

@@ -0,0 +1,289 @@
+package backend
+
+import (
+	"context"
+	"fmt"
+	"kpt-pasture/model"
+	"kpt-pasture/util"
+	"net/http"
+	"regexp"
+	"strconv"
+	"time"
+
+	pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
+	"gitee.com/xuyiping_admin/pkg/xerr"
+)
+
+func (s *StoreEntry) CalvingCowList(ctx context.Context, req *pasturePb.ItemsRequest, pagination *pasturePb.PaginationModel) (*pasturePb.CalvingItemsResponse, error) {
+	userModel, err := s.GetUserModel(ctx)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	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`).
+		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).
+		Where("a.pasture_id = ?", userModel.AppPasture.Id)
+
+	if req.EndDay != "" {
+		dateTime := util.TimeParseLocalEndUnix(req.EndDay)
+		pref.Where("a.plan_day <= ?", dateTime)
+	}
+
+	if req.PenId > 0 {
+		pref.Where("b.pen_id = ?", req.PenId)
+	}
+
+	if err = pref.Order("a.plan_day DESC").
+		Count(&count).
+		Limit(int(pagination.PageSize)).
+		Offset(int(pagination.PageOffset)).
+		Find(&calvingItems).Error; err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	breedStatusMap := s.CowBreedStatusMap()
+	for _, v := range calvingItems {
+		breedStatusName := ""
+		if breedStatus, ok := breedStatusMap[v.BreedStatus]; ok {
+			breedStatusName = breedStatus
+		}
+		v.BreedStatusName = breedStatusName
+	}
+
+	return &pasturePb.CalvingItemsResponse{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &pasturePb.CalvingItemsData{
+			Total:    int32(count),
+			Page:     pagination.Page,
+			PageSize: pagination.PageSize,
+			Header: map[string]string{
+				"id":              "编号",
+				"cowId":           "牛号",
+				"earNumber":       "耳标号",
+				"breedStatusName": "繁殖状态",
+				"penName":         "栏舍",
+				"lact":            "胎次",
+				"matingAge":       "配后天数",
+				"dayAge":          "日龄",
+				"status":          "是否完成",
+				"bullId":          "配种公牛号",
+				"planDay":         "预产时间",
+				"matingAtFormat":  "配种时间",
+				"currentWeight":   "体重",
+			},
+			List: calvingItems,
+		},
+	}, nil
+}
+
+func (s *StoreEntry) DryMilkCowList(ctx context.Context, req *pasturePb.ItemsRequest, pagination *pasturePb.PaginationModel) (*pasturePb.DruMilkItemsResponse, error) {
+	userModel, err := s.GetUserModel(ctx)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	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`).
+		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).
+		Where("a.pasture_id = ?", userModel.AppPasture.Id)
+
+	if req.EndDay != "" {
+		dateTime := util.TimeParseLocalEndUnix(req.EndDay)
+		pref.Where("a.plan_day <= ?", dateTime)
+	}
+
+	if req.PenId > 0 {
+		pref.Where("b.pen_id = ?", req.PenId)
+	}
+
+	if err = pref.Order("a.plan_day DESC").
+		Count(&count).
+		Limit(int(pagination.PageSize)).
+		Offset(int(pagination.PageOffset)).
+		Find(&dryMilkItems).Error; err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	systemBasic, err := s.GetSystemBasicByName(ctx, userModel.AppPasture.Id, model.PregnancyAge)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	breedStatusMap := s.CowBreedStatusMap()
+	for _, v := range dryMilkItems {
+		breedStatusName := ""
+		if breedStatus, ok := breedStatusMap[v.BreedStatus]; ok {
+			breedStatusName = breedStatus
+		}
+		v.BreedStatusName = breedStatusName
+		v.CalvingAtFormat = time.Now().AddDate(0, 0, int(systemBasic.MinValue-v.PregnancyAge)).Format(model.LayoutDate2)
+	}
+
+	return &pasturePb.DruMilkItemsResponse{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &pasturePb.DruMilkItemsData{
+			Total:    int32(count),
+			Page:     pagination.Page,
+			PageSize: pagination.PageSize,
+			Header: map[string]string{
+				"id":              "编号",
+				"cowId":           "牛号",
+				"earNumber":       "耳标号",
+				"breedStatusName": "繁殖状态",
+				"penName":         "栏舍",
+				"lact":            "胎次",
+				"pregnancyAge":    "怀孕天数",
+				"dayAge":          "日龄",
+				"status":          "是否完成",
+				"bullNumber":      "配种公牛号",
+				"planDay":         "干奶时间",
+				"calvingAtFormat": "预产日期",
+			},
+			List: dryMilkItems,
+		},
+	}, nil
+}
+
+// TreatmentCowList 治疗清单
+func (s *StoreEntry) TreatmentCowList(ctx context.Context, req *pasturePb.ItemsRequest, pagination *pasturePb.PaginationModel) (interface{}, error) {
+	userModel, err := s.GetUserModel(ctx)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	diseaseItems := make([]*model.EventCowDisease, 0)
+	count := int64(0)
+
+	pref := s.DB.Table(fmt.Sprintf("%s as a", new(model.EventCowDisease).TableName())).
+		Joins(fmt.Sprintf("JOIN %s AS b on a.cow_id = b.id", new(model.Cow).TableName())).
+		Where("a.pasture_id = ?", userModel.AppPasture.Id).
+		Where("a.health_status IN (?)", []pasturePb.HealthStatus_Kind{pasturePb.HealthStatus_Disease, pasturePb.HealthStatus_Treatment})
+
+	if req.PenId > 0 {
+		pref.Where("b.pen_id = ?", req.PenId)
+	}
+
+	if err = pref.Order("a.disease_at DESC").
+		Count(&count).
+		Limit(int(pagination.PageSize)).
+		Offset(int(pagination.PageOffset)).
+		Find(&diseaseItems).Error; err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	return &pasturePb.EventCowDiseaseResponse{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &pasturePb.EventCowDiseaseData{
+			List:     model.EventCowDiseaseSlice(diseaseItems).ToPB(s.HealthStatusMap()),
+			Total:    int32(count),
+			PageSize: pagination.PageSize,
+			Page:     pagination.Page,
+			Header: map[string]string{
+				"id":                   "编号",
+				"cowId":                "牛号",
+				"earNumber":            "耳标",
+				"diagnoseName":         "疾病名称",
+				"healthStatus":         "健康状态",
+				"lastPrescriptionName": "处方名称",
+				"treatmentDays":        "治疗天数",
+				"onsetDays":            "发病天数",
+				"penName":              "栏舍名称",
+			},
+		},
+	}, nil
+}
+
+// WorkOrderCowList 暂时不处理工单业务
+func (s *StoreEntry) WorkOrderCowList(ctx context.Context, req *pasturePb.ItemsRequest, pagination *pasturePb.PaginationModel) (interface{}, error) {
+	return nil, nil
+}
+
+// Paginate 函数用于对切片进行分页
+func Paginate(slice []*pasturePb.CalendarToDoList, req *pasturePb.CalendarToDoRequest, pagination *pasturePb.PaginationModel) ([]*pasturePb.CalendarToDoList, int32) {
+	newSlice := make([]*pasturePb.CalendarToDoList, 0)
+
+	if req.CalendarType > 0 {
+		calendarTypeName := CalendarTypeMap()[req.CalendarType]
+		if len(calendarTypeName) > 0 {
+			re := regexp.MustCompile(`[a-zA-Z]`) // 使用正则表达式替换匹配的字母为空字符串
+			calendarTypeName = re.ReplaceAllString(calendarTypeName, "")
+			for _, v := range slice {
+				if v.CalendarTypeName != calendarTypeName {
+					continue
+				}
+				newSlice = append(newSlice, v)
+			}
+		}
+	} else {
+		newSlice = append(newSlice, slice...)
+	}
+
+	if req.CowId > 0 {
+		filteredSlice := make([]*pasturePb.CalendarToDoList, 0)
+		for _, v := range newSlice {
+			if v.CowId != req.CowId {
+				continue
+			}
+			filteredSlice = append(filteredSlice, v)
+		}
+		newSlice = filteredSlice
+	}
+	total := int32(len(newSlice))
+	// 计算起始索引
+	start := (pagination.Page - 1) * pagination.PageSize
+
+	// 如果起始索引超出切片长度,返回空切片
+	if start >= int32(len(newSlice)) {
+		return []*pasturePb.CalendarToDoList{}, total
+	}
+
+	// 计算结束索引
+	end := start + pagination.PageSize
+
+	// 如果结束索引超出切片长度,调整到切片末尾
+	if end > int32(len(newSlice)) {
+		end = int32(len(newSlice))
+	}
+
+	// 返回分页后的切片
+	return newSlice[start:end], total
+}
+
+func ProgressList(dataList []*pasturePb.CalendarToDoList, toDayCompletedCountMap map[string]int32) map[string]*pasturePb.ProgressList {
+	res := make(map[string]*pasturePb.ProgressList)
+	for cn, cc := range toDayCompletedCountMap {
+		res[cn] = &pasturePb.ProgressList{
+			CalendarName:   cn,
+			CompletedCount: cc,
+		}
+
+		for _, d := range dataList {
+			calendarName := CalendarTypeMap()[d.CalendarType]
+			if calendarName != cn {
+				continue
+			}
+			res[cn].CalendarTypeKind = d.CalendarType
+			res[cn].IncompleteTotal += 1
+		}
+
+		if res[cn].IncompleteTotal > 0 && res[cn].CompletedCount > 0 {
+			res[cn].Progress = strconv.FormatFloat(float64(res[cn].CompletedCount)/float64(res[cn].IncompleteTotal)*100, 'f', 2, 64)
+		} else {
+			res[cn].Progress = "0%"
+		}
+	}
+
+	return res
+}

+ 4 - 0
module/backend/config_data_breed.go

@@ -367,6 +367,10 @@ func CalendarTypeEnumList(isAll string) []*pasturePb.ConfigOptionsList {
 		Value:    int32(pasturePb.CalendarType_Calving),
 		Label:    "产犊",
 		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.CalendarType_DryMilk),
+		Label:    "干奶",
+		Disabled: true,
 	})
 	return configOptions
 }

+ 3 - 7
module/backend/config_data_other.go

@@ -458,7 +458,7 @@ func (s *StoreEntry) EventTypeEnumList(isAll string) []*pasturePb.ConfigOptionsL
 		Disabled: true,
 	}, &pasturePb.ConfigOptionsList{
 		Value:    int32(pasturePb.EventType_Transfer_Ben),
-		Label:    "转",
+		Label:    "转",
 		Disabled: true,
 	}, &pasturePb.ConfigOptionsList{
 		Value:    int32(pasturePb.EventType_Body_Score),
@@ -493,12 +493,8 @@ func (s *StoreEntry) EventTypeEnumList(isAll string) []*pasturePb.ConfigOptionsL
 		Label:    "死亡",
 		Disabled: true,
 	}, &pasturePb.ConfigOptionsList{
-		Value:    int32(pasturePb.EventType_Transfer_Out),
-		Label:    "转出",
-		Disabled: true,
-	}, &pasturePb.ConfigOptionsList{
-		Value:    int32(pasturePb.EventType_Transfer_In),
-		Label:    "转入",
+		Value:    int32(pasturePb.EventType_Dry_Milk),
+		Label:    "干奶",
 		Disabled: true,
 	}, &pasturePb.ConfigOptionsList{
 		Value:    int32(pasturePb.EventType_Out),

+ 1 - 2
module/backend/enum_map.go

@@ -319,9 +319,8 @@ func (s *StoreEntry) eventCategoryMap() map[pasturePb.EventType_Kind]pasturePb.E
 		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_Dry_Milk:         pasturePb.EventCategory_Breed,
 		pasturePb.EventType_Estrus:           pasturePb.EventCategory_Breed,
 		pasturePb.EventType_Calving:          pasturePb.EventCategory_Breed,
 		pasturePb.EventType_Seme_Time:        pasturePb.EventCategory_Breed,

+ 120 - 0
module/backend/event_breed_more_two.go

@@ -0,0 +1,120 @@
+package backend
+
+import (
+	"context"
+	"kpt-pasture/model"
+	"net/http"
+
+	"gorm.io/gorm"
+
+	"gitee.com/xuyiping_admin/pkg/xerr"
+
+	pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
+)
+
+func (s *StoreEntry) DryMilkList(ctx context.Context, req *pasturePb.SearchEventRequest, pagination *pasturePb.PaginationModel) (*pasturePb.EventMilkResponse, error) {
+	userModel, err := s.GetUserModel(ctx)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	dryMilkList := make([]*model.EventDryMilk, 0)
+	var count int64 = 0
+	pref := s.DB.Model(new(model.EventDryMilk)).
+		Where("pasture_id = ?", userModel.AppPasture.Id).
+		Where("is_show = ?", pasturePb.IsShow_Ok)
+
+	if req.EarNumber != "" {
+		pref.Where("ear_number = ?", req.EarNumber)
+	}
+
+	if req.StartDayAt > 0 && req.EndDayAt > 0 && req.StartDayAt <= req.EndDayAt {
+		pref.Where("reality_day BETWEEN ? AND ?", req.StartDayAt, req.EndDayAt)
+	}
+
+	if err = pref.Order("id desc").
+		Count(&count).Limit(int(pagination.PageSize)).
+		Offset(int(pagination.PageOffset)).
+		Find(&dryMilkList).Error; err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	return &pasturePb.EventMilkResponse{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &pasturePb.EventMilkData{
+			List:     model.EventDryMilkSlice(dryMilkList).ToPB(),
+			Total:    int32(count),
+			PageSize: pagination.PageSize,
+			Page:     pagination.Page,
+		},
+	}, nil
+}
+
+func (s *StoreEntry) DryMilkBatch(ctx context.Context, req *pasturePb.EventMilkBatch) error {
+	if len(req.Items) <= 0 {
+		return nil
+	}
+
+	if len(req.Items) > 50 {
+		return xerr.Custom("最多只能添加50条数据")
+	}
+
+	userModel, err := s.GetUserModel(ctx)
+	if err != nil {
+		return xerr.WithStack(err)
+	}
+
+	penMap := s.PenMap(ctx, userModel.AppPasture.Id)
+	if err = s.DB.Transaction(func(tx *gorm.DB) error {
+		for _, v := range req.Items {
+			cowInfo, err := s.GetCowInfoByCowId(ctx, userModel.AppPasture.Id, int64(v.CowId))
+			if err != nil {
+				return xerr.WithStack(err)
+			}
+
+			cowInfo.EventDryMilkUpdate(int64(v.DryMilkAt), penMap[v.PenId])
+			if err = tx.Select("milk_kind", "last_dry_milk_at", "pen_id", "pen_name").
+				Where("id = ?", cowInfo.Id).
+				Updates(cowInfo).Error; err != nil {
+				return xerr.WithStack(err)
+			}
+
+			eventDryMilk := &model.EventDryMilk{}
+			if err = tx.Model(new(model.EventDryMilk)).
+				Where("pasture_id = ?", userModel.AppPasture.Id).
+				Where("cow_id = ?", cowInfo.Id).
+				Where("is_show = ?", pasturePb.IsShow_No).
+				First(eventDryMilk).Error; err != nil {
+				return xerr.WithStack(err)
+			}
+
+			if eventDryMilk.Id <= 0 {
+				return xerr.Customf("不存在该数据: %s", cowInfo.EarNumber)
+			}
+
+			operationUser, err := s.GetSystemUserById(ctx, int64(v.OperationId))
+			if err != nil {
+				return xerr.Customf("获取操作人员信息失败")
+			}
+
+			eventDryMilk.EventDryMilkUpdate(cowInfo, int64(v.DryMilkAt), penMap[v.PenId], operationUser, userModel.SystemUser, v.Remarks)
+			if err = tx.Select("lact", "reality_day", "plan_day", "is_show", "operation_id", "operation_name", "message_id", "message_name", "pen_id", "pen_name", "remarks").
+				Where("id = ?", eventDryMilk.Id).
+				Updates(eventDryMilk).Error; err != nil {
+				return xerr.WithStack(err)
+			}
+
+			// 记录日志
+			cowLog := s.SubmitEventLog(ctx, userModel.AppPasture.Id, cowInfo, pasturePb.EventType_Dry_Milk, eventDryMilk)
+			if err = tx.Table(cowLog.TableName()).Create(cowLog).Error; err != nil {
+				return xerr.WithStack(err)
+			}
+		}
+
+		return nil
+	}); err != nil {
+		return xerr.WithStack(err)
+	}
+	return nil
+}

+ 5 - 2
module/backend/event_cow_log.go

@@ -132,8 +132,11 @@ func (s *StoreEntry) SubmitEventLog(ctx context.Context, pastureId int64, cow *m
 		desc = fmt.Sprintf("死亡原因: %s", data.ReasonName)
 		operationUser.Id = data.OperationId
 		operationUser.Name = data.OperationName
-	case pasturePb.EventType_Transfer_Out:
-	case pasturePb.EventType_Transfer_In:
+	case pasturePb.EventType_Dry_Milk:
+		data := req.(*model.EventDryMilk)
+		eventAt = data.RealityDay
+		operationUser.Id = data.OperationId
+		operationUser.Name = data.OperationName
 	case pasturePb.EventType_Out:
 		data := req.(*model.EventDeparture)
 		eventAt = data.DepartureAt

+ 12 - 0
module/backend/interface.go

@@ -160,18 +160,23 @@ type EventService interface {
 	// EnterList 入场列表
 	EnterList(ctx context.Context, req *pasturePb.SearchEventRequest, pagination *pasturePb.PaginationModel) (*pasturePb.SearchEnterEventResponse, error)
 	CreateEnter(ctx context.Context, req *pasturePb.EventEnterRequest) error
+
 	// GroupTransferList 转群
 	GroupTransferList(ctx context.Context, req *pasturePb.SearchEventRequest, pagination *pasturePb.PaginationModel) (*pasturePb.SearchTransferGroupEventResponse, error)
 	CreateGroupTransfer(ctx context.Context, req *pasturePb.TransferGroupEventRequest) error
+
 	// BodyScoreList 体况评分
 	BodyScoreList(ctx context.Context, req *pasturePb.SearchEventRequest, pagination *pasturePb.PaginationModel) (*pasturePb.SearchBodyScoreEventResponse, error)
 	CreateBodyScore(ctx context.Context, req *pasturePb.BodyScoreEventRequest) error
+
 	// CalvingList 分娩
 	CalvingList(ctx context.Context, req *pasturePb.SearchEventRequest, pagination *pasturePb.PaginationModel) (*pasturePb.SearchLavingEventResponse, error)
 	CalvingCreate(ctx context.Context, req *pasturePb.EventCalving) error
+
 	// PregnantCheckList 孕检
 	PregnantCheckList(ctx context.Context, req *pasturePb.SearchEventRequest, pagination *pasturePb.PaginationModel) (*pasturePb.EventPregnantCheckResponse, error)
 	PregnantCheckCreateBatch(ctx context.Context, req *pasturePb.EventPregnantCheckBatch) error
+
 	// MatingList 配种
 	MatingList(ctx context.Context, req *pasturePb.SearchEventRequest, pagination *pasturePb.PaginationModel) (*pasturePb.EventMatingResponse, error)
 	MatingBatch(ctx context.Context, req *pasturePb.EventMatingBatch) error
@@ -182,12 +187,19 @@ type EventService interface {
 	// AbortionList 流产
 	AbortionList(ctx context.Context, req *pasturePb.SearchEventRequest, pagination *pasturePb.PaginationModel) (*pasturePb.EventAbortionResponse, error)
 	AbortionCreateBatch(ctx context.Context, req *pasturePb.EventAbortionBatch) error
+
+	// DryMilkList 干奶
+	DryMilkList(ctx context.Context, req *pasturePb.SearchEventRequest, pagination *pasturePb.PaginationModel) (*pasturePb.EventMilkResponse, error)
+	DryMilkBatch(ctx context.Context, req *pasturePb.EventMilkBatch) error
+
 	// WeaningBatch 断奶
 	WeaningBatch(ctx context.Context, req *pasturePb.EventWeaningBatch) error
+
 	// SameTimeCreate 同期
 	SameTimeCreate(ctx context.Context, req *pasturePb.EventSameTime) error
 	SameTimeBatch(ctx context.Context, req *pasturePb.EventSameTimeBatch) error
 	SameTimeList(ctx context.Context, req *pasturePb.SearchEventRequest, pagination *pasturePb.PaginationModel) (*pasturePb.SearchSameTimeResponse, error)
+
 	// WeightList 称重
 	WeightList(ctx context.Context, req *pasturePb.SearchEventRequest, pagination *pasturePb.PaginationModel) (*pasturePb.SearchWeightEventResponse, error)
 	WeightBatch(ctx context.Context, req *pasturePb.BatchEventWeight) error

+ 10 - 10
module/backend/neck_ring_warning.go

@@ -25,7 +25,16 @@ func (s *StoreEntry) EstrusOrAbortionCowList(ctx context.Context, req *pasturePb
 	neckRingEstrusList := make([]*model.NeckRingEstrusWarning, 0)
 	var pref *gorm.DB
 	switch req.Kind {
-	case "estrus":
+	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)
+	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)
@@ -47,15 +56,6 @@ func (s *StoreEntry) EstrusOrAbortionCowList(ctx context.Context, req *pasturePb
 					Where("b.calving_age > ?", systemBasic.MinValue).
 					Or("b.lact = ?", 0))).
 			Where("a.is_show = ?", pasturePb.IsShow_Ok)
-	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)
 	}
 
 	if len(req.EarNumber) > 0 {

+ 1 - 1
module/backend/sql.go

@@ -358,7 +358,7 @@ func (s *StoreEntry) GetSystemDeptListById(ctx context.Context, id int64) (*mode
 	return systemDept, nil
 }
 
-func (s *StoreEntry) GetSystemBasicByName(ctx context.Context, name string) (*model.SystemBasic, error) {
+func (s *StoreEntry) GetSystemBasicByName(ctx context.Context, pastureId int64, name string) (*model.SystemBasic, error) {
 	systemBasic := &model.SystemBasic{}
 	if err := s.DB.Model(new(model.SystemBasic)).
 		Where("name = ?", name).

+ 36 - 21
module/crontab/cow_cron.go

@@ -64,35 +64,34 @@ func (e *Entry) Indicators() error {
 
 	pastureList := e.FindPastureList()
 	startTime, endTime := util.GetMonthStartAndEndTimestamp()
-	zaplog.Info("Indicators", zap.Any("startTime", startTime), zap.Any("indicatorsDetailsList", indicatorsDetailsList))
 	for _, indicatorsDetail := range indicatorsDetailsList {
 		pastureIndicatorList := map[int64]string{}
 		switch indicatorsDetail.Kind {
-		case "all_cow":
+		case model.AllCow:
 			pastureIndicatorList = e.FindPastureAllCow(pastureList)
-		case "calving_interval":
+		case model.CalvingInterval:
 			pastureIndicatorList = e.FindCalvingInterval(pastureList, startTime, endTime)
-		case "output_number":
+		case model.OutputNumber:
 			pastureIndicatorList = e.FindOutputNumber(pastureList, startTime, endTime)
-		case "input_number":
+		case model.InputNumber:
 			pastureIndicatorList = e.FindInputNumber(pastureList, startTime, endTime)
-		case "sales_volume":
+		case model.SalesVolume:
 			pastureIndicatorList = e.FindSalesVolume(pastureList, startTime, endTime)
-		case "calving_number":
+		case model.CalvingNumber:
 			pastureIndicatorList = e.FindCalvingNumber(pastureList, startTime, endTime)
-		case "adult_abortion_rate":
+		case model.AdultAbortionRate:
 			pastureIndicatorList = e.FindAdultAbortionRate(pastureList, "adult", startTime, endTime)
-		case "youth_abortion_rate":
+		case model.YouthAbortionRate:
 			pastureIndicatorList = e.FindAdultAbortionRate(pastureList, "youth", startTime, endTime)
-		case "all_die_number":
+		case model.AllDieNumber:
 			pastureIndicatorList = e.FindDepartureNumber(pastureList, pasturePb.DepartureType_Death, startTime, endTime)
-		case "disease_number":
+		case model.DiseaseNumber:
 			pastureIndicatorList = e.FindDiseaseNumber(pastureList, startTime, endTime)
-		case "cure_number":
+		case model.CureNumber:
 			pastureIndicatorList = e.FindCureNumber(pastureList, startTime, endTime)
-		case "out_number":
+		case model.OutNumber:
 			pastureIndicatorList = e.FindDepartureNumber(pastureList, pasturePb.DepartureType_Out, startTime, endTime)
-		case "calf_die_number":
+		case model.CalfDieNumber:
 			pastureIndicatorList = e.FindCalfDieNumber(pastureList, pasturePb.DepartureType_Death, startTime, endTime)
 		}
 
@@ -198,7 +197,7 @@ func (e *Entry) ImmunizationPlan() error {
 
 		todayCount = int32(len(newImmunizationPlanCowList))
 		if todayCount > 0 {
-			endDay := nowTime.AddDate(0, 0, model.CalendarTypeCycleMap[pasturePb.CalendarType_Immunisation])
+			endDay := nowTime.AddDate(0, 0, model.CalendarTypeEndDaysMap[pasturePb.CalendarType_Immunisation])
 			e.CreatedCalendar(plan.PastureId, pasturePb.CalendarType_Immunisation, nowTime.Format(model.LayoutDate2), endDay.Format(model.LayoutDate2), todayCount)
 		}
 		e.CreateCrontabLog(ImmunizationPlan)
@@ -330,7 +329,9 @@ func (e *Entry) SystemBasicCrontab() error {
 			model.PregnantCheckForSecond,
 			model.WeaningAge,
 			model.PregnancyAge,
-		}).Find(&systemBasicList).Error; err != nil {
+			model.DryMilkAge,
+		}).Order("pasture_id").
+		Find(&systemBasicList).Error; err != nil {
 		zaplog.Error("crontab", zap.Any("PregnancyCheck", err))
 		return xerr.WithStack(err)
 	}
@@ -338,7 +339,8 @@ func (e *Entry) SystemBasicCrontab() error {
 	currWeekValue := time.Now().Weekday()
 	for _, systemBasic := range systemBasicList {
 		// 周执行
-		if systemBasic.Name == model.PregnantCheckForFirst && systemBasic.WeekValue >= 0 && time.Weekday(systemBasic.WeekValue) != currWeekValue {
+		if systemBasic.Name == model.PregnantCheckForFirst && systemBasic.WeekValue >= 0 &&
+			time.Weekday(systemBasic.WeekValue) != currWeekValue {
 			continue
 		}
 
@@ -364,9 +366,14 @@ func (e *Entry) SystemBasicCrontab() error {
 			pref.Where("day_age = ?", systemBasic.MinValue).
 				Where("NOT EXISTS (SELECT 1 FROM event_weaning WHERE event_weaning.cow_id = cow.id AND event_weaning.status = ?)", pasturePb.IsShow_No)
 		case model.PregnancyAge: // 产犊清单
-			pref.Where("pregnancy_age BETWEEN ? AND ?", systemBasic.MinValue, systemBasic.MaxValue).
+			pref.Where("pregnancy_age = ?", systemBasic.MinValue-10).
 				Where("breed_status = ?", pasturePb.BreedStatus_Pregnant).
 				Where("NOT EXISTS (SELECT 1 FROM event_calving WHERE event_calving.cow_id = cow.id AND event_calving.status = ?)", pasturePb.IsShow_No)
+		case model.DryMilkAge: // 干奶清单
+			pref.Where("pregnancy_age = ?", systemBasic.MinValue).
+				Where("breed_status = ?", pasturePb.BreedStatus_Pregnant).
+				Where("is_pregnant = ?", pasturePb.IsShow_Ok).
+				Where("NOT EXISTS (SELECT 1 FROM event_dry_milk WHERE event_dry_milk.cow_id = cow.id AND event_dry_milk.status = ?)", pasturePb.IsShow_No)
 		default:
 			continue
 		}
@@ -427,7 +434,7 @@ func (e *Entry) InitEventData(cowList []*model.Cow, systemBasic *model.SystemBas
 	case model.PregnantCheckForFirst, model.PregnantCheckForSecond:
 		penMap, _ := e.GetPenMapList(systemBasic.PastureId)
 		calendarType = pasturePb.CalendarType_Pregnancy_Check
-		startDay, endDay = nowTime.Format(model.LayoutDate2), nowTime.AddDate(0, 0, model.CalendarTypeCycleMap[calendarType]).Format(model.LayoutDate2)
+		startDay, endDay = nowTime.Format(model.LayoutDate2), nowTime.AddDate(0, 0, model.CalendarTypeEndDaysMap[calendarType]).Format(model.LayoutDate2)
 		eventPregnantCheckDataList := model.NewEventPregnantCheckList(systemBasic.PastureId, cowList, penMap, systemBasic.Name, startDay, endDay)
 		if err := e.DB.Model(new(model.EventPregnantCheck)).Create(eventPregnantCheckDataList).Error; err != nil {
 			zaplog.Error("crontab", zap.Any("InitEventData", err), zap.Any("eventPregnantCheckDataList", eventPregnantCheckDataList))
@@ -435,7 +442,7 @@ func (e *Entry) InitEventData(cowList []*model.Cow, systemBasic *model.SystemBas
 		}
 	case model.WeaningAge:
 		calendarType = pasturePb.CalendarType_Weaning
-		startDay, endDay = nowTime.Format(model.LayoutDate2), nowTime.AddDate(0, 0, model.CalendarTypeCycleMap[calendarType]).Format(model.LayoutDate2)
+		startDay, endDay = nowTime.Format(model.LayoutDate2), nowTime.AddDate(0, 0, model.CalendarTypeEndDaysMap[calendarType]).Format(model.LayoutDate2)
 		eventWeaningDataList := model.NewEventWeaningList(systemBasic.PastureId, cowList, startDay, endDay)
 		if err := e.DB.Model(new(model.EventWeaning)).Create(eventWeaningDataList).Error; err != nil {
 			zaplog.Error("crontab", zap.Any("InitEventData", err), zap.Any("eventWeaningDataList", eventWeaningDataList))
@@ -443,12 +450,20 @@ func (e *Entry) InitEventData(cowList []*model.Cow, systemBasic *model.SystemBas
 		}
 	case model.PregnancyAge:
 		calendarType = pasturePb.CalendarType_Calving
-		startDay, endDay = nowTime.Format(model.LayoutDate2), nowTime.AddDate(0, 0, model.CalendarTypeCycleMap[calendarType]).Format(model.LayoutDate2)
+		startDay, endDay = nowTime.Format(model.LayoutDate2), nowTime.AddDate(0, 0, model.CalendarTypeEndDaysMap[calendarType]).Format(model.LayoutDate2)
 		eventCalvingList := model.NewEventCalvingList(systemBasic.PastureId, cowList, startDay, endDay)
 		if err := e.DB.Model(new(model.EventCalving)).Create(eventCalvingList).Error; err != nil {
 			zaplog.Error("crontab", zap.Any("InitEventData", err), zap.Any("eventCalvingList", eventCalvingList))
 			return
 		}
+	case model.DryMilkAge:
+		calendarType = pasturePb.CalendarType_DryMilk
+		startDay, endDay = nowTime.Format(model.LayoutDate2), nowTime.AddDate(0, 0, model.CalendarTypeEndDaysMap[calendarType]).Format(model.LayoutDate2)
+		eventCalvingList := model.NewEventDryMilkList(systemBasic.PastureId, cowList, startDay, endDay)
+		if err := e.DB.Model(new(model.EventDryMilk)).Create(eventCalvingList).Error; err != nil {
+			zaplog.Error("crontab", zap.Any("InitEventData", err), zap.Any("eventCalvingList", eventCalvingList))
+			return
+		}
 	}
 	e.CreatedCalendar(systemBasic.PastureId, calendarType, startDay, endDay, int32(len(cowList)))
 }

+ 1 - 1
module/crontab/estrus_warning.go

@@ -61,7 +61,7 @@ func (e *Entry) UpdateNeckRingWarning(pastureId int64) (err error) {
 	zaplog.Info("UpdateNeckRingWarning", zap.Any("neckRingEstrusWarningList", neckRingEstrusWarningList))
 
 	if len(neckRingEstrusWarningList) > 0 {
-		if err = e.DB.CreateInBatches(neckRingEstrusWarningList, 50).Error; err != nil {
+		if err = e.DB.Create(neckRingEstrusWarningList).Error; err != nil {
 			return xerr.WithStack(err)
 		}
 	} else {

+ 1 - 1
module/crontab/health_warning.go

@@ -100,7 +100,7 @@ func (e *Entry) UpdateNeckRingHealth(pastureId int64) error {
 		if newScore > healthValue {
 			continue
 		}
-		cowInfo, err := e.GetCowById(v.CowId)
+		cowInfo, err := e.GetCowById(pastureId, v.CowId)
 		if err != nil {
 			continue
 		}

+ 3 - 0
module/crontab/neck_ring_calculate.go

@@ -82,6 +82,9 @@ func (e *Entry) EntryUpdateActiveHabit(pastureId int64) (err error) {
 
 	// 健康预警
 	e.HealthWarning(pastureId, processIds)
+
+	// 栏舍行为曲线
+	e.PenBehavior(pastureId, processIds)
 	return nil
 }
 

+ 2 - 2
module/crontab/neck_ring_estrus.go

@@ -138,8 +138,8 @@ func (e *Entry) CowEstrusWarning(pastureId int64, xToday *XToday, nowTime time.T
 
 		if (int32(maxCft) > before3Data.DayHigh || before3Data.CowId == 0 || b48 > B48) && int32(maxCft)+cow21Estrus.HadJust > xToday.ActiveLow {
 			level := calculateActiveLevel(maxCft, cow21Estrus, xToday)
-			cowInfo := e.FindCowInfoByCowId(cowId)
-			if cowInfo == nil {
+			cowInfo, err := e.GetCowById(pastureId, cowId)
+			if err != nil || cowInfo == nil {
 				zaplog.Error("CowEstrusWarning", zap.Any("FindCowInfoByCowId", cowId))
 				continue
 			}

+ 80 - 0
module/crontab/pen_behavior.go

@@ -0,0 +1,80 @@
+package crontab
+
+import (
+	"fmt"
+	"kpt-pasture/model"
+
+	"gitee.com/xuyiping_admin/pkg/logger/zaplog"
+	"go.uber.org/zap"
+)
+
+// PenBehavior 栏舍行为曲线
+func (e *Entry) PenBehavior(pastureId int64, processIds []int64) {
+	neckRingOriginalList := make([]*model.NeckRingOriginal, 0)
+	if err := e.DB.Model(new(model.NeckRingOriginal)).
+		Where("id IN (?)", processIds).
+		Where("pasture_id = ?", pastureId).
+		Order("heat_date,neck_ring_number,frameid").
+		Find(&neckRingOriginalList).Error; err != nil {
+		zaplog.Error("PenBehavior", zap.Any("error", err), zap.Any("processIds", processIds))
+	}
+
+	cowIds := make([]int64, 0)
+	for _, v := range neckRingOriginalList {
+		cowIds = append(cowIds, v.Id)
+	}
+
+	cowInfoList, err := e.GetCowByIds(pastureId, cowIds)
+	if err != nil {
+		zaplog.Error("PenBehavior", zap.Any("error", err), zap.Any("cowIds", cowIds))
+		return
+	}
+	cowMap := make(map[string]*model.Cow)
+	for _, v := range cowInfoList {
+		if v.NeckRingNumber == "" {
+			continue
+		}
+		cowMap[v.NeckRingNumber] = v
+	}
+
+	penData := make(map[string]*model.PenBehaviorData)
+	for _, v := range neckRingOriginalList {
+		cowInfo, ok := cowMap[v.NeckRingNumber]
+		if !ok {
+			zaplog.Error("PenBehavior", zap.Any("error", err), zap.Any("neckRingNumber", v.NeckRingNumber))
+			continue
+		}
+
+		key := fmt.Sprintf("%s_%d_%d", v.ActiveDate, cowInfo.PenId, v.Frameid)
+		if penData[key] == nil {
+			penData[key] = &model.PenBehaviorData{
+				PastureId: pastureId,
+				PenId:     cowInfo.PenId,
+				PenName:   cowInfo.PenName,
+				HeatDate:  v.ActiveDate,
+				Frameid:   v.Frameid,
+				CowCount:  1,
+				AvgHigh:   v.High,
+			}
+		} else {
+			penData[key].CowCount++
+			penData[key].AvgHigh += v.High
+			penData[key].SumRumina += ifThenElse(v.Rumina >= 8, 1, 0)
+			penData[key].SumIntake += ifThenElse(v.Intake >= 8, 1, 0)
+			penData[key].SumRest += ifThenElse(v.Inactive >= 8, 1, 0)
+			penData[key].SumGasp += ifThenElse(v.Gasp >= 8, 1, 0)
+		}
+	}
+	// 计算平均值
+	for _, data := range penData {
+		data.AvgHigh = data.AvgHigh / data.CowCount
+	}
+
+}
+
+func ifThenElse(condition bool, a, b int32) int32 {
+	if condition {
+		return a
+	}
+	return b
+}

+ 27 - 14
module/crontab/sql.go

@@ -25,16 +25,41 @@ func (e *Entry) FindPastureList() []*model.AppPastureList {
 	return res
 }
 
-func (e *Entry) GetCowById(cowId int64) (*model.Cow, error) {
+func (e *Entry) GetCowById(pastureId, cowId int64) (*model.Cow, error) {
 	cowInfo := &model.Cow{}
 	if err := e.DB.Model(new(model.Cow)).
 		Where("id = ?", cowId).
 		Where("admission_status = ?", pasturePb.AdmissionStatus_Admission).
 		First(cowInfo).Error; err != nil {
-		return nil, err
+		return nil, xerr.WithStack(err)
+	}
+	return cowInfo, nil
+}
+
+func (e *Entry) GetCowByIds(pastureId int64, cowIds []int64) ([]*model.Cow, error) {
+	cowInfoList := make([]*model.Cow, 0)
+	if err := e.DB.Model(new(model.Cow)).
+		Where("pasture_id = ?", pastureId).
+		Where("id IN (?)", cowIds).
+		Where("admission_status = ?", pasturePb.AdmissionStatus_Admission).
+		Find(&cowInfoList).Error; err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	return cowInfoList, nil
+}
+
+func (e *Entry) GetCowByNeckRingNumber(pastureId int64, neckRingNumber string) (*model.Cow, error) {
+	cowInfo := &model.Cow{}
+	if err := e.DB.Model(new(model.Cow)).
+		Where("pasture_id = ?", pastureId).
+		Where("neck_ring_number = ?", neckRingNumber).
+		Where("admission_status = ?", pasturePb.AdmissionStatus_Admission).
+		First(cowInfo).Error; err != nil {
+		return nil, xerr.WithStack(err)
 	}
 	return cowInfo, nil
 }
+
 func (e *Entry) GetPenMapList(pastureId int64) (map[int32]*model.Pen, error) {
 	penList := make([]*model.Pen, 0)
 	if err := e.DB.Model(new(model.Pen)).
@@ -79,18 +104,6 @@ func (e *Entry) GetTwoEstrus(pastureId, cowId int64, startActiveTime, endActiveT
 	return newCowEstrus
 }
 
-func (e *Entry) FindCowInfoByCowId(cowId int64) *model.Cow {
-	res := &model.Cow{}
-	if err := e.DB.Model(new(model.Cow)).
-		Where("id = ?", cowId).
-		Where("admission_status = ?", pasturePb.AdmissionStatus_Admission).
-		First(res).Error; err != nil {
-		zaplog.Error("FindCowInfoByCowId", zap.Any("cowId", cowId), zap.Any("err", err))
-		return nil
-	}
-	return res
-}
-
 func (e *Entry) IsExistEventEstrus(pastureId, cowId int64) *model.EventEstrus {
 	res := &model.EventEstrus{}
 	if err := e.DB.Model(new(model.EventEstrus)).