327 lines
7.9 KiB
Go
327 lines
7.9 KiB
Go
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
|
|
}
|