package system import ( "context" "errors" "strconv" "strings" "time" "kra/app/system/biz" "gorm.io/gorm" ) type departmentRepo struct{ data Provider } func NewDepartmentRepo(data Provider) biz.DepartmentRepo { return &departmentRepo{data: data} } type departmentPO struct { ID uint `gorm:"primaryKey"` CreatedAt time.Time UpdatedAt time.Time DeletedAt gorm.DeletedAt `gorm:"index"` Name string `gorm:"index"` ParentID uint Ancestors string Sort int LeaderID uint Status *bool `gorm:"default:true"` } func (departmentPO) TableName() string { return "sys_departments" } type userDepartmentPO struct { // Match the reference join model: no composite primary key, so repeated IDs in a // request are persisted in the same order/shape as the reference service. UserID uint `gorm:"column:sys_user_id"` DepartmentID uint `gorm:"column:sys_department_id"` } func (userDepartmentPO) TableName() string { return "sys_user_departments" } type authorityDepartmentPO struct { AuthorityID uint `gorm:"column:sys_authority_authority_id;index"` DepartmentID uint `gorm:"column:sys_department_id"` } func (authorityDepartmentPO) TableName() string { return "sys_authority_departments" } func deptFromPO(po departmentPO) *biz.Department { return &biz.Department{ID: po.ID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, Name: po.Name, ParentID: po.ParentID, Ancestors: po.Ancestors, Sort: po.Sort, LeaderID: po.LeaderID, Status: po.Status} } func (r *departmentRepo) attachDepartmentLeaders(ctx context.Context, departments []*biz.Department) error { leaderIDs := make([]uint, 0, len(departments)) for _, department := range departments { if department.LeaderID != 0 { leaderIDs = append(leaderIDs, department.LeaderID) } } if len(leaderIDs) == 0 { return nil } var leaders []userPO if err := r.data.DB().WithContext(ctx).Where("id IN ?", leaderIDs).Find(&leaders).Error; err != nil { return err } leaderByID := make(map[uint]*biz.User, len(leaders)) for i := range leaders { leaderByID[leaders[i].ID] = baseBizUser(&leaders[i]) } for _, department := range departments { department.Leader = leaderByID[department.LeaderID] } return nil } func (r *departmentRepo) CreateDepartment(ctx context.Context, v *biz.Department) error { v.Ancestors = "0" if v.ParentID != 0 { var parent departmentPO if err := r.data.DB().WithContext(ctx).First(&parent, v.ParentID).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return errors.New("父部门不存在") } return err } v.Ancestors = strings.Trim(parent.Ancestors+","+strconv.FormatUint(uint64(parent.ID), 10), ",") } return r.data.DB().WithContext(ctx).Create(&departmentPO{Name: v.Name, ParentID: v.ParentID, Ancestors: v.Ancestors, Sort: v.Sort, LeaderID: v.LeaderID, Status: v.Status}).Error } func (r *departmentRepo) UpdateDepartment(ctx context.Context, v *biz.Department) error { if v.ParentID == v.ID { return errors.New("父部门不能是自己") } v.Ancestors = "0" if v.ParentID != 0 { var parent departmentPO if err := r.data.DB().WithContext(ctx).First(&parent, v.ParentID).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return errors.New("父部门不存在") } return err } v.Ancestors = strings.Trim(parent.Ancestors+","+strconv.FormatUint(uint64(parent.ID), 10), ",") } return r.data.DB().WithContext(ctx).Model(&departmentPO{}).Where("id = ?", v.ID).Updates(map[string]any{"name": v.Name, "parent_id": v.ParentID, "ancestors": v.Ancestors, "sort": v.Sort, "leader_id": v.LeaderID, "status": v.Status}).Error } func (r *departmentRepo) DeleteDepartment(ctx context.Context, id uint) error { if id == 0 { return errors.New("部门ID不能为空") } var count int64 if err := r.data.DB().WithContext(ctx).Model(&departmentPO{}).Where("parent_id = ?", id).Count(&count).Error; err != nil { return err } if count > 0 { return errors.New("存在子部门,不允许删除") } if err := r.data.DB().WithContext(ctx).Model(&userPO{}).Where("dept_id = ?", id).Count(&count).Error; err != nil { return err } if count > 0 { return errors.New("该部门下存在用户,不允许删除") } if err := r.data.DB().WithContext(ctx).Model(&userDepartmentPO{}).Where("sys_department_id = ?", id).Count(&count).Error; err != nil { return err } if count > 0 { return errors.New("该部门下存在用户,不允许删除") } return r.data.DB().WithContext(ctx).Delete(&departmentPO{}, id).Error } func (r *departmentRepo) FindDepartment(ctx context.Context, id uint) (*biz.Department, error) { var po departmentPO if err := r.data.DB().WithContext(ctx).First(&po, id).Error; err != nil { return nil, err } department := deptFromPO(po) if err := r.attachDepartmentLeaders(ctx, []*biz.Department{department}); err != nil { return nil, err } return department, nil } func (r *departmentRepo) ListDepartments(ctx context.Context, name string) ([]*biz.Department, error) { var pos []departmentPO db := r.data.DB().WithContext(ctx).Order("sort") if name != "" { db = db.Where("name LIKE ?", "%"+name+"%") } if err := db.Find(&pos).Error; err != nil { return nil, err } nodes := map[uint]*biz.Department{} items := make([]*biz.Department, 0, len(pos)) for _, po := range pos { item := deptFromPO(po) if name == "" { // Recursive GORM child queries initialize leaf collections to an empty // slice. Preserve that tree response shape while search and single-item // queries keep the model's nil children value. item.Children = []*biz.Department{} } nodes[po.ID] = item items = append(items, item) } if err := r.attachDepartmentLeaders(ctx, items); err != nil { return nil, err } if name != "" { return items, nil } roots := make([]*biz.Department, 0) for _, po := range pos { n := nodes[po.ID] if p := nodes[po.ParentID]; p != nil { p.Children = append(p.Children, n) } else if po.ParentID == 0 { roots = append(roots, n) } } return roots, nil } func (r *departmentRepo) DepartmentUserIDs(ctx context.Context, id uint) ([]uint, error) { var ids []uint err := r.data.DB().WithContext(ctx).Model(&userDepartmentPO{}).Where("sys_department_id = ?", id).Pluck("sys_user_id", &ids).Error return ids, err } func (r *departmentRepo) SetDepartmentUsers(ctx context.Context, id uint, ids []uint) error { return r.replaceUserDepartments(ctx, id, ids) } func (r *departmentRepo) replaceUserDepartments(ctx context.Context, id uint, ids []uint) error { if id == 0 { return errors.New("部门ID不能为空") } return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error { var current []uint if err := tx.Model(&userDepartmentPO{}).Where("sys_department_id = ?", id).Pluck("sys_user_id", ¤t).Error; err != nil { return err } if err := tx.Where("sys_department_id = ?", id).Delete(&userDepartmentPO{}).Error; err != nil { return err } links := make([]userDepartmentPO, 0, len(ids)) for _, uid := range ids { links = append(links, userDepartmentPO{UserID: uid, DepartmentID: id}) } if len(links) > 0 { if err := tx.Create(&links).Error; err != nil { return err } } target := make(map[uint]struct{}, len(ids)) for _, uid := range ids { target[uid] = struct{}{} } removed := make([]uint, 0) for _, uid := range current { if _, ok := target[uid]; !ok { removed = append(removed, uid) } } if len(removed) > 0 { if err := tx.Model(&userPO{}).Where("dept_id = ? AND id IN ?", id, removed).Update("dept_id", 0).Error; err != nil { return err } } if len(ids) > 0 { if err := tx.Model(&userPO{}).Where("dept_id = ? AND id IN ?", 0, ids).Update("dept_id", id).Error; err != nil { return err } } return nil }) } func (r *departmentRepo) SetUserDepartments(ctx context.Context, uid uint, ids []uint, primary uint) error { return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error { var user userPO if err := tx.First(&user, uid).Error; err != nil { return errors.New("查询用户数据失败") } if err := tx.Where("sys_user_id = ?", uid).Delete(&userDepartmentPO{}).Error; err != nil { return err } links := make([]userDepartmentPO, 0, len(ids)) for _, id := range ids { links = append(links, userDepartmentPO{UserID: uid, DepartmentID: id}) } if len(links) > 0 { if err := tx.Create(&links).Error; err != nil { return err } } if primary == 0 && len(ids) > 0 { primary = ids[0] } if primary != 0 { inSet := false for _, id := range ids { if id == primary { inSet = true break } } if !inSet { return errors.New("主部门必须在归属部门范围内") } } return tx.Model(&userPO{}).Where("id = ?", uid).Update("dept_id", primary).Error }) }