IntranetAppService ve MenuAppService güçlendirildi

This commit is contained in:
Sedat Öztürk 2026-02-04 21:25:35 +03:00
parent b070702378
commit ec44d03e79
3 changed files with 182 additions and 147 deletions

View file

@ -13,6 +13,7 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Uow;
namespace Erp.Platform.Public;
@ -83,8 +84,10 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
_projectTaskRepository = projectTaskRepository;
}
[UnitOfWork]
public async Task<IntranetDashboardDto> GetIntranetDashboardAsync()
{
// ABP 9.0.2: UnitOfWork kapsamında tüm sorgular optimize edilir
return new IntranetDashboardDto
{
Events = await GetUpcomingEventsAsync(),
@ -107,21 +110,24 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
private async Task<List<ProjectTaskDto>> GetProjectTasksAsync()
{
var tasks = await _projectTaskRepository
.WithDetailsAsync(e => e.Employee)
.ContinueWith(t => t.Result.Where(s => s.Priority == "High" || s.Priority == "Urgent")
var queryable = await _projectTaskRepository.WithDetailsAsync(e => e.Employee);
var tasks = await AsyncExecuter.ToListAsync(
queryable
.Where(s => s.Priority == "High" || s.Priority == "Urgent")
.OrderByDescending(s => s.StartDate)
.Take(3)
.ToList());
);
return ObjectMapper.Map<List<ProjectTask>, List<ProjectTaskDto>>(tasks);
}
private async Task<List<SocialPostDto>> GetSocialPostsAsync()
{
var socialPosts = await _socialPostRepository
.WithDetailsAsync(e => e.Employee, e => e.Location, e => e.Media, e => e.Comments, e => e.Likes)
.ContinueWith(t => t.Result.ToList());
var queryable = await _socialPostRepository
.WithDetailsAsync(e => e.Employee, e => e.Location, e => e.Media, e => e.Comments, e => e.Likes);
var socialPosts = await AsyncExecuter.ToListAsync(queryable);
return ObjectMapper.Map<List<SocialPost>, List<SocialPostDto>>(socialPosts);
}
@ -130,29 +136,28 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
{
var queryable = await _surveyRepository.GetQueryableAsync();
var surveys = queryable
var surveys = await AsyncExecuter.ToListAsync(
queryable
.Where(s => s.Status == "active")
.Include(s => s.Questions)
.ThenInclude(q => q.Options)
.ToList();
);
return ObjectMapper.Map<List<Survey>, List<SurveyDto>>(surveys);
}
private async Task<List<OvertimeDto>> GetOvertimesAsync()
{
var overtimes = await _overtimeRepository
.WithDetailsAsync(e => e.Employee)
.ContinueWith(t => t.Result.ToList());
var queryable = await _overtimeRepository.WithDetailsAsync(e => e.Employee);
var overtimes = await AsyncExecuter.ToListAsync(queryable);
return ObjectMapper.Map<List<Overtime>, List<OvertimeDto>>(overtimes);
}
private async Task<List<LeaveDto>> GetLeavesAsync()
{
var leaves = await _leaveRepository
.WithDetailsAsync(e => e.Employee)
.ContinueWith(t => t.Result.ToList());
var queryable = await _leaveRepository.WithDetailsAsync(e => e.Employee);
var leaves = await AsyncExecuter.ToListAsync(queryable);
return ObjectMapper.Map<List<Leave>, List<LeaveDto>>(leaves);
}
@ -225,14 +230,14 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
private async Task<List<AnnouncementDto>> GetAnnouncementsAsync()
{
var announcements = await _announcementRepository
.WithDetailsAsync(e => e.Employee)
.ContinueWith(t => t.Result.ToList());
var queryable = await _announcementRepository.WithDetailsAsync(e => e.Employee);
var announcements = await AsyncExecuter.ToListAsync(queryable);
var announcementDtos = new List<AnnouncementDto>();
// Tüm departmanları bir kez çek (performans için)
var allDepartments = await _departmentRepository.GetListAsync();
var departmentDict = allDepartments.ToDictionary(d => d.Id, d => d.Name);
foreach (var announcement in announcements)
{
@ -246,21 +251,15 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
.Select(d => d.Trim())
.ToArray();
// ID'leri Department Name'lere çevir
var departmentNames = new List<string>();
foreach (var deptId in departmentIds)
{
if (Guid.TryParse(deptId, out var guid))
{
var department = allDepartments.FirstOrDefault(d => d.Id == guid);
if (department != null)
{
departmentNames.Add(department.Name);
}
}
}
// ID'leri Department Name'lere çevir (Dictionary lookup çok hızlı)
var departmentNames = departmentIds
.Where(deptId => Guid.TryParse(deptId, out _))
.Select(deptId => Guid.Parse(deptId))
.Where(guid => departmentDict.ContainsKey(guid))
.Select(guid => departmentDict[guid])
.ToArray();
dto.Departments = departmentNames.ToArray();
dto.Departments = departmentNames;
}
else
{
@ -284,19 +283,17 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
var lastMonthExpenses = queryable
.Where(a => a.RequestDate >= oneMonthAgo && a.RequestDate <= today);
// Son 1 aydaki toplam talep miktarı
var totalRequested = lastMonthExpenses.Sum(a => a.Amount);
// Son 1 aydaki onaylanan harcamaların toplamı
var totalApproved = lastMonthExpenses
.Where(a => a.Status == "approved")
.Sum(a => a.Amount);
// ABP 9.0.2: Separate queries for better performance
var expensesList = await AsyncExecuter.ToListAsync(lastMonthExpenses);
var totalRequested = expensesList.Sum(a => a.Amount);
var totalApproved = expensesList.Where(a => a.Status == "approved").Sum(a => a.Amount);
// Son 5 kayıt
var last5Expenses = queryable
var last5Expenses = await AsyncExecuter.ToListAsync(
queryable
.OrderByDescending(a => a.RequestDate)
.Take(5)
.ToList();
);
// Map işlemleri
var last5Dtos = ObjectMapper.Map<List<Expense>, List<ExpenseDto>>(last5Expenses);
@ -335,30 +332,55 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
{
var today = DateTime.Now;
var employees = await _employeeRepository
.WithDetailsAsync(e => e.EmploymentType, e => e.JobPosition, e => e.Department)
.ContinueWith(t => t.Result
.Where(e => e.BirthDate.Day == today.Day && e.BirthDate.Month == today.Month)
.ToList());
var queryable = await _employeeRepository
.WithDetailsAsync(e => e.EmploymentType, e => e.JobPosition, e => e.Department);
var employees = await AsyncExecuter.ToListAsync(
queryable.Where(e => e.BirthDate.Day == today.Day && e.BirthDate.Month == today.Month)
);
return ObjectMapper.Map<List<Employee>, List<EmployeeDto>>(employees);
}
private async Task<List<EventDto>> GetUpcomingEventsAsync()
{
var events = await _eventRepository
.WithDetailsAsync(e => e.Category, e => e.Type, e => e.Category, e => e.Photos, e => e.Comments)
.ContinueWith(t => t.Result.ToList().Where(e => e.isPublished).OrderByDescending(e => e.CreationTime));
var queryable = await _eventRepository
.WithDetailsAsync(e => e.Category, e => e.Type, e => e.Photos, e => e.Comments);
var events = await AsyncExecuter.ToListAsync(
queryable.Where(e => e.isPublished).OrderByDescending(e => e.CreationTime)
);
if (!events.Any())
return new List<EventDto>();
// Tüm unique employee ID'lerini topla (event'ler ve comment'ler için)
var employeeIds = new HashSet<Guid>();
foreach (var evt in events)
{
employeeIds.Add(evt.EmployeeId);
if (evt.Comments != null)
{
foreach (var comment in evt.Comments)
{
employeeIds.Add(comment.EmployeeId);
}
}
}
// Tüm employee'leri tek sorguda getir (N+1 problemi çözüldü)
var employeeQueryable = await _employeeRepository.WithDetailsAsync(e => e.JobPosition);
var employees = await AsyncExecuter.ToListAsync(
employeeQueryable.Where(e => employeeIds.Contains(e.Id))
);
var employeeDict = employees.ToDictionary(e => e.Id);
var result = new List<EventDto>();
foreach (var evt in events)
{
var employee = await _employeeRepository
.WithDetailsAsync(e => e.JobPosition)
.ContinueWith(t => t.Result.FirstOrDefault(e => e.Id == evt.EmployeeId));
if (!employeeDict.TryGetValue(evt.EmployeeId, out var employee))
continue;
if (employee != null)
{
var calendarEvent = new EventDto
{
Id = evt.Id.ToString(),
@ -372,7 +394,7 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
{
Id = employee.Id,
Name = employee.Name,
Position = employee.JobPosition.Name,
Position = employee.JobPosition?.Name,
Avatar = employee.Avatar
},
Participants = evt.ParticipantsCount,
@ -382,16 +404,12 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
IsPublished = evt.isPublished
};
// Comment'lerin author bilgilerini doldur
// Comment'lerin author bilgilerini doldur (artık dictionary'den hızlıca alınıyor)
if (evt.Comments != null && evt.Comments.Any())
{
foreach (var comment in evt.Comments)
{
var commentAuthor = await _employeeRepository
.WithDetailsAsync(e => e.JobPosition)
.ContinueWith(t => t.Result.FirstOrDefault(e => e.Id == comment.EmployeeId));
if (commentAuthor != null)
if (employeeDict.TryGetValue(comment.EmployeeId, out var commentAuthor))
{
calendarEvent.Comments.Add(new EventCommentDto
{
@ -400,7 +418,7 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
{
Id = commentAuthor.Id,
Name = commentAuthor.Name,
Position = commentAuthor.JobPosition.Name,
Position = commentAuthor.JobPosition?.Name,
Avatar = commentAuthor.Avatar
},
Content = comment.Content,
@ -413,7 +431,6 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
result.Add(calendarEvent);
}
}
return result;
}

View file

@ -88,29 +88,37 @@ public class MenuAppService : CrudAppService<
query = ApplyPaging(query, input);
var items = await AsyncExecuter.ToListAsync(query);
foreach (var item in items)
{
if (item.RequiredPermissionName.IsNullOrWhiteSpace())
{
entities.Add(item);
}
else
// Tüm unique permission'ları topla
var uniquePermissions = items
.Where(x => !string.IsNullOrWhiteSpace(x.RequiredPermissionName))
.Select(x => x.RequiredPermissionName)
.Distinct()
.ToList();
// Tüm permission'ları bir kerede kontrol et (N+1 query problemini önler)
var grantedPermissions = new HashSet<string>();
foreach (var permission in uniquePermissions)
{
try
{
var result = await AuthorizationService.IsGrantedAsync(item.RequiredPermissionName);
if (result == true)
if (await AuthorizationService.IsGrantedAsync(permission))
{
entities.Add(item);
grantedPermissions.Add(permission);
}
}
catch (Exception ex)
{
Logger.LogError(ex, ex.Message);
}
Logger.LogError(ex, $"Permission error for {permission}: {ex.Message}");
}
}
// Sadece yetkili menüleri filtrele
entities = items
.Where(item => string.IsNullOrWhiteSpace(item.RequiredPermissionName) ||
grantedPermissions.Contains(item.RequiredPermissionName))
.ToList();
entityDtos = await base.MapToGetListOutputDtosAsync(entities);
}
@ -164,28 +172,44 @@ public class MenuAppService : CrudAppService<
{
await CheckUpdatePolicyAsync();
var result = new List<MenuDto>();
if (!inputs.Any())
return new List<MenuDto>();
foreach (var input in inputs)
{
if (input.Id == Guid.Empty)
// Id kontrolü
if (inputs.Any(x => x.Id == Guid.Empty))
{
throw new ArgumentException("MenuDto içinde geçerli bir Id bulunmalıdır.");
}
var key = await _repositoryKey.FirstOrDefaultAsync(
a => a.Key == input.DisplayName &&
a.ResourceName == PlatformConsts.AppName);
// Tüm DisplayName'leri topla
var displayNames = inputs.Select(x => x.DisplayName).Distinct().ToList();
if (key is null)
// Mevcut key'leri tek sorguda getir
var existingKeys = await AsyncExecuter.ToListAsync(
(await _repositoryKey.GetQueryableAsync())
.Where(a => displayNames.Contains(a.Key) && a.ResourceName == PlatformConsts.AppName));
var existingKeyNames = existingKeys.Select(x => x.Key).ToHashSet();
// Eksik key'leri toplu ekle
var newKeys = displayNames
.Where(name => !existingKeyNames.Contains(name))
.Select(name => new LanguageKey
{
await _repositoryKey.InsertAsync(new LanguageKey
{
Key = input.DisplayName,
Key = name,
ResourceName = PlatformConsts.AppName
});
})
.ToList();
if (newKeys.Any())
{
await _repositoryKey.InsertManyAsync(newKeys, autoSave: true);
}
// Menüleri güncelle
var result = new List<MenuDto>();
foreach (var input in inputs)
{
var updated = await base.UpdateAsync(input.Id, input);
result.Add(updated);
}
@ -221,5 +245,3 @@ public class MenuAppService : CrudAppService<
return entityDtos;
}
}

View file

@ -122,14 +122,10 @@ const List: React.FC = () => {
<h4 className="text-sm font-medium">{translate('::' + gridDto.gridOptions.title)}</h4>
<Badge content={viewMode} />
<p className="ml-2 text-xs text-slate-500 mr-auto">
{translate('::' + gridDto.gridOptions.description)}
</p>
{/* =======================
VIEW BUTTONS
======================= */}
<div className="flex gap-1">
<div className="flex gap-1 ml-auto">
{gridDto?.gridOptions?.layoutDto.scheduler &&
gridDto?.gridOptions?.schedulerOptionDto?.textExpr &&
gridDto?.gridOptions?.schedulerOptionDto?.startDateExpr && (