Explorar o código

analysis: 孕检报告

Yi hai 5 meses
pai
achega
14484d4b21

+ 6 - 1
README.md

@@ -33,4 +33,9 @@ lint:
 
 todo列表:
 - [x] module/crontab/crontab.go 中119行[Limit(100)] 待优化,case为产后日期类型待测试
-- [ ] 后台添加配种数据时候,不知道该牛只是同期还是自然发情还是人工揭发?
+- [ ] 后台添加配种数据时候,不知道该牛只是同期还是自然发情还是人工揭发?
+- [ ] 青年牛转后备牛事件(到达主动停配期主动转?)
+- [ ] 后备牛到达主动停配期后的牛只放在哪个模块(配种清单,发情清单)
+- [ ] 发情清单和配种清单更新机制
+- [ ] 前后端部署架构【k8s,docker-compose,docker-swarm】namespace隔离,需要考虑的问题【1.一次性任务,2. 定时任务 3. 数据收集,4. 日志收集 5. 报警介入】
+- [ ] 所有事件录入梳理【批量录入,excel导入,信息人员与操作人员统一规范】

+ 1 - 1
go.mod

@@ -3,7 +3,7 @@ module kpt-pasture
 go 1.17
 
 require (
-	gitee.com/xuyiping_admin/go_proto v0.0.0-20241024082136-e206f3f7ead0
+	gitee.com/xuyiping_admin/go_proto v0.0.0-20241025100516-892af77790ab
 	gitee.com/xuyiping_admin/pkg v0.0.0-20241010101255-0c6bd229b939
 	github.com/dgrijalva/jwt-go v3.2.0+incompatible
 	github.com/eko/gocache v1.1.0

+ 6 - 0
go.sum

@@ -116,6 +116,12 @@ gitee.com/xuyiping_admin/go_proto v0.0.0-20241024075542-0db2fc7bfe0f h1:HinhCcyY
 gitee.com/xuyiping_admin/go_proto v0.0.0-20241024075542-0db2fc7bfe0f/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
 gitee.com/xuyiping_admin/go_proto v0.0.0-20241024082136-e206f3f7ead0 h1:ewJf5F8O3uHKKQX4lXy1KL/P9BCIbqRG7pjzyH5Rr9s=
 gitee.com/xuyiping_admin/go_proto v0.0.0-20241024082136-e206f3f7ead0/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20241025075257-8a8f1cc7c720 h1:7xa/+kqFgclmxB6EYNpA33Ak430zaFPdFIcTMziuf+8=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20241025075257-8a8f1cc7c720/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20241025081249-c24e81100ed5 h1:FIMbHC1Wv/rhXjVDzYaIMgxhhiQsD7agTmNztgL5kKY=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20241025081249-c24e81100ed5/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20241025100516-892af77790ab h1:0k7CeNzABQf8kDylWaPgyZuzH0VgjNxZiACHAeD/spk=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20241025100516-892af77790ab/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
 gitee.com/xuyiping_admin/pkg v0.0.0-20231218082641-aac597b8a015 h1:dfb5dRd57L2HKjdwLT93UFmPYFPOmEl56gtZmqcNnaE=
 gitee.com/xuyiping_admin/pkg v0.0.0-20231218082641-aac597b8a015/go.mod h1:Fk4GYI/v0IK3XFrm1Gn+VkgCz5Y7mfswD5hsTJYOG6A=
 gitee.com/xuyiping_admin/pkg v0.0.0-20241008063555-121a776fd331 h1:qJcpJ3o+O7uxDqR0I9LijQmi607IKvhf4mGum/ZUPT0=

+ 30 - 0
http/handler/analysis/analysis.go

@@ -144,3 +144,33 @@ func AbortionRate(c *gin.Context) {
 	}
 	ginutil.JSONResp(c, res)
 }
+
+func PregnancyReport(c *gin.Context) {
+	var req pasturePb.PregnancyReportRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.StartDayTime, valid.Required),
+		valid.Field(&req.EndDayTime, valid.Required),
+		valid.Field(&req.CowType, valid.Required),
+	); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	pagination := &pasturePb.PaginationModel{
+		Page:       int32(c.GetInt(middleware.Page)),
+		PageSize:   int32(c.GetInt(middleware.PageSize)),
+		PageOffset: int32(c.GetInt(middleware.PageOffset)),
+	}
+
+	res, err := middleware.Dependency(c).StoreEventHub.OpsService.PregnancyReport(c, &req, pagination)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	ginutil.JSONResp(c, res)
+}

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

