package backend import ( "context" "errors" "kpt-pasture/model" "kpt-pasture/util" "net/http" "regexp" "strconv" "strings" "time" "gorm.io/gorm" "gitee.com/xuyiping_admin/pkg/logger/zaplog" "gitee.com/xuyiping_admin/pkg/xerr" "go.uber.org/zap" pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow" ) func (s *StoreEntry) NeckRingWarning(ctx context.Context) (*pasturePb.IndexNeckRingResponse, error) { userModel, err := s.GetUserModel(ctx) if err != nil { return nil, xerr.WithStack(err) } var count int64 neckRingEstrusList := make([]*model.NeckRingEstrusWarning, 0) estrusWarningLevelItems := map[int32]int32{ int32(pasturePb.EstrusLevel_Low): 0, int32(pasturePb.EstrusLevel_Middle): 0, int32(pasturePb.EstrusLevel_High): 0, } pref, err := s.EstrusWarningQuery(ctx, userModel.AppPasture.Id) if err != nil { return nil, xerr.Customf("系统错误!") } if err = pref.Order("a.level DESC"). Count(&count). Find(&neckRingEstrusList).Error; err != nil { return nil, xerr.WithStack(err) } countEstrusWarning := 0 nowTime := time.Now().Local() optimumMating := map[string]int32{ "front": 0, "middle": 0, "behind": 0, } for _, v := range neckRingEstrusList { estrusWarningLevelItems[int32(v.Level)] += 1 countEstrusWarning += 1 cowInfo, _ := s.GetCowInfoByEarNumber(ctx, userModel.AppPasture.Id, v.EarNumber) pzHour := v.CalculatePzHour(cowInfo.Lact) optimumMatingStartTime := pzHour.Add(-4 * time.Hour) optimumMatingEndTime := pzHour.Add(4 * time.Hour) // 判断当前时间是否在 pzHour-4h 到 pzHour+4h 之间 if nowTime.After(optimumMatingStartTime) && nowTime.Before(optimumMatingEndTime) { optimumMating["middle"] += 1 } if nowTime.After(optimumMatingEndTime) { optimumMating["behind"] += 1 } if nowTime.Before(optimumMatingStartTime) { optimumMating["front"] += 1 } } abortionCount := int64(0) pref, err = s.AbortionWarningQuery(ctx, userModel.AppPasture.Id) if err != nil { return nil, xerr.Customf("系统错误!") } if err = pref.Group("cow_id").Count(&abortionCount).Error; err != nil { return nil, xerr.WithStack(err) } healthWarningNumber := int64(0) if err = s.DB.Model(new(model.NeckRingHealth)). Where("pasture_id = ?", userModel.AppPasture.Id). Group("cow_id"). Count(&healthWarningNumber).Error; err != nil { zaplog.Error("NeckRingWarning", zap.Any("estrusWarningNumber", err)) } return &pasturePb.IndexNeckRingResponse{ Code: http.StatusOK, Msg: "ok", Data: &pasturePb.NeckRingData{ EstrusWarningNumber: int32(countEstrusWarning), HealthWarningNumber: int32(healthWarningNumber), AbortionWarningNumber: int32(abortionCount), StressWarningNumber: 0, EstrusWarningLevelItems: estrusWarningLevelItems, OptimumMating: optimumMating, }, }, nil } func (s *StoreEntry) FocusIndicatorsList(ctx context.Context, dimension string) (*pasturePb.IndexFocusIndicatorsResponse, error) { userModel, err := s.GetUserModel(ctx) if err != nil { return nil, xerr.WithStack(err) } userFocusIndicators := userModel.SystemUser.IndicatorsKinds if len(userFocusIndicators) <= 0 { userFocusIndicators = model.DefaultFocusIndicators } userFocusIndicatorsList := strings.Split(userFocusIndicators, ",") indicatorsDataList := make([]*model.IndicatorsData, 0) pref := s.DB.Model(new(model.IndicatorsData)). Where("pasture_id = ?", userModel.AppPasture.Id). Where("kind in (?)", userFocusIndicatorsList) /*if dimension == "Year" { pref.Where("date = ?", time.Now().Local().Format(model.LayoutMonth)) }*/ nowTime := time.Now().Local() if dimension == "Month" { pref.Where("date = ?", nowTime.Format(model.LayoutMonth)) } if err = pref.Find(&indicatorsDataList).Error; err != nil { zaplog.Error("FocusIndicators", zap.Any("err", err)) } indicatorsDetailsMap, _, err := s.GetIndicatorsDetailsMap(ctx) if err != nil { return nil, xerr.WithStack(err) } focusIndicatorsList := make([]*pasturePb.FocusIndicators, 0) for _, v := range indicatorsDataList { indicatorsDetails, ok := indicatorsDetailsMap[v.Kind] if !ok { continue } onYear, onMonth := "", "" isUp := pasturePb.IsShow_Ok if dimension == "Year" { return nil, xerr.Custom("暂不支持该维度") } if dimension == "Month" { lastMonth := nowTime.AddDate(0, -1, 0).Format(model.LayoutMonth) oldIndicators, _ := s.GetIndicatorsDataByDate(userModel.AppPasture.Id, v.Kind, lastMonth) if oldIndicators != nil { if oldIndicators.Value != "" && oldIndicators.Value != "0" { oldValue, _ := strconv.ParseFloat(oldIndicators.Value, 64) currValue, _ := strconv.ParseFloat(v.Value, 64) onMonthValue := (oldValue - currValue) / oldValue * 100 omv := util.RoundToTwoDecimals(onMonthValue) if omv < 0 { isUp = pasturePb.IsShow_No } onMonth = strconv.FormatFloat(omv, 'f', 2, 64) + "%" } } } focusIndicatorsList = append(focusIndicatorsList, &pasturePb.FocusIndicators{ Kind: indicatorsDetails.Kind, Name: indicatorsDetails.Name, Value: v.Value, Describe: indicatorsDetails.Zh, UnitName: indicatorsDetails.Unit, OnMonth: onMonth, OnYear: onYear, IsUp: isUp, }) } indicatorsDetailsList, _ := s.FindIndicatorsDetailsList(ctx) return &pasturePb.IndexFocusIndicatorsResponse{ Code: http.StatusOK, Msg: "ok", Data: &pasturePb.FocusData{ FocusIndicators: focusIndicatorsList, IndicatorsSet: model.IndicatorsDetailsSlice(indicatorsDetailsList).ToPB(userFocusIndicatorsList), }, }, err } func (s *StoreEntry) FocusIndicatorsSet(ctx context.Context, req *pasturePb.IndexFocusIndicatorsSetRequest) error { userModel, err := s.GetUserModel(ctx) if err != nil { return xerr.WithStack(err) } if len(req.IndicatorsKind) <= 0 { return nil } userFocusIndicators := strings.Join(req.IndicatorsKind, ",") if err = s.DB.Model(new(model.SystemUser)). Where("id = ?", userModel.SystemUser.Id). Update("indicators_kinds", userFocusIndicators).Error; err != nil { return xerr.WithStack(err) } return nil } func (s *StoreEntry) DataWarningSet(ctx context.Context, req *pasturePb.IndexDataWarningSetRequest) error { userModel, err := s.GetUserModel(ctx) if err != nil { return xerr.WithStack(err) } pastureId := userModel.AppPasture.Id if len(req.WarningDataSet) <= 0 { return xerr.Custom("请选择预警数据") } defaultDataWarning, _ := s.FindDataWarning(ctx, pastureId, model.DefaultUserId) if len(defaultDataWarning) <= 0 { return xerr.Custom("默认预警数据不存在,请联系管理员!") } userDataWarningList, err := s.FindDataWarning(ctx, pastureId, userModel.SystemUser.Id) if err != nil { return xerr.WithStack(err) } if len(userDataWarningList) <= 0 { // 新增 return s.addUserDataWarning(ctx, pastureId, userModel.SystemUser.Id, defaultDataWarning, req.WarningDataSet) } return s.updateUserDataWarning(ctx, pastureId, userModel.SystemUser.Id, userDataWarningList, req.WarningDataSet) } func (s *StoreEntry) DataWarningList(ctx context.Context) (*pasturePb.IndexDataWarningResponse, error) { userModel, err := s.GetUserModel(ctx) if err != nil { return nil, xerr.WithStack(err) } pastureId := userModel.AppPasture.Id defaultDataWarning, _ := s.FindDataWarning(ctx, pastureId, model.DefaultUserId) if len(defaultDataWarning) <= 0 { return nil, xerr.Custom("默认预警数据有误,请联系管理员!") } var isExist bool // 判断是否存在自己的设置的数据 userDataWarning, _ := s.FindDataWarning(ctx, pastureId, userModel.SystemUser.Id) if len(userDataWarning) == 0 { // 如果用户没有配置自己的预警数据,则使用默认数据 isExist = true userDataWarning = defaultDataWarning } newTime := time.Now().Local().Unix() needUpdateWarningIds := make([]int64, 0) for _, warningData := range userDataWarning { // 如果预警数据更新时间大于预警条件更新时间,并且更新时间距离当前时间小于2小时,则跳过 if warningData.DataUpdateAt > warningData.ConditionUpdateAt && newTime-warningData.DataUpdateAt < int64(2*time.Hour) { continue } needUpdateWarningIds = append(needUpdateWarningIds, warningData.Id) } // 需要重新计算更新的warningId if len(needUpdateWarningIds) > 0 { s.UpdateWarningData(ctx, pastureId, needUpdateWarningIds) } userDataWarningItems := make([]*model.DataWarningItems, 0) // 计算过后重新获取数据 if isExist { userDataWarning, _ = s.FindDataWarning(ctx, pastureId, model.DefaultUserId) userDataWarningItems, _ = s.FindDataWarningItems(ctx, pastureId, model.DefaultUserId) } else { userDataWarning, _ = s.FindDataWarning(ctx, pastureId, userModel.SystemUser.Id) userDataWarningItems, _ = s.FindDataWarningItems(ctx, pastureId, userModel.SystemUser.Id) } return &pasturePb.IndexDataWarningResponse{ Code: http.StatusOK, Msg: "ok", Data: &pasturePb.DataWarning{ DataSet: model.DataWarningItemsSlice(userDataWarningItems).ToPB(userDataWarning), DataShow: model.DataWarningSlice(userDataWarning).ToPB(), }, }, nil } func (s *StoreEntry) DataWarningPop(ctx context.Context, req *pasturePb.WarningDataListRequest, pagination *pasturePb.PaginationModel) (*model.WarningDataPopResponse, error) { userModel, err := s.GetUserModel(ctx) if err != nil { return nil, xerr.WithStack(err) } if req.Kind <= pasturePb.DataWarningType_Invalid { return nil, xerr.Custom("请选择预警数据") } pastureId := userModel.AppPasture.Id dataWaringItem := &model.DataWarning{} if err = s.DB.Model(new(model.DataWarning)). Where("pasture_id = ?", pastureId). Where("user_id = ?", userModel.SystemUser.Id). Where("kind = ?", req.Kind). First(dataWaringItem).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { if err = s.DB.Model(new(model.DataWarning)). Where("pasture_id = ?", pastureId). Where("user_id = ?", model.DefaultUserId). Where("kind = ?", req.Kind). First(dataWaringItem).Error; err != nil { return nil, xerr.WithStack(err) } } else { return nil, xerr.Custom("预警数据不存在,请联系管理员!") } } headers, headerSort, err := dataWaringItem.GetWarningColumn() if err != nil { return nil, xerr.WithStack(err) } resp := &model.WarningDataPopResponse{ Code: http.StatusOK, Msg: "ok", Data: &model.WarningPop{ DataList: make([]interface{}, 0), Title: dataWaringItem.Name, Kind: dataWaringItem.Kind, HeaderSort: headerSort, Headers: headers, Page: pagination.Page, PageSize: pagination.PageSize, Total: 0, }, } query, params, err := s.BuildQuery(dataWaringItem.Id) if err != nil { zaplog.Error("UpdateWarningData", zap.Any("BuildQuery", err), zap.Any("warningId", dataWaringItem.Id)) return resp, nil } if len(query) == 0 || len(params) == 0 { return resp, nil } var count int64 cowList := make([]*model.Cow, 0) if err = s.DB.Model(new(model.Cow)). Where("pasture_id = ?", pastureId). Where(query, params...). Count(&count). Limit(int(pagination.PageSize)). Offset(int(pagination.PageOffset)). Find(&cowList). Error; err != nil { zaplog.Error("UpdateWarningData", zap.Any("err", err), zap.Any("query", query), zap.Any("params", params)) } cowTypeMap := s.CowTypeMap() breedStatusMap := s.CowBreedStatusMap() cowKindMap := s.CowKindMap() cowSourceMap := s.CowSourceMap() admissionStatusMap := s.AdmissionStatusMap() healthStatusMap := s.HealthStatusMap() purposeMap := s.PurposeMap() resp.Data.Total = int32(count) systemBasic, err := s.GetSystemBasicByName(ctx, userModel.AppPasture.Id, model.PregnancyAge) if err != nil { return nil, xerr.Custom("请在基础参数配置妊娠天数") } resp.Data.DataList = model.CowSlice(cowList).ToPB( cowTypeMap, breedStatusMap, cowKindMap, cowSourceMap, admissionStatusMap, healthStatusMap, purposeMap, systemBasic.MinValue, ) return resp, nil } // 新增用户预警数据 func (s *StoreEntry) addUserDataWarning(ctx context.Context, pastureId, userId int64, defaultDataWarning []*model.DataWarning, warningDataSet []*pasturePb.WarningDataSet) error { // 将默认预警数据按 Kind 映射 defaultDataWarningMap := make(map[pasturePb.DataWarningType_Kind]*model.DataWarning) for _, v := range defaultDataWarning { defaultDataWarningMap[v.Kind] = v } // 在事务中执行新增操作 return s.DB.Transaction(func(tx *gorm.DB) error { addedKinds := make(map[pasturePb.DataWarningType_Kind]bool) // 记录已添加的 Kind for _, set := range warningDataSet { dataWarning := model.NewDataWarning(pastureId, userId, set.Kind, pasturePb.IsShow_Ok, defaultDataWarningMap[set.Kind]) // 如果该 Kind 已添加,跳过 if !addedKinds[set.Kind] { // 创建新的预警数据 if err := tx.Create(dataWarning).Error; err != nil { return xerr.WithStack(err) } } else { oldDataWarning := &model.DataWarning{} if err := tx.Model(new(model.DataWarning)). Where("user_id = ?", userId). Where("kind = ?", set.Kind). First(oldDataWarning).Error; err != nil { return xerr.WithStack(err) } dataWarning.Id = oldDataWarning.Id } // 创建预警项数据 if err := tx.Create(model.NewDataWarningItems(pastureId, userId, dataWarning, set)).Error; err != nil { return xerr.WithStack(err) } addedKinds[set.Kind] = true } return nil }) } // 更新用户预警数据 func (s *StoreEntry) updateUserDataWarning(ctx context.Context, pastureId, userId int64, userDataWarningList []*model.DataWarning, warningDataSet []*pasturePb.WarningDataSet) error { // 将请求数据按 WarningId 和 Id 映射 warningIsShowMap := make(map[int32]*pasturePb.WarningDataSet) warningItemDataMap := make(map[int32]*pasturePb.WarningDataSet) for _, set := range warningDataSet { warningIsShowMap[set.WarningId] = set warningItemDataMap[set.Id] = set } // 获取用户预警项数据 userDataWarningItems, err := s.FindDataWarningItems(ctx, pastureId, userId) if err != nil { return xerr.WithStack(err) } if len(userDataWarningItems) == 0 { return xerr.Custom("预警数据有误,请联系管理员!") } // 在事务中执行更新操作 return s.DB.Transaction(func(tx *gorm.DB) error { // 更新预警数据的 IsShow 字段 for _, warning := range userDataWarningList { if data, ok := warningIsShowMap[int32(warning.Id)]; ok { if strings.ContainsAny(warning.Name, "0123456789") { re := regexp.MustCompile(`\d+`) warning.Name = re.ReplaceAllString(warning.Name, data.Value) warning.Description = re.ReplaceAllString(warning.Description, data.Value) } if err = tx.Model(&model.DataWarning{}). Where("id = ?", warning.Id). Updates(map[string]interface{}{ "is_show": data.IsShow, "name": warning.Name, "description": warning.Description, }).Error; err != nil { return xerr.WithStack(err) } } } // 更新预警项数据的 IsShow 和 Value 字段 for _, item := range userDataWarningItems { if set, ok := warningItemDataMap[int32(item.Id)]; ok { if err = tx.Model(&model.DataWarningItems{}). Where("id = ?", item.Id). Updates(map[string]interface{}{ "is_show": set.IsShow, "value": set.Value, }).Error; err != nil { return xerr.WithStack(err) } } } return nil }) } func (s *StoreEntry) FindDataWarning(ctx context.Context, pastureId, userId int64) ([]*model.DataWarning, error) { dataWarningList := make([]*model.DataWarning, 0) if err := s.DB.Model(new(model.DataWarning)). Where("user_id = ?", userId). Where("pasture_id = ?", pastureId). Find(&dataWarningList).Error; err != nil { return nil, xerr.WithStack(err) } return dataWarningList, nil } func (s *StoreEntry) FindDataWarningItems(ctx context.Context, pastureId, userId int64) ([]*model.DataWarningItems, error) { dataWarningItemsList := make([]*model.DataWarningItems, 0) if err := s.DB.Model(new(model.DataWarningItems)). Where("pasture_id = ?", pastureId). Where("user_id = ?", userId). Find(&dataWarningItemsList).Error; err != nil { return nil, xerr.WithStack(err) } return dataWarningItemsList, nil } func (s *StoreEntry) FindDataWarningMap(ctx context.Context, pastureId, userId int64) (map[int64]*model.DataWarning, error) { dataWarning, err := s.FindDataWarning(ctx, pastureId, userId) if err != nil { return nil, xerr.Custom("默认预警数据有误,请联系管理员!") } dataWarningMap := make(map[int64]*model.DataWarning) for _, v := range dataWarning { dataWarningMap[v.Id] = v } return dataWarningMap, nil } func (s *StoreEntry) FindDataWarningItemsMap(ctx context.Context, userId int64) (map[int64]*model.DataWarningItems, error) { dataWarningItemsList := make([]*model.DataWarningItems, 0) if err := s.DB.Model(new(model.DataWarningItems)). Where("user_id = ?", userId). Find(&dataWarningItemsList).Error; err != nil { return nil, xerr.WithStack(err) } dataWarningItemsMap := make(map[int64]*model.DataWarningItems) for _, v := range dataWarningItemsList { dataWarningItemsMap[v.Id] = v } return dataWarningItemsMap, nil } // UpdateWarningData 更新计算数据 func (s *StoreEntry) UpdateWarningData(ctx context.Context, pastureId int64, needUpdateWarningIds []int64) { if len(needUpdateWarningIds) <= 0 { return } for _, warningId := range needUpdateWarningIds { query, params, err := s.BuildQuery(warningId) if err != nil { zaplog.Error("UpdateWarningData", zap.Any("BuildQuery", err), zap.Any("warningId", warningId)) } if len(query) == 0 || len(params) == 0 { continue } var count int64 if err = s.DB.Model(new(model.Cow)). Where("pasture_id = ?", pastureId). Where(query, params...). Count(&count).Error; err != nil { zaplog.Error("UpdateWarningData", zap.Any("err", err), zap.Any("query", query), zap.Any("params", params)) } if err = s.DB.Model(new(model.DataWarning)). Where("id = ?", warningId). Updates(map[string]interface{}{ "data_value": count, "data_update_at": time.Now().Local().Unix(), }).Error; err != nil { zaplog.Error("UpdateWarningData", zap.Any("update", err)) } } }