609f7d06cf
- pets-fe: 微信原生小程序(首页/计划/记录/报告/社区/引导), 服务端驱动、无假数据;弹层改用 scroll-view,打开时隐藏自定义 tabBar - pets-be: Gin + GORM(MySQL, sundynix_ 前缀) + MinIO,统一响应/分页, 微信 code2session 登录,provider-neutral AI(DeepSeek),go:embed React 后台 - 修复:分段选择类型不匹配(字符串 vs 数字)导致选不中 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
187 lines
6.2 KiB
Go
187 lines
6.2 KiB
Go
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
|
|
}
|