package service import ( "fmt" "time" "github.com/sundynix/pets-be/internal/model" ) // WeeklyReport 周报聚合 type WeeklyReport struct { Summary string `json:"summary"` TasksCompleted int64 `json:"tasks_completed"` WeightGain float64 `json:"weight_gain"` HighRiskCount int64 `json:"high_risk_count"` HealthStatus string `json:"health_status"` NextWeekFocus string `json:"next_week_focus"` } // GetWeeklyReport 计算最近 7 天周报 func (s *Service) GetWeeklyReport(userID, petID uint) (*WeeklyReport, error) { pet, err := s.ownedPet(userID, petID) if err != nil { return nil, err } weekAgo := time.Now().AddDate(0, 0, -7) var tasksCompleted int64 s.db.Model(&model.DailyTask{}). Where("pet_id = ? AND done = ? AND updated_at >= ?", petID, true, weekAgo). Count(&tasksCompleted) // 体重增长:最近 7 天最新 - 最早 var latest, earliest model.HealthRecord gain := 0.0 if err := s.db.Where("pet_id = ? AND type = ? AND occurred_at >= ?", petID, model.RecordWeight, weekAgo). Order("occurred_at desc").First(&latest).Error; err == nil { if err := s.db.Where("pet_id = ? AND type = ? AND occurred_at >= ?", petID, model.RecordWeight, weekAgo). Order("occurred_at asc").First(&earliest).Error; err == nil { gain = latest.NumValue - earliest.NumValue } } var highRisk int64 s.db.Model(&model.HealthRecord{}). Where("pet_id = ? AND type = ? AND category = ? AND occurred_at >= ?", petID, model.RecordSymptom, "高", weekAgo). Count(&highRisk) status := "稳定成长" if highRisk > 0 { status = "需要关注" } return &WeeklyReport{ Summary: fmt.Sprintf("本周 %s 完成 %d 项任务,体重 %+.1fkg,无高风险异常记录。", pet.Name, tasksCompleted, gain), TasksCompleted: tasksCompleted, WeightGain: gain, HighRiskCount: highRisk, HealthStatus: status, NextWeekFocus: "第 2 针疫苗提醒、继续观察体重趋势、避免频繁更换食物。", }, nil } // BillCategory 账单分类项 type BillCategory struct { Category string `json:"category"` Amount float64 `json:"amount"` Percent int `json:"percent"` } // Bill 账单聚合 type Bill struct { Period string `json:"period"` Total float64 `json:"total"` MaxSingle float64 `json:"max_single"` Categories []BillCategory `json:"categories"` } // GetBill 账单(period=month 取本月) func (s *Service) GetBill(userID, petID uint, period string) (*Bill, error) { if _, err := s.ownedPet(userID, petID); err != nil { return nil, err } now := time.Now() var start time.Time if period == "year" { start = time.Date(now.Year(), 1, 1, 0, 0, 0, 0, now.Location()) } else { period = "month" start = time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location()) } type row struct { Category string Amount float64 } var rows []row s.db.Model(&model.HealthRecord{}). Select("category, sum(num_value) as amount"). Where("pet_id = ? AND type = ? AND occurred_at >= ?", petID, model.RecordCost, start). Group("category").Scan(&rows) var total, maxSingle float64 s.db.Model(&model.HealthRecord{}). Select("coalesce(sum(num_value),0)"). Where("pet_id = ? AND type = ? AND occurred_at >= ?", petID, model.RecordCost, start). Scan(&total) s.db.Model(&model.HealthRecord{}). Select("coalesce(max(num_value),0)"). Where("pet_id = ? AND type = ? AND occurred_at >= ?", petID, model.RecordCost, start). Scan(&maxSingle) cats := make([]BillCategory, 0, len(rows)) for _, r := range rows { pct := 0 if total > 0 { pct = int(r.Amount / total * 100) } cats = append(cats, BillCategory{Category: r.Category, Amount: r.Amount, Percent: pct}) } return &Bill{Period: period, Total: total, MaxSingle: maxSingle, Categories: cats}, nil } // HealthSummary 健康摘要 type HealthSummary struct { VaccineProgress string `json:"vaccine_progress"` DewormStatus string `json:"deworm_status"` WeightTrend string `json:"weight_trend"` AnomalyCount int64 `json:"anomaly_count"` } // GetHealthSummary 健康摘要聚合 func (s *Service) GetHealthSummary(userID, petID uint) (*HealthSummary, error) { if _, err := s.ownedPet(userID, petID); err != nil { return nil, err } var vaccineDone int64 s.db.Model(&model.HealthRecord{}).Where("pet_id = ? AND type = ?", petID, model.RecordVaccine).Count(&vaccineDone) dewormStatus := "暂无计划" var dewormReminder model.Reminder if err := s.db.Where("pet_id = ? AND type = ?", petID, model.ReminderDeworm).Order("next_due_date asc").First(&dewormReminder).Error; err == nil && dewormReminder.NextDueDate != nil { dewormStatus = "下次 " + dewormReminder.NextDueDate.Format("1月2日") } var anomalies int64 s.db.Model(&model.HealthRecord{}).Where("pet_id = ? AND type = ?", petID, model.RecordSymptom).Count(&anomalies) return &HealthSummary{ VaccineProgress: fmt.Sprintf("%d/3,即将到期", vaccineDone), DewormStatus: dewormStatus, WeightTrend: "稳定增长", AnomalyCount: anomalies, }, nil } // Poster 成长海报数据 type Poster struct { PetName string `json:"pet_name"` PetEmoji string `json:"pet_emoji"` Age string `json:"age"` Weight string `json:"weight"` Stage string `json:"stage"` TasksCompleted int64 `json:"tasks_completed"` WeightRecords int64 `json:"weight_records"` VaccineRecords int64 `json:"vaccine_records"` HighRiskCount int64 `json:"high_risk_count"` Headline string `json:"headline"` } // GetPoster 生成海报聚合数据 func (s *Service) GetPoster(userID, petID uint) (*Poster, error) { pet, err := s.ownedPet(userID, petID) if err != nil { return nil, err } var tasksDone, weightRecs, vaccineRecs int64 s.db.Model(&model.DailyTask{}).Where("pet_id = ? AND done = ?", petID, true).Count(&tasksDone) s.db.Model(&model.HealthRecord{}).Where("pet_id = ? AND type = ?", petID, model.RecordWeight).Count(&weightRecs) s.db.Model(&model.HealthRecord{}).Where("pet_id = ? AND type = ?", petID, model.RecordVaccine).Count(&vaccineRecs) return &Poster{ PetName: pet.Name, PetEmoji: pet.Emoji, Age: pet.Age, Weight: pet.Weight, Stage: pet.Stage, TasksCompleted: tasksDone, WeightRecords: weightRecs, VaccineRecords: vaccineRecs, HighRiskCount: 0, Headline: "稳定成长", }, nil }