Browse Source

event: enter 入场增加批次号

Yi 2 weeks ago
parent
commit
33bcc025a3

+ 1 - 1
go.mod

@@ -3,7 +3,7 @@ module kpt-pasture
 go 1.17
 
 require (
-	gitee.com/xuyiping_admin/go_proto v0.0.0-20250521021325-89e4fc71dd39
+	gitee.com/xuyiping_admin/go_proto v0.0.0-20250521031814-2eb66df61c58
 	gitee.com/xuyiping_admin/pkg v0.0.0-20250514071642-f92d2ac9a85d
 	github.com/dgrijalva/jwt-go v3.2.0+incompatible
 	github.com/eclipse/paho.mqtt.golang v1.4.3

+ 2 - 0
go.sum

@@ -105,6 +105,8 @@ gitee.com/xuyiping_admin/go_proto v0.0.0-20250515070041-363e94a8dbac/go.mod h1:B
 gitee.com/xuyiping_admin/go_proto v0.0.0-20250521020200-04806a79ad46/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
 gitee.com/xuyiping_admin/go_proto v0.0.0-20250521021325-89e4fc71dd39 h1:/7MllucrQJnIxXY9fapeEOucVejTYY1IxsyIe8tLhlA=
 gitee.com/xuyiping_admin/go_proto v0.0.0-20250521021325-89e4fc71dd39/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250521031814-2eb66df61c58 h1:PDG62cMpWaD/V11wty6sjiM4t2/sYFBg9u2S1tiZWbs=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250521031814-2eb66df61c58/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=
 gitee.com/xuyiping_admin/pkg v0.0.0-20250514071642-f92d2ac9a85d h1:vBXmMRggF7mZVPGRDgavZ87igJgkezwX0a3v1/XtIMQ=

+ 19 - 0
http/handler/config/config.go

@@ -2,6 +2,7 @@ package config
 
 import (
 	"kpt-pasture/http/middleware"
+	"net/http"
 	"strconv"
 
 	"gitee.com/xuyiping_admin/pkg/valid"
@@ -153,3 +154,21 @@ func SystemBaseConfigOptions(c *gin.Context) {
 	}
 	ginutil.JSONResp(c, res)
 }
+
+func FindCowHistoryBatchNumber(c *gin.Context) {
+	res, err := middleware.BackendOperation(c).OpsService.FindCowHistoryBatchNumber(c)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	c.JSON(http.StatusOK, res)
+}
+
+func GenerateBatchNumber(c *gin.Context) {
+	res, err := middleware.BackendOperation(c).OpsService.GenerateBatchNumber(c)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	c.JSON(http.StatusOK, res)
+}

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

@@ -53,6 +53,7 @@ func EnterEventCreate(c *gin.Context) {
 		valid.Field(&req.PenId, valid.Required),
 		valid.Field(&req.OperationId, valid.Required),
 		valid.Field(&req.Weight, valid.Required),
+		valid.Field(&req.BatchNumber, valid.Required),
 	); err != nil {
 		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
 		return

+ 3 - 0
http/route/config_api.go

@@ -26,5 +26,8 @@ func ConfigAPI(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
 		configRoute.GET("/disease/type/options", config.DiseaseTypeOptions)
 		configRoute.GET("/disease/options", config.DiseaseOptions)
 		configRoute.GET("/prescription/options", config.PrescriptionOptions)
+
+		configRoute.GET("/cow/batch/number", config.FindCowHistoryBatchNumber)
+		configRoute.GET("/generate/batch/number", config.GenerateBatchNumber)
 	}
 }

+ 2 - 0
model/cow.go

@@ -68,6 +68,7 @@ type Cow struct {
 	LastSecondWeightAt    int64                          `json:"lastSecondWeightAt"`    // 最后第二次称重时间
 	LastAbortionAt        int64                          `json:"lastAbortionAt"`        // 最近一次流产时间
 	LastWeightAt          int64                          `json:"lastWeightAt"`          // 最近一次称重时间
+	BatchNumber           string                         `json:"batchNumber"`           // 批次号
 	CreatedAt             int64                          `json:"createdAt"`
 	UpdatedAt             int64                          `json:"updatedAt"`
 }
@@ -699,6 +700,7 @@ func NewEnterCow(pastureId int64, req *pasturePb.EventEnterRequest, penMap map[i
 		LastBullNumber:      req.BullNumber,
 		LastAbortionAt:      int64(req.AbortionAt),
 		AdmissionPrice:      req.Price,
+		BatchNumber:         req.BatchNumber,
 	}
 	cow.AdmissionAge = cow.GetAdmissionAge()
 	cow.DayAge = cow.GetDayAge()

+ 13 - 0
model/event_enter.go

@@ -74,6 +74,7 @@ func NewEventEnter(pastureId, cowId int64, req *pasturePb.EventEnterRequest) *Ev
 		MessengerName:    req.MessengerName,
 		OperationId:      int64(req.OperationId),
 		OperationName:    req.OperationName,
+		BatchNumber:      req.BatchNumber,
 	}
 }
 
