package service import ( "strings" "time" "gorm.io/datatypes" "gorm.io/gorm" "github.com/sundynix/pets-be/internal/model" ) // PetInput 建/改宠物入参 type PetInput struct { Name string Emoji string Type string Gender string Birthday *time.Time Weight string Stage string Age string Color string Breed string Goals datatypes.JSON } func normalizeWeight(w string) string { w = strings.TrimSpace(w) if w == "" { return "" } if strings.Contains(w, "kg") { return w } return w + "kg" } // ListPets 用户的全部宠物 func (s *Service) ListPets(userID uint) ([]model.Pet, error) { var pets []model.Pet err := s.db.Where("user_id = ?", userID).Order("id asc").Find(&pets).Error return pets, err } // GetPet 取单只宠物(校验归属) func (s *Service) GetPet(userID, petID uint) (*model.Pet, error) { return s.ownedPet(userID, petID) } // CreatePet 新增宠物并生成默认数据 func (s *Service) CreatePet(userID uint, in PetInput) (*model.Pet, error) { pet := model.Pet{ UserID: userID, Name: in.Name, Emoji: in.Emoji, Type: in.Type, Gender: in.Gender, Birthday: in.Birthday, Weight: normalizeWeight(in.Weight), Stage: in.Stage, Age: in.Age, Color: in.Color, Breed: in.Breed, HealthStatus: "正常", Goals: in.Goals, } if err := s.db.Transaction(func(tx *gorm.DB) error { if err := tx.Create(&pet).Error; err != nil { return err } return s.seedPetDefaults(tx, &pet) }); err != nil { return nil, err } return &pet, nil } // UpdatePet 更新宠物字段 func (s *Service) UpdatePet(userID, petID uint, fields map[string]any) (*model.Pet, error) { if _, err := s.ownedPet(userID, petID); err != nil { return nil, err } if w, ok := fields["weight"].(string); ok { fields["weight"] = normalizeWeight(w) } if err := s.db.Model(&model.Pet{}).Where("id = ?", petID).Updates(fields).Error; err != nil { return nil, err } return s.ownedPet(userID, petID) } // DeletePet 删除宠物并级联清除其记录/任务/计划/提醒/每日建议(不留孤儿数据) func (s *Service) DeletePet(userID, petID uint) error { if _, err := s.ownedPet(userID, petID); err != nil { return err } return s.db.Transaction(func(tx *gorm.DB) error { // 先删计划明细(按 plan_id),再删计划 var planIDs []uint tx.Model(&model.Plan{}).Where("pet_id = ?", petID).Pluck("id", &planIDs) if len(planIDs) > 0 { if err := tx.Where("plan_id IN ?", planIDs).Delete(&model.PlanTask{}).Error; err != nil { return err } } for _, m := range []any{ &model.HealthRecord{}, &model.DailyTask{}, &model.Reminder{}, &model.Plan{}, &model.DailyAdvice{}, } { if err := tx.Where("pet_id = ?", petID).Delete(m).Error; err != nil { return err } } return tx.Where("id = ? AND user_id = ?", petID, userID).Delete(&model.Pet{}).Error }) } // Onboarding 建首宠 + 标记用户已引导 func (s *Service) Onboarding(userID uint, in PetInput) (*model.Pet, error) { pet, err := s.CreatePet(userID, in) if err != nil { return nil, err } if err := s.db.Model(&model.User{}).Where("id = ?", userID).Update("onboarded", true).Error; err != nil { return nil, err } return pet, nil } // seedPetDefaults 为新宠生成默认今日任务、提醒、30 天计划 func (s *Service) seedPetDefaults(tx *gorm.DB, pet *model.Pet) error { today := time.Now() tasks := []model.DailyTask{ {PetID: pet.ID, UserID: pet.UserID, TaskDate: today, Title: "记录一次体重", Description: "幼年期建议每周至少记录 2 次", Priority: "重要"}, {PetID: pet.ID, UserID: pet.UserID, TaskDate: today, Title: "观察饮水和排便", Description: "换粮、应激都可能影响排便状态", SheetType: "poop"}, {PetID: pet.ID, UserID: pet.UserID, TaskDate: today, Title: "检查疫苗预约", Description: "第 2 针疫苗还有 5 天", SheetType: "vaccine"}, } if err := tx.Create(&tasks).Error; err != nil { return err } vaccineDue := today.AddDate(0, 0, 18) dewormDue := today.AddDate(0, 0, 12) reminders := []model.Reminder{ {PetID: pet.ID, UserID: pet.UserID, Type: model.ReminderVaccine, Title: "第 2 针疫苗", NextDueDate: &vaccineDue}, {PetID: pet.ID, UserID: pet.UserID, Type: model.ReminderDeworm, Title: "体内外驱虫", NextDueDate: &dewormDue}, {PetID: pet.ID, UserID: pet.UserID, Type: model.ReminderWeight, Title: "体重记录", Frequency: "每周二、周五"}, {PetID: pet.ID, UserID: pet.UserID, Type: model.ReminderMonthlyReport, Title: "月度报告", Frequency: "每月 1 日"}, } if err := tx.Create(&reminders).Error; err != nil { return err } start := today end := today.AddDate(0, 0, 30) plan := model.Plan{ PetID: pet.ID, UserID: pet.UserID, Kind: model.PlanThirtyDay, Stage: pet.Stage, StartDate: &start, EndDate: &end, CompletionPct: 40, Status: "active", Tasks: []model.PlanTask{ {Day: 0, DayLabel: "今天", Title: "观察排便状态", Description: "记录颜色、形态、次数,发现软便可连续观察。", SheetType: "taskDetail"}, {Day: 1, DayLabel: "明天", Title: "检查疫苗预约", Description: "距离下一针还有 5 天,提前确认医院和时间。", SheetType: "vaccine"}, {Day: 7, DayLabel: "第 7 天", Title: "体重趋势检查", Description: "幼年期每周称重,观察是否稳定增长。", SheetType: "weight"}, {Day: 14, DayLabel: "第 14 天", Title: "复盘饮食与便便", Description: "如果近期换粮,建议把换粮过程和异常记录合并查看。", SheetType: "taskDetail"}, }, } return tx.Create(&plan).Error }