Browse Source

event: 配种数据提交优化

Yi 2 months ago
parent
commit
420d489932

+ 1 - 2
config/app.develop.yaml

@@ -45,8 +45,7 @@ cron:
   cow_pregnant: "0 00 15 * * ?"
   neck_ring: "*/30 * * * * ?"
 mqtt:
-  broker: "kptyun.com"
-  port: 1983
+  broker: "kptyun.com:1983"
   username: "kptmqtt"
   password: "kepaiteng"
   sub_topic: "kptmqtt"

+ 0 - 1
config/app.go

@@ -159,7 +159,6 @@ type AsynqRedisSetting struct {
 
 type MqttSetting struct {
 	Broker            string `json:"broker" yaml:"broker"`
-	Port              int    `json:"port" yaml:"port"`
 	UserName          string `json:"username" yaml:"username"`
 	Password          string `json:"password" yaml:"password"`
 	SubTopic          string `json:"sub_topic"  yaml:"sub_topic"`

+ 1 - 23
config/app.test.yaml

@@ -22,30 +22,8 @@ redis_setting:
 jwt_secret: "sUd7j%UfJMt59ywh"
 cache_key_suffix: "gmym"
 
-side_work_setting:
-  asynq_setting:
-    redis:
-      addr: '47.92.95.119:6389'
-      db: 0
-      pool_size: 10
-    concurrency: 5
-    queues:
-      work: 20
-      low: 10
-      default: 5
-cron:
-  crontab_start_run: false
-  update_cow_info: "0 01 1 * * ?"
-  generate_work_order: "0 05 1 * * ?"
-  immunization_plan: "0 10 1 * * ?"
-  same_time_plan: "0 15 1 * * ?"
-  update_same_time: "0 20 1 * * ?"
-  system_basic_crontab: "0 25 1 * * ?"
-  cow_pregnant: "0 00 15 * * ?"
-  neck_ring: "0 30 * * * ?"
 mqtt:
-  broker: "kptyun.com"
-  port: 1983
+  broker: "kptyun.com:1983"
   username: "kptmqtt"
   password: "kepaiteng"
   sub_topic: "kptmqtt"

+ 1 - 1
go.mod

@@ -3,7 +3,7 @@ module kpt-pasture
 go 1.17
 
 require (
-	gitee.com/xuyiping_admin/go_proto v0.0.0-20241230072900-e3918c6dc9ee
+	gitee.com/xuyiping_admin/go_proto v0.0.0-20250103030611-2b0c271353c7
 	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

@@ -98,6 +98,18 @@ gitee.com/xuyiping_admin/go_proto v0.0.0-20241226082956-7a730dffe7ba h1:6j33896H
 gitee.com/xuyiping_admin/go_proto v0.0.0-20241226082956-7a730dffe7ba/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
 gitee.com/xuyiping_admin/go_proto v0.0.0-20241230072900-e3918c6dc9ee h1:x5daNVOieKpmVAREZAFuuFaqjJlDBHJ2dw0J1dyhfzc=
 gitee.com/xuyiping_admin/go_proto v0.0.0-20241230072900-e3918c6dc9ee/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250102040428-4907b3d9afb5 h1:1Mh6q2pKo2w/WeJVeI3GuZLDL0WGqlluuJKypUDDYcg=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250102040428-4907b3d9afb5/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250102061132-210fa4d452be h1:w2OuLWbvxhZqHEoYdiSTQNWIrYuDbmaOSWnWgm2hcRk=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250102061132-210fa4d452be/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250102061445-bf2614513688 h1:gUcI0NVK7YfrdHtp+MqZ1uGLPaVEnqrt0zoB5oVyWC4=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250102061445-bf2614513688/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250102074750-5967657b1847 h1:Z08PI5I5hkjo2zW3Ibv1TKd28O6ERSqMsNGMYVaUIe4=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250102074750-5967657b1847/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250102080429-bbf8aeeb608c h1:Fqi07TncsGpYg4B+T9VsMPG4a1zmokH5xnUmmYHhlpI=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250102080429-bbf8aeeb608c/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250103030611-2b0c271353c7 h1:mk2G7/x2g2WLbCFw7ThZ7F5HOJ9AfAidG7ilhit67bM=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250103030611-2b0c271353c7/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=

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

@@ -242,7 +242,7 @@ func Departure(c *gin.Context) {
 			return valid.ValidateStruct(&s,
 				valid.Field(&s.DepartureAt, valid.Required),
 				valid.Field(&s.DepartureType, valid.Required),
-				valid.Field(&s.DepartureReason, valid.Required),
+				valid.Field(&s.DepartureReasonKind, valid.Required),
 				valid.Field(&s.OperationId, valid.Required),
 				valid.Field(&s.CowId, valid.Required),
 			)
@@ -263,3 +263,30 @@ func Departure(c *gin.Context) {
 		Data: &operationPb.Success{Success: true},
 	})
 }
+
+func CowEarNumber(c *gin.Context) {
+	var req pasturePb.EventReplaceEarNumber
+	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.EarNumber, valid.Required),
+	); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	if err := middleware.BackendOperation(c).OpsService.CowEarNumberUpdate(c, &req); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
+}

+ 2 - 2
http/handler/event/event_breed.go

@@ -200,7 +200,7 @@ func EstrusEventList(c *gin.Context) {
 	ginutil.JSONResp(c, res)
 }
 
