163 lines
6.5 KiB
C#
163 lines
6.5 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Threading.Tasks;
|
||
using Microsoft.AspNetCore.Authorization;
|
||
using Microsoft.AspNetCore.Mvc;
|
||
using Sozsoft.Platform.Entities;
|
||
using Sozsoft.Platform.Extensions;
|
||
using Volo.Abp.Domain.Repositories;
|
||
using Volo.Abp.Identity;
|
||
|
||
namespace Sozsoft.Platform.OrgChart;
|
||
|
||
[Authorize]
|
||
[Route("api/app/org-chart")]
|
||
public class OrgChartAppService : PlatformAppService
|
||
{
|
||
private readonly IRepository<Branch, Guid> _branchRepository;
|
||
private readonly IRepository<Department, Guid> _departmentRepository;
|
||
private readonly IRepository<JobPosition, Guid> _jobPositionRepository;
|
||
private readonly IIdentityUserRepository _userRepository;
|
||
|
||
public OrgChartAppService(
|
||
IRepository<Branch, Guid> branchRepository,
|
||
IRepository<Department, Guid> departmentRepository,
|
||
IRepository<JobPosition, Guid> jobPositionRepository,
|
||
IIdentityUserRepository userRepository)
|
||
{
|
||
_branchRepository = branchRepository;
|
||
_departmentRepository = departmentRepository;
|
||
_jobPositionRepository = jobPositionRepository;
|
||
_userRepository = userRepository;
|
||
}
|
||
|
||
[HttpGet("departments")]
|
||
public async Task<List<OrgChartNodeDto>> GetDepartmentsAsync()
|
||
{
|
||
var departments = await _departmentRepository.GetListAsync();
|
||
var jobPositions = await _jobPositionRepository.GetListAsync();
|
||
var users = await _userRepository.GetListAsync();
|
||
|
||
var jobById = jobPositions.ToDictionary(x => x.Id);
|
||
var positionsByDepartment = jobPositions
|
||
.GroupBy(position => position.DepartmentId)
|
||
.ToDictionary(group => group.Key, group => group.OrderBy(position => position.Name).ToList());
|
||
var usersByJobPosition = GroupUsersByJobPosition(users);
|
||
var topPositionByDepartment = new Dictionary<Guid, JobPosition>();
|
||
|
||
foreach (var department in departments)
|
||
{
|
||
if (!positionsByDepartment.TryGetValue(department.Id, out var departmentPositions))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
// Top position means: no parent, parent missing, or parent belongs to another department.
|
||
var topCandidates = departmentPositions
|
||
.Where(position =>
|
||
!position.ParentId.HasValue ||
|
||
!jobById.ContainsKey(position.ParentId.Value) ||
|
||
jobById[position.ParentId.Value].DepartmentId != department.Id)
|
||
.OrderBy(position => position.Name)
|
||
.ToList();
|
||
|
||
var selectedTop = topCandidates.FirstOrDefault() ?? departmentPositions
|
||
.OrderBy(position => position.Name)
|
||
.First();
|
||
|
||
topPositionByDepartment[department.Id] = selectedTop;
|
||
}
|
||
|
||
var nodes = departments.Select(d =>
|
||
{
|
||
topPositionByDepartment.TryGetValue(d.Id, out var topPosition);
|
||
|
||
return new OrgChartNodeDto
|
||
{
|
||
Id = d.Id,
|
||
Name = d.Name,
|
||
NodeType = OrgChartNodeType.Department,
|
||
ParentId = ResolveParent(d.ParentId, d.BranchId, departments.Select(x => x.Id)),
|
||
JobPositionId = topPosition?.Id,
|
||
JobPositionName = topPosition?.Name,
|
||
Users = topPosition != null && usersByJobPosition.TryGetValue(topPosition.Id, out var positionUsers)
|
||
? positionUsers
|
||
: [],
|
||
};
|
||
}).ToList();
|
||
|
||
return await WithBranchRootsAsync(nodes);
|
||
}
|
||
|
||
[HttpGet("job-positions")]
|
||
public async Task<List<OrgChartNodeDto>> GetJobPositionsAsync()
|
||
{
|
||
var jobPositions = await _jobPositionRepository.GetListAsync();
|
||
var departments = await _departmentRepository.GetListAsync();
|
||
var users = await _userRepository.GetListAsync();
|
||
|
||
var deptDict = departments.ToDictionary(d => d.Id, d => d.Name);
|
||
var usersByJobPosition = GroupUsersByJobPosition(users);
|
||
|
||
var nodes = jobPositions.Select(jp => new OrgChartNodeDto
|
||
{
|
||
Id = jp.Id,
|
||
Name = jp.Name,
|
||
NodeType = OrgChartNodeType.JobPosition,
|
||
ParentId = ResolveParent(jp.ParentId, jp.BranchId, jobPositions.Select(x => x.Id)),
|
||
DepartmentId = jp.DepartmentId,
|
||
DepartmentName = deptDict.TryGetValue(jp.DepartmentId, out var name) ? name : null,
|
||
JobPositionId = jp.Id,
|
||
JobPositionName = jp.Name,
|
||
Users = usersByJobPosition.TryGetValue(jp.Id, out var positionUsers)
|
||
? positionUsers
|
||
: [],
|
||
}).ToList();
|
||
|
||
return await WithBranchRootsAsync(nodes);
|
||
}
|
||
|
||
// Üstü olmayan (ya da üstü silinmiş) kayıt şubesinin altına bağlanır; şube ağacın köküdür.
|
||
private static Guid? ResolveParent(Guid? parentId, Guid branchId, IEnumerable<Guid> knownIds)
|
||
=> parentId.HasValue && knownIds.Contains(parentId.Value) ? parentId : branchId;
|
||
|
||
// Yalnızca altında kayıt bulunan şubeler düğüm olur; Branch IMultiTenant değildir, kiracı süzgeci elle yazılır.
|
||
private async Task<List<OrgChartNodeDto>> WithBranchRootsAsync(List<OrgChartNodeDto> nodes)
|
||
{
|
||
var nodeIds = nodes.Select(n => n.Id).ToHashSet();
|
||
var branchIds = nodes
|
||
.Where(n => n.ParentId.HasValue && !nodeIds.Contains(n.ParentId.Value))
|
||
.Select(n => n.ParentId!.Value)
|
||
.ToHashSet();
|
||
if (branchIds.Count == 0)
|
||
{
|
||
return nodes;
|
||
}
|
||
|
||
var tenantId = CurrentTenant.Id;
|
||
var branches = await _branchRepository.GetListAsync(b => b.TenantId == tenantId && branchIds.Contains(b.Id));
|
||
var branchNodes = branches
|
||
.OrderBy(b => b.Name)
|
||
.Select(b => new OrgChartNodeDto { Id = b.Id, Name = b.Name, NodeType = OrgChartNodeType.Branch });
|
||
|
||
return branchNodes.Concat(nodes).ToList();
|
||
}
|
||
|
||
private static Dictionary<Guid, List<OrgChartUserDto>> GroupUsersByJobPosition(
|
||
IEnumerable<IdentityUser> users)
|
||
{
|
||
return users
|
||
.GroupBy(user => user.GetJobPositionId())
|
||
.ToDictionary(
|
||
group => group.Key,
|
||
group => group.Select(user => new OrgChartUserDto
|
||
{
|
||
Id = user.Id,
|
||
FullName = user.GetFullName(),
|
||
Email = user.Email,
|
||
UserName = user.UserName,
|
||
PhoneNumber = user.PhoneNumber
|
||
}).ToList());
|
||
}
|
||
}
|