Browse Source

mating: 配种事件梳理

Yi 4 tháng trước cách đây
mục cha
commit
a6d541593f

+ 2 - 1
model/cow.go

@@ -35,9 +35,10 @@ type Cow struct {
 	IsPregnant          pasturePb.IsShow_Kind          `json:"isPregnant"`
 	HealthStatus        pasturePb.HealthStatus_Kind    `json:"healthStatus"`
 	WeaningAt           int64                          `json:"weaningAt"`
-	BirthAt             int64                          `json:"birthAti"`
+	BirthAt             int64                          `json:"birthAt"`
 	AdmissionAt         int64                          `json:"admissionAt"`
 	FirstMatingAt       int64                          `json:"firstMatingAt"`
+	MatingTimes         int32                          `json:"matingTimes"`
 	LastEstrusAt        int64                          `json:"lastEstrusAt"`
 	LastCalvingAt       int64                          `json:"lastCalvingAt"`
 	LastMatingAt        int64                          `json:"lastMatingAt"`

+ 6 - 0
model/event_mating.go

@@ -240,3 +240,9 @@ type MultiFactorPregnancyRateChart struct {
 	PregnantRateMap map[string]map[string]string `json:"pregnantRateMap"`
 	KepMap          []string                     `json:"kepMap"`
 }
+
+type MatingTimes struct {
+	Mt            int32
+	CowId         int64
+	EventMatingId int64
+}

+ 1 - 1
model/event_pregnant_check.go

@@ -99,7 +99,7 @@ func (e EventPregnantCheckSlice) ToPB3(
 	res := make([]*pasturePb.PregnancyReportTable, len(e))
 	for i, v := range e {
 		pregnancyCheckName := ""
-		if checkName, ok := PregnantCheckNameMap[v.PregnantCheckName]; ok {
+		if checkName, ok := PregnantCheckNameKeyMap[v.PregnantCheckName]; ok {
 			pregnancyCheckName = checkName
 		}
 

+ 6 - 1
model/system_basic.go

@@ -14,11 +14,16 @@ const (
 	ValueTypeRange = 2 // 范围值
 )
 
-var PregnantCheckNameMap = map[string]string{
+var PregnantCheckNameKeyMap = map[string]string{
 	PregnantCheckForFirst:  "初检",
 	PregnantCheckForSecond: "复检",
 }
 
+var PregnantCheckNameValueMap = map[int64]string{
+	1: "PregnantCheckForFirst",
+	2: "PregnantCheckForSecond",
+}
+
 type SystemBasic struct {
 	Id           int32                 `json:"id"`
 	Name         string                `json:"name"`

+ 22 - 0
module/backend/config_data_other.go

@@ -377,3 +377,25 @@ func (s *StoreEntry) AuditStatusEnumList(isAll string) []*pasturePb.ConfigOption
 	})
 	return configOptions
 }
+
+func (s *StoreEntry) PregnantCheckNameEnumList(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(1),
+		Label:    "初检",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(2),
+		Label:    "复检",
+		Disabled: true,
+	})
+	return configOptions
+}

+ 1 - 0
module/backend/enum_options.go

@@ -192,6 +192,7 @@ func (s *StoreEntry) SystemBaseConfigOptions(ctx context.Context, optionsName, i
 		"neckRingStatus":             s.NeckRingStatusEnumList,
 		"outType":                    s.OutTypeEnumList,
 		"auditStatus":                s.AuditStatusEnumList,
+		"pregnantCheckName":          s.PregnantCheckNameEnumList,
 	}
 
 	getConfigFunc, ok := getConfigFuncMap[optionsName]

+ 66 - 41
module/backend/event_breed.go

@@ -5,6 +5,7 @@ import (
 	"encoding/json"
 	"fmt"
 	"kpt-pasture/model"
+	"kpt-pasture/util"
 	"net/http"
 	"strings"
 	"time"
@@ -337,40 +338,51 @@ func (s *StoreEntry) MatingCreate(ctx context.Context, req *pasturePb.EventMatin
 	// 所有牛只
 	cowIds := make([]int64, 0)
 	// 需要更新配次的牛只
-	matingTimes := make([]int64, 0)
+	matingTimes := make([]*model.MatingTimes, 0)
+	// 需要更新空怀的牛只
+	emptyCowIds := make([]int64, 0)
 
 	for _, cow := range eventCheckModel.CowList {
-		// 牛号&& 胎次 && 已配 && 配种结果未知
-		count1, err := s.GetEventMatingIsExIstByCowId(ctx, cow, pasturePb.IsShow_Ok)
-		if err != nil {
-			return xerr.WithStack(err)
+		// 1. 第一次配种,创建配种信息(自然发情牛只,未经过同期的牛只)
+		if cow.LastMatingAt <= 0 {
+			newMatingList = append(newMatingList, model.NewEventMating2(cow, req, eventCheckModel.CurrentUser))
+			continue
 		}
 
-		// 牛号&& 胎次 && 未配 && 配种结果未知
-		count2, err := s.GetEventMatingIsExIstByCowId(ctx, cow, pasturePb.IsShow_No)
+		eventMatingHistory, err := s.GetEventMatingIsExIstByCowId(ctx, cow)
 		if err != nil {
 			return xerr.WithStack(err)
 		}
 
-		// 1. 第一次配种,创建配种信息(自然发情牛只,未经过同期的牛只)
-		if cow.LastMatingAt <= 0 {
-			newMatingList = append(newMatingList, model.NewEventMating2(cow, req, eventCheckModel.CurrentUser))
+		// 2. 同期更新配种信息 牛号&& 胎次 && 未配 && 配种结果未知   ==> 同期初配
+		if eventMatingHistory.Status == pasturePb.IsShow_No &&
+			eventMatingHistory.MatingResult == pasturePb.MatingResult_Unknown {
+			updateMatingList = append(updateMatingList, eventMatingHistory.Id)
+			continue
 		}
 
-		lastMatingAt := time.Unix(cow.LastMatingAt, 0).Format(model.LayoutDate2)
-		currentMatingAt := time.Unix(int64(req.MatingAt), 0).Format(model.LayoutDate2)
-		lastAddOneDayAt := time.Unix(cow.LastMatingAt, 0).AddDate(0, 0, 1).Format(model.LayoutDate2)
+		lastMatingAt := time.Unix(cow.LastMatingAt, 0)
+		currentMatingAt := time.Unix(int64(req.MatingAt), 0)
+		daysBetween := util.DaysBetween(currentMatingAt.Unix(), lastMatingAt.Unix())
 
-		//  2. 如何两次配种时间为连续两天之内&&有过一次配种记录,则更新为复配状态
-		if (currentMatingAt == lastMatingAt || currentMatingAt == lastAddOneDayAt) && count1 == 1 {
-			matingReMatchIds = append(matingReMatchIds, cow.Id)
+		//  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, cow.Id)
+			matingTimes = append(matingTimes, &model.MatingTimes{
+				Mt:            cow.MatingTimes + 1,
+				CowId:         cow.Id,
+				EventMatingId: eventMatingHistory.Id,
+			})
 		}
 
-		// 3. 同期更新配种信息
-		if count2 == 1 {
-			updateMatingList = append(updateMatingList, cow.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)
 	}
@@ -392,7 +404,7 @@ func (s *StoreEntry) MatingCreate(ctx context.Context, req *pasturePb.EventMatin
 		// 更新配种事件数据(初配)
 		if len(updateMatingList) > 0 {
 			if err = tx.Model(new(model.EventMating)).
-				Where("cow_id IN ?", updateMatingList).
+				Where("id IN ?", updateMatingList).
 				Where("status = ?", pasturePb.IsShow_No).
 				Updates(map[string]interface{}{
 					"mating_result":       pasturePb.MatingResult_Unknown,
@@ -411,7 +423,7 @@ func (s *StoreEntry) MatingCreate(ctx context.Context, req *pasturePb.EventMatin
 		// 更新已配种的牛只为复配状态
 		if len(matingReMatchIds) > 0 {
 			if err = tx.Model(new(model.EventMating)).
-				Where("cow_id IN ?", matingReMatchIds).
+				Where("id IN ?", matingReMatchIds).
 				Where("mating_result = ?", pasturePb.MatingResult_Unknown).
 				Where("status = ?", pasturePb.IsShow_Ok).
 				Update("mating_result", pasturePb.MatingResult_ReMatch).
@@ -420,6 +432,16 @@ func (s *StoreEntry) MatingCreate(ctx context.Context, req *pasturePb.EventMatin
 			}
 		}
 
+		// 更新上次已配种的牛只为空怀
+		if len(emptyCowIds) > 0 {
+			if err = tx.Model(new(model.EventMating)).
+				Where("id IN ?", emptyCowIds).
+				Update("mating_result", pasturePb.BreedStatus_Empty).
+				Error; err != nil {
+				return xerr.WithStack(err)
+			}
+		}
+
 		// 创建配种事件数据
 		if len(newMatingList) > 0 {
 			if err = tx.Create(newMatingList).Error; err != nil {
@@ -462,22 +484,22 @@ func (s *StoreEntry) MatingCreate(ctx context.Context, req *pasturePb.EventMatin
 		}
 
 		// 更新配次
-		if err = tx.Model(new(model.EventMating)).
-			Where("cow_id IN ?", matingTimes).
-			Where("reality_day = ?", req.MatingAt).
-			Updates(map[string]interface{}{
-				"mating_times": gorm.Expr("mating_times + 1"),
-			}).Error; err != nil {
-			return xerr.WithStack(err)
-		}
-
-		if err = tx.Model(new(model.Cow)).
-			Where("id IN ?", matingTimes).
-			Where("admission_status = ?", pasturePb.AdmissionStatus_Admission).
-			Updates(map[string]interface{}{
-				"mating_times": gorm.Expr("mating_times + 1"),
-			}).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
@@ -642,9 +664,12 @@ func (s *StoreEntry) AbortionCreate(ctx context.Context, req *pasturePb.EventAbo
 	newEventAbortion := model.NewEventAbortion(cow, req, abortionReasonsMap)
 
 	lastCowMating := &model.EventMating{}
-	if err = s.DB.Where("cow_id = ?", cow.Id).Order("id desc").First(lastCowMating).Error; err != nil {
+	if err = s.DB.Model(new(model.EventMating)).Where("cow_id = ?", cow.Id).
+		Where("mating_result = ?", pasturePb.MatingResult_Pregnant).
+		Order("id desc").First(lastCowMating).Error; err != nil {
 		return xerr.WithStack(err)
 	}
+
 	if err = s.DB.Transaction(func(tx *gorm.DB) error {
 		// 创建牛只流产事件数据
 		if err = tx.Create(newEventAbortion).Error; err != nil {
@@ -656,13 +681,13 @@ func (s *StoreEntry) AbortionCreate(ctx context.Context, req *pasturePb.EventAbo
 				"is_pregnant":      pasturePb.IsShow_No,
 				"last_abortion_at": req.AbortionAt,
 				"breed_status":     pasturePb.BreedStatus_Abort,
-				"lact":             cow.Lact + 1,
 			}).Error; err != nil {
 			return xerr.WithStack(err)
 		}
 
 		// 更新最近一次配种结果为流产
-		if err = tx.Model(new(model.EventMating)).Where("id = ?", lastCowMating.Id).
+		if err = tx.Model(new(model.EventMating)).
+			Where("id = ?", lastCowMating.Id).
 			Updates(map[string]interface{}{
 				"mating_result":    pasturePb.MatingResult_Abort,
 				"mating_result_at": req.AbortionAt,

+ 10 - 7
module/backend/sql.go

@@ -313,16 +313,19 @@ func (s *StoreEntry) GetOutboundLogsByOutboundId(ctx context.Context, id int64)
 	return list, nil
 }
 
-func (s *StoreEntry) GetEventMatingIsExIstByCowId(ctx context.Context, cow *model.Cow, status pasturePb.IsShow_Kind) (int64, error) {
-	count := int64(0)
+func (s *StoreEntry) GetEventMatingIsExIstByCowId(
+	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).
-		Where("status = ?", status).
-		Where("mating_result = ?", pasturePb.MatingResult_Unknown).
 		Order("id desc").
-		Count(&count).Error; err != nil {
-		return 0, xerr.WithStack(err)
+		First(newEventMating).Error; err != nil {
+		if !errors.Is(err, gorm.ErrRecordNotFound) {
+			return nil, xerr.WithStack(err)
+		}
 	}
-	return count, nil
+	return newEventMating, nil
 }

+ 17 - 0
util/util.go

@@ -290,3 +290,20 @@ func RemoveDuplicates(slice []string) []string {
 	// 返回不重复的切片
 	return result
 }
+
+func DaysBetween(startDayUnix int64, endDayUnix int64) int64 {
+	time1 := time.Unix(startDayUnix, 0)
+	time2 := time.Unix(endDayUnix, 0)
+	// Truncate to the start of the day (00:00:00)
+	startOfDay1 := time.Date(time1.Year(), time1.Month(), time1.Day(), 0, 0, 0, 0, time1.Location())
+	startOfDay2 := time.Date(time2.Year(), time2.Month(), time2.Day(), 0, 0, 0, 0, time2.Location())
+
+	// Calculate the difference in days
+	daysDiff := int64(startOfDay2.Sub(startOfDay1).Hours() / 24)
+	// Adjust sign if time2 is before time1
+	if startOfDay2.Before(startOfDay1) {
+		daysDiff = -daysDiff
+	}
+
+	return daysDiff
+}

+ 21 - 0
util/util_test.go

@@ -389,4 +389,25 @@ func TestRemoveDuplicates(t *testing.T) {
 		got := RemoveDuplicates(tt.actual)
 		assert.Equal(t, tt.got, got)
 	}
+
+}
+
+func TestDaysBetween(t *testing.T) {
+	tests := []struct {
+		actual []int64
+		got    int64
+	}{
+		{
+			actual: []int64{
+				1730772000,
+				1730908800,
+			},
+			got: 2,
+		},
+	}
+
+	for _, v := range tests {
+		got := DaysBetween(v.actual[0], v.actual[1])
+		assert.Equal(t, got, v.got)
+	}
 }