-func EstrusBatch(c *gin.Context) {
+func EstrusBatchMating(c *gin.Context) {
 	var req pasturePb.EventEstrus
 	if err := ginutil.BindProto(c, &req); err != nil {
 		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
@@ -217,7 +217,7 @@ func EstrusBatch(c *gin.Context) {
 		return
 	}
 
-	if err := middleware.BackendOperation(c).OpsService.EstrusBatch(c, &req); err != nil {
+	if err := middleware.BackendOperation(c).OpsService.EstrusBatchMating(c, &req); err != nil {
 		apierr.ClassifiedAbort(c, err)
 		return
 	}

+ 3 - 5
http/route/event_api.go

@@ -38,22 +38,18 @@ func EventAPI(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
 		eventRoute.POST("/mating/list", event.MatingEventList)
 		eventRoute.POST("/mating/create", event.MatingCreate)
 		// 发情
-		eventRoute.POST("/estrus/batch", event.EstrusBatch)
+		eventRoute.POST("/estrus/mating/batch/", event.EstrusBatchMating)
 		// 同期
 		eventRoute.POST("/same/time/list", event.SameTimeList)
 		eventRoute.POST("/same/time/create", event.SameTimeCreate)
 		eventRoute.POST("/same/time/batch", event.SameTimeBatch)
-
 		// 流产
 		eventRoute.POST("/abortion/list", event.AbortionList)
 		eventRoute.POST("/abortion/batch", event.AbortionCreateBatch)
-
 		// 断奶
 		eventRoute.POST("/weaning/batch", event.WeaningCreateBatch)
-
 		// 发病
 		eventRoute.POST("/disease/create", event.CowDiseaseCreate)
-
 		// 疾病诊断
 		eventRoute.POST("/disease/diagnose", event.CowDiseaseDiagnose)
 		// 疾病治疗
@@ -65,5 +61,7 @@ func EventAPI(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
 		eventRoute.POST("/disease/curable/batch", event.CowDiseaseCurable)
 		// 离场(死亡淘汰)
 		eventRoute.POST("/departure/batch", event.Departure)
+		// 更换耳标号
+		eventRoute.PUT("/cow/ear/number", event.CowEarNumber)
 	}
 }

+ 72 - 42
model/cow.go

@@ -11,47 +11,48 @@ import (
 
 type Cow struct {
 	Id                  int64                          `json:"id"`
-	PastureId           int64                          `json:"pastureId"`
-	Sex                 pasturePb.Genders_Kind         `json:"sex"`
-	NeckRingNumber      string                         `json:"neckRingNumber"`
-	EarNumber           string                         `json:"earNumber"`
-	EarOldNumber        string                         `json:"earOldNumber"`
-	PenId               int32                          `json:"penId"`
-	PenName             string                         `json:"penName"`
-	Lact                int32                          `json:"lact"`
-	DayAge              int32                          `json:"dayAge"`
-	CalvingAge          int64                          `json:"calvingAge"`
-	PregnancyAge        int32                          `json:"pregnancyAge"` // 怀孕天数 孕检结果有阳性更新,产犊后至0
-	AdmissionAge        int32                          `json:"admissionAge"`
-	AbortionAge         int64                          `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"`
-	SourceId            pasturePb.CowSource_Kind       `json:"sourceId"`
-	FatherNumber        string                         `json:"fatherNumber"`
-	MotherNumber        string                         `json:"motherNumber"`
-	AdmissionStatus     pasturePb.AdmissionStatus_Kind `json:"admissionStatus"`
-	IsPregnant          pasturePb.IsShow_Kind          `json:"isPregnant"`
-	HealthStatus        pasturePb.HealthStatus_Kind    `json:"healthStatus"`
-	WeaningAt           int64                          `json:"weaningAt"`
-	BirthAt             int64                          `json:"birthAt"`
-	AdmissionAt         int64                          `json:"admissionAt"`
-	FirstMatingAt       int64                          `json:"firstMatingAt"`
-	MatingTimes         int32                          `json:"matingTimes"`
-	WeeklyActive        int32                          `json:"weeklyActive"`
-	LastEstrusAt        int64                          `json:"lastEstrusAt"`
-	LastCalvingAt       int64                          `json:"lastCalvingAt"`
-	LastMatingAt        int64                          `json:"lastMatingAt"`
-	LastBullNumber      string                         `json:"lastBullNumber"`
-	LastPregnantCheckAt int64                          `json:"lastPregnantCheckAt"`
-	LastDryMilkAt       int64                          `json:"lastDryMilkAt"`
-	LastSecondWeight    int64                          `json:"lastSecondWeight"`
-	LastSecondWeightAt  int64                          `json:"lastSecondWeightAt"`
-	LastAbortionAt      int64                          `json:"lastAbortionAt"`
-	LastWeightAt        int64                          `json:"lastWeightAt"`
+	PastureId           int64                          `json:"pastureId"`           // 牧场id
+	Sex                 pasturePb.Genders_Kind         `json:"sex"`                 // 性别
+	NeckRingNumber      string                         `json:"neckRingNumber"`      // 脖环号
+	EarNumber           string                         `json:"earNumber"`           // 耳标号
+	EarOldNumber        string                         `json:"earOldNumber"`        // 旧耳标号
+	PenId               int32                          `json:"penId"`               // 栏舍id
+	PenName             string                         `json:"penName"`             // 栏舍名称
+	Lact                int32                          `json:"lact"`                // 胎次
+	DayAge              int32                          `json:"dayAge"`              // 日龄
+	CalvingAge          int64                          `json:"calvingAge"`          // 产后天使
+	PregnancyAge        int32                          `json:"pregnancyAge"`        // 怀孕天数 孕检结果有阳性更新,产犊后至0
+	AdmissionAge        int32                          `json:"admissionAge"`        // 入场日龄
+	AbortionAge         int64                          `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"`     // 入场体重
+	SourceId            pasturePb.CowSource_Kind       `json:"sourceId"`            // 来源哪里
+	FatherNumber        string                         `json:"fatherNumber"`        // 父号
+	MotherNumber        string                         `json:"motherNumber"`        // 母号
+	AdmissionStatus     pasturePb.AdmissionStatus_Kind `json:"admissionStatus"`     // 在场状态
+	IsPregnant          pasturePb.IsShow_Kind          `json:"isPregnant"`          // 是否怀孕
+	HealthStatus        pasturePb.HealthStatus_Kind    `json:"healthStatus"`        // 健康状态
+	WeaningAt           int64                          `json:"weaningAt"`           // 断奶时间
+	BirthAt             int64                          `json:"birthAt"`             // 出生时间
+	AdmissionAt         int64                          `json:"admissionAt"`         // 入场时间
+	DepartureAt         int64                          `json:"departureAt"`         // 离场时间
+	FirstMatingAt       int64                          `json:"firstMatingAt"`       // 首次配种时间
+	MatingTimes         int32                          `json:"matingTimes"`         // 配种次数
+	WeeklyActive        int32                          `json:"weeklyActive"`        // 每周活跃度
+	LastEstrusAt        int64                          `json:"lastEstrusAt"`        // 最后一次发情时间
+	LastCalvingAt       int64                          `json:"lastCalvingAt"`       // 最后一次产犊时间
+	LastMatingAt        int64                          `json:"lastMatingAt"`        // 最后一次配种时间
+	LastBullNumber      string                         `json:"lastBullNumber"`      // 最后一次配种牛号
+	LastPregnantCheckAt int64                          `json:"lastPregnantCheckAt"` // 最后一次孕检时间
+	LastDryMilkAt       int64                          `json:"lastDryMilkAt"`       // 最近一次干奶日期
+	LastSecondWeight    int64                          `json:"lastSecondWeight"`    // 最后第二次称重
+	LastSecondWeightAt  int64                          `json:"lastSecondWeightAt"`  // 最后第二次称重时间
+	LastAbortionAt      int64                          `json:"lastAbortionAt"`      // 最近一次流产时间
+	LastWeightAt        int64                          `json:"lastWeightAt"`        // 最近一次称重时间
 	CreatedAt           int64                          `json:"createdAt"`
 	UpdatedAt           int64                          `json:"updatedAt"`
 }
@@ -112,12 +113,40 @@ func (c *Cow) EventPenUpdate(pen *Pen) {
 	c.PenName = pen.Name
 }
 
-// EventEarNumberUpdate 更新脖环
+// EventEarNumberUpdate 更新耳标号
 func (c *Cow) EventEarNumberUpdate(newEarNumber string) {
 	c.EarOldNumber = c.EarNumber
 	c.EarNumber = newEarNumber
 }
 
+// EventDepartureUpdate 更新牛只离场信息
+func (c *Cow) EventDepartureUpdate(departureAt int64, departureType pasturePb.DepartureType_Kind) {
+	if departureType == pasturePb.DepartureType_Death {
+		c.AdmissionStatus = pasturePb.AdmissionStatus_Die
+		c.HealthStatus = pasturePb.HealthStatus_Dead
+	}
+
+	if departureType == pasturePb.DepartureType_Out {
+		c.AdmissionStatus = pasturePb.AdmissionStatus_Out
+		c.HealthStatus = pasturePb.HealthStatus_Out
+	}
+	c.DepartureAt = departureAt
+}
+
+// EventMatingUpdate 配种更新
+func (c *Cow) EventMatingUpdate(matingAt int64, bullNumber string, isReMating bool) {
+	c.LastMatingAt = matingAt
+	if c.LastBullNumber != "" {
+		c.FirstMatingAt = matingAt
+	}
+	c.LastBullNumber = bullNumber
+	c.IsPregnant = pasturePb.IsShow_No
+	c.BreedStatus = pasturePb.BreedStatus_Breeding
+	if !isReMating {
+		c.MatingTimes += 1
+	}
+}
+
 type CowSlice []*Cow
 
 func (c CowSlice) ToPB(
@@ -186,6 +215,7 @@ func (c CowSlice) ToPB(
 		if v.LastCalvingAt > 0 {
 			lastCalvingAtFormat = time.Unix(v.LastCalvingAt, 0).Format(LayoutDate2)
 		}
+
 		res[i] = &pasturePb.CowDetails{
 			CowId:                     int32(v.Id),
 			Sex:                       sex,

+ 1 - 1
model/event_calving.go

@@ -38,7 +38,7 @@ func (e *EventCalving) TableName() string {
 	return "event_calving"
 }
 
-func (e *EventCalving) Update(operationUser *SystemUser, req *pasturePb.EventCalving, cow *Cow) {
+func (e *EventCalving) EventUpdate(operationUser *SystemUser, req *pasturePb.EventCalving, cow *Cow) {
 	if operationUser != nil {
 		e.OperationId = operationUser.Id
 		e.OperationName = operationUser.Name

+ 0 - 32
model/event_cow_die_out.go

@@ -1,32 +0,0 @@
-package model
-
-type EventCowDieOut struct {
-	Id            int64  `json:"id"`
-	PastureId     int64  `json:"pastureId"`
-	CowId         int64  `json:"cowId"`
-	KindType      int32  `json:"kindType"`
-	Reasons       string `json:"reasons"`
-	RemoveAt      int64  `json:"removeAt"`
-	Remarks       string `json:"remarks"`
-	OperationId   int64  `json:"operationId"`
-	OperationName string `json:"operationName"`
-	CreatedAt     int64  `json:"createdAt"`
-	UpdatedAt     int64  `json:"updatedAt"`
-}
-
-func (e *EventCowDieOut) TableName() string {
-	return "event_cow_die_out"
-}
-
-func NewEventCowDieOut(operationUser *SystemUser) *EventCowDieOut {
-	return &EventCowDieOut{
-		PastureId:     0,
-		CowId:         0,
-		KindType:      0,
-		Reasons:       "",
-		RemoveAt:      0,
-		Remarks:       "",
-		OperationId:   operationUser.Id,
-		OperationName: operationUser.Name,
-	}
-}

+ 21 - 18
model/event_cow_log.go

@@ -9,24 +9,25 @@ import (
 const ShardTableNumber = 6
 
 type EventCowLog struct {
-	Id               int64                    `json:"id"`
-	PastureId        int64                    `json:"pastureId"`
-	CowId            int64                    `json:"cowId"`
-	DayAge           int32                    `json:"dayAge"`
-	Lact             int32                    `json:"lact"`
-	PenId            int32                    `json:"penId"`
-	PenName          string                   `json:"penName"`
-	CowType          pasturePb.CowType_Kind   `json:"cowType"`
-	CowTypeName      string                   `json:"cowTypeName"`
-	EventType        pasturePb.EventType_Kind `json:"eventType"`
-	EventTypeName    string                   `json:"eventTypeName"`
-	EventDescription string                   `json:"eventDescription"`
-	OperationId      int64                    `json:"operationId"`
-	OperationName    string                   `json:"operationName"`
-	EventAt          int64                    `json:"eventAt"`
-	Remarks          string                   `json:"remarks"`
-	CreatedAt        int64                    `json:"createdAt"`
-	UpdatedAt        int64                    `json:"updatedAt"`
+	Id               int64                        `json:"id"`
+	PastureId        int64                        `json:"pastureId"`
+	CowId            int64                        `json:"cowId"`
+	DayAge           int32                        `json:"dayAge"`
+	Lact             int32                        `json:"lact"`
+	PenId            int32                        `json:"penId"`
+	PenName          string                       `json:"penName"`
+	CowType          pasturePb.CowType_Kind       `json:"cowType"`
+	CowTypeName      string                       `json:"cowTypeName"`
+	EventCategoryId  pasturePb.EventCategory_Kind `json:"eventCategoryId"`
+	EventType        pasturePb.EventType_Kind     `json:"eventType"`
+	EventTypeName    string                       `json:"eventTypeName"`
+	EventDescription string                       `json:"eventDescription"`
+	OperationId      int64                        `json:"operationId"`
+	OperationName    string                       `json:"operationName"`
+	EventAt          int64                        `json:"eventAt"`
+	Remarks          string                       `json:"remarks"`
+	CreatedAt        int64                        `json:"createdAt"`
+	UpdatedAt        int64                        `json:"updatedAt"`
 }
 
 func (e *EventCowLog) UnShardTableName() string {
@@ -45,6 +46,7 @@ type EventCowLogModel struct {
 	CurrentUser      *SystemUser
 	EventAt          int64
 	ExposeEstrusType pasturePb.ExposeEstrusType_Kind
+	EventCategoryId  pasturePb.EventCategory_Kind
 	PenName          string
 	Remarks          string
 	CowTypeName      string
@@ -68,6 +70,7 @@ func NewEventCowLog(req *EventCowLogModel) *EventCowLog {
 		PenName:          req.PenName,
 		CowType:          req.Cow.CowType,
 		CowTypeName:      req.CowTypeName,
+		EventCategoryId:  req.EventCategoryId,
 		EventType:        req.EventType,
 		EventTypeName:    req.EventTypeName,
 		EventDescription: req.Description,

+ 1 - 1
model/event_cow_same_time.go

@@ -37,7 +37,7 @@ func (s *EventCowSameTime) TableName() string {
 	return "event_cow_same_time"
 }
 
-func (s *EventCowSameTime) Update(drugs *Drugs, usage float32, remarks string, currentUser, operationUser *SystemUser) {
+func (s *EventCowSameTime) EventUpdate(drugs *Drugs, usage float32, remarks string, currentUser, operationUser *SystemUser) {
 	s.Status = pasturePb.IsShow_Ok
 	s.DrugsId = drugs.Id
 	s.Unit = drugs.Unit

+ 9 - 1
model/event_departure.go

@@ -13,6 +13,7 @@ type EventDeparture struct {
 	ReasonId      int32                        `json:"reasonId"`
 	ReasonName    string                       `json:"reasonName"`
 	Remarks       string                       `json:"remarks"`
+	Weight        int32                        `json:"weight"`
 	OperationId   int64                        `json:"operationId"`
 	OperationName string                       `json:"operationName"`
 	MessageId     int64                        `json:"messageId"`
@@ -27,12 +28,13 @@ func (e *EventDeparture) TableName() string {
 
 func NewEventDeparture(pastureId int64, cow *Cow, req *pasturePb.EventDeparture, reasonName string, currentUser, operationUser *SystemUser) *EventDeparture {
 	return &EventDeparture{
+		PastureId:     pastureId,
 		CowId:         cow.Id,
 		Lact:          cow.Lact,
 		DayAge:        cow.GetDayAge(),
 		DepartureAt:   int64(req.DepartureAt),
 		DepartureType: req.DepartureType,
-		ReasonId:      req.DepartureReason,
+		ReasonId:      req.DepartureReasonKind,
 		ReasonName:    reasonName,
 		Remarks:       req.Remarks,
 		OperationId:   operationUser.Id,
@@ -41,3 +43,9 @@ func NewEventDeparture(pastureId int64, cow *Cow, req *pasturePb.EventDeparture,
 		MessageName:   currentUser.Name,
 	}
 }
+
+type EventDepartureModel struct {
+	EventDeparture *EventDeparture
+	Cow            *Cow
+	DepartureAt    int64
+}

+ 32 - 17
model/event_mating.go

@@ -1,6 +1,7 @@
 package model
 
 import (
+	"kpt-pasture/util"
 	"time"
 
 	pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
@@ -40,7 +41,7 @@ func (e *EventMating) TableName() string {
 	return "event_mating"
 }
 
-func (e *EventMating) EventUpdate(matingAt int64, bullNumber string, operationUser, currentUser *SystemUser) {
+func (e *EventMating) EventUpdate(matingAt int64, bullNumber string, isReMating bool, operationUser, currentUser *SystemUser) {
 	e.MatingResult = pasturePb.MatingResult_Unknown
 	e.Status = pasturePb.IsShow_Ok
 	e.RealityDay = matingAt
@@ -49,29 +50,42 @@ func (e *EventMating) EventUpdate(matingAt int64, bullNumber string, operationUs
 	e.OperationId = operationUser.Id
 	e.MessageName = currentUser.Name
 	e.MessageId = currentUser.Id
+	if !isReMating {
+		e.MatingTimes += 1
+	}
+}
+
+// EventMatingResultUpdate 配种结果更新
+func (e *EventMating) EventMatingResultUpdate(matingResult pasturePb.MatingResult_Kind, resultAt int64) {
+	e.MatingResult = matingResult
+	e.MatingResultAt = resultAt
 }
 
-// EventAbortionUpdate 流产更新
-func (e *EventMating) EventAbortionUpdate(abortionAt int64) {
-	e.MatingResult = pasturePb.MatingResult_Abort
-	e.MatingResultAt = abortionAt
+// IsReMating 判断是不是复配
+func (e *EventMating) IsReMating(cow *Cow, matingAt int64) bool {
+	lastMatingAt := time.Unix(cow.LastMatingAt, 0)
+	currentMatingAt := time.Unix(matingAt, 0)
+	daysBetween := util.DaysBetween(currentMatingAt.Unix(), lastMatingAt.Unix())
+	if (currentMatingAt.Format(LayoutDate2) == lastMatingAt.Format(LayoutDate2) || daysBetween == 1) && e.Status == pasturePb.IsShow_Ok && e.MatingResult == pasturePb.MatingResult_Unknown {
+		return true
+	}
+	return false
 }
 
-func EventMatingUpdateMap(matingAt int64, bullNumber string, operationUser, currentUser *SystemUser) map[string]interface{} {
-	return map[string]interface{}{
-		"mating_result":       pasturePb.MatingResult_Unknown,
-		"status":              pasturePb.IsShow_Ok,
-		"reality_day":         matingAt,
-		"frozen_semen_number": bullNumber,
-		"operation_id":        operationUser.Id,
-		"operation_name":      operationUser.Name,
-		"message_id":          currentUser.Id,
-		"message_name":        currentUser.Name,
+// IsEmptyMating 判断上次配种结果是不是空怀
+func (e *EventMating) IsEmptyMating(cow *Cow, matingAt int64) bool {
+	lastMatingAt := time.Unix(cow.LastMatingAt, 0)
+	currentMatingAt := time.Unix(matingAt, 0)
+	daysBetween := util.DaysBetween(currentMatingAt.Unix(), lastMatingAt.Unix())
+	if (e.MatingResult == pasturePb.MatingResult_Unknown || e.MatingResult == pasturePb.MatingResult_ReMatch) && daysBetween >= 2 {
+		return true
 	}
+	return false
 }
 
 func NewEventMating(pastureId int64, cow *Cow, planDay int64, exposeEstrusType pasturePb.ExposeEstrusType_Kind) *EventMating {
 	return &EventMating{
+		PastureId:        pastureId,
 		CowId:            cow.Id,
 		Lact:             cow.Lact,
 		PenId:            cow.PenId,
@@ -86,8 +100,8 @@ func NewEventMating(pastureId int64, cow *Cow, planDay int64, exposeEstrusType p
 	}
 }
 
-// NewEventMating2 自然发情的牛只
-func NewEventMating2(cow *Cow, req *pasturePb.EventMating, currentUser *SystemUser) *EventMating {
+// NewEventMatingNaturalEstrus 自然发情的牛只
+func NewEventMatingNaturalEstrus(cow *Cow, req *pasturePb.EventMating, currentUser *SystemUser) *EventMating {
 	return &EventMating{
 		PastureId:         currentUser.PastureId,
 		CowId:             cow.Id,
@@ -108,6 +122,7 @@ func NewEventMating2(cow *Cow, req *pasturePb.EventMating, currentUser *SystemUs
 		MessageName:       currentUser.Name,
 		FrozenSemenNumber: req.FrozenSemenNumber,
 		Remarks:           req.Remarks,
+		MatingTimes:       cow.MatingTimes + 1,
 	}
 }
 

+ 6 - 0
model/frozen_semen.go

@@ -4,6 +4,7 @@ import pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
 
 type FrozenSemen struct {
 	Id              int64                          `json:"id"`
+	PastureId       int64                          `json:"pasture_id"`
 	ParentId        int64                          `json:"parent_id"`
 	Producer        string                         `json:"producer"`
 	BullId          string                         `json:"bull_id"`
@@ -20,6 +21,7 @@ type FrozenSemen struct {
 
 func NewFrozenSemen(req *pasturePb.SearchFrozenSemenList, currentUser *SystemUser) *FrozenSemen {
 	return &FrozenSemen{
+		PastureId:       currentUser.PastureId,
 		Producer:        req.Producer,
 		BullId:          req.BullId,
 		KindId:          req.CowKind,
@@ -36,6 +38,10 @@ func (e *FrozenSemen) TableName() string {
 	return "frozen_semen"
 }
 
+func (e *FrozenSemen) EventQuantityUpdate(quantity int32) {
+	e.Quantity -= quantity
+}
+
 type FrozenSemenSlice []*FrozenSemen
 
 func (e FrozenSemenSlice) ToPB(

+ 14 - 2
model/frozen_semen_log.go

@@ -1,6 +1,11 @@
 package model
 
-import pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
+import (
+	"fmt"
+	"strings"
+
+	pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
+)
 
 type FrozenSemenLog struct {
 	Id            int64  `json:"id"`
@@ -20,9 +25,16 @@ func (e *FrozenSemenLog) TableName() string {
 }
 
 func NewEventFrozenSemenLog(req *pasturePb.EventMating) *FrozenSemenLog {
+	cowIds := ""
+	for _, v := range req.CowIds {
+		cowIds += fmt.Sprintf("%d,", v)
+	}
+	if len(cowIds) > 0 {
+		cowIds = strings.TrimRight(cowIds, ",")
+	}
 	return &FrozenSemenLog{
 		BullId:        req.FrozenSemenNumber,
-		CowIds:        req.CowIds,
+		CowIds:        cowIds,
 		OperationId:   int64(req.OperationId),
 		OperationName: req.OperationName,
 		Quantity:      req.FrozenSemenCount,

+ 4 - 1
model/neck_ring_original.go

@@ -12,6 +12,7 @@ type NeckRingOriginal struct {
 	Id                 int64                         `json:"id"`
 	Uuid               string                        `json:"uuid"`
 	FrameId            int32                         `json:"frameId"`
+	CowId              string                        `json:"cowId"`
 	Low                int32                         `json:"low"`
 	High               int32                         `json:"high"`
 	Rumina             int32                         `json:"rumina"`
@@ -22,10 +23,12 @@ type NeckRingOriginal struct {
 	Voltage            int32                         `json:"voltage"`
 	Upper              int32                         `json:"upper"`
 	Version            int32                         `json:"version"`
+	Csq                int32                         `json:"csq"`
 	Sign               int32                         `json:"sign"`
 	Remain             int32                         `json:"remain"`
 	Feed               int32                         `json:"feed"`
-	Imei               string                        `json:"imei" `
+	Imei               string                        `json:"imei"`
+	Nccid              string                        `json:"nccid"`
 	Temp               int32                         `json:"temp"`
 	Gasp               int32                         `json:"gasp"`
 	Hours              int32                         `json:"hours"`

+ 34 - 0
module/backend/config_data_base.go

@@ -151,3 +151,37 @@ func (s *StoreEntry) DeadReasonEnumList(isAll string) []*pasturePb.ConfigOptions
 	})
 	return configOptions
 }
+
+func (s *StoreEntry) MatingResultEnumList(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.MatingResult_Unknown),
+		Label:    "未知",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.MatingResult_ReMatch),
+		Label:    "复配",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.MatingResult_Pregnant),
+		Label:    "怀孕",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.MatingResult_Empty),
+		Label:    "空怀",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.MatingResult_Abort),
+		Label:    "流产",
+		Disabled: true,
+	})
+	return configOptions
+}

+ 7 - 2
module/backend/config_data_other.go

@@ -240,10 +240,15 @@ func (s *StoreEntry) FrozenSemenTypeEnumList(isAll string) []*pasturePb.ConfigOp
 	return configOptions
 }
 
-func (s *StoreEntry) BullNumberEnumList(isAll string) []*pasturePb.BullOptionsList {
+func (s *StoreEntry) BullNumberEnumList(isAll string, currentUser *model.SystemUser) []*pasturePb.BullOptionsList {
+
 	frozenSemenList := make([]*model.FrozenSemen, 0)
 	bullNumberList := make([]*pasturePb.BullOptionsList, 0)
-	if err := s.DB.Where("quantity > 0").Group("bull_id").Find(&frozenSemenList).Error; err != nil {
+	if err := s.DB.Model(new(model.FrozenSemen)).
+		Where("pasture_id = ?", currentUser.PastureId).
+		Where("quantity > 0").
+		Group("bull_id").
+		Find(&frozenSemenList).Error; err != nil {
 		zaplog.Error("BullNumberEnumList", zap.Any("Find", err))
 	}
 

+ 8 - 0
module/backend/enum_map.go

@@ -270,3 +270,11 @@ func (s *StoreEntry) OutReasonMap() map[pasturePb.OutReason_Kind]string {
 	}
 	return res
 }
+
+func (s *StoreEntry) MatingResultMap() map[pasturePb.MatingResult_Kind]string {
+	res := make(map[pasturePb.MatingResult_Kind]string)
+	for _, v := range s.MatingResultEnumList("") {
+		res[pasturePb.MatingResult_Kind(v.Value)] = v.Label
+	}
+	return res
+}

+ 14 - 2
module/backend/enum_options.go

@@ -129,14 +129,21 @@ func (s *StoreEntry) CowTransferPenReasonOptions(ctx context.Context) (*pastureP
 }
 
 func (s *StoreEntry) SystemUserOptions(ctx context.Context, depId int) (*pasturePb.ConfigOptionsListResponse, error) {
+	currentUser, err := s.GetCurrentSystemUser(ctx)
+	if err != nil {
+		return nil, xerr.Custom("当前用户信息错误,请退出重新登录")
+	}
+
 	systemUserList := make([]*model.SystemUser, 0)
 	pref := s.DB.Table(new(model.SystemUser).TableName()).
+		Where("pasture_id = ?", currentUser.PastureId).
 		Where("is_delete = ?", pasturePb.IsShow_Ok).
 		Where("is_show =? ", pasturePb.IsShow_Ok)
 	if depId != -1 && depId > 0 {
 		pref = pref.Where("dept_id = ?", depId)
 	}
-	if err := pref.Find(&systemUserList).Error; err != nil {
+
+	if err = pref.Find(&systemUserList).Error; err != nil {
 		return nil, xerr.WithStack(err)
 	}
 	return &pasturePb.ConfigOptionsListResponse{
@@ -147,10 +154,15 @@ func (s *StoreEntry) SystemUserOptions(ctx context.Context, depId int) (*pasture
 }
 
 func (s *StoreEntry) BullOptions(ctx context.Context) (*pasturePb.BullOptionsListResponse, error) {
+	currentUser, err := s.GetCurrentSystemUser(ctx)
+	if err != nil {
+		return nil, xerr.Custom("当前用户信息错误,请退出重新登录")
+	}
+
 	return &pasturePb.BullOptionsListResponse{
 		Code:    http.StatusOK,
 		Message: "ok",
-		Data:    s.BullNumberEnumList(""),
+		Data:    s.BullNumberEnumList("", currentUser),
 	}, nil
 }
 

+ 59 - 15
module/backend/event_base.go

@@ -319,7 +319,7 @@ func (s *StoreEntry) WeightBatch(ctx context.Context, req *pasturePb.EventWeight
 		// 更新牛只信息
 		cow.EventWeightUpdate(int64(item.Weight), int64(req.WeightAt))
 		if err = s.DB.Model(new(model.Cow)).
-			Select("last_second_weight_at,last_second_weight,last_weight_at,current_weight").
+			Select("last_second_weight_at", "last_second_weight", "last_weight_at", "current_weight").
 			Where("id = ?", cow.Id).
 			Where("admission_status = ?", pasturePb.AdmissionStatus_Admission).
 			Updates(cow).Error; err != nil {
@@ -345,7 +345,7 @@ func (s *StoreEntry) DepartureBatch(ctx context.Context, req *pasturePb.EventDep
 		return xerr.Custom("当前登录用户失败,请退出重新登录")
 	}
 
-	newEventDepartureList := make([]*model.EventDeparture, 0)
+	newEventDepartureModelList := make([]*model.EventDepartureModel, 0)
 	cow := &model.Cow{}
 	for _, item := range req.Item {
 		cow, err = s.GetCowInfoByCowId(ctx, currentUser.PastureId, int64(item.CowId))
@@ -362,29 +362,73 @@ func (s *StoreEntry) DepartureBatch(ctx context.Context, req *pasturePb.EventDep
 		reasonName := ""
 		switch item.DepartureType {
 		case pasturePb.DepartureType_Death:
-			reasonName = s.DeadReasonMap()[pasturePb.DeadReason_Kind(item.DepartureReason)]
+			reasonName = s.DeadReasonMap()[pasturePb.DeadReason_Kind(item.DepartureReasonKind)]
 		case pasturePb.DepartureType_Out:
-			reasonName = s.OutReasonMap()[pasturePb.OutReason_Kind(item.DepartureReason)]
+			reasonName = s.OutReasonMap()[pasturePb.OutReason_Kind(item.DepartureReasonKind)]
+		default:
+			return xerr.Custom("未知的离场类型")
 		}
-		newEventDepartureList = append(newEventDepartureList, model.NewEventDeparture(currentUser.PastureId, cow, item, reasonName, currentUser, operationUser))
+		newEventDeparture := model.NewEventDeparture(currentUser.PastureId, cow, item, reasonName, currentUser, operationUser)
+		newEventDepartureModelList = append(newEventDepartureModelList, &model.EventDepartureModel{
+			EventDeparture: newEventDeparture,
+			Cow:            cow,
+			DepartureAt:    int64(item.DepartureAt),
+		})
 	}
 
-	if len(newEventDepartureList) <= 0 {
+	if len(newEventDepartureModelList) <= 0 {
 		return nil
 	}
 
-	if err = s.DB.Create(newEventDepartureList).Error; err != nil {
+	if err = s.DB.Transaction(func(tx *gorm.DB) error {
+		// 记录事件日志
+		for _, item := range newEventDepartureModelList {
+			eventType := pasturePb.EventType_Death
+			if item.EventDeparture.DepartureType == pasturePb.DepartureType_Out {
+				eventType = pasturePb.EventType_Out
+			}
+
+			if err = tx.Create(item.EventDeparture).Error; err != nil {
+				return xerr.WithStack(err)
+			}
+
+			cow.EventDepartureUpdate(item.DepartureAt, item.EventDeparture.DepartureType)
+			if err = tx.Model(cow).
+				Select("admission_status", "health_status", "departure_at").
+				Where("id = ?", cow.Id).
+				Updates(cow).Error; err != nil {
+				return xerr.WithStack(err)
+			}
+
+			// 记录事件日志
+			cowLogs := s.SubmitEventLog(ctx, currentUser, item.Cow, eventType, pasturePb.ExposeEstrusType_Invalid, item)
+			tx.Table(cowLogs.TableName()).Create(cowLogs)
+		}
+		return nil
+	}); err != nil {
 		return xerr.WithStack(err)
 	}
+	return nil
+}
 
-	// 记录事件日志
-	for _, item := range newEventDepartureList {
-		eventType := pasturePb.EventType_Death
-		if item.DepartureType == pasturePb.DepartureType_Out {
-			eventType = pasturePb.EventType_Out
-		}
-		cowLogs := s.SubmitEventLog(ctx, currentUser, cow, eventType, pasturePb.ExposeEstrusType_Invalid, item)
-		s.DB.Table(cowLogs.TableName()).Create(cowLogs)
+func (s *StoreEntry) CowEarNumberUpdate(ctx context.Context, req *pasturePb.EventReplaceEarNumber) (err error) {
+	currentUser, err := s.GetCurrentSystemUser(ctx)
+	if err != nil {
+		return xerr.Custom("当前登录用户失败,请退出重新登录")
 	}
+
+	cow, err := s.GetCowInfoByCowId(ctx, currentUser.PastureId, int64(req.CowId))
+	if err != nil {
+		return xerr.Custom("未找到该牛只信息")
+	}
+
+	cow.EventEarNumberUpdate(req.EarNumber)
+	if err = s.DB.Model(cow).
+		Select("ear_number", "ear_old_number").
+		Where("id = ?", cow.Id).
+		Updates(cow).Error; err != nil {
+		return xerr.WithStack(err)
+	}
+
 	return nil
 }

+ 204 - 285
module/backend/event_breed.go

@@ -111,8 +111,10 @@ func (s *StoreEntry) CalvingCreate(ctx context.Context, req *pasturePb.EventCalv
 	}
 	if err = s.DB.Transaction(func(tx *gorm.DB) error {
 		// 更新产犊事件表
-		newEventCalving.Update(operationUser, req, cow)
+		newEventCalving.EventUpdate(operationUser, req, cow)
 		if err = tx.Model(new(model.EventCalving)).
+			Select("operation_id", "operation_name", "reality_day", "day_age", "pregnancy_age", "bull_number",
+				"calving_level", "child_number", "status", "is_inducing_childbirth", "pen_id", "dystocia_reason", "remarks").
 			Where("id = ?", newEventCalving.Id).
 			Updates(newEventCalving).Error; err != nil {
 			return xerr.WithStack(err)
@@ -139,7 +141,7 @@ func (s *StoreEntry) CalvingCreate(ctx context.Context, req *pasturePb.EventCalv
 		// 更新母牛信息
 		cow.EventCalvingUpdate(int64(req.CalvingAt))
 		if err = tx.Model(cow).
-			Select("calving_age,mating_times,pregnancy_age,lact,breed_status,is_pregnant,last_calving_at").
+			Select("calving_age", "mating_times", "pregnancy_age", "lact", "breed_status", "is_pregnant", "last_calving_at").
 			Where("id = ?", cow.Id).
 			Updates(cow).Error; err != nil {
 			return xerr.WithStack(err)
@@ -151,126 +153,6 @@ func (s *StoreEntry) CalvingCreate(ctx context.Context, req *pasturePb.EventCalv
 	return nil
 }
 
-func (s *StoreEntry) EstrusList(ctx context.Context, req *pasturePb.EstrusItemsRequest, pagination *pasturePb.PaginationModel) (*pasturePb.EstrusItemsResponse, error) {
-	currentUser, err := s.GetCurrentSystemUser(ctx)
-	if err != nil {
-		return nil, xerr.Custom("用户信息错误,请退出重新登录")
-	}
-
-	estrusList := make([]*model.EventEstrus, 0)
-	var count int64 = 0
-	pref := s.DB.Table(new(model.EventEstrus).TableName()).
-		Where("is_show = ?", pasturePb.IsShow_Ok).
-		Where("pasture_id = ?", currentUser.PastureId)
-	if len(req.CowIds) > 0 {
-		cowIds := strings.Split(util.ArrayInt32ToStrings(req.CowIds, ","), ",")
-		pref.Where("cow_id IN ?", cowIds)
-	}
-
-	if req.Level > 0 {
-		pref.Where("level = ?", req.Level)
-	}
-
-	if len(req.PenIds) > 0 {
-		penIds := strings.Split(util.ArrayInt32ToStrings(req.PenIds, ","), ",")
-		pref.Where("pen_id IN ?", penIds)
-	}
-
-	if err = pref.Order("id desc").
-		Count(&count).Limit(int(pagination.PageSize)).
-		Offset(int(pagination.PageOffset)).
-		Find(&estrusList).Error; err != nil {
-		return nil, xerr.WithStack(err)
-	}
-
-	getCowInfoByCowId := GetCowInfoByCowId
-	getCowLastEvent := GetCowLastEvent
-	return &pasturePb.EstrusItemsResponse{
-		Code:    http.StatusOK,
-		Message: "ok",
-		Data: &pasturePb.EstrusItemsData{
-			List:     model.EstrusSlice(estrusList).ToPB(s.DB, getCowInfoByCowId, getCowLastEvent),
-			Total:    int32(count),
-			PageSize: pagination.PageSize,
-			Page:     pagination.Page,
-		},
-	}, nil
-}
-
-func (s *StoreEntry) EstrusBatch(ctx context.Context, req *pasturePb.EventEstrus) (err error) {
-	currentUser, err := s.GetCurrentSystemUser(ctx)
-	if err != nil {
-		return xerr.Custom("当前用户信息错误,请退出重新登录")
-	}
-	operationUser, err := s.GetSystemUserById(ctx, currentUser.PastureId, int64(req.OperationId))
-	if err != nil {
-		return xerr.Customf("该用户不存在: %d", req.OperationId)
-	}
-	req.OperationName = operationUser.Name
-
-	eventEstrusIds := make([]string, 0)
-	eventMatingList := make([]*model.EventMating, 0)
-	for _, cowId := range req.CowIds {
-		cowInfo := GetCowInfoByCowId(s.DB, int64(cowId))
-		if cowInfo == nil {
-			return xerr.Custom("牛只信息不存在")
-		}
-
-		newEventMating := model.NewEventMating(currentUser.PastureId, cowInfo, time.Now().Unix(), pasturePb.ExposeEstrusType_Neck_Ring)
-		eventEstrus, ok := s.EventEstrusIsExist(ctx, currentUser.PastureId, int64(cowId))
-		if ok {
-			newEventMating.EventEstrusId = eventEstrus.Id
-			newEventMating.MatingTimes = cowInfo.MatingTimes + 1
-			newEventMating.Status = pasturePb.IsShow_Ok
-			newEventMating.RealityDay = int64(req.MatingAt)
-			newEventMating.OperationId = operationUser.Id
-			newEventMating.OperationName = operationUser.Name
-			newEventMating.FrozenSemenNumber = req.BullNumber
-			newEventMating.Remarks = req.Remarks
-			newEventMating.MessageId = currentUser.Id
-			newEventMating.MessageName = currentUser.Name
-			eventEstrusIds = append(eventEstrusIds, strconv.FormatInt(eventEstrus.Id, 10))
-		}
-		eventMatingList = append(eventMatingList, newEventMating)
-	}
-	if len(eventMatingList) <= 0 {
-		return nil
-	}
-	defer func() {
-		// 记录牛只事件日志
-		if err == nil {
-			for _, v := range eventMatingList {
-				cow, _ := s.GetCowInfoByCowId(ctx, currentUser.PastureId, v.CowId)
-				cowLogs := s.SubmitEventLog(ctx, currentUser, cow, pasturePb.EventType_Mating, pasturePb.ExposeEstrusType_Neck_Ring, req)
-				s.DB.Table(cowLogs.TableName()).Create(cowLogs)
-			}
-		}
-	}()
-
-	if err = s.DB.Transaction(func(tx *gorm.DB) error {
-		if len(eventEstrusIds) > 0 {
-			if err = tx.Model(new(model.EventEstrus)).
-				Where("id IN ?", eventEstrusIds).
-				Updates(map[string]interface{}{
-					"is_show": pasturePb.IsShow_No,
-					"result":  pasturePb.EstrusResult_Correct,
-				}).Error; err != nil {
-				return xerr.WithStack(err)
-			}
-		}
-
-		if len(eventMatingList) > 0 {
-			if err = tx.Create(eventMatingList).Error; err != nil {
-				return xerr.WithStack(err)
-			}
-		}
-		return nil
-	}); err != nil {
-		return xerr.WithStack(err)
-	}
-	return nil
-}
-
 func (s *StoreEntry) SameTimeCreate(ctx context.Context, req *pasturePb.EventSameTime) (err error) {
 	currentUser, err := s.GetCurrentSystemUser(ctx)
 	if err != nil {
@@ -294,7 +176,7 @@ func (s *StoreEntry) SameTimeCreate(ctx context.Context, req *pasturePb.EventSam
 		return xerr.Customf("该药品不存在: %d", req.DrugsId)
 	}
 	req.DrugsName = drugs.Name
-	eventCowSameTime.Update(drugs, req.Usage, req.Remarks, operationUser, currentUser)
+	eventCowSameTime.EventUpdate(drugs, req.Usage, req.Remarks, operationUser, currentUser)
 
 	defer func() {
 		if err == nil {
@@ -306,7 +188,7 @@ func (s *StoreEntry) SameTimeCreate(ctx context.Context, req *pasturePb.EventSam
 	}()
 
 	if err = s.DB.Model(new(model.EventCowSameTime)).
-		Select("status,drugs_id,unit,usage,remarks,operation_id,operation_name").
+		Select("status", "drugs_id", "unit", "usage", "remarks", "operation_id", "operation_name").
 		Where("id = ?", eventCowSameTime.Id).
 		Updates(eventCowSameTime).Error; err != nil {
 		return xerr.WithStack(err)
@@ -375,9 +257,9 @@ func (s *StoreEntry) SameTimeBatch(ctx context.Context, req *pasturePb.EventSame
 	if err = s.DB.Transaction(func(tx *gorm.DB) error {
 		for _, v := range eventCowSameTimeList {
 			// 更新SameTime
-			v.Update(drugs, req.Usage, req.Remarks, currentUser, operationUser)
+			v.EventUpdate(drugs, req.Usage, req.Remarks, currentUser, operationUser)
 			if err = tx.Model(new(model.EventCowSameTime)).
-				Select("status,drugs_id,unit,usage,remarks,operation_id,operation_name").
+				Select("status", "drugs_id", "unit", "usage", "remarks", "operation_id", "operation_name").
 				Where("id = ?", v.Id).
 				Updates(v).Error; err != nil {
 				return xerr.WithStack(err)
@@ -428,36 +310,45 @@ func (s *StoreEntry) SameTimeList(
 	}, nil
 }
 
-func (s *StoreEntry) MatingList(ctx context.Context, req *pasturePb.SearchEventRequest, pagination *pasturePb.PaginationModel) (*pasturePb.MatingEventResponse, error) {
+func (s *StoreEntry) EstrusList(ctx context.Context, req *pasturePb.EstrusItemsRequest, pagination *pasturePb.PaginationModel) (*pasturePb.EstrusItemsResponse, error) {
 	currentUser, err := s.GetCurrentSystemUser(ctx)
 	if err != nil {
-		return nil, xerr.Custom("当前用户信息错误,请退出重新登录")
+		return nil, xerr.Custom("用户信息错误,请退出重新登录")
 	}
 
-	matingList := make([]*model.EventMating, 0)
+	estrusList := make([]*model.EventEstrus, 0)
 	var count int64 = 0
-	pref := s.DB.Model(new(model.EventMating)).
-		Where("mating_result > ?", pasturePb.MatingResult_Invalid).
-		Where("status = ?", pasturePb.IsShow_No).
+	pref := s.DB.Table(new(model.EventEstrus).TableName()).
+		Where("is_show = ?", pasturePb.IsShow_Ok).
 		Where("pasture_id = ?", currentUser.PastureId)
-	if len(req.CowId) > 0 {
-		cowIds := strings.Split(req.CowId, ",")
+	if len(req.CowIds) > 0 {
+		cowIds := strings.Split(util.ArrayInt32ToStrings(req.CowIds, ","), ",")
 		pref.Where("cow_id IN ?", cowIds)
 	}
 
+	if req.Level > 0 {
+		pref.Where("level = ?", req.Level)
+	}
+
+	if len(req.PenIds) > 0 {
+		penIds := strings.Split(util.ArrayInt32ToStrings(req.PenIds, ","), ",")
+		pref.Where("pen_id IN ?", penIds)
+	}
+
 	if err = pref.Order("id desc").
 		Count(&count).Limit(int(pagination.PageSize)).
 		Offset(int(pagination.PageOffset)).
-		Find(&matingList).Error; err != nil {
+		Find(&estrusList).Error; err != nil {
 		return nil, xerr.WithStack(err)
 	}
 
-	exposeEstrusTypeMap := s.ExposeEstrusTypeMap()
-	return &pasturePb.MatingEventResponse{
+	getCowInfoByCowId := GetCowInfoByCowId
+	getCowLastEvent := GetCowLastEvent
+	return &pasturePb.EstrusItemsResponse{
 		Code:    http.StatusOK,
 		Message: "ok",
-		Data: &pasturePb.SearchMatingData{
-			List:     model.EventMatingSlice(matingList).ToPB(exposeEstrusTypeMap),
+		Data: &pasturePb.EstrusItemsData{
+			List:     model.EstrusSlice(estrusList).ToPB(s.DB, getCowInfoByCowId, getCowLastEvent),
 			Total:    int32(count),
 			PageSize: pagination.PageSize,
 			Page:     pagination.Page,
@@ -465,190 +356,218 @@ func (s *StoreEntry) MatingList(ctx context.Context, req *pasturePb.SearchEventR
 	}, nil
 }
 
-// MatingCreate 牛只配种
-func (s *StoreEntry) MatingCreate(ctx context.Context, req *pasturePb.EventMating) (err error) {
-	eventCheckModel, err := s.MatingCreateCheck(ctx, req)
+func (s *StoreEntry) EstrusBatchMating(ctx context.Context, req *pasturePb.EventEstrus) (err error) {
+	currentUser, err := s.GetCurrentSystemUser(ctx)
 	if err != nil {
-		return xerr.WithStack(err)
-	}
-
-	frozenSemen := &model.FrozenSemen{}
-	if err = s.DB.Where("bull_id = ?", req.FrozenSemenNumber).
-		First(frozenSemen).Error; err != nil {
-		return xerr.Custom("未找到冻精信息")
+		return xerr.Custom("当前用户信息错误,请退出重新登录")
 	}
-
-	if frozenSemen.Quantity < req.FrozenSemenCount {
-		return xerr.Custom("冻精数量不足")
+	operationUser, err := s.GetSystemUserById(ctx, currentUser.PastureId, int64(req.OperationId))
+	if err != nil {
+		return xerr.Customf("该用户不存在: %d", req.OperationId)
 	}
+	req.OperationName = operationUser.Name
 
-	// 更新复配的牛只
-	matingReMatchIds := make([]int64, 0)
-	// 新建配种信息的牛只
-	newMatingList := make([]*model.EventMating, 0)
-	// 更新配种信息牛只
-	updateMatingList := make([]int64, 0)
-	// 所有牛只
-	cowIds := make([]int64, 0)
-	// 需要更新配次的牛只
-	matingTimes := make([]*MatingTimes, 0)
-	// 需要更新空怀的牛只
-	emptyCowIds := make([]int64, 0)
-
-	for _, cow := range eventCheckModel.CowList {
-		// 1. 第一次配种,创建配种信息(自然发情牛只,未经过同期的牛只)
-		if cow.LastMatingAt <= 0 {
-			newMatingList = append(newMatingList, model.NewEventMating2(cow, req, eventCheckModel.CurrentUser))
-			continue
+	eventEstrusIds := make([]string, 0)
+	eventMatingList := make([]*model.EventMating, 0)
+	cowIds := make([]int32, 0)
+	for _, cowId := range req.CowIds {
+		cowInfo := GetCowInfoByCowId(s.DB, int64(cowId))
+		if cowInfo == nil {
+			return xerr.Custom("牛只信息不存在")
 		}
 
-		eventMatingHistory, err := s.GetEventMatingIsExIstByCowId(ctx, cow)
+		newEventMating := model.NewEventMating(currentUser.PastureId, cowInfo, time.Now().Unix(), pasturePb.ExposeEstrusType_Neck_Ring)
+		eventEstrus, ok, err := s.FindEventEstrusByCowId(ctx, currentUser.PastureId, int64(cowId))
 		if err != nil {
 			return xerr.WithStack(err)
 		}
 
-		// 2. 同期更新配种信息 牛号&& 胎次 && 未配 && 配种结果未知   ==> 同期初配
-		if eventMatingHistory.Status == pasturePb.IsShow_No &&
-			eventMatingHistory.MatingResult == pasturePb.MatingResult_Unknown {
-			updateMatingList = append(updateMatingList, eventMatingHistory.Id)
+		if !ok {
 			continue
 		}
 
-		lastMatingAt := time.Unix(cow.LastMatingAt, 0)
-		currentMatingAt := time.Unix(int64(req.MatingAt), 0)
-		daysBetween := util.DaysBetween(currentMatingAt.Unix(), lastMatingAt.Unix())
-
-		//  3. 如何两次配种时间为连续两天之内&&有过一次配种记录,则更新为复配状态  牛号&& 胎次 && 已配 && 配种结果未知   ==> 复配
-		if (currentMatingAt.Format(model.LayoutDate2) == lastMatingAt.Format(model.LayoutDate2) || daysBetween == 1) &&
-			eventMatingHistory.Status == pasturePb.IsShow_Ok &&
-			eventMatingHistory.MatingResult == pasturePb.MatingResult_Unknown {
-			matingReMatchIds = append(matingReMatchIds, eventMatingHistory.Id)
-		} else {
-			matingTimes = append(matingTimes, &MatingTimes{
-				Mt:            cow.MatingTimes + 1,
-				CowId:         cow.Id,
-				EventMatingId: eventMatingHistory.Id,
-			})
-		}
-
-		// 4. 提交的配种数据中,如何定位出空怀的牛只,
-		if (eventMatingHistory.MatingResult == pasturePb.MatingResult_Unknown || eventMatingHistory.MatingResult == pasturePb.MatingResult_ReMatch) &&
-			daysBetween >= 2 {
-			emptyCowIds = append(emptyCowIds, eventMatingHistory.Id)
-			newMatingList = append(newMatingList, model.NewEventMating2(cow, req, eventCheckModel.CurrentUser))
-		}
-		cowIds = append(cowIds, cow.Id)
+		newEventMating.EventEstrusId = eventEstrus.Id
+		eventEstrusIds = append(eventEstrusIds, strconv.FormatInt(eventEstrus.Id, 10))
+		eventMatingList = append(eventMatingList, newEventMating)
+		cowIds = append(cowIds, cowId)
 	}
-
-	if len(cowIds) != len(updateMatingList)+len(matingReMatchIds)+len(newMatingList) {
-		zaplog.Error("MatingCreate",
-			zap.Any("cowIds", cowIds),
-			zap.Any("updateMatingList", updateMatingList),
-			zap.Any("newMatingList", newMatingList),
-			zap.Any("matingReMatchIds", matingReMatchIds),
-			zap.Any("req", req),
-		)
-		return xerr.Custom("配种信息有误")
+	if len(eventMatingList) <= 0 {
+		return nil
 	}
-	defer func() {
-		if err != nil {
-			// 记录事件日志
-			for _, cowId := range cowIds {
-				cow, _ := s.GetCowInfoByCowId(ctx, eventCheckModel.CurrentUser.PastureId, cowId)
-				cowLogs := s.SubmitEventLog(ctx, eventCheckModel.CurrentUser, cow, pasturePb.EventType_Mating, req.ExposeEstrusType, req)
-				s.DB.Table(cowLogs.TableName()).Create(cowLogs)
-			}
-		}
-	}()
 
-	itemFrozenSemenLog := model.NewEventFrozenSemenLog(req)
 	if err = s.DB.Transaction(func(tx *gorm.DB) error {
-		// 更新配种事件数据(初配)
-		if len(updateMatingList) > 0 {
-			if err = tx.Model(new(model.EventMating)).
-				Where("id IN ?", updateMatingList).
-				Where("status = ?", pasturePb.IsShow_No).
-				Updates(
-					model.EventMatingUpdateMap(int64(req.MatingAt), req.FrozenSemenNumber, eventCheckModel.OperationUser, eventCheckModel.CurrentUser),
-				).Error; err != nil {
+		if len(eventEstrusIds) > 0 {
+			if err = tx.Model(new(model.EventEstrus)).
+				Where("id IN ?", eventEstrusIds).
+				Updates(map[string]interface{}{
+					"is_show": pasturePb.IsShow_No,
+					"result":  pasturePb.EstrusResult_Correct,
+				}).Error; err != nil {
 				return xerr.WithStack(err)
 			}
 		}
 
-		// 更新已配种的牛只为复配状态
-		if len(matingReMatchIds) > 0 {
-			if err = tx.Model(new(model.EventMating)).
-				Where("id IN ?", matingReMatchIds).
-				Where("mating_result = ?", pasturePb.MatingResult_Unknown).
-				Where("status = ?", pasturePb.IsShow_Ok).
-				Update("mating_result", pasturePb.MatingResult_ReMatch).
-				Error; err != nil {
+		if len(eventMatingList) > 0 {
+			if err = tx.Create(eventMatingList).Error; err != nil {
 				return xerr.WithStack(err)
 			}
 		}
+		return nil
+	}); err != nil {
+		return xerr.WithStack(err)
+	}
+
+	// 配种信息
+	eventMating := &pasturePb.EventMating{
+		CowIds:            cowIds,
+		FrozenSemenNumber: req.BullNumber,
+		FrozenSemenCount:  req.Consumption,
+		OperationId:       req.OperationId,
+		Remarks:           req.Remarks,
+		MatingAt:          req.MatingAt,
+		ExposeEstrusType:  pasturePb.ExposeEstrusType_Neck_Ring,
+	}
+	if err = s.MatingCreate(ctx, eventMating); err != nil {
+		zaplog.Error("EstrusBatchMating", zap.Any("MatingCreate", err), zap.Any("eventMating", eventMating))
+		return xerr.WithStack(err)
+	}
+	return nil
+}
 
-		// 更新上次已配种的牛只为空怀
-		if len(emptyCowIds) > 0 {
-			if err = tx.Model(new(model.EventMating)).
-				Where("id IN ?", emptyCowIds).
-				Update("mating_result", pasturePb.BreedStatus_Empty).
-				Error; err != nil {
+func (s *StoreEntry) MatingList(ctx context.Context, req *pasturePb.SearchEventRequest, pagination *pasturePb.PaginationModel) (*pasturePb.MatingEventResponse, error) {
+	currentUser, err := s.GetCurrentSystemUser(ctx)
+	if err != nil {
+		return nil, xerr.Custom("当前用户信息错误,请退出重新登录")
+	}
+
+	matingList := make([]*model.EventMating, 0)
+	var count int64 = 0
+	pref := s.DB.Model(new(model.EventMating)).
+		Where("mating_result > ?", pasturePb.MatingResult_Invalid).
+		Where("status = ?", pasturePb.IsShow_No).
+		Where("pasture_id = ?", currentUser.PastureId)
+	if len(req.CowId) > 0 {
+		cowIds := strings.Split(req.CowId, ",")
+		pref.Where("cow_id IN ?", cowIds)
+	}
+
+	if err = pref.Order("id desc").
+		Count(&count).Limit(int(pagination.PageSize)).
+		Offset(int(pagination.PageOffset)).
+		Find(&matingList).Error; err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	exposeEstrusTypeMap := s.ExposeEstrusTypeMap()
+	return &pasturePb.MatingEventResponse{
+		Code:    http.StatusOK,
+		Message: "ok",
+		Data: &pasturePb.SearchMatingData{
+			List:     model.EventMatingSlice(matingList).ToPB(exposeEstrusTypeMap),
+			Total:    int32(count),
+			PageSize: pagination.PageSize,
+			Page:     pagination.Page,
+		},
+	}, nil
+}
+
+// MatingCreate 牛只配种
+func (s *StoreEntry) MatingCreate(ctx context.Context, req *pasturePb.EventMating) (err error) {
+	eventCheckModel, err := s.MatingCreateCheck(ctx, req)
+	if err != nil {
+		return xerr.WithStack(err)
+	}
+
+	if err = s.DB.Transaction(func(tx *gorm.DB) error {
+		for _, cow := range eventCheckModel.CowList {
+			lastEventMating, ok, err := s.FindLastEventMatingByCowId(ctx, eventCheckModel.CurrentUser.PastureId, cow.Id)
+			if err != nil {
 				return xerr.WithStack(err)
 			}
-		}
 
-		// 创建配种事件数据
-		if len(newMatingList) > 0 {
-			if err = tx.Create(newMatingList).Error; err != nil {
+			var (
+				newMating     *model.EventMating
+				isReMating    = false
+				isEmptyMating = false
+			)
+
+			// 1. 自然发情牛只,未经过同期的牛只和未佩戴脖环的牛只
+			if !ok {
+				newMating = model.NewEventMatingNaturalEstrus(cow, req, eventCheckModel.CurrentUser)
+				if err = tx.Create(newMating).Error; err != nil {
+					return xerr.WithStack(err)
+				}
+			} else {
+				// 2. 所有有配种数据的牛只
+				if lastEventMating == nil || lastEventMating.Id <= 0 {
+					zaplog.Error("MatingCreate", zap.Any("cow", cow), zap.Any("CurrentUser", eventCheckModel.CurrentUser))
+					continue
+				}
+
+				// 复配 => 本胎次配次不加1
+				isReMating = lastEventMating.IsReMating(cow, int64(req.MatingAt))
+				// 空怀
+				isEmptyMating = lastEventMating.IsEmptyMating(cow, int64(req.MatingAt))
+
+				// 2。1. 提交的配种数据中,定位出空怀的牛只,
+				if isEmptyMating {
+					// 把上次配种结果信息更新成空怀
+					lastEventMating.EventMatingResultUpdate(pasturePb.MatingResult_Empty, int64(req.MatingAt))
+					if err = tx.Model(lastEventMating).
+						Select("mating_result", "mating_result_at").
+						Where("id = ?", lastEventMating.Id).
+						Updates(lastEventMating).Error; err != nil {
+						return xerr.WithStack(err)
+					}
+					// 先创建一条新的配种数据
+					newMating = model.NewEventMating(eventCheckModel.CurrentUser.PastureId, cow, int64(req.MatingAt), req.ExposeEstrusType)
+					newMating.EventUpdate(int64(req.MatingAt), req.FrozenSemenNumber, isReMating, eventCheckModel.OperationUser, eventCheckModel.CurrentUser)
+					if err = tx.Model(newMating).Create(newMating).Error; err != nil {
+						return xerr.WithStack(err)
+					}
+					if err = s.UpdateMatingResultEventCowLogByCowId(ctx, cow.Id, s.MatingResultMap()[pasturePb.MatingResult_Empty]); err != nil {
+						zaplog.Error("MatingCreate", zap.Any("UpdateEventCowLogByCowId", err), zap.Any("cow", cow))
+					}
+				}
+				// 2.2.  同期初配
+				if !isEmptyMating && !isReMating {
+					lastEventMating.EventUpdate(int64(req.MatingAt), req.FrozenSemenNumber, isReMating, eventCheckModel.OperationUser, eventCheckModel.CurrentUser)
+					if err = tx.Model(lastEventMating).
+						Select("mating_at", "status", "reality_day", "frozen_semen_number", "operation_id", "operation_name", "message_id", "message_name").
+						Where("id = ?", lastEventMating.Id).
+						Updates(lastEventMating).Error; err != nil {
+						return xerr.WithStack(err)
+					}
+				}
+			}
+
+			// 牛只基本中配种信息更新
+			cow.EventMatingUpdate(int64(req.MatingAt), req.FrozenSemenNumber, isReMating)
+			if err = tx.Model(cow).
+				Select("last_mating_at", "mating_times", "last_bull_number", "first_mating_at", "is_pregnant", "breed_status").
+				Where("id = ?", cow.Id).
+				Updates(cow).Error; err != nil {
 				return xerr.WithStack(err)
 			}
+			// 记录日志
+			cowLogs := s.SubmitEventLog(ctx, eventCheckModel.CurrentUser, cow, pasturePb.EventType_Mating, req.ExposeEstrusType, req)
+			tx.Table(cowLogs.TableName()).Create(cowLogs)
 		}
 
 		// 创建冻精使用记录日志
+		itemFrozenSemenLog := model.NewEventFrozenSemenLog(req)
 		if err = tx.Create(itemFrozenSemenLog).Error; err != nil {
 			return xerr.WithStack(err)
 		}
 
-		// 更新牛只的繁殖状态为配种
-		if err = tx.Model(new(model.Cow)).
-			Where("id IN ?", cowIds).
-			Where("admission_status = ?", pasturePb.AdmissionStatus_Admission).
-			Updates(map[string]interface{}{
-				"breed_status":     pasturePb.BreedStatus_Breeding,
-				"last_bull_number": req.FrozenSemenNumber,
-				"last_mating_at":   int64(req.MatingAt),
-			}).Error; err != nil {
-			return xerr.WithStack(err)
-		}
-
 		// 减去精液的数量
-		if err = tx.Model(frozenSemen).
-			Where("id = ?", frozenSemen.Id).
-			Where("quantity > 0").
-			UpdateColumn("quantity",
-				gorm.Expr("quantity - ?", req.FrozenSemenCount),
-			).Error; err != nil {
+		eventCheckModel.FrozenSemen.EventQuantityUpdate(req.FrozenSemenCount)
+		if err = tx.Model(eventCheckModel.FrozenSemen).
+			Select("quantity").
+			Where("id = ?", eventCheckModel.FrozenSemen.Id).
+			Updates(eventCheckModel.FrozenSemen).Error; err != nil {
 			return xerr.WithStack(err)
 		}
-
-		// 更新配次
-		if len(matingTimes) > 0 {
-			for _, mt := range matingTimes {
-				if err = tx.Model(new(model.EventMating)).
-					Where("id = ?", mt.EventMatingId).
-					Update("mating_times", mt.Mt).Error; err != nil {
-					return xerr.WithStack(err)
-				}
-
-				if err = tx.Model(new(model.Cow)).
-					Where("id = ?", mt.CowId).
-					Where("admission_status = ?", pasturePb.AdmissionStatus_Admission).
-					Where("breed_status = ?", pasturePb.BreedStatus_Breeding).
-					Update("mating_times", mt.Mt).Error; err != nil {
-					return xerr.WithStack(err)
-				}
-			}
-		}
 		return nil
 	}); err != nil {
 		return xerr.WithStack(err)
@@ -701,7 +620,7 @@ func (s *StoreEntry) WeaningBatch(ctx context.Context, req *pasturePb.EventWeani
 		for _, v := range eventWeaningList {
 			v.EventUpdate(int64(req.WeaningAt), req.Remarks, req.PenId, operation, currentUser)
 			if err = tx.Model(new(model.EventWeaning)).
-				Select("status,reality_day,operation_id,operation_name,message_id,message_name,remarks,after_pen_id").
+				Select("status", "reality_day", "operation_id", "operation_name", "message_id", "message_name", "remarks", "after_pen_id").
 				Where("id = ?", v.Id).
 				Updates(v).Error; err != nil {
 				return xerr.WithStack(err)
@@ -712,7 +631,7 @@ func (s *StoreEntry) WeaningBatch(ctx context.Context, req *pasturePb.EventWeani
 			}
 			cowInfo.EventWeaningUpdate(int64(req.WeaningAt), req.PenId, int64(cowWeightMap[cowInfo.Id]*1000))
 			if err = tx.Model(new(model.Cow)).
-				Select("pen_id,current_weight,weaning_at,last_weight_at").
+				Select("pen_id", "current_weight", "weaning_at", "last_weight_at").
 				Where("id = ?", v.CowId).
 				Updates(cowInfo).Error; err != nil {
 				return xerr.WithStack(err)

+ 31 - 42
module/backend/event_breed_more.go

@@ -3,11 +3,14 @@ package backend
 import (
 	"context"
 	"encoding/json"
-	"fmt"
 	"kpt-pasture/model"
 	"net/http"
 	"strings"
 
+	"go.uber.org/zap"
+
+	"gitee.com/xuyiping_admin/pkg/logger/zaplog"
+
 	pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
 	"gitee.com/xuyiping_admin/pkg/xerr"
 	"gorm.io/gorm"
@@ -50,78 +53,64 @@ func (s *StoreEntry) PregnantCheckList(ctx context.Context, req *pasturePb.Searc
 }
 
 func (s *StoreEntry) PregnantCheckCreateBatch(ctx context.Context, req *pasturePb.EventPregnantCheckBatch) (err error) {
-	currentUser, err := s.GetCurrentSystemUser(ctx)
-	if err != nil {
-		return xerr.Custom("当前用户信息错误,请退出重新登录")
-	}
-
 	pregnantCheckBatchModelList, err := s.PregnantCheckDataCheck(ctx, req)
 	if err != nil {
 		return xerr.WithStack(err)
 	}
 
-	cowIds := make([]int64, 0)
-	// 更新孕检牛只
-	newPregnantCheckBatchModelList := make([]*PregnantCheckBatchModel, 0)
-	for _, item := range pregnantCheckBatchModelList {
-		newPregnantCheckBatchModelList = append(newPregnantCheckBatchModelList, item)
-		cowIds = append(cowIds, item.Cow.Id)
-	}
-
-	defer func() {
-		if err == nil {
-			// 提交事件日志
-			for _, cowId := range cowIds {
-				cow, _ := s.GetCowInfoByCowId(ctx, currentUser.PastureId, cowId)
-				cowLogs := s.SubmitEventLog(ctx, currentUser, cow, pasturePb.EventType_Pregnancy_Check, pasturePb.ExposeEstrusType_Invalid, req)
-				s.DB.Table(cowLogs.TableName()).Create(cowLogs)
-			}
-		}
-	}()
-
 	if err = s.DB.Transaction(func(tx *gorm.DB) error {
-		for _, item := range newPregnantCheckBatchModelList {
+		for _, item := range pregnantCheckBatchModelList {
 			breedStatus := pasturePb.BreedStatus_Pregnant
 			isPregnant := pasturePb.IsShow_Ok
+			matingResult := pasturePb.MatingResult_Pregnant
+
 			if item.PregnantCheckResult == pasturePb.PregnantCheckResult_UnPregnant {
 				breedStatus = pasturePb.BreedStatus_Empty
 				isPregnant = pasturePb.IsShow_No
+				matingResult = pasturePb.MatingResult_Empty
 			}
 
 			// 更新上一次配种结果数据,如何是复检无胎则更新为流产
 			if item.EventPregnancyCheck.PregnantCheckName == model.PregnantCheckForSecond {
 				breedStatus = pasturePb.BreedStatus_Abort
+				matingResult = pasturePb.MatingResult_Abort
 			}
 
 			item.Cow.EventPregnantCheckUpdate(breedStatus, int64(item.PregnantCheckAt), isPregnant)
 			// 更新牛只基本信息
-			if err = tx.Model(&model.Cow{}).
-				Select("breed_status,last_pregnant_check_at,is_pregnant").
-				Where("cow_id = ?", item.Cow.Id).
+			if err = tx.Model(item.Cow).
+				Select("breed_status", "last_pregnant_check_at", "is_pregnant").
+				Where("id = ?", item.Cow.Id).
 				Updates(item.Cow).Error; err != nil {
 				return xerr.WithStack(err)
 			}
 
-			if err = tx.Model(new(model.EventMating)).
-				Where("id = (?)", fmt.Sprintf(`
-					SELECT id FROM event_mating where cow_id = %d AND status = %d ORDER BY id DESC LIMIT 1
-				`, item.Cow.Id, pasturePb.IsShow_Ok)).
-				Updates(map[string]interface{}{
-					"mating_result":    breedStatus,
-					"mating_result_at": item.PregnantCheckAt,
-				}).Error; err != nil {
+			// 更新最近一次配种结果数据
+			item.LastMating.EventMatingResultUpdate(matingResult, int64(item.PregnantCheckAt))
+			if err = tx.Model(item.LastMating).
+				Where("id = ?", item.LastMating.Id).
+				Select("mating_result", "mating_result_at").
+				Updates(item.LastMating).Error; err != nil {
 				return xerr.WithStack(err)
 			}
 
-			// 更新事件表
-			item.EventPregnancyCheck.EventUpdate(int64(item.PregnantCheckAt), item.PregnantCheckResult, item.PregnantCheckMethod, item.OperationUser, currentUser, item.Remarks)
 			// 更新孕检事件表
+			item.EventPregnancyCheck.EventUpdate(int64(item.PregnantCheckAt), item.PregnantCheckResult, item.PregnantCheckMethod, item.OperationUser, item.CurrentUser, item.Remarks)
 			if err = tx.Model(new(model.EventPregnantCheck)).
 				Select("reality_day,pregnant_check_result,pregnant_check_method,operation_id,operation_name,message_id,message_name,remarks,status").
 				Where("id = ?", item.EventPregnancyCheck.Id).
 				Updates(item.EventPregnancyCheck).Error; err != nil {
 				return xerr.WithStack(err)
 			}
+
+			// 记录日志
+			cowLogs := s.SubmitEventLog(ctx, item.CurrentUser, item.Cow, pasturePb.EventType_Pregnancy_Check, pasturePb.ExposeEstrusType_Invalid, req)
+			tx.Table(cowLogs.TableName()).Create(cowLogs)
+
+			// 更新上次配种结果
+			if err = s.UpdateMatingResultEventCowLogByCowId(ctx, item.Cow.Id, s.MatingResultMap()[matingResult]); err != nil {
+				zaplog.Error("PregnantCheckCreateBatch", zap.Any("UpdateMatingResultEventCowLogByCowId", err))
+			}
 		}
 
 		return nil
@@ -158,7 +147,7 @@ func (s *StoreEntry) AbortionCreate(ctx context.Context, req *pasturePb.EventAbo
 		return xerr.WithStack(err)
 	}
 	// 更新最近一次配种结果为流产
-	lastCowMating.EventAbortionUpdate(int64(req.AbortionAt))
+	lastCowMating.EventMatingResultUpdate(pasturePb.MatingResult_Abort, int64(req.AbortionAt))
 	// 更新流产数据
 	cow.EventAbortionUpdate(int64(req.AbortionAt))
 
@@ -175,7 +164,7 @@ func (s *StoreEntry) AbortionCreate(ctx context.Context, req *pasturePb.EventAbo
 		}
 		// 更新牛只状态
 		if err = tx.Model(new(model.Cow)).
-			Select("is_pregnant,last_abortion_at,breed_status").
+			Select("is_pregnant", "last_abortion_at", "breed_status").
 			Where("id = ?", req.CowId).
 			Updates(cow).Error; err != nil {
 			return xerr.WithStack(err)
@@ -183,7 +172,7 @@ func (s *StoreEntry) AbortionCreate(ctx context.Context, req *pasturePb.EventAbo
 
 		// 更新最近一次配种结果为流产
 		if err = tx.Model(new(model.EventMating)).
-			Select("mating_result,mating_result_at").
+			Select("mating_result", "mating_result_at").
 			Where("id = ?", lastCowMating.Id).
 			Updates(lastCowMating).Error; err != nil {
 		}

+ 30 - 3
module/backend/event_check.go

@@ -15,6 +15,7 @@ import (
 // EventCheckBatchModel 批量事件
 type EventCheckBatchModel struct {
 	CowList       []*model.Cow
+	FrozenSemen   *model.FrozenSemen
 	CurrentUser   *model.SystemUser
 	OperationUser *model.SystemUser
 }
@@ -28,6 +29,8 @@ type PregnantCheckBatchModel struct {
 	PregnantCheckResult pasturePb.PregnantCheckResult_Kind // 孕检结果
 	PregnantCheckMethod pasturePb.PregnantCheckMethod_Kind // 孕检方式
 	Remarks             string
+	CurrentUser         *model.SystemUser
+	LastMating          *model.EventMating
 }
 
 // MatingTimes 更新配次
@@ -47,7 +50,11 @@ func (s *StoreEntry) MatingCreateCheck(ctx context.Context, req *pasturePb.Event
 		return nil, xerr.Custom("当前用户信息错误,请退出重新登录")
 	}
 
-	cowList, err := s.ParseCowIds(ctx, currentUser.PastureId, req.CowIds)
+	cowIds := make([]int64, 0)
+	for _, v := range req.CowIds {
+		cowIds = append(cowIds, int64(v))
+	}
+	cowList, err := s.GetCowInfoByCowIds(ctx, currentUser.PastureId, cowIds)
 	if err != nil {
 		return nil, xerr.WithStack(err)
 	}
@@ -57,10 +64,21 @@ func (s *StoreEntry) MatingCreateCheck(ctx context.Context, req *pasturePb.Event
 		return nil, xerr.WithStack(err)
 	}
 
+	frozenSemen := &model.FrozenSemen{}
+	if err = s.DB.Where("bull_id = ?", req.FrozenSemenNumber).
+		First(frozenSemen).Error; err != nil {
+		return nil, xerr.Custom("未找到冻精信息")
+	}
+
+	if frozenSemen.Quantity < req.FrozenSemenCount {
+		return nil, xerr.Custom("冻精数量不足")
+	}
+
 	for _, cow := range cowList {
 		if cow.Sex != pasturePb.Genders_Female {
 			return nil, xerr.Customf("牛只: %d,不是母牛", cow.Id)
 		}
+
 		if int64(req.MatingAt) < cow.LastMatingAt {
 			return nil, xerr.Customf("牛只: %d,最近一次配种时间: %d,不能小于本次配种时间: %d", cow.Id, cow.LastMatingAt, req.MatingAt)
 		}
@@ -81,8 +99,10 @@ func (s *StoreEntry) MatingCreateCheck(ctx context.Context, req *pasturePb.Event
 			return nil, xerr.Customf("牛只: %d,当前状态为: %s,不能进行配种", cow.Id, cow.BreedStatus.String())
 		}
 	}
+
 	return &EventCheckBatchModel{
 		CowList:       cowList,
+		FrozenSemen:   frozenSemen,
 		CurrentUser:   currentUser,
 		OperationUser: operationUser,
 	}, nil
@@ -116,16 +136,21 @@ func (s *StoreEntry) PregnantCheckDataCheck(ctx context.Context, req *pasturePb.
 			return nil, xerr.Customf("牛只: %d,当前状态为: %s,不能进行孕检", cow.Id, cow.BreedStatus.String())
 		}
 
-		itemEventPregnantCheck, err := s.GetEventPregnantCheckIsExIstByCowId(ctx, cow)
+		itemEventPregnantCheck, ok, err := s.FindEventPregnantCheckIsExIstByCowId(ctx, cow)
 		if err != nil {
 			zaplog.Error("GetEventPregnantCheckIsExIstByCowId", zap.Any("err", err), zap.Any("cow", cow))
 			return nil, xerr.Customf("该牛只孕检事件数据异常, cowId: %d", pregnantCheckData.CowId)
 		}
 
-		if itemEventPregnantCheck.Id <= 0 {
+		if itemEventPregnantCheck.Id <= 0 || !ok {
 			return nil, xerr.Customf("该牛只孕检事件数据异常, cowId: %d", pregnantCheckData.CowId)
 		}
 
+		lastEventMating, ok, err := s.FindLastEventMatingByCowId(ctx, currentUser.PastureId, cow.Id)
+		if err != nil || !ok {
+			return nil, xerr.Customf("该牛只配种事件数据异常, cowId: %d", pregnantCheckData.CowId)
+		}
+
 		pregnantCheckBatchModelList = append(pregnantCheckBatchModelList, &PregnantCheckBatchModel{
 			Cow:                 cow,
 			OperationUser:       operationUser,
@@ -134,6 +159,8 @@ func (s *StoreEntry) PregnantCheckDataCheck(ctx context.Context, req *pasturePb.
 			PregnantCheckResult: pregnantCheckData.PregnantCheckResult,
 			Remarks:             pregnantCheckData.Remarks,
 			EventPregnancyCheck: itemEventPregnantCheck,
+			CurrentUser:         currentUser,
+			LastMating:          lastEventMating,
 		})
 	}
 

+ 65 - 22
module/backend/event_cow_log.go

@@ -4,15 +4,14 @@ import (
 	"context"
 	"fmt"
 	"kpt-pasture/model"
+	"strings"
 	"time"
 
+	"gitee.com/xuyiping_admin/pkg/xerr"
+
 	pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
 )
 
-type EventEventLogHandle interface {
-	SubmitEventLog()
-}
-
 func (s *StoreEntry) SubmitEventLog(
 	ctx context.Context,
 	currentUser *model.SystemUser,
@@ -22,16 +21,18 @@ func (s *StoreEntry) SubmitEventLog(
 	req interface{},
 ) *model.EventCowLog {
 	var (
-		desc, remarks = "", ""
-		eventTypeName = s.EventTypeMap()[eventType]
-		eventAt       = int64(0)
-		penMap        = s.PenMap(ctx, currentUser.PastureId)
-		cowTypeMap    = s.CowTypeMap()
-		operationUser = &model.SystemUser{}
+		desc, remarks   = "", ""
+		eventTypeName   = s.EventTypeMap()[eventType]
+		eventAt         = int64(0)
+		eventCategoryId = pasturePb.EventCategory_Invalid
+		penMap          = s.PenMap(ctx, currentUser.PastureId)
+		cowTypeMap      = s.CowTypeMap()
+		operationUser   = &model.SystemUser{}
 	)
 
 	switch eventType {
 	case pasturePb.EventType_Enter:
+		eventCategoryId = pasturePb.EventCategory_Base
 	case pasturePb.EventType_Transfer_Ben:
 		data := req.(*model.EventTransferGroup)
 		transferAt, _ := time.Parse(model.LayoutDate2, data.TransferDate)
@@ -39,8 +40,10 @@ func (s *StoreEntry) SubmitEventLog(
 		remarks = data.Remarks
 		operationUser.Id = data.OperationId
 		operationUser.Name = data.OperationName
+		eventCategoryId = pasturePb.EventCategory_Base
 		desc = fmt.Sprintf("转出栏舍: %s; 转入栏舍: %s", penMap[data.PenOutId].Name, penMap[data.PenInId].Name)
 	case pasturePb.EventType_Body_Score:
+		eventCategoryId = pasturePb.EventCategory_Other
 	case pasturePb.EventType_Pregnancy_Check:
 		data := req.(*pasturePb.EventPregnantCheckBatch)
 		for _, v := range data.Item {
@@ -48,16 +51,17 @@ func (s *StoreEntry) SubmitEventLog(
 				continue
 			}
 			eventAt = int64(v.PregnantCheckAt)
+			eventCategoryId = pasturePb.EventCategory_Breed
 			if v.PregnantCheckResult == pasturePb.PregnantCheckResult_Pregnant {
-				desc += fmt.Sprintf("孕检方式: %s; 孕检结果: 怀孕", s.PregnantCheckMethodMap()[v.PregnantCheckMethod])
+				desc += fmt.Sprintf("孕检方式: %s; 孕检结果: %s", s.PregnantCheckMethodMap()[v.PregnantCheckMethod], s.MatingResultMap()[pasturePb.MatingResult_Pregnant])
 			}
 			if v.PregnantCheckResult == pasturePb.PregnantCheckResult_UnPregnant {
 				desc += fmt.Sprintf("孕检方式: %s", s.PregnantCheckMethodMap()[v.PregnantCheckMethod])
 				if cow.BreedStatus == pasturePb.BreedStatus_Abort {
-					desc += fmt.Sprintf("; 复检结果: 流产")
+					desc += fmt.Sprintf("; 复检结果: %s", s.MatingResultMap()[pasturePb.MatingResult_Abort])
 				}
 				if cow.BreedStatus == pasturePb.BreedStatus_Empty {
-					desc += fmt.Sprintf("; 孕检结果: 空怀")
+					desc += fmt.Sprintf("; 孕检结果: %s", s.MatingResultMap()[pasturePb.MatingResult_Empty])
 				}
 			}
 			operationUser.Id = int64(v.OperationId)
@@ -72,6 +76,7 @@ func (s *StoreEntry) SubmitEventLog(
 		operationUser.Id = int64(data.OperationId)
 		operationUser.Name = data.OperationName
 		remarks = data.Remarks
+		eventCategoryId = pasturePb.EventCategory_Breed
 	case pasturePb.EventType_Calving:
 		data := req.(*pasturePb.EventCalving)
 		eventAt = int64(data.CalvingAt)
@@ -87,6 +92,7 @@ func (s *StoreEntry) SubmitEventLog(
 		operationUser.Id = int64(data.OperationId)
 		operationUser.Name = data.OperationName
 		remarks = data.Remarks
+		eventCategoryId = pasturePb.EventCategory_Breed
 	case pasturePb.EventType_Seme_Time:
 		data := req.(*pasturePb.EventSameTime)
 		eventAt = int64(data.SameTimeAt)
@@ -94,6 +100,7 @@ func (s *StoreEntry) SubmitEventLog(
 		operationUser.Id = int64(data.OperationId)
 		operationUser.Name = data.OperationName
 		remarks = data.Remarks
+		eventCategoryId = pasturePb.EventCategory_Breed
 	case pasturePb.EventType_Mating:
 		data := req.(*pasturePb.EventMating)
 		eventAt = int64(data.MatingAt)
@@ -101,24 +108,31 @@ func (s *StoreEntry) SubmitEventLog(
 		operationUser.Id = int64(data.OperationId)
 		operationUser.Name = data.OperationName
 		remarks = data.Remarks
+		eventCategoryId = pasturePb.EventCategory_Breed
 	case pasturePb.EventType_Birth:
 		eventAt = cow.BirthAt
 		desc = fmt.Sprintf("出生体重: %fKG;母号:%s;父号: %s", float32(cow.BirthWeight)/100, cow.MotherNumber, cow.LastBullNumber)
+		eventCategoryId = pasturePb.EventCategory_Breed
 	case pasturePb.EventType_Death:
 		data := req.(*model.EventDeparture)
 		eventAt = data.DepartureAt
 		desc = fmt.Sprintf("死亡原因: %s", data.ReasonName)
 		operationUser.Id = data.OperationId
 		operationUser.Name = data.OperationName
+		eventCategoryId = pasturePb.EventCategory_Base
 	case pasturePb.EventType_Transfer_Out:
+		eventCategoryId = pasturePb.EventCategory_Base
 	case pasturePb.EventType_Transfer_In:
+		eventCategoryId = pasturePb.EventCategory_Base
 	case pasturePb.EventType_Out:
 		data := req.(*model.EventDeparture)
 		eventAt = data.DepartureAt
 		desc = fmt.Sprintf("淘汰原因: %s", data.ReasonName)
 		operationUser.Id = data.OperationId
 		operationUser.Name = data.OperationName
+		eventCategoryId = pasturePb.EventCategory_Base
 	case pasturePb.EventType_Immunication:
+		eventCategoryId = pasturePb.EventCategory_Health
 	case pasturePb.EventType_Weaning:
 		data := req.(*pasturePb.EventWeaningBatchRequest)
 		eventAt = int64(data.WeaningAt)
@@ -131,7 +145,9 @@ func (s *StoreEntry) SubmitEventLog(
 				break
 			}
 		}
+		eventCategoryId = pasturePb.EventCategory_Other
 	case pasturePb.EventType_Sale:
+		eventCategoryId = pasturePb.EventCategory_Other
 	case pasturePb.EventType_Abort:
 		data := req.(*pasturePb.EventAbortionRequest)
 		eventAt = int64(data.AbortionAt)
@@ -139,6 +155,7 @@ func (s *StoreEntry) SubmitEventLog(
 		operationUser.Name = data.OperationName
 		remarks = data.Remarks
 		desc = fmt.Sprintf("流产原因: %s", s.AbortionReasonsMap()[data.AbortionReasons])
+		eventCategoryId = pasturePb.EventCategory_Base
 	case pasturePb.EventType_Weight:
 		data := req.(*pasturePb.EventWeight)
 		eventAt = int64(data.WeightAt)
@@ -151,19 +168,45 @@ func (s *StoreEntry) SubmitEventLog(
 				break
 			}
 		}
+		eventCategoryId = pasturePb.EventCategory_Base
 	case pasturePb.EventType_Castrated:
+		eventCategoryId = pasturePb.EventCategory_Health
 	case pasturePb.EventType_Insect_Repellent:
+		eventCategoryId = pasturePb.EventCategory_Health
 	}
 	newEventCowLogModel := &model.EventCowLogModel{
-		Cow:           cow,
-		CowTypeName:   cowTypeMap[cow.CowType],
-		OperationUser: operationUser,
-		EventAt:       eventAt,
-		EventType:     eventType,
-		EventTypeName: eventTypeName,
-		Description:   desc,
-		Remarks:       remarks,
-		PenName:       penMap[cow.PenId].Name,
+		Cow:             cow,
+		CowTypeName:     cowTypeMap[cow.CowType],
+		OperationUser:   operationUser,
+		EventCategoryId: eventCategoryId,
+		EventAt:         eventAt,
+		EventType:       eventType,
+		EventTypeName:   eventTypeName,
+		Description:     desc,
+		Remarks:         remarks,
+		PenName:         penMap[cow.PenId].Name,
 	}
 	return model.NewEventCowLog(newEventCowLogModel)
 }
+
+func (s *StoreEntry) UpdateMatingResultEventCowLogByCowId(ctx context.Context, cowId int64, newResult string) error {
+	newEventCowLog := &model.EventCowLog{CowId: cowId}
+	if err := s.DB.Table(newEventCowLog.TableName()).
+		Where("cow_id = ?", cowId).
+		Where("event_category_id = ?", pasturePb.EventCategory_Breed).
+		Where("event_type = ?", pasturePb.EventType_Mating).
+		Order("id desc").
+		First(newEventCowLog).Error; err != nil {
+		return xerr.WithStack(err)
+	} else {
+		desc := strings.ReplaceAll(newEventCowLog.EventDescription, "未知 ", newResult)
+		if err = s.DB.Table(newEventCowLog.TableName()).
+			Where("cow_id = ?", cowId).
+			Where("event_category_id = ?", pasturePb.EventCategory_Breed).
+			Where("event_type = ?", pasturePb.EventType_Mating).
+			Update("event_description", desc).Error; err != nil {
+			return xerr.WithStack(err)
+		}
+	}
+	return nil
+}

+ 4 - 4
module/backend/event_health.go

@@ -57,7 +57,7 @@ func (s *StoreEntry) CowDiseaseCreate(ctx context.Context, req *pasturePb.EventC
 				return
 			}
 			cow.EventPenUpdate(penData)
-			s.DB.Model(new(model.Cow)).Select("pen_id,pen_name").Where("id = ?", cow.Id).Updates(cow)
+			s.DB.Model(new(model.Cow)).Select("pen_id", "pen_name").Where("id = ?", cow.Id).Updates(cow)
 		}
 	}()
 	// PC端直接跳过诊断过程
@@ -255,7 +255,7 @@ func (s *StoreEntry) CowDiseaseDiagnose(ctx context.Context, req *pasturePb.CowD
 		// 未发病更新
 		eventCowDisease.EventUnDiseaseUpdate(currentUser, req.Remarks)
 		if err = s.DB.Model(eventCowDisease).
-			Select("diagnosed_result,diagnose_operation_id,diagnose_operation_name,remarks").
+			Select("diagnosed_result", "diagnose_operation_id", "diagnose_operation_name", "remarks").
 			Where("id = ?", req.Id).
 			Updates(eventCowDisease).Error; err != nil {
 			return xerr.WithStack(err)
@@ -276,7 +276,7 @@ func (s *StoreEntry) CowDiseaseDiagnose(ctx context.Context, req *pasturePb.CowD
 	if err = s.DB.Transaction(func(tx *gorm.DB) error {
 		eventCowDisease.EventDiseaseUpdate(disease, systemUser, req.Temperature)
 		if err = tx.Model(eventCowDisease).
-			Select("health_status,diagnosed_result,diagnose_id,diagnose_name,temperature,diagnose_operation_id,diagnose_operation_name,diagnosed_at").
+			Select("health_status", "diagnosed_result", "diagnose_id", "diagnose_name", "temperature", "diagnose_operation_id", "diagnose_operation_name", "diagnosed_at").
 			Where("id = ?", req.Id).
 			Where("cow_id = ?", req.CowId).
 			Updates(eventCowDisease).Error; err != nil {
@@ -540,7 +540,7 @@ func (s *StoreEntry) CowDiseaseCurable(ctx context.Context, req *pasturePb.Event
 		for _, eventCowDisease := range eventCowDiseaseList {
 			eventCowDisease.EventCurableUpdate(int64(req.CurableAt))
 			if err = tx.Model(eventCowDisease).
-				Select("health_status,diagnosed_result,curable_at").
+				Select("health_status", "diagnosed_result", "curable_at").
 				Where("id = ?", eventCowDisease.Id).
 				Where("health_status = ?", pasturePb.HealthStatus_Treatment).
 				Updates(eventCowDisease).Error; err != nil {

+ 4 - 3
module/backend/interface.go

@@ -170,8 +170,8 @@ type EventService interface {
 	// MatingList 配种
 	MatingList(ctx context.Context, req *pasturePb.SearchEventRequest, pagination *pasturePb.PaginationModel) (*pasturePb.MatingEventResponse, error)
 	MatingCreate(ctx context.Context, req *pasturePb.EventMating) error
-	// EstrusBatch 发情
-	EstrusBatch(ctx context.Context, req *pasturePb.EventEstrus) error
+	// EstrusBatchMating 发情批量处理配种
+	EstrusBatchMating(ctx context.Context, req *pasturePb.EventEstrus) error
 	// AbortionList 流产
 	AbortionList(ctx context.Context, req *pasturePb.SearchEventRequest, pagination *pasturePb.PaginationModel) (*pasturePb.EventAbortionResponse, error)
 	AbortionCreate(ctx context.Context, req *pasturePb.EventAbortionRequest) error
@@ -200,7 +200,8 @@ type EventService interface {
 	CowDiseaseCurable(ctx context.Context, req *pasturePb.EventCowCurableRequest) error
 	// DepartureBatch 离场
 	DepartureBatch(ctx context.Context, req *pasturePb.EventDepartureBatch) error
-
+	// CowEarNumberUpdate 修改耳号
+	CowEarNumberUpdate(ctx context.Context, req *pasturePb.EventReplaceEarNumber) error
 	// SubmitEventLog 记录提交事件结果日志
 	SubmitEventLog(ctx context.Context, currentUser *model.SystemUser, cow *model.Cow, eventType pasturePb.EventType_Kind, exposeEstrusType pasturePb.ExposeEstrusType_Kind, req interface{}) *model.EventCowLog
 }

+ 23 - 37
module/backend/sql.go

@@ -36,7 +36,8 @@ func (s *StoreEntry) GetCurrentSystemUser(ctx context.Context) (*model.SystemUse
 	}
 	// 根据用户token获取用户数据
 	systemUser := &model.SystemUser{Name: userName}
-	if err = s.DB.Model(new(model.SystemUser)).Where("name = ?", userName).
+	if err = s.DB.Model(new(model.SystemUser)).
+		Where("name = ?", userName).
 		Where("is_show = ? and is_delete = ?", pasturePb.IsShow_Ok, pasturePb.IsShow_Ok).
 		First(systemUser).Error; err != nil {
 		if errors.Is(err, gorm.ErrRecordNotFound) {
@@ -288,22 +289,23 @@ func (s *StoreEntry) GetSystemBasicByName(ctx context.Context, name string) (*mo
 	return systemBasic, nil
 }
 
-func (s *StoreEntry) GetLastEventMating(ctx context.Context, cowId int64) (*model.EventMating, error) {
-	res := &model.EventMating{}
+// FindLastEventMatingByCowId 根据cowId获取最近一次配种表信息
+func (s *StoreEntry) FindLastEventMatingByCowId(ctx context.Context, pastureId, cowId int64) (*model.EventMating, bool, error) {
+	newEventMating := &model.EventMating{}
 	if err := s.DB.Model(new(model.EventMating)).
 		Where("cow_id = ?", cowId).
-		Order("reality_day desc").
-		First(res).Error; err != nil {
+		Where("pasture_id = ?", pastureId).
+		Order("id desc").
+		First(newEventMating).Error; err != nil {
 		if errors.Is(err, gorm.ErrRecordNotFound) {
-			return res, nil
-		} else {
-			return nil, xerr.WithStack(err)
+			return nil, false, nil
 		}
+		return nil, false, xerr.WithStack(err)
 	}
-	return res, nil
+	return newEventMating, true, nil
 }
 
-func (s *StoreEntry) EventEstrusIsExist(ctx context.Context, pastureId, cowId int64) (*model.EventEstrus, bool) {
+func (s *StoreEntry) FindEventEstrusByCowId(ctx context.Context, pastureId, cowId int64) (*model.EventEstrus, bool, error) {
 	newEventEstrus := &model.EventEstrus{}
 	if err := s.DB.Model(new(model.EventEstrus)).
 		Where("cow_id = ?", cowId).
@@ -311,13 +313,12 @@ func (s *StoreEntry) EventEstrusIsExist(ctx context.Context, pastureId, cowId in
 		Where("is_show = ? ", pasturePb.IsShow_Ok).
 		First(newEventEstrus).Error; err != nil {
 		if errors.Is(err, gorm.ErrRecordNotFound) {
-			return nil, false
+			return nil, false, nil
+		} else {
+			return nil, false, xerr.WithStack(err)
 		}
 	}
-	if newEventEstrus.Id <= 0 {
-		return nil, false
-	}
-	return newEventEstrus, true
+	return newEventEstrus, true, nil
 }
 
 func (s *StoreEntry) GetOutboundById(ctx context.Context, id int64) (*model.Outbound, error) {
@@ -340,27 +341,10 @@ func (s *StoreEntry) GetOutboundDetailByOutboundId(ctx context.Context, id int64
 	return list, nil
 }
 
-func (s *StoreEntry) GetEventMatingIsExIstByCowId(
+func (s *StoreEntry) FindEventPregnantCheckIsExIstByCowId(
 	ctx context.Context,
 	cow *model.Cow,
-) (*model.EventMating, error) {
-	newEventMating := &model.EventMating{}
-	if err := s.DB.Model(new(model.EventMating)).
-		Where("cow_id = ?", cow.Id).
-		Where("lact = ?", cow.Lact).
-		Order("id desc").
-		First(newEventMating).Error; err != nil {
-		if !errors.Is(err, gorm.ErrRecordNotFound) {
-			return nil, xerr.WithStack(err)
-		}
-	}
-	return newEventMating, nil
-}
-
-func (s *StoreEntry) GetEventPregnantCheckIsExIstByCowId(
-	ctx context.Context,
-	cow *model.Cow,
-) (*model.EventPregnantCheck, error) {
+) (*model.EventPregnantCheck, bool, error) {
 	newEventPregnantCheck := &model.EventPregnantCheck{}
 	if err := s.DB.Model(new(model.EventMating)).
 		Where("cow_id = ?", cow.Id).
@@ -368,11 +352,13 @@ func (s *StoreEntry) GetEventPregnantCheckIsExIstByCowId(
 		Where("status = ?", pasturePb.IsShow_No).
 		Order("id desc").
 		First(newEventPregnantCheck).Error; err != nil {
-		if !errors.Is(err, gorm.ErrRecordNotFound) {
-			return nil, xerr.WithStack(err)
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return nil, false, nil
+		} else {
+			return nil, false, xerr.WithStack(err)
 		}
 	}
-	return newEventPregnantCheck, nil
+	return newEventPregnantCheck, true, nil
 }
 
 func (s *StoreEntry) DiseaseMaps(ctx context.Context) (map[int64]*model.Disease, error) {

+ 1 - 0
module/backend/system_service.go

@@ -67,6 +67,7 @@ func (s *StoreEntry) Login(ctx context.Context, req *pasturePb.SearchUserRequest
 			Username:     systemUser.Name,
 			Roles:        []string{systemRole.Name},
 			Avatar:       systemUser.Avatar,
+			PastureId:    int32(systemUser.PastureId),
 		},
 	}, nil
 }

+ 0 - 5
module/crontab/cow_cron.go

@@ -381,8 +381,3 @@ func (e *Entry) InitEventData(cowList []*model.Cow, systemBasic *model.SystemBas
 		}
 	}
 }
-
-// DiseaseCheck 疾病检查
-func (e *Entry) DiseaseCheck() error {
-	return nil
-}

+ 171 - 33
module/mqtt/handle.go

@@ -1,14 +1,12 @@
 package mqtt
 
 import (
-	"encoding/json"
 	"kpt-pasture/model"
 	"kpt-pasture/util"
 	"strconv"
+	"strings"
 	"sync"
 
-	pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
-
 	"gitee.com/xuyiping_admin/pkg/logger/zaplog"
 	"gitee.com/xuyiping_admin/pkg/xerr"
 	"go.uber.org/zap"
@@ -109,36 +107,176 @@ func (e *Entry) CreatedData(DSMLog *DataInsertNeckRingLog) error {
 }
 
 func (e *Entry) MsgDataFormat(msg []byte) *model.NeckRingOriginal {
-	neckLog := &Behavior{}
-	if err := json.Unmarshal(msg, neckLog); err != nil {
-		zaplog.Error("MsgDataFormat", zap.Any("err", err), zap.Any("msg", string(msg)))
-	}
-	if neckLog.Imei != "" {
-		// 存储到数据库
-		activeDate, hours := util.GetNeckRingActiveTimer(neckLog.FrameId)
-		voltage, _ := strconv.ParseInt(strconv.FormatInt(int64(neckLog.BAT), 16), 10, 64)
-		activeDateTimeType := pasturePb.ActiveTimeType_Twenty_Minutes
-		if neckLog.FrameId%10 == 8 {
-			activeDateTimeType = pasturePb.ActiveTimeType_Two_Hours
-		}
-		return &model.NeckRingOriginal{
-			Uuid:           neckLog.UUID,
-			Imei:           neckLog.ECowId,
-			ActiveDate:     activeDate,
-			Hours:          int32(hours),
-			FrameId:        neckLog.FrameId,
-			Rumina:         neckLog.RuMina,
-			Intake:         neckLog.Intake,
-			Inactive:       neckLog.Inactive,
-			Other:          neckLog.Other,
-			High:           neckLog.Activitys,
-			Active:         neckLog.High,
-			Voltage:        int32(voltage),
-			Version:        neckLog.Sver,
-			Remain:         neckLog.Remain,
-			ReceiveNumber:  neckLog.Imei,
-			ActiveDateType: activeDateTimeType,
+	msgData := make(map[string]interface{})
+	pairs := strings.Split(util.MsgFormat(string(msg)), " ")
+	for _, pair := range pairs {
+		parts := strings.SplitN(pair, ":", 2)
+		if len(parts) != 2 {
+			continue
+		}
+		key, value := parts[0], parts[1]
+		if len(key) == 0 {
+			continue
 		}
+		msgData[key] = value
+	}
+
+	softVer := int64(0)
+	if softVerInter, ok := msgData["SOFT_VER"]; ok {
+		if softVerstr, ok := softVerInter.(string); ok {
+			softVer, _ = strconv.ParseInt(softVerstr, 10, 64)
+		}
+	}
+
+	if softVer <= 0 {
+		if softVerInter, ok := msgData["soft_ver"]; ok {
+			if softVerstr, ok := softVerInter.(string); ok {
+				softVer, _ = strconv.ParseInt(softVerstr, 10, 64)
+			}
+		}
+	}
+
+	uuid := ""
+	if uuidInter, ok := msgData["uuid"]; ok {
+		if uuidStr, ok := uuidInter.(string); ok {
+			uuid = uuidStr
+		}
+	}
+
+	frameId := int64(0)
+	if frameIdInter, ok := msgData["frameid"]; ok {
+		if frameId64, ok := frameIdInter.(string); ok {
+			frameId, _ = strconv.ParseInt(frameId64, 10, 64)
+		}
+	}
+
+	cowId := ""
+	if cowIdInter, ok := msgData["cowid"]; ok {
+		if cowIdStr, ok := cowIdInter.(string); ok {
+			cowId = cowIdStr
+		}
+	}
+
+	csq := int64(0)
+	if csqInter, ok := msgData["csq"]; ok {
+		if csq32, ok := csqInter.(string); ok {
+			csq, _ = strconv.ParseInt(csq32, 10, 64)
+		}
+	}
+
+	temp := float64(0)
+	if tempInter, ok := msgData["Temp"]; ok {
+		if tempFloat, ok := tempInter.(string); ok {
+			temp, _ = strconv.ParseFloat(tempFloat, 64)
+		}
+	}
+
+	if temp <= 0 {
+		if tempInter, ok := msgData["temp"]; ok {
+			if tempFloat, ok := tempInter.(string); ok {
+				temp, _ = strconv.ParseFloat(tempFloat, 64)
+			}
+		}
+	}
+
+	imei := ""
+	if imeiInter, ok := msgData["imei"]; ok {
+		if imeiStr, ok := imeiInter.(string); ok {
+			imei = imeiStr
+		}
+	}
+
+	active := int64(0)
+	if activeInter, ok := msgData["active"]; ok {
+		if active32, ok := activeInter.(string); ok {
+			active, _ = strconv.ParseInt(active32, 10, 64)
+		}
+	}
+
+	inAction := int64(0)
+	if inActionInter, ok := msgData["inactive"]; ok {
+		if inAction32, ok := inActionInter.(string); ok {
+			inAction, _ = strconv.ParseInt(inAction32, 10, 64)
+		}
+	}
+
+	ruMina := int64(0)
+	if ruMinaInter, ok := msgData["Rumina"]; ok {
+		if ruMina32, ok := ruMinaInter.(string); ok {
+			ruMina, _ = strconv.ParseInt(ruMina32, 10, 64)
+		}
+	}
+	if ruMina <= 0 {
+		if ruMinaInter, ok := msgData["rumina"]; ok {
+			if ruMina32, ok := ruMinaInter.(string); ok {
+				ruMina, _ = strconv.ParseInt(ruMina32, 10, 64)
+			}
+		}
+	}
+
+	intake := int64(0)
+	if intakeInter, ok := msgData["Intake"]; ok {
+		if intake32, ok := intakeInter.(string); ok {
+			intake, _ = strconv.ParseInt(intake32, 10, 64)
+		}
+	}
+	if intake <= 0 {
+		if intakeInter, ok := msgData["intake"]; ok {
+			if intake32, ok := intakeInter.(string); ok {
+				intake, _ = strconv.ParseInt(intake32, 10, 64)
+			}
+		}
+	}
+
+	gasp := int64(0)
+	if gaspInter, ok := msgData["gasp"]; ok {
+		if gasp32, ok := gaspInter.(string); ok {
+			gasp, _ = strconv.ParseInt(gasp32, 10, 64)
+		}
+	}
+
+	other := int64(0)
+	if otherInter, ok := msgData["other"]; ok {
+		if other32, ok := otherInter.(string); ok {
+			other, _ = strconv.ParseInt(other32, 10, 64)
+		}
+	}
+
+	reMain := int64(0)
+	if reMainInter, ok := msgData["Remain"]; ok {
+		if reMain32, ok := reMainInter.(string); ok {
+			reMain, _ = strconv.ParseInt(reMain32, 10, 64)
+		}
+	}
+	if ruMina <= 0 {
+		if reMainInter, ok := msgData["remain"]; ok {
+			if reMain32, ok := reMainInter.(string); ok {
+				reMain, _ = strconv.ParseInt(reMain32, 10, 64)
+			}
+		}
+	}
+	nccId := ""
+	if nccIdInter, ok := msgData["nccid"]; ok {
+		if nccIdStr, ok := nccIdInter.(string); ok {
+			nccId = nccIdStr
+		}
+	}
+
+	return &model.NeckRingOriginal{
+		Version:  int32(softVer),
+		Uuid:     uuid,
+		CowId:    cowId,
+		FrameId:  int32(frameId),
+		Csq:      int32(csq),
+		Temp:     int32(temp * 100),
+		Imei:     imei,
+		Active:   int32(active),
+		Inactive: int32(inAction),
+		Rumina:   int32(ruMina),
+		Intake:   int32(intake),
+		Gasp:     int32(gasp),
+		Other:    int32(other),
+		Remain:   int32(reMain),
+		Nccid:    nccId,
 	}
-	return nil
 }

+ 15 - 23
module/mqtt/model.go

@@ -1,27 +1,19 @@
 package mqtt
 
 type Behavior struct {
-	UUID      string `json:"uuid"`
-	ECowId    string `json:"ecowid"`
-	FrameId   int32  `json:"frameid"`
-	High      int32  `json:"High"`
-	Intake    int32  `json:"Intake"`
-	RuMina    int32  `json:"Rumina"`
-	Other     int32  `json:"Other"`
-	Activitys int32  `json:"activitys"`
-	Inactive  int32  `json:"inactive"`
-	Sver      int32  `json:"Sver"`
-	Remain    int32  `json:"Remain"`
-	RFRssi    int32  `json:"RFRssi"`
-	STATUS    int32  `json:"STATUS"`
-	BAT       int32  `json:"BAT"`
-	Imei      string `json:"imei"`
-	Gasp      int32  `json:"gasp"`
-}
-
-type NeckRingWrapper struct {
-	Type     string `json:"type"`
-	NeckRing struct {
-		NeckPck []*Behavior `json:"neck"` // neck_pck neck
-	} `json:"NeckRing"`
+	SoftVer  int32   `json:"soft_ver"`
+	UUID     string  `json:"uuid"`
+	FrameId  int32   `json:"frameid"`
+	CowId    string  `json:"cowid"`
+	Csq      int32   `json:"csq"`
+	Temp     float32 `json:"temp"`
+	Imei     string  `json:"imei"`
+	NccId    string  `json:"nccid"`
+	Active   int32   `json:"active"`
+	InActive int32   `json:"inactive"`
+	Rumina   int32   `json:"rumina"`
+	Intake   int32   `json:"intake"`
+	Gasp     int32   `json:"gasp"`
+	Other    int32   `json:"other"`
+	Remain   int32   `json:"Remain"`
 }

+ 1 - 2
service/mqtt/interface.go

@@ -61,7 +61,7 @@ var connectLostHandler = func(client golangMqtt.Client, err error) {
 func NewServer(config *config.AppConfig) IMqttServer {
 	conf := config.Mqtt
 	opts := golangMqtt.NewClientOptions()
-	opts.AddBroker(fmt.Sprintf("tcp://%s:%d", conf.Broker, conf.Port))
+	opts.AddBroker(fmt.Sprintf("tcp://%s", conf.Broker))
 	opts.SetClientID(util.GenerateRandomNumberString(16))
 	opts.SetUsername(conf.UserName)
 	opts.SetPassword(conf.Password)
@@ -90,7 +90,6 @@ func (s *IMqttClient) Run() {
 	// 设置信号监听以优雅关闭服务器
 	stop := make(chan os.Signal, 1)
 	signal.Notify(stop, os.Kill, os.Interrupt, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
-
 	go func() {
 		for {
 			select {