@@ -130,3 +131,15 @@ func (e EventEnterSlice) ToPB(
 	}
 	return res
 }
+
+type HistoryBatchNumberResponse struct {
+	Code int32    `json:"code"`
+	Msg  string   `json:"msg"`
+	Data []string `json:"data"`
+}
+
+type GenerateBatchNumberResponse struct {
+	Code int32  `json:"code"`
+	Msg  string `json:"msg"`
+	Data string `json:"data"`
+}

+ 5 - 0
model/indicators_data.go

@@ -37,6 +37,11 @@ const (
 	YouthFirstCheckRate       = "youth_first_check_rate"
 	YouthSecondCheckRate      = "youth_second_check_rate"
 	ForbiddenCowNumber        = "forbidden_cow_number"
+	AvgRegistrationDays       = "avg_registration_days"
+	AvgPregnancyDays          = "avg_pregnancy_days"
+	AvgGestationalAge         = "avg_gestational_age"
+	Month17UnPregnancyRate    = "month17_un_pregnancy_rate"
+	Month20UnPregnancyRate    = "month20_un_pregnancy_rate"
 )
 
 type IndicatorsData struct {

+ 44 - 0
module/backend/config_data_extend.go

@@ -1,9 +1,15 @@
 package backend
 
 import (
+	"context"
+	"fmt"
 	"kpt-pasture/model"
+	"kpt-pasture/util"
+	"net/http"
+	"time"
 
 	pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
+	"gitee.com/xuyiping_admin/pkg/xerr"
 )
 
 func (s *StoreEntry) WorkOrderSubUnitEnumList(isAll string) []*pasturePb.ConfigOptionsList {
@@ -353,3 +359,41 @@ func (s *StoreEntry) NeckRingErrorEnumList(isAll string) []*pasturePb.ConfigOpti
 	})
 	return configOptions
 }
+
+// FindCowHistoryBatchNumber 获取历史批次号
+func (s *StoreEntry) FindCowHistoryBatchNumber(ctx context.Context) (*model.HistoryBatchNumberResponse, error) {
+	userModel, err := s.GetUserModel(ctx)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	res := make([]string, 0)
+	if err = s.DB.Model(new(model.EventEnter)).
+		Select("batch_number").
+		Where("pasture_id = ?", userModel.AppPasture.Id).
+		Where("batch_number != ''").
+		Group("batch_number").
+		Find(&res).Error; err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	return &model.HistoryBatchNumberResponse{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: res,
+	}, nil
+}
+
+func (s *StoreEntry) GenerateBatchNumber(ctx context.Context) (*model.GenerateBatchNumberResponse, error) {
+	userModel, err := s.GetUserModel(ctx)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	data := fmt.Sprintf("%s%d%s", time.Now().Local().Format(model.LayoutDate2), userModel.AppPasture.Id, util.GenerateRandomNumberString(8))
+	return &model.GenerateBatchNumberResponse{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: data,
+	}, nil
+}

+ 3 - 0
module/backend/interface.go

