package service import ( "context" "errors" "fmt" "strings" "giter.top/smart/internal/iam/entity" "giter.top/smart/internal/iam/repository" "giter.top/smart/pkg/utils/id" ) // DeptService 部门 type DeptService interface { Tree(ctx context.Context, tenantID string, keyword string, leaderID *string) ([]DeptNode, error) Create(ctx context.Context, tenantID string, req *CreateDeptRequest) (*entity.Dept, error) Update(ctx context.Context, tenantID string, id string, req *UpdateDeptRequest) (*entity.Dept, error) Delete(ctx context.Context, tenantID string, ids []string) error Get(ctx context.Context, tenantID string, id string) (*entity.Dept, error) } type CreateDeptRequest struct { ParentID string `json:"parent_id"` DeptName string `json:"dept_name" binding:"required,max=128"` LeaderID *string `json:"leader_id"` SortOrder int `json:"sort_order"` } type UpdateDeptRequest struct { ParentID *string `json:"parent_id"` DeptName *string `json:"dept_name" binding:"omitempty,max=128"` LeaderID *string `json:"leader_id"` SortOrder *int `json:"sort_order"` } // DeptNode 树节点 type DeptNode struct { entity.Dept Children []DeptNode `json:"children,omitempty"` } type deptService struct { depts repository.DeptRepository users repository.UserRepository } func NewDeptService(depts repository.DeptRepository, users repository.UserRepository) DeptService { return &deptService{depts: depts, users: users} } func isDeptRoot(d *entity.Dept) bool { return d.ParentID == "" || d.ParentID == "0" } func (s *deptService) Tree(ctx context.Context, tenantID string, keyword string, leaderID *string) ([]DeptNode, error) { rows, err := s.depts.ListByTenant(ctx, tenantID) if err != nil { return nil, err } filtered := rows if keyword != "" || leaderID != nil { filtered = make([]entity.Dept, 0) for _, d := range rows { if keyword != "" && !strings.Contains(d.DeptName, keyword) { continue } if leaderID != nil && (d.LeaderID == nil || *d.LeaderID != *leaderID) { continue } filtered = append(filtered, d) } if keyword != "" || leaderID != nil { filtered = s.includeAncestors(rows, filtered) } } return buildDeptTree(filtered, ""), nil } func (s *deptService) includeAncestors(all []entity.Dept, matched []entity.Dept) []entity.Dept { idSet := map[string]struct{}{} byID := map[string]entity.Dept{} for _, d := range all { byID[d.ID] = d } for _, d := range matched { cur := d for { idSet[cur.ID] = struct{}{} if isDeptRoot(&cur) { break } p, ok := byID[cur.ParentID] if !ok { break } cur = p } } out := make([]entity.Dept, 0, len(idSet)) for _, d := range all { if _, ok := idSet[d.ID]; ok { out = append(out, d) } } return out } func buildDeptTree(rows []entity.Dept, parentID string) []DeptNode { children := map[string][]entity.Dept{} for _, d := range rows { pid := d.ParentID if d.ParentID == "0" { pid = "" } children[pid] = append(children[pid], d) } var walk func(pid string) []DeptNode walk = func(pid string) []DeptNode { list := children[pid] out := make([]DeptNode, 0, len(list)) for _, d := range list { out = append(out, DeptNode{Dept: d, Children: walk(d.ID)}) } return out } return walk(parentID) } func (s *deptService) Create(ctx context.Context, tenantID string, req *CreateDeptRequest) (*entity.Dept, error) { parentKey := req.ParentID if parentKey == "0" { parentKey = "" } ok, err := s.depts.ExistsSiblingName(ctx, tenantID, parentKey, req.DeptName, "") if err != nil { return nil, err } if ok { return nil, fmt.Errorf("同级部门名称已存在") } d := &entity.Dept{ ID: id.New(), TenantID: tenantID, ParentID: parentKey, DeptName: req.DeptName, LeaderID: req.LeaderID, SortOrder: req.SortOrder, Status: 1, } if err := s.depts.Create(ctx, d); err != nil { return nil, err } path := fmt.Sprintf("/%s/", d.ID) if parentKey != "" { p, err := s.depts.GetByID(ctx, parentKey) if err != nil { return nil, err } if p.TenantID != tenantID { return nil, fmt.Errorf("父部门不属于当前租户") } base := p.DeptPath if base == "" { base = fmt.Sprintf("/%s/", p.ID) } path = base + fmt.Sprintf("%s/", d.ID) } if err := s.depts.UpdatePath(ctx, d.ID, path); err != nil { return nil, err } d.DeptPath = path return d, nil } func (s *deptService) Update(ctx context.Context, tenantID string, id string, req *UpdateDeptRequest) (*entity.Dept, error) { d, err := s.depts.GetByID(ctx, id) if err != nil { if errors.Is(err, repository.ErrNotFound) { return nil, fmt.Errorf("部门不存在") } return nil, err } if d.TenantID != tenantID { return nil, fmt.Errorf("部门不属于当前租户") } if isDeptRoot(d) { if req.ParentID != nil && *req.ParentID != "" && *req.ParentID != "0" { return nil, fmt.Errorf("根部门禁止移动") } if req.DeptName != nil && *req.DeptName != "" && *req.DeptName != d.DeptName { return nil, fmt.Errorf("根部门禁止重命名") } } curParent := d.ParentID if curParent == "0" { curParent = "" } var newParentForName string if req.ParentID != nil { np := *req.ParentID if np == "0" { np = "" } newParentForName = np } else { newParentForName = curParent } if req.DeptName != nil && *req.DeptName != "" { ok, err := s.depts.ExistsSiblingName(ctx, tenantID, newParentForName, *req.DeptName, id) if err != nil { return nil, err } if ok { return nil, fmt.Errorf("同级部门名称已存在") } d.DeptName = *req.DeptName } if req.ParentID != nil { npID := *req.ParentID if npID == "0" { npID = "" } if npID != curParent { if npID == id { return nil, fmt.Errorf("不能将部门移动到自身之下") } if npID != "" { if s.isDescendant(ctx, id, npID) { return nil, fmt.Errorf("禁止移动至子部门(防环)") } np, err := s.depts.GetByID(ctx, npID) if err != nil { return nil, fmt.Errorf("父部门无效") } if np.TenantID != tenantID { return nil, fmt.Errorf("父部门不属于当前租户") } d.ParentID = npID base := np.DeptPath if base == "" { base = fmt.Sprintf("/%s/", np.ID) } d.DeptPath = base + fmt.Sprintf("%s/", d.ID) } else { d.ParentID = "" d.DeptPath = fmt.Sprintf("/%s/", d.ID) } _ = s.depts.UpdatePath(ctx, d.ID, d.DeptPath) } } if req.LeaderID != nil { d.LeaderID = req.LeaderID } if req.SortOrder != nil { d.SortOrder = *req.SortOrder } if err := s.depts.Update(ctx, d); err != nil { return nil, err } return d, nil } func (s *deptService) isDescendant(ctx context.Context, rootID, nodeID string) bool { if nodeID == rootID { return true } cur, err := s.depts.GetByID(ctx, nodeID) if err != nil { return false } for i := 0; i < 64 && cur.ParentID != "" && cur.ParentID != "0"; i++ { if cur.ParentID == rootID { return true } cur, err = s.depts.GetByID(ctx, cur.ParentID) if err != nil { return false } } return false } func (s *deptService) Delete(ctx context.Context, tenantID string, ids []string) error { for _, did := range ids { d, err := s.depts.GetByID(ctx, did) if err != nil { return err } if d.TenantID != tenantID { return fmt.Errorf("部门 %s 不属于当前租户", did) } if isDeptRoot(d) { return fmt.Errorf("根部门禁止删除") } n, err := s.depts.CountChildren(ctx, did) if err != nil { return err } if n > 0 { return fmt.Errorf("部门 %s 存在子部门", did) } uc, err := s.users.CountByDept(ctx, did) if err != nil { return err } if uc > 0 { return fmt.Errorf("部门 %s 仍存在用户", did) } if err := s.depts.Delete(ctx, did); err != nil { return err } } return nil } func (s *deptService) Get(ctx context.Context, tenantID string, id string) (*entity.Dept, error) { d, err := s.depts.GetByID(ctx, id) if err != nil { return nil, err } if d.TenantID != tenantID { return nil, fmt.Errorf("部门不属于当前租户") } return d, nil }