| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556 | package backendimport (	"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/logger/zaplog"	"gitee.com/xuyiping_admin/pkg/xerr"	"go.uber.org/zap")// WeightScatterPlot 体重散点图 获取图表数据func (s *StoreEntry) WeightScatterPlot(ctx context.Context, req *pasturePb.SearchGrowthCurvesRequest, pagination *pasturePb.PaginationModel) (*pasturePb.GrowthCurvesResponse, error) {	userModel, err := s.GetUserModel(ctx)	if err != nil {		return nil, xerr.Custom("当前用户信息错误,请退出重新登录")	}	// 查询数据	cowList := make([]*model.Cow, 0)	pref := s.DB.Model(new(model.Cow)).		Where("admission_status = ?", pasturePb.AdmissionStatus_Admission).		Where("pasture_id = ?", userModel.AppPasture.Id)	if req.EarNumber != "" {		pref.Where("ear_number = ?", req.EarNumber)	}	if len(req.PenIds) > 0 {		pref.Where("pen_id IN (?)", req.PenIds)	}	if len(req.AdmissionDate) == 2 {		t0, _ := util.TimeParseLocal(model.LayoutDate2, req.AdmissionDate[0])		t1, _ := util.TimeParseLocal(model.LayoutDate2, req.AdmissionDate[1])		pref.Where("admission_at BETWEEN ? AND ?", t0.Unix(), t1.Unix()+86399)	}	var count int64	if err = pref.Count(&count).		Limit(int(pagination.PageSize)).		Offset(int(pagination.PageOffset)).		Find(&cowList).Error; err != nil {		return nil, err	}	if err != nil {		return nil, xerr.WithStack(err)	}	// 计算图表数据	chartsList := &pasturePb.Charts{		CowId:        make([]int32, 0),		Weight:       make([]float32, 0),		AdmissionAge: make([]int32, 0),	}	cowData := make([]*pasturePb.CowList, 0)	for _, cow := range cowList {		currentWeight := float32(cow.CurrentWeight) / 1000		admissionAtFormat := ""		if cow.AdmissionAt > 0 {			admissionAtFormat = time.Unix(cow.AdmissionAt, 0).Format(model.LayoutDate2)		}		cowData = append(cowData, &pasturePb.CowList{			CowId:                    int32(cow.Id),			EarNumber:                cow.EarNumber,			DayAge:                   cow.GetDayAge(),			PenName:                  cow.PenName,			CurrentWeight:            currentWeight,			BirthAt:                  int32(cow.BirthAt),			BirthWeight:              float32(cow.BirthWeight) / 1000,			LastWeightAt:             int32(cow.LastWeightAt),			AverageDailyWeightGain:   float32(cow.GetAverageDailyWeight()),			PreviousStageDailyWeight: float32(cow.GetPreviousStageDailyWeight()),			AdmissionAge:             cow.GetAdmissionAge(),			AdmissionAtFormat:        admissionAtFormat,		})		chartsList.CowId = append(chartsList.CowId, int32(cow.Id))		chartsList.Weight = append(chartsList.Weight, currentWeight)		chartsList.AdmissionAge = append(chartsList.AdmissionAge, cow.GetAdmissionAge())	}	// 返回数据	return &pasturePb.GrowthCurvesResponse{		Code: http.StatusOK,		Msg:  "success",		Data: &pasturePb.GrowthCurveData{			Table:  cowData,			Charts: chartsList,		},	}, nil}func (s *StoreEntry) WeightRange(ctx context.Context, req *pasturePb.WeightRangeRequest) (*pasturePb.WeightRangeResponse, error) {	userModel, err := s.GetUserModel(ctx)	if err != nil {		return nil, xerr.WithStack(err)	}	cowWeightRange := make([]*model.CowWeightRange, 0)	prefix := s.DB.Model(new(model.Cow)).		Where("admission_status = ?", pasturePb.AdmissionStatus_Admission).		Where("pasture_id = ?", userModel.AppPasture.Id)	if req.CowKind > 0 {		prefix.Where("cow_kind = ?", req.CowKind)	}	if err = prefix.Select(`		CASE 			WHEN current_weight BETWEEN 0 AND 50000 THEN '0-50'  			WHEN current_weight BETWEEN 50001 AND 100000 THEN '51-100' 			WHEN current_weight BETWEEN 100001 AND 150000 THEN '101-150' 			WHEN current_weight BETWEEN 150001 AND 200000 THEN '151-200' 			WHEN current_weight BETWEEN 200001 AND 250000 THEN '201-250' 			WHEN current_weight BETWEEN 250001 AND 300000 THEN '251-300' 			WHEN current_weight BETWEEN 300001 AND 350000 THEN '301-350' 			WHEN current_weight BETWEEN 350001 AND 400000 THEN '351-400' 			WHEN current_weight BETWEEN 400001 AND 450000 THEN '401-450' 			WHEN current_weight BETWEEN 450001 AND 500000 THEN '451-500' 			WHEN current_weight BETWEEN 500001 AND 550000 THEN '500-550' 			WHEN current_weight BETWEEN 550001 AND 600000 THEN '551-600' 			WHEN current_weight BETWEEN 600001 AND 650000 THEN '601-650' 			WHEN current_weight BETWEEN 650001 AND 700000 THEN '651-700' 			WHEN current_weight BETWEEN 700001 AND 750000 THEN '701-750' 			ELSE '750+'  		END AS weight_range, 		COUNT(*) AS count `,	).Group("weight_range").Order("MIN(current_weight)").Find(&cowWeightRange).Error; err != nil {		return nil, err	}	if len(cowWeightRange) == 0 {		return &pasturePb.WeightRangeResponse{			Code: http.StatusOK,			Msg:  "ok",			Data: &pasturePb.WeightRangeData{				CowList: make([]*pasturePb.CowList, 0),				WeightBarChart: &pasturePb.WeightBarChart{					Header: make([]string, 0),					Data:   make([]int32, 0),				},			},		}, nil	}	header := make([]string, 0)	data := make([]int32, 0)	for _, v := range cowWeightRange {		header = append(header, v.WeightRange)		data = append(data, v.Count)	}	// 牛只详情列表	pref := s.DB.Model(new(model.Cow)).		Where("admission_status = ?", pasturePb.AdmissionStatus_Admission).		Where("pasture_id = ?", userModel.AppPasture.Id)	if req.CowKind > 0 {		pref.Where("cow_kind = ?", req.CowKind)	}	cowList := make([]*model.Cow, 0)	if req.MinWeight >= 0 && req.MaxWeight >= 0 && req.MinWeight < req.MaxWeight {		pref.Where("current_weight BETWEEN  ? AND ? ", req.MinWeight*1000, req.MaxWeight*1000)	}	if err = pref.Find(&cowList).Error; err != nil {		return nil, err	}	penMap := s.PenMap(ctx, userModel.AppPasture.Id)	return &pasturePb.WeightRangeResponse{		Code: http.StatusOK,		Msg:  "ok",		Data: &pasturePb.WeightRangeData{			CowList: model.CowSlice(cowList).WeightRangeToPB(penMap),			WeightBarChart: &pasturePb.WeightBarChart{				Header: header,				Data:   data,			},		},	}, nil}func (s *StoreEntry) MatingTimely(ctx context.Context, req *pasturePb.MatingTimelyRequest) (*model.MatingTimelyResponse, error) {	userModel, err := s.GetUserModel(ctx)	if err != nil {		return nil, xerr.WithStack(err)	}	matingTimelyChart := make([]*model.MatingTimelyChart, 0)	pastureWhereSql := fmt.Sprintf(" AND pasture_id = %d", userModel.AppPasture.Id)	sql := `SELECT calving_age,cow_type, DATE_FORMAT(FROM_UNIXTIME(reality_day), '%Y-%m-%d') AS reality_day, lact_group		FROM (			SELECT calving_age, cow_type,reality_day, '0' AS lact_group			FROM event_mating			WHERE lact = 0 AND status = 1 ` + pastureWhereSql + `			UNION ALL			SELECT calving_age,cow_type, reality_day, '1' AS lact_group			FROM event_mating			WHERE lact = 1 AND status = 1 ` + pastureWhereSql + `			UNION ALL			SELECT calving_age,cow_type,  reality_day, '2' AS lact_group			FROM event_mating			WHERE lact = 2 AND status = 1 ` + pastureWhereSql + `			UNION ALL			SELECT calving_age, cow_type, reality_day, '3+' AS lact_group			FROM event_mating			WHERE lact >= 3 AND status = 1 ` + pastureWhereSql + `		) AS subquery WHERE 1 = 1 `	whereSql := ""	if req.CowType > 0 {		whereSql += fmt.Sprintf("AND cow_type = %d ", req.CowType)	}	if req.StartDayAt > 0 && req.EndDayAt > 0 {		whereSql += fmt.Sprintf("AND reality_day BETWEEN %d AND %d", req.StartDayAt, req.EndDayAt)	}	if err = s.DB.Raw(fmt.Sprintf("%s %s", sql, whereSql)).Find(&matingTimelyChart).Error; err != nil {		return nil, err	}	chart := &model.CowMatingChart{		Lact0: make([][]string, 0),		Lact1: make([][]string, 0),		Lact2: make([][]string, 0),		Lact3: make([][]string, 0),	}	if len(matingTimelyChart) == 0 {		return &model.MatingTimelyResponse{			Code: http.StatusOK,			Msg:  "ok",			Data: &model.MatingTimelyData{				CowList: make([]*pasturePb.CowList, 0),				Chart:   chart,			},		}, nil	}	for _, v := range matingTimelyChart {		t, _ := util.TimeParseLocal(model.LayoutDate2, v.RealityDay)		switch v.LactGroup {		case "0":			chart.Lact0 = append(chart.Lact0, []string{fmt.Sprintf("%d", t.Day()), fmt.Sprintf("%d", v.CalvingAge), v.RealityDay})		case "1":			chart.Lact1 = append(chart.Lact1, []string{fmt.Sprintf("%d", t.Day()), fmt.Sprintf("%d", v.CalvingAge), v.RealityDay})		case "2":			chart.Lact2 = append(chart.Lact2, []string{fmt.Sprintf("%d", t.Day()), fmt.Sprintf("%d", v.CalvingAge), v.RealityDay})		case "3+":			chart.Lact3 = append(chart.Lact3, []string{fmt.Sprintf("%d", t.Day()), fmt.Sprintf("%d", v.CalvingAge), v.RealityDay})		}	}	// 牛只详情列表	eventMatingList := make([]*model.EventMating, 0)	pref := s.DB.Model(new(model.EventMating)).		Where("status = ?", pasturePb.IsShow_Ok)	if req.CowType > 0 {		pref.Where("cow_type = ?", req.CowType)	}	if req.StartDayAt > 0 && req.EndDayAt > 0 {		pref.Where("reality_day BETWEEN ? AND ?", req.StartDayAt, req.EndDayAt)	}	if err = pref.Find(&eventMatingList).Error; err != nil {		return nil, err	}	return &model.MatingTimelyResponse{		Code: http.StatusOK,		Msg:  "ok",		Data: &model.MatingTimelyData{			CowList: model.EventMatingSlice(eventMatingList).ToPB2(),			Chart:   chart,		},	}, nil}func (s *StoreEntry) PenWeight(ctx context.Context, req *pasturePb.PenWeightRequest, pagination *pasturePb.PaginationModel) (*pasturePb.PenWeightResponse, error) {	userModel, err := s.GetUserModel(ctx)	if err != nil {		return nil, xerr.WithStack(err)	}	penWeightList := make([]*model.PenWeight, 0)	pref := s.DB.Model(new(model.Cow)).		Select(`			pen_id,			CEILING(AVG(current_weight) / 1000 ) AS avg_weight,			CEILING(SUM(current_weight) / 1000 ) AS all_weight,			COUNT(*) AS cow_count`,		).		Where("pasture_id = ?", userModel.AppPasture.Id).		Where("admission_status = ?", pasturePb.AdmissionStatus_Admission)	if len(req.PenId) > 0 && req.BarId <= 0 {		pref.Where("pen_id IN ?", req.PenId)	}	if err = pref.Group("pen_id").		Order("pen_id").		Find(&penWeightList).Error; err != nil {		return nil, err	}	chart := &pasturePb.PenWeightChart{		Header:    make([]string, 0),		AllWeight: make([]int32, 0),		AvgWeight: make([]int32, 0),		CowCount:  make([]int32, 0),	}	if len(penWeightList) <= 0 {		return &pasturePb.PenWeightResponse{			Code: http.StatusOK,			Msg:  "ok",			Data: &pasturePb.PenWeightData{				CowList: make([]*pasturePb.CowList, 0),				Chart:   chart,			},		}, nil	}	cowList := make([]*model.Cow, 0)	var count int64 = 0	prefList := s.DB.Model(new(model.Cow)).		Where("admission_status = ?", pasturePb.AdmissionStatus_Admission)	if len(req.PenId) > 0 {		prefList.Where("pen_id IN (?)", req.PenId)	} else if req.BarId > 0 {		prefList.Where("pen_id = ?", []int32{req.BarId})	}	if err = prefList.Count(&count).Limit(int(pagination.PageSize)).		Offset(int(pagination.PageOffset)).Order("pen_id").		Find(&cowList).Error; err != nil {		return nil, xerr.WithStack(err)	}	penMap := s.PenMap(ctx, userModel.AppPasture.Id)	return &pasturePb.PenWeightResponse{		Code: http.StatusOK,		Msg:  "ok",		Data: &pasturePb.PenWeightData{			CowList:  model.CowSlice(cowList).ToPB2(penWeightList),			Total:    int32(count),			Page:     pagination.Page,			PageSize: pagination.PageSize,			Chart:    model.PenWeightSlice(penWeightList).ToPB(penMap),		},	}, nil}func (s *StoreEntry) AbortionRate(ctx context.Context, req *pasturePb.AbortionRateRequest) (*pasturePb.AbortionRateResponse, error) {	userModel, err := s.GetUserModel(ctx)	if err != nil {		return nil, xerr.WithStack(err)	}	dayTimeList, err := util.GetMonthsInRange(req.StartDayTime, req.EndDayTime)	if err != nil {		return nil, xerr.WithStack(err)	}	// 历史每月怀孕牛头数量	cowPregnantMonthList := make([]*model.CowPregnantMonth, 0)	pref := s.DB.Model(new(model.EventMating)).		Select(`COUNT(cow_id) AS cow_count,DATE_FORMAT(FROM_UNIXTIME(reality_day),'%Y-%m') as month`).		Where("cow_type = ?", req.CowType).		Where("pasture_id = ?", userModel.AppPasture.Id).		Where("status = ?", pasturePb.IsShow_Ok).		Where("mating_result = ?", pasturePb.MatingResult_Pregnant).		Where("DATE_FORMAT(FROM_UNIXTIME(reality_day),'%Y-%m') IN (?)", dayTimeList)	if req.Lact >= 0 && req.Lact <= 3 {		pref.Where("lact = ?", req.Lact)	} else {		pref.Where("lact > ?", req.Lact)	}	if err = pref.Group("month").		Find(&cowPregnantMonthList).Error; err != nil {		return nil, xerr.WithStack(err)	}	// 历史每月流产牛头数量	cowAbortionMonthList := make([]*model.CowPregnantMonth, 0)	pref2 := s.DB.Model(new(model.EventAbortion)).		Select(`COUNT(cow_id) AS cow_count,DATE_FORMAT(FROM_UNIXTIME(abortion_at),'%Y-%m') as month`).		Where("cow_type = ?", req.CowType).		Where("DATE_FORMAT(FROM_UNIXTIME(abortion_at),'%Y-%m') IN ?", dayTimeList)	if req.Lact >= 0 {		pref2.Where("lact = ?", req.Lact)	}	if err = pref2.Group("month").Find(&cowAbortionMonthList).Error; err != nil {		return nil, xerr.WithStack(err)	}	chart := &pasturePb.AbortionRateChart{		Header:             make([]string, 0),		AbortionCountMonth: make([]int32, 0),		PregnantCountMonth: make([]int32, 0),		AbortionRateMonth:  make([]float32, 0),	}	table := make([]*pasturePb.AbortionRateTable, 0)	for _, v2 := range cowAbortionMonthList {		pregnantCountMonth := int32(0)		for _, v := range cowPregnantMonthList {			if v.Month == v2.Month {				pregnantCountMonth = v.CowCount			}		}		abortionRateMonth := float64(0)		if pregnantCountMonth > 0 && v2.CowCount > 0 {			abortionRateMonth = util.RoundToTwoDecimals(float64(v2.CowCount) / float64(pregnantCountMonth) * 100)		}		chart.Header = append(chart.Header, v2.Month)		chart.AbortionCountMonth = append(chart.AbortionCountMonth, v2.CowCount)		chart.PregnantCountMonth = append(chart.PregnantCountMonth, pregnantCountMonth)		chart.AbortionRateMonth = append(chart.AbortionRateMonth, float32(abortionRateMonth))		table = append(table, &pasturePb.AbortionRateTable{			AbortionCount: v2.CowCount,			MonthName:     v2.Month,			PregnantCount: pregnantCountMonth,			AbortionRate:  float32(abortionRateMonth),		})	}	return &pasturePb.AbortionRateResponse{		Code: http.StatusOK,		Msg:  "ok",		Data: &pasturePb.AbortionRateData{			Chart: chart,			Table: table,		},	}, nil}func (s *StoreEntry) TwentyOnePregnantRate(ctx context.Context, req *pasturePb.TwentyOnePregnantRateRequest) (*pasturePb.TwentyOnePregnantRateResponse, error) {	userModel, err := s.GetUserModel(ctx)	if err != nil {		return nil, xerr.WithStack(err)	}	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)	}	chart := &pasturePb.TwentyOnePregnantRateChart{		Header:       make([]string, 0),		PregnantRate: make([]float32, 0),		MatingRate:   make([]float32, 0),	}	// 牛只主动停配期	systemBasicName := ""	switch req.CowType {	case pasturePb.CowType_Breeding_Calf:		systemBasicName = model.ProactivelyStopBreedingForAdult	case pasturePb.CowType_Reserve_Calf:		systemBasicName = model.ProactivelyStopBreedingForBackup	default:		return nil, xerr.Customf("不支持的牛只类型: %d", req.CowType)	}	systemBasic, err := s.GetSystemBasicByName(ctx, userModel.AppPasture.Id, systemBasicName)	if err != nil {		return nil, xerr.WithStack(err)	}	stopBreedingDay := systemBasic.MinValue * 86400	dateCowList := make([][]*model.Cow, len(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)		}		middleDayUnix := util.TimeParseLocalEndUnix(middleDay)		chart.Header = append(chart.Header, fmt.Sprintf("%s ~ %s", v[0], v[1]))		cowList := s.TwentyOnePregnantCowList(userModel.AppPasture.Id, 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{		Code: http.StatusOK,		Msg:  "ok",		Data: &pasturePb.TwentyOnePregnantRateData{			Chart: chart,			Table: &pasturePb.TwentyOnePregnantRateTable{				List:  twentyOnePregnantRateList,				Total: int32(len(dataRange)),			},		},	}, nil}// TwentyOnePregnantCowList 21天牛只停配期牛只列表func (s *StoreEntry) TwentyOnePregnantCowList(	pastureId int64,	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("pasture_id = ?", pastureId).			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}
 |