@@ -158,6 +158,9 @@ type ConfigDataService interface {
 	DiseaseTypeOptions(ctx context.Context, isChildren string) (*pasturePb.ConfigOptionsListResponse, error)
 	DiseaseOptions(ctx context.Context) (*pasturePb.ConfigOptionsListResponse, error)
 	PrescriptionOptions(ctx context.Context) (*pasturePb.ConfigOptionsListResponse, error)
+
+	FindCowHistoryBatchNumber(ctx context.Context) (*model.HistoryBatchNumberResponse, error)
+	GenerateBatchNumber(ctx context.Context) (*model.GenerateBatchNumberResponse, error)
 }
 
 //go:generate mockgen -destination mock/EventService.go -package kptservicemock kpt-pasture/module/backend EventService

+ 10 - 0
module/crontab/cow_cron.go

@@ -127,6 +127,16 @@ func (e *Entry) Indicators() error {
 			pastureIndicatorList = e.PregnantCheckRate(pastureList, startTime, endTime, false, model.PregnantCheckForSecond)
 		case model.ForbiddenCowNumber:
 			pastureIndicatorList = e.ForbiddenCowNumber(pastureList, startTime, endTime)
+		case model.AvgRegistrationDays:
+			pastureIndicatorList = e.AvgRegistrationDays(pastureList)
+		case model.AvgPregnancyDays:
+			pastureIndicatorList = e.AvgPregnancyDays(pastureList, startTime, endTime)
+		case model.AvgGestationalAge:
+			pastureIndicatorList = e.AvgGestationalAge(pastureList)
+		case model.Month17UnPregnancyRate:
+			pastureIndicatorList = e.MonthUnPregnancyRate(pastureList, 17)
+		case model.Month20UnPregnancyRate:
+			pastureIndicatorList = e.MonthUnPregnancyRate(pastureList, 20)
 		}
 
 		for pastureId, value := range pastureIndicatorList {

+ 3 - 1
module/crontab/cow_indicators_base.go

@@ -143,6 +143,7 @@ func (e *Entry) FindSalesVolume(pastureList []*model.AppPastureList, startTime,
 	return res
 }
 
+// UpdatePastureIndicators 指标收集,不存在时创建,存在时更新
 func (e *Entry) UpdatePastureIndicators(pastureId int64, indicatorsDetails *model.IndicatorsDetails, dateTime int64, value string) {
 	date := time.Unix(dateTime, 0).Local().Format(model.LayoutMonth)
 	where := &model.IndicatorsData{
@@ -161,7 +162,8 @@ func (e *Entry) UpdatePastureIndicators(pastureId int64, indicatorsDetails *mode
 
 	var existData model.IndicatorsData
 	if err := e.DB.Model(new(model.IndicatorsData)).
-		Where(where).First(&existData).Error; err != nil {
+		Where(where).
+		First(&existData).Error; err != nil {
 		if errors.Is(err, gorm.ErrRecordNotFound) {
 			if err = e.DB.Model(new(model.IndicatorsData)).Create(data).Error; err != nil {
 				zaplog.Error("UpdatePastureIndicators", zap.Any("Create", err))

+ 133 - 0
module/crontab/cow_indicators_breed.go

@@ -3,6 +3,8 @@ package crontab
 import (
 	"fmt"
 	"kpt-pasture/model"
+	"kpt-pasture/util"
+	"time"
 
 	pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
 
@@ -36,6 +38,7 @@ func (e *Entry) LactationCow(pastureList []*model.AppPastureList, milkKind pastu
 			Where("pasture_id = ?", pasture.Id).
 			Where("admission_status = ?", pasturePb.AdmissionStatus_Admission).
 			Where("milk_kind = ?", milkKind).
+			Where("sex = ?", pasturePb.Genders_Female).
 			Count(&count).Error; err != nil {
 			zaplog.Error("LactationCow", zap.Any("pasture_id", pasture.Id), zap.Any("err", err))
 		}
@@ -241,6 +244,7 @@ func (e *Entry) CowPregnantRate(pastureList []*model.AppPastureList, caseName st
 			Where("pasture_id = ?", pasture.Id).
 			Where("breed_status = ?", pasturePb.BreedStatus_Pregnant).
 			Where("admission_status = ?", pasturePb.AdmissionStatus_Admission).
+			Where("sex = ?", pasturePb.Genders_Female).
 			Find(&cowList).Error; err != nil {
 			zaplog.Error("YouthPregnantRate", zap.Any("pasture_id", pasture.Id), zap.Any("error", err))
 		}
@@ -309,6 +313,7 @@ func (e *Entry) PregnantCheckRate(pastureList []*model.AppPastureList, startAt,
 
 }
 
+// ForbiddenCowNumber 禁配牛数
 func (e *Entry) ForbiddenCowNumber(pastureList []*model.AppPastureList, startAt, endAt int64) map[int64]string {
 	res := make(map[int64]string)
 	for _, pasture := range pastureList {
@@ -324,3 +329,131 @@ func (e *Entry) ForbiddenCowNumber(pastureList []*model.AppPastureList, startAt,
 	}
 	return res
 }
+
+// AvgRegistrationDays 平均配准天数
+func (e *Entry) AvgRegistrationDays(pastureList []*model.AppPastureList) map[int64]string {
+	res := make(map[int64]string)
+	for _, pasture := range pastureList {
+		cowList := make([]*model.Cow, 0)
+		if err := e.DB.Model(new(model.Cow)).
+			Where("pasture_id = ?", pasture.Id).
+			Where("admission_status = ?", pasturePb.AdmissionStatus_Admission).
+			Where("breed_status = ?", pasturePb.BreedStatus_Pregnant).
+			Where("sex = ?", pasturePb.Genders_Female).
+			Where("lact >= ?", 1).
+			Find(&cowList).Error; err != nil {
+			zaplog.Error("RegistrationDays", zap.Any("err", err))
+		}
+
+		regDays := int64(0)
+		count := int64(0)
+		for _, cow := range cowList {
+			if cow.LastMatingAt > 0 && cow.LastCalvingAt > 0 {
+				regDays += util.DaysBetween(cow.LastMatingAt, cow.LastCalvingAt)
+				count++
+			}
+		}
+		if count > 0 {
+			res[pasture.Id] = fmt.Sprintf("%d", regDays/count)
+		} else {
+			res[pasture.Id] = "0"
+		}
+	}
+
+	return res
+}
+
+// AvgPregnancyDays 平均怀孕天数
+func (e *Entry) AvgPregnancyDays(pastureList []*model.AppPastureList, startAt, endAt int64) map[int64]string {
+	res := make(map[int64]string)
+	for _, pasture := range pastureList {
+		eventCalvingList := make([]*model.EventCalving, 0)
+		if err := e.DB.Model(new(model.EventCalving)).
+			Where("pasture_id = ?", pasture.Id).
+			Where("status = ?", pasturePb.IsShow_Ok).
+			Where("reality_day BETWEEN ? AND ?", startAt, endAt).
+			Find(&eventCalvingList).Error; err != nil {
+			zaplog.Error("FindCalvingInterval", zap.Any("pasture_id", pasture.Id), zap.Any("err", err))
+		}
+
+		allPregnancyAge := int32(0)
+		count := int32(0)
+		for _, v := range eventCalvingList {
+			if v.PregnancyAge > 0 {
+				allPregnancyAge += v.PregnancyAge
+				count++
+			}
+		}
+		if count > 0 {
+			res[pasture.Id] = fmt.Sprintf("%d", allPregnancyAge/count)
+		} else {
+			res[pasture.Id] = "0"
+		}
+	}
+	return res
+}
+
+// AvgGestationalAge 平均受孕日龄
+func (e *Entry) AvgGestationalAge(pastureList []*model.AppPastureList) map[int64]string {
+	res := make(map[int64]string)
+	for _, pasture := range pastureList {
+		eventMatingList := make([]*model.EventMating, 0)
+		if err := e.DB.Model(new(model.EventMating)).
+			Where("pasture_id = ?", pasture.Id).
+			Where("status = ?", pasturePb.IsShow_Ok).
+			Where("mating_result = ?", pasturePb.MatingResult_Pregnant).
+			Where("lact = ?", 0).
+			Find(&eventMatingList).Error; err != nil {
+			zaplog.Error("FindGestationalAge", zap.Any("pasture_id", pasture.Id), zap.Any("err", err))
+		}
+
+		allDayAge := int32(0)
+		count := int32(0)
+		for _, v := range eventMatingList {
+			if v.DayAge > 0 && v.RealityDay > 0 {
+				allDayAge += v.DayAge
+				count++
+			}
+		}
+		if count > 0 {
+			res[pasture.Id] = fmt.Sprintf("%d", allDayAge/count)
+		} else {
+			res[pasture.Id] = "0"
+		}
+	}
+	return res
+}
+
+// MonthUnPregnancyRate 17-20月龄未孕比例
+func (e *Entry) MonthUnPregnancyRate(pastureList []*model.AppPastureList, month int32) map[int64]string {
+	res := make(map[int64]string)
+	for _, pasture := range pastureList {
+		cowList := make([]*model.Cow, 0)
+		if err := e.DB.Model(new(model.Cow)).
+			Where("pasture_id = ?", pasture.Id).
+			Where("admission_status = ?", pasturePb.AdmissionStatus_Admission).
+			Where("sex = ?", pasturePb.Genders_Female).
+			Find(&cowList).Error; err != nil {
+			zaplog.Error("AvgEmptyDays", zap.Any("pastureId", pasture.Id), zap.Any("err", err))
+		}
+
+		nowTime := time.Now().Local()
+		count := int32(0)
+		for _, cow := range cowList {
+			if cow.BirthAt <= 0 {
+				continue
+			}
+			birthAt := time.Unix(cow.BirthAt, 0).Local()
+			monthYear := util.GetMonths(birthAt, nowTime)
+			if int32(monthYear) >= month {
+				count++
+			}
+		}
+		if count > 0 {
+			res[pasture.Id] = fmt.Sprintf("%.2f", float64(count)/float64(len(cowList)))
+		} else {
+			res[pasture.Id] = "0"
+		}
+	}
+	return res
+}

+ 1 - 1
module/crontab/cow_neck_ring_error.go

@@ -267,7 +267,7 @@ func (e *Entry) NeckRingErrorOfReceivingLess(pastureId int64, neckRing *model.Ne
 func (e *Entry) NeckRingErrorOfDataLatency(pastureId int64, neckRing *model.NeckRing, habitMinId int64, dateTime string) pasturePb.NeckRingNumberError_Kind {
 	res := make([]*model.NeckRingErrorModel, 0)
 	if err := e.DB.Model(new(model.NeckActiveHabit)).
-		Select(`h.cow_id, h.ear_number, SUM(IF(TIMESTAMPDIFF(HOUR, UNIX_TIMESTAMP(h.active_time), h.created_at)>9, 1, 0)) AS nb, COUNT(1) AS nba, ROUND(AVG(h.voltage), 0) AS voltage`).
+		Select(`cow_id,ear_number, SUM(IF(TIMESTAMPDIFF(HOUR, UNIX_TIMESTAMP(active_time), created_at)>9, 1, 0)) AS nb, COUNT(1) AS nba, ROUND(AVG(voltage), 0) AS voltage`).
 		Where("id >= ?", habitMinId).
 		Where("cow_id = ?", neckRing.CowId).
 		Where("filter_high > ?", 200).

+ 23 - 0
util/util.go

@@ -360,6 +360,29 @@ func DaysBetween(startDayUnix int64, endDayUnix int64) int64 {
 	return daysDiff
 }
 
+// GetMonths 计算两个日期之间的月龄(近似值)
+func GetMonths(birthDate, now time.Time) int {
+	years := now.Year() - birthDate.Year()
+	months := int(now.Month()) - int(birthDate.Month())
+
+	// 如果当前月份小于出生月份,年份减1,月份补12
+	if months < 0 {
+		years--
+		months += 12
+	}
+
+	// 考虑天数差异(可选:如果当前日期小于出生日期,减少1个月)
+	if now.Day() < birthDate.Day() {
+		months--
+	}
+
+	totalMonths := years*12 + months
+	if totalMonths < 0 {
+		return 0
+	}
+	return totalMonths
+}
+
 // GetDaysBetween 获取两个日期之间的所有天数
 // 2024-10-01 ~ 2024-10-07 => [2024-10-01,2024-10-02,2024-10-03,2024-10-04,2024-10-05,2024-10-06,2024-10-07]
 func GetDaysBetween(startDate, endDate string) ([]string, error) {