package service import ( "errors" "photowall/internal/model" "strings" "gorm.io/gorm" ) type SchoolService struct { db *gorm.DB } func NewSchoolService(db *gorm.DB) *SchoolService { return &SchoolService{db: db} } type SchoolQuery struct { RegionID uint `form:"region_id"` SchoolType model.SchoolType `form:"school_type"` Keyword string `form:"keyword"` Status string `form:"status"` // pending / active / rejected / all Page int `form:"page"` PageSize int `form:"page_size"` } // PageResp 通用分页响应 type PageResp struct { List interface{} `json:"list"` Total int64 `json:"total"` Page int `json:"page"` } type SchoolListResp = PageResp // 别名兼容 type SchoolStatsResp struct { Total int64 `json:"total"` Pending int64 `json:"pending"` Active int64 `json:"active"` Rejected int64 `json:"rejected"` } func (s *SchoolService) List(q *SchoolQuery) (*SchoolListResp, error) { if q.Page <= 0 { q.Page = 1 } if q.PageSize <= 0 || q.PageSize > 100 { q.PageSize = 20 } db := s.db.Model(&model.School{}) if q.RegionID > 0 { // 支持查该地区自身及所有下级地区下的学校 regionIDs := s.getSelfAndDescendantRegionIDs(q.RegionID) db = db.Where("region_id IN ?", regionIDs) } if q.SchoolType != "" { // school_types 为逗号分隔的多学段字段;旧数据可能为空,回退比较主类型 db = db.Where("FIND_IN_SET(?, school_types) > 0 OR ((school_types IS NULL OR school_types = '') AND school_type = ?)", q.SchoolType, q.SchoolType) } if q.Status != "" && q.Status != "all" { db = db.Where("status = ?", q.Status) } else if q.Status == "" { // 公开默认只查 active db = db.Where("status = ? OR status = ''", model.SchoolStatusActive) } if q.Keyword != "" { db = db.Where("name LIKE ? OR name_en LIKE ?", "%"+q.Keyword+"%", "%"+q.Keyword+"%") } var total int64 db.Count(&total) var list []model.School err := db.Order("id desc").Offset((q.Page - 1) * q.PageSize).Limit(q.PageSize).Find(&list).Error if err != nil { return nil, err } // 填充地区路径 for i := range list { list[i].RegionPath = s.getRegionPathString(list[i].RegionID) } return &SchoolListResp{List: list, Total: total, Page: q.Page}, nil } // getSelfAndDescendantRegionIDs 获取自身及所有下级地区ID func (s *SchoolService) getSelfAndDescendantRegionIDs(rootID uint) []uint { res := []uint{rootID} var children []uint s.db.Model(&model.Region{}).Where("parent_id = ?", rootID).Pluck("id", &children) for _, cID := range children { res = append(res, s.getSelfAndDescendantRegionIDs(cID)...) } return res } func (s *SchoolService) getRegionPathString(regionID uint) string { if regionID == 0 { return "" } var names []string curID := regionID for curID > 0 { var r model.Region if err := s.db.First(&r, curID).Error; err != nil { break } names = append([]string{r.Name}, names...) curID = r.ParentID } return strings.Join(names, " / ") } // Stats 统计学校审核状态数据 func (s *SchoolService) Stats() (*SchoolStatsResp, error) { var total, pending, active, rejected int64 s.db.Model(&model.School{}).Count(&total) s.db.Model(&model.School{}).Where("status = ?", model.SchoolStatusPending).Count(&pending) s.db.Model(&model.School{}).Where("status = ? OR status = ''", model.SchoolStatusActive).Count(&active) s.db.Model(&model.School{}).Where("status = ?", model.SchoolStatusRejected).Count(&rejected) return &SchoolStatsResp{ Total: total, Pending: pending, Active: active, Rejected: rejected, }, nil } // Create 创建学校(挂在 county 或 city 级地区下) func (s *SchoolService) Create(school *model.School) error { var region model.Region if err := s.db.First(®ion, school.RegionID).Error; err != nil { return errors.New("所属地区不存在") } if region.Level != model.LevelCounty && region.Level != model.LevelCity { return errors.New("学校必须挂载在市级或区/县级地区下") } if school.Name == "" { return errors.New("学校名称不能为空") } // 规整多学段:优先取 school_types,其次回退到单一主类型 types := model.ParseSchoolTypes(school.SchoolTypes) if len(types) == 0 && school.SchoolType != "" { types = model.NormalizeSchoolTypes([]string{string(school.SchoolType)}) } if len(types) == 0 { return errors.New("请选择学校类型") } school.SchoolTypes = model.JoinSchoolTypes(types) school.SchoolType = types[0] if school.Status == "" { school.Status = model.SchoolStatusActive } return s.db.Create(school).Error } // Approve 审核通过学校 func (s *SchoolService) Approve(id uint) error { res := s.db.Model(&model.School{}).Where("id = ?", id).Updates(map[string]interface{}{ "status": model.SchoolStatusActive, "reject_reason": "", }) if res.Error != nil { return res.Error } if res.RowsAffected == 0 { return errors.New("学校不存在") } return nil } // Reject 打回学校并填写原因 func (s *SchoolService) Reject(id uint, reason string) error { if reason == "" { return errors.New("请输入打回原因") } res := s.db.Model(&model.School{}).Where("id = ?", id).Updates(map[string]interface{}{ "status": model.SchoolStatusRejected, "reject_reason": reason, }) if res.Error != nil { return res.Error } if res.RowsAffected == 0 { return errors.New("学校不存在") } return nil } func (s *SchoolService) GetByID(id uint) (*model.School, error) { var school model.School if err := s.db.First(&school, id).Error; err != nil { return nil, errors.New("学校不存在") } school.RegionPath = s.getRegionPathString(school.RegionID) return &school, nil } func (s *SchoolService) Update(id uint, updates map[string]interface{}) error { if regVal, ok := updates["region_id"]; ok { var regID uint switch v := regVal.(type) { case float64: regID = uint(v) case int: regID = uint(v) case int64: regID = uint(v) case uint: regID = v } if regID > 0 { var region model.Region if err := s.db.First(®ion, regID).Error; err != nil { return errors.New("所属地区不存在") } if region.Level != model.LevelCounty && region.Level != model.LevelCity { return errors.New("学校必须挂载在市级或区/县级地区下") } } } // 多学段:school_types 变更时同步刷新主类型 school_type if raw, ok := updates["school_types"]; ok { typesStr, _ := raw.(string) types := model.ParseSchoolTypes(typesStr) if len(types) == 0 { return errors.New("请选择学校类型") } updates["school_types"] = model.JoinSchoolTypes(types) updates["school_type"] = types[0] } res := s.db.Model(&model.School{}).Where("id = ?", id).Updates(updates) if res.Error != nil { return res.Error } if res.RowsAffected == 0 { return errors.New("学校不存在") } return nil } func (s *SchoolService) Delete(id uint) error { // 检查是否有班级引用 var count int64 s.db.Model(&model.Class{}).Where("school_id = ?", id).Count(&count) if count > 0 { return errors.New("该学校下已有班级,无法删除") } // 同时删除所属学院 s.db.Where("school_id = ?", id).Delete(&model.College{}) return s.db.Delete(&model.School{}, id).Error } // ---- 学院 ---- func (s *SchoolService) ListColleges(schoolID uint) ([]model.College, error) { var list []model.College err := s.db.Where("school_id = ?", schoolID).Order("name").Find(&list).Error return list, err } func (s *SchoolService) CreateCollege(schoolID uint, name string) (*model.College, error) { if name == "" { return nil, errors.New("学院名称不能为空") } var school model.School if err := s.db.First(&school, schoolID).Error; err != nil { return nil, errors.New("学校不存在") } if !school.HasCollegeType() { return nil, errors.New("该学校类型不支持学院层级") } c := &model.College{SchoolID: schoolID, Name: name} if err := s.db.Create(c).Error; err != nil { return nil, err } return c, nil } func (s *SchoolService) UpdateCollege(id uint, name string) error { res := s.db.Model(&model.College{}).Where("id = ?", id).Update("name", name) if res.Error != nil { return res.Error } if res.RowsAffected == 0 { return errors.New("学院不存在") } return nil } func (s *SchoolService) DeleteCollege(id uint) error { var count int64 s.db.Model(&model.Class{}).Where("college_id = ?", id).Count(&count) if count > 0 { return errors.New("该学院下已有班级,无法删除") } return s.db.Delete(&model.College{}, id).Error }