@@ -131,7 +131,6 @@ func CowDiseaseTreatmentDetail(c *gin.Context) {
 
 	if err := valid.ValidateStruct(&req,
 		valid.Field(&req.CowId, valid.Required),
-		valid.Field(&req.DiseaseId, valid.Required),
 	); err != nil {
 		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
 		return

+ 2 - 0
http/route/analysis_api.go

@@ -19,5 +19,7 @@ func AnalysisAPI(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
 		pastureRoute.POST("/pen/weight", analysis.PenWeight)
 		pastureRoute.POST("/abortion/rate", analysis.AbortionRate)
 		pastureRoute.POST("/twentyone/pregnant/rate", analysis.TwentyonePregnantRate)
+
+		pastureRoute.POST("/pregnancy/report", analysis.PregnancyReport)
 	}
 }

+ 40 - 0
model/event_pregnant_check.go

@@ -10,6 +10,7 @@ import (
 type EventPregnantCheck struct {
 	Id                  int64                              `json:"id"`
 	CowId               int64                              `json:"cowId"`
+	CowType             pasturePb.CowType_Kind             `json:"cowType"`
 	DayAge              int32                              `json:"dayAge"`
 	Lact                int8                               `json:"lact"`
 	MatingAge           int32                              `json:"matingAge"`
@@ -34,6 +35,7 @@ func (e *EventPregnantCheck) TableName() string {
 func NewEventPregnantCheck(cow *Cow, pregnantCheckName string) *EventPregnantCheck {
 	return &EventPregnantCheck{
 		CowId:             cow.Id,
+		CowType:           cow.CowType,
 		DayAge:            cow.GetDayAge(),
 		Lact:              int8(cow.Lact),
 		PlanDay:           util.TimeParseLocalUnix(time.Now().Format(LayoutDate2)),
@@ -118,3 +120,41 @@ func (e EventPregnantCheckSlice) ToPB2() []*PregnantCheckBody {
 	}
 	return res
 }
+
+func (e EventPregnantCheckSlice) ToPB3(
+	pregnantCheckResultMap map[pasturePb.PregnantCheckResult_Kind]string,
+	pregnantCheckMethodMap map[pasturePb.PregnantCheckMethod_Kind]string,
+) []*pasturePb.PregnancyReportTable {
+	res := make([]*pasturePb.PregnancyReportTable, len(e))
+	for i, v := range e {
+		pregnancyCheckName := ""
+		if checkName, ok := PregnantCheckNameMap[v.PregnantCheckName]; ok {
+			pregnancyCheckName = checkName
+		}
+
+		pregnantCheckMethodName := ""
+		if checkMethodName, ok := pregnantCheckMethodMap[v.PregnantCheckMethod]; ok {
+			pregnantCheckMethodName = checkMethodName
+		}
+
+		pregnantCheckResultName := ""
+		if checkResultName, ok := pregnantCheckResultMap[v.PregnantCheckResult]; ok {
+			pregnantCheckResultName = checkResultName
+		}
+
+		res[i] = &pasturePb.PregnancyReportTable{
+			Id:                      int32(v.Id),
+			CowId:                   int32(v.CowId),
+			Lact:                    int32(v.Lact),
+			PregnancyCheckName:      pregnancyCheckName,
+			PregnancyCheckAtFormat:  time.Unix(v.RealityDay, 0).Format(LayoutDate2),
+			MatingAge:               v.MatingAge,
+			PregnantCheckMethod:     v.PregnantCheckMethod,
+			PregnantCheckMethodName: pregnantCheckMethodName,
+			PregnantCheckResult:     v.PregnantCheckResult,
+			PregnantCheckResultName: pregnantCheckResultName,
+			OperationName:           v.OperationName,
+		}
+	}
+	return res
+}

+ 74 - 12
module/backend/analysis.go

@@ -9,6 +9,9 @@ import (
 	"strings"
 	"time"
 
+	"gitee.com/xuyiping_admin/pkg/logger/zaplog"
+	"go.uber.org/zap"
+
 	"gitee.com/xuyiping_admin/pkg/xerr"
 
 	pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
@@ -404,6 +407,18 @@ func (s *StoreEntry) AbortionRate(ctx context.Context, req *pasturePb.AbortionRa
 }
 
 func (s *StoreEntry) TwentyonePregnantRate(ctx context.Context, req *pasturePb.TwentyOnePregnantRateRequest) (*pasturePb.TwentyOnePregnantRateResponse, error) {
+	startUnix := util.TimeParseLocalUnix(req.StartDate)
+	endUnix := util.TimeParseLocalUnix(req.EndDate)
+
+	if startUnix > endUnix {
+		return nil, xerr.Customf("开始时间不能大于结束时间: %s ~ %d", req.StartDate, req.EndDate)
+	}
+
+	nowDateTime := time.Now()
+	if endUnix > nowDateTime.Unix() {
+		return nil, xerr.Customf("结束时间不能大于当前时间: %s ~ %s", req.EndDate, nowDateTime.Format(model.LayoutDate2))
+	}
+
 	dataRange, err := util.Get21DayPeriods(req.StartDate, req.EndDate)
 	if err != nil {
 		return nil, xerr.WithStack(err)
@@ -418,9 +433,9 @@ func (s *StoreEntry) TwentyonePregnantRate(ctx context.Context, req *pasturePb.T
 	systemBasicName := ""
 	switch req.CowType {
 	case pasturePb.CowType_Breeding_Calf:
-		systemBasicName = "proactively_stop_breeding_for_adult"
+		systemBasicName = model.ProactivelyStopBreedingForAdult
 	case pasturePb.CowType_Reserve_Calf:
-		systemBasicName = "proactively_stop_breeding_for_backup"
+		systemBasicName = model.ProactivelyStopBreedingForBackup
 	default:
 		return nil, xerr.Customf("不支持的牛只类型: %d", req.CowType)
 	}
@@ -430,17 +445,31 @@ func (s *StoreEntry) TwentyonePregnantRate(ctx context.Context, req *pasturePb.T
 		return nil, xerr.WithStack(err)
 	}
 
-	stopBreedingDay := systemBasic.MinValue
-
-	fmt.Println(stopBreedingDay)
+	stopBreedingDay := systemBasic.MinValue * 86400
+	dateCowList := make([][]*model.Cow, len(dataRange))
 
-	for _, v := range dataRange {
+	twentyOnePregnantRateList := make([]*pasturePb.TwentyOnePregnantRateList, 0)
+	for i, v := range dataRange {
 		middleDay, err := util.GetRangeDayMiddleDay(v, 11)
 		if err != nil {
 			return nil, xerr.WithStack(err)
 		}
-
-		chart.Header = append(chart.Header, middleDay)
+		middleDayUnix := util.TimeParseLocalEndUnix(middleDay)
+		chart.Header = append(chart.Header, fmt.Sprintf("%s ~ %s", v[0], v[1]))
+		cowList := s.TwentyonePregnantCowList(ctx, req.CowType, stopBreedingDay, middleDayUnix, []int64{})
+		twentyOnePregnantRateList = append(twentyOnePregnantRateList, &pasturePb.TwentyOnePregnantRateList{
+			StartDay:             v[0],
+			EndDay:               v[1],
+			ShouldBreedCount:     int32(len(cowList)),
+			RealityBreedCount:    0,
+			BreedRate:            0,
+			ShouldPregnantCount:  0,
+			RealityPregnantCount: 0,
+			PregnantRate:         0,
+			RealityAbortionCount: 0,
+			AbortionRate:         0,
+		})
+		dateCowList[i] = cowList
 	}
 
 	return &pasturePb.TwentyOnePregnantRateResponse{
@@ -449,11 +478,44 @@ func (s *StoreEntry) TwentyonePregnantRate(ctx context.Context, req *pasturePb.T
 		Data: &pasturePb.TwentyOnePregnantRateData{
 			Chart: chart,
 			Table: &pasturePb.TwentyOnePregnantRateTable{
-				List:     nil,
-				Total:    0,
-				PageSize: 0,
-				Page:     0,
+				List:  twentyOnePregnantRateList,
+				Total: int32(len(dataRange)),
 			},
 		},
 	}, nil
 }
+
+// TwentyonePregnantCowList 21天牛只停配期牛只列表
+func (s *StoreEntry) TwentyonePregnantCowList(
+	ctx context.Context,
+	cowType pasturePb.CowType_Kind,
+	stopBreedingDay int32,
+	middleDay int64,
+	notInCow []int64,
+) []*model.Cow {
+	cowList := make([]*model.Cow, 0)
+	switch cowType {
+	case pasturePb.CowType_Reserve_Calf:
+		pref := s.DB.Model(new(model.Cow)).
+			Where("cow_type = ?", cowType).
+			Where("admission_status = ?", pasturePb.AdmissionStatus_Admission).
+			Where("is_pregnant = ?", pasturePb.IsShow_No).
+			Where("lact = ?", 0).
+			Where("birth_at + ? < ?", stopBreedingDay, middleDay)
+		if len(notInCow) > 0 {
+			pref = pref.Where("id NOT IN ?", notInCow)
+		}
+		if err := pref.Find(&cowList).Error; err != nil {
+			zaplog.Error("TwentyonePregnantCowList",
+				zap.Any("cowType", cowType),
+				zap.Any("stopBreedingDay", stopBreedingDay),
+				zap.Any("middleDay", middleDay),
+				zap.Any("notInCow", notInCow),
+			)
+		}
+	case pasturePb.CowType_Breeding_Calf:
+
+	}
+
+	return cowList
+}

+ 54 - 0
module/backend/analysis_other.go

@@ -0,0 +1,54 @@
+package backend
+
+import (
+	"context"
+	"kpt-pasture/model"
+	"kpt-pasture/util"
+	"net/http"
+
+	"gitee.com/xuyiping_admin/pkg/xerr"
+
+	pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
+)
+
+// PregnancyReport 孕检报告列表
+func (s *StoreEntry) PregnancyReport(ctx context.Context, req *pasturePb.PregnancyReportRequest, pagination *pasturePb.PaginationModel) (*pasturePb.PregnancyReportResponse, error) {
+	startDayUnix := util.TimeParseLocalUnix(req.StartDayTime)
+	endDayUnix := util.TimeParseLocalEndUnix(req.EndDayTime)
+	if startDayUnix > endDayUnix || startDayUnix == 0 || endDayUnix == 0 {
+		return nil, xerr.Custom("开始时间不能大于结束时间")
+	}
+
+	eventPregnantCheckList := make([]*model.EventPregnantCheck, 0)
+	pref := s.DB.Model(new(model.EventPregnantCheck)).Where("status = ?", pasturePb.IsShow_Ok).Where("cow_type = ?", req.CowType)
+
+	if startDayUnix > 0 && endDayUnix > 0 && endDayUnix >= startDayUnix {
+		pref = pref.Where("create_time BETWEEN  ? AND ?", startDayUnix, endDayUnix)
+	}
+
+	if req.PregnantCheckResult > 0 {
+		pref = pref.Where("pregnant_check_result = ?", req.PregnantCheckResult)
+	}
+
+	var count int64 = 0
+	if err := pref.Count(&count).
+		Limit(int(pagination.PageSize)).
+		Offset(int(pagination.PageOffset)).
+		Order("id desc").
+		Find(&eventPregnantCheckList).Error; err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	pregnantCheckResultMap := s.PregnantCheckResultMap()
+	pregnantCheckMethodMap := s.PregnantCheckMethodMap()
+	return &pasturePb.PregnancyReportResponse{
+		Code:    http.StatusOK,
+		Message: "ok",
+		Data: &pasturePb.PregnancyReportData{
+			List:     model.EventPregnantCheckSlice(eventPregnantCheckList).ToPB3(pregnantCheckResultMap, pregnantCheckMethodMap),
+			Total:    int32(count),
+			PageSize: pagination.PageSize,
+			Page:     pagination.Page,
+		},
+	}, nil
+}

+ 26 - 3
module/backend/event_health.go

@@ -311,6 +311,14 @@ func (s *StoreEntry) CowDiseaseTreatment(ctx context.Context, req *pasturePb.Cow
 			}).Error; err != nil {
 			return xerr.WithStack(err)
 		}
+
+		if err = tx.Model(new(model.Prescription)).
+			Where("id = ?", prescription.Id).
+			Updates(map[string]interface{}{
+				"use_count": prescription.UseCount + 1,
+			}).Error; err != nil {
+			return xerr.WithStack(err)
+		}
 		return nil
 	}); err != nil {
 		return xerr.WithStack(err)
@@ -320,10 +328,14 @@ func (s *StoreEntry) CowDiseaseTreatment(ctx context.Context, req *pasturePb.Cow
 }
 
 // CowDiseaseTreatmentDetail 发病牛只治疗详情列表
-func (s *StoreEntry) CowDiseaseTreatmentDetail(ctx context.Context, req *pasturePb.EventCowTreatmentDetailRequest, pagination *pasturePb.PaginationModel) (*pasturePb.EventCowTreatmentDetailResponse, error) {
+func (s *StoreEntry) CowDiseaseTreatmentDetail(
+	ctx context.Context,
+	req *pasturePb.EventCowTreatmentDetailRequest,
+	pagination *pasturePb.PaginationModel,
+) (*pasturePb.EventCowTreatmentDetailResponse, error) {
 	eventCowDiseaseList := make([]*model.EventCowDisease, 0)
 	var count int64 = 0
-	pref := s.DB.Where("cow_id = ?", req.CowId)
+	pref := s.DB.Model(new(model.EventCowDisease)).Where("cow_id = ?", req.CowId)
 
 	if req.DiseaseId > 0 {
 		pref.Where("disease_id = ?", req.DiseaseId)
@@ -347,7 +359,8 @@ func (s *StoreEntry) CowDiseaseTreatmentDetail(ctx context.Context, req *pasture
 		cowDiseaseIds[i] = v.Id
 	}
 
-	if err := s.DB.Where("cow_disease_id IN ?", cowDiseaseIds).
+	if err := s.DB.Model(new(model.EventCowTreatment)).
+		Select("*").Where("cow_disease_id IN ?", cowDiseaseIds).
 		Where("cow_id = ?", req.CowId).
 		Group("cow_disease_id").
 		Order("id desc").
@@ -355,6 +368,16 @@ func (s *StoreEntry) CowDiseaseTreatmentDetail(ctx context.Context, req *pasture
 		return nil, xerr.WithStack(err)
 	}
 
+	for _, v := range eventCowTreatmentList {
+		for _, disease := range eventCowDiseaseList {
+			if int64(v.CowDiseaseId) != disease.Id {
+				continue
+			}
+			v.DiseaseName = disease.DiseaseName
+			v.DiseaseAt = int32(disease.DiseaseAt)
+		}
+	}
+
 	return &pasturePb.EventCowTreatmentDetailResponse{
 		Code:    http.StatusOK,
 		Message: "ok",

+ 1 - 0
module/backend/interface.go

@@ -213,6 +213,7 @@ type AnalyseService interface {
 	PenWeight(ctx context.Context, req *pasturePb.PenWeightRequest, pagination *pasturePb.PaginationModel) (*pasturePb.PenWeightResponse, error)
 	AbortionRate(ctx context.Context, req *pasturePb.AbortionRateRequest) (*pasturePb.AbortionRateResponse, error)
 	TwentyonePregnantRate(ctx context.Context, req *pasturePb.TwentyOnePregnantRateRequest) (*pasturePb.TwentyOnePregnantRateResponse, error)
+	PregnancyReport(ctx context.Context, req *pasturePb.PregnancyReportRequest, pagination *pasturePb.PaginationModel) (*pasturePb.PregnancyReportResponse, error)
 }
 
 //go:generate mockgen -destination mock/DashboardService.go -package kptservicemock kpt-pasture/module/backend DashboardService

+ 3 - 4
util/util.go

@@ -9,10 +9,9 @@ import (
 )
 
 const (
-	LocationName = "Asia/Shanghai"
-	LayoutTime   = "2006-01-02 15:04:05"
-	Layout       = "2006-01-02"
-	LayoutMonth  = "2006-01"
+	LayoutTime  = "2006-01-02 15:04:05"
+	Layout      = "2006-01-02"
+	LayoutMonth = "2006-01"
 )
 
 // TimeParseLocalUnix 获取当天零点的时间戳