sozsoft-platform/api/src/Sozsoft.Platform.Application/OrgChart/OrgChartAppService.cs

164 lines
6.5 KiB
C#
Raw Normal View History

using System;
2026-05-04 19:50:21 +00:00
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;
2026-05-04 19:50:21 +00:00
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;
2026-05-04 19:50:21 +00:00
private readonly IRepository<Department, Guid> _departmentRepository;
private readonly IRepository<JobPosition, Guid> _jobPositionRepository;
private readonly IIdentityUserRepository _userRepository;
public OrgChartAppService(
IRepository<Branch, Guid> branchRepository,
2026-05-04 19:50:21 +00:00
IRepository<Department, Guid> departmentRepository,
IRepository<JobPosition, Guid> jobPositionRepository,
IIdentityUserRepository userRepository)
{
_branchRepository = branchRepository;
2026-05-04 19:50:21 +00:00
_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);
2026-07-11 20:35:32 +00:00
var positionsByDepartment = jobPositions
.GroupBy(position => position.DepartmentId)
.ToDictionary(group => group.Key, group => group.OrderBy(position => position.Name).ToList());
var usersByJobPosition = GroupUsersByJobPosition(users);
2026-05-04 19:50:21 +00:00
var topPositionByDepartment = new Dictionary<Guid, JobPosition>();
foreach (var department in departments)
{
2026-07-11 20:35:32 +00:00
if (!positionsByDepartment.TryGetValue(department.Id, out var departmentPositions))
2026-05-04 19:50:21 +00:00
{
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 =>
2026-05-04 19:50:21 +00:00
{
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,
2026-07-11 20:35:32 +00:00
Users = topPosition != null && usersByJobPosition.TryGetValue(topPosition.Id, out var positionUsers)
? positionUsers
: [],
};
2026-05-04 19:50:21 +00:00
}).ToList();
return await WithBranchRootsAsync(nodes);
2026-05-04 19:50:21 +00:00
}
[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);
2026-07-11 20:35:32 +00:00
var usersByJobPosition = GroupUsersByJobPosition(users);
2026-05-04 19:50:21 +00:00
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)),
2026-05-04 19:50:21 +00:00
DepartmentId = jp.DepartmentId,
DepartmentName = deptDict.TryGetValue(jp.DepartmentId, out var name) ? name : null,
JobPositionId = jp.Id,
JobPositionName = jp.Name,
2026-07-11 20:35:32 +00:00
Users = usersByJobPosition.TryGetValue(jp.Id, out var positionUsers)
? positionUsers
: [],
2026-05-04 19:50:21 +00:00
}).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();
2026-05-04 19:50:21 +00:00
}
2026-07-11 20:35:32 +00:00
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());
}
2026-05-04 19:50:21 +00:00
}