package service import ( "errors" "log" "sort" "strconv" "strings" "github.com/sundynix/pets-be/internal/model" ) // BreedGroup 同一首字母下的品种。小程序端的 A-Z 索引直接照这个渲染 type BreedGroup struct { Initial string `json:"initial"` Items []model.Breed `json:"items"` } // ListBreeds 某个物种的品种,按首字母分组。只返回启用的 func (s *Service) ListBreeds(species string) []BreedGroup { if species != model.SpeciesCat && species != model.SpeciesDog { return []BreedGroup{} } var all []model.Breed s.db.Where("species = ? AND enabled = ?", species, true). Order("initial asc, sort asc, id asc").Find(&all) if len(all) == 0 { // 不硬编码兜底一份:那等于把「后台可配」又变回代码常量, // 改了配置的人会发现改了没用。宁可让选择器明显是空的 log.Printf("[warn] %s 没有任何启用的品种,建档第二步会选不了品种", species) return []BreedGroup{} } byInitial := map[string][]model.Breed{} for _, b := range all { k := strings.ToUpper(b.Initial) if k == "" { k = "#" // 没填首字母的兜到最后,别让它凭空消失 } byInitial[k] = append(byInitial[k], b) } keys := make([]string, 0, len(byInitial)) for k := range byInitial { keys = append(keys, k) } sort.Strings(keys) out := make([]BreedGroup, 0, len(keys)) for _, k := range keys { out = append(out, BreedGroup{Initial: k, Items: byInitial[k]}) } return out } // AdminListBreeds 后台用:连停用的一起返回 func (s *Service) AdminListBreeds(species string) []model.Breed { q := s.db.Order("species asc, initial asc, sort asc, id asc") if species == model.SpeciesCat || species == model.SpeciesDog { q = q.Where("species = ?", species) } var all []model.Breed q.Find(&all) if all == nil { return []model.Breed{} } return all } // SaveBreed 新增或更新 func (s *Service) SaveBreed(in *model.Breed) error { in.Name = strings.TrimSpace(in.Name) in.Initial = strings.ToUpper(strings.TrimSpace(in.Initial)) if in.Name == "" { return ErrInvalidParam } if in.Species != model.SpeciesCat && in.Species != model.SpeciesDog { return errors.New("物种只能是 cat 或 dog") } if len(in.Initial) != 1 || in.Initial[0] < 'A' || in.Initial[0] > 'Z' { return errors.New("首字母要填一个 A-Z 的字母(用来做侧边索引)") } // 同物种下不允许同名:品种是拿来做统计口径的,重名会把同一种猫劈成两半 var n int64 q := s.db.Model(&model.Breed{}).Where("species = ? AND name = ?", in.Species, in.Name) if in.ID != "" { q = q.Where("id <> ?", in.ID) } q.Count(&n) if n > 0 { return errors.New("这个物种下已经有「" + in.Name + "」了") } if in.ID == "" { return s.db.Create(in).Error } return s.db.Model(&model.Breed{}).Where("id = ?", in.ID).Updates(map[string]any{ "species": in.Species, "name": in.Name, "initial": in.Initial, "sort": in.Sort, "enabled": in.Enabled, }).Error } // DeleteBreed 已经有宠物在用的品种不许删——删了那些档案的品种就变成一个 // 查不到的字符串。想让它不再被选到应该用 enabled=false func (s *Service) DeleteBreed(id string) error { var b model.Breed if err := s.db.First(&b, "id = ?", id).Error; err != nil { return ErrNotFound } var used int64 s.db.Model(&model.Pet{}).Where("breed = ?", b.Name).Count(&used) if used > 0 { return errors.New("已经有 " + strconv.FormatInt(used, 10) + " 个档案填了「" + b.Name + "」,删了它们的品种就查不到了。想隐藏请改成停用") } return s.db.Delete(&model.Breed{}, "id = ?", id).Error }