Add new domain models and interfaces for project management features

This commit is contained in:
2025-12-08 14:47:03 +03:30
parent b7a7fb01d7
commit 27e8a26ed8
295 changed files with 24896 additions and 26 deletions

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<RootNamespace>_0_Framework</RootNamespace> <RootNamespace>_0_Framework</RootNamespace>
</PropertyGroup> </PropertyGroup>

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<AssemblyName>BackgroundInstitutionContract.Task</AssemblyName> <AssemblyName>BackgroundInstitutionContract.Task</AssemblyName>

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup> </PropertyGroup>

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup> </PropertyGroup>

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@@ -88,6 +88,22 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CompanyManagement.Infrastru
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BackgroundInstitutionContract.Task", "BackgroundInstitutionContract\BackgroundInstitutionContract.Task\BackgroundInstitutionContract.Task.csproj", "{F78FBB92-294B-88BA-168D-F0C578B0D7D6}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BackgroundInstitutionContract.Task", "BackgroundInstitutionContract\BackgroundInstitutionContract.Task\BackgroundInstitutionContract.Task.csproj", "{F78FBB92-294B-88BA-168D-F0C578B0D7D6}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ProgramManager", "ProgramManager", "{67AFF7B6-4C4F-464C-A90D-9BDB644D83A9}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{48F6F6A5-7340-42F8-9216-BEB7A4B7D5A1}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Application", "Application", "{9D85672B-D48E-40B5-9804-0CE220E0E64C}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Domain", "Domain", "{D74D1E3B-3BE3-47EE-9914-785A8AD536E5}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Infrastructure", "Infrastructure", "{C0AE9368-D4E7-450B-9713-929D319DE690}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GozareshgirProgramManager.Application", "ProgramManager\src\Application\GozareshgirProgramManager.Application\GozareshgirProgramManager.Application.csproj", "{B57EB542-C028-4A77-9386-9DFF1E60FDCB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GozareshgirProgramManager.Domain", "ProgramManager\src\Domain\GozareshgirProgramManager.Domain\GozareshgirProgramManager.Domain.csproj", "{D2B4F1D7-6336-4B30-910C-219F4119303F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GozareshgirProgramManager.Infrastructure", "ProgramManager\src\Infrastructure\GozareshgirProgramManager.Infrastructure\GozareshgirProgramManager.Infrastructure.csproj", "{408281FE-615F-4CBE-BD95-2E86F5ACC6C3}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@@ -198,6 +214,18 @@ Global
{F78FBB92-294B-88BA-168D-F0C578B0D7D6}.Debug|Any CPU.Build.0 = Debug|Any CPU {F78FBB92-294B-88BA-168D-F0C578B0D7D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F78FBB92-294B-88BA-168D-F0C578B0D7D6}.Release|Any CPU.ActiveCfg = Release|Any CPU {F78FBB92-294B-88BA-168D-F0C578B0D7D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F78FBB92-294B-88BA-168D-F0C578B0D7D6}.Release|Any CPU.Build.0 = Release|Any CPU {F78FBB92-294B-88BA-168D-F0C578B0D7D6}.Release|Any CPU.Build.0 = Release|Any CPU
{B57EB542-C028-4A77-9386-9DFF1E60FDCB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B57EB542-C028-4A77-9386-9DFF1E60FDCB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B57EB542-C028-4A77-9386-9DFF1E60FDCB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B57EB542-C028-4A77-9386-9DFF1E60FDCB}.Release|Any CPU.Build.0 = Release|Any CPU
{D2B4F1D7-6336-4B30-910C-219F4119303F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D2B4F1D7-6336-4B30-910C-219F4119303F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D2B4F1D7-6336-4B30-910C-219F4119303F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D2B4F1D7-6336-4B30-910C-219F4119303F}.Release|Any CPU.Build.0 = Release|Any CPU
{408281FE-615F-4CBE-BD95-2E86F5ACC6C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{408281FE-615F-4CBE-BD95-2E86F5ACC6C3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{408281FE-615F-4CBE-BD95-2E86F5ACC6C3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{408281FE-615F-4CBE-BD95-2E86F5ACC6C3}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@@ -234,6 +262,13 @@ Global
{BF98173C-42AF-4897-A7CB-4CACEB2B52A2} = {86921E1B-2AFA-4B8A-9403-EE16D58B5B26} {BF98173C-42AF-4897-A7CB-4CACEB2B52A2} = {86921E1B-2AFA-4B8A-9403-EE16D58B5B26}
{97E148FA-3C36-40DD-B121-D90C1C0F3B47} = {C10E256D-7E7D-4C77-B416-E577A34AF924} {97E148FA-3C36-40DD-B121-D90C1C0F3B47} = {C10E256D-7E7D-4C77-B416-E577A34AF924}
{F78FBB92-294B-88BA-168D-F0C578B0D7D6} = {C10E256D-7E7D-4C77-B416-E577A34AF924} {F78FBB92-294B-88BA-168D-F0C578B0D7D6} = {C10E256D-7E7D-4C77-B416-E577A34AF924}
{48F6F6A5-7340-42F8-9216-BEB7A4B7D5A1} = {67AFF7B6-4C4F-464C-A90D-9BDB644D83A9}
{9D85672B-D48E-40B5-9804-0CE220E0E64C} = {48F6F6A5-7340-42F8-9216-BEB7A4B7D5A1}
{D74D1E3B-3BE3-47EE-9914-785A8AD536E5} = {48F6F6A5-7340-42F8-9216-BEB7A4B7D5A1}
{C0AE9368-D4E7-450B-9713-929D319DE690} = {48F6F6A5-7340-42F8-9216-BEB7A4B7D5A1}
{B57EB542-C028-4A77-9386-9DFF1E60FDCB} = {9D85672B-D48E-40B5-9804-0CE220E0E64C}
{D2B4F1D7-6336-4B30-910C-219F4119303F} = {D74D1E3B-3BE3-47EE-9914-785A8AD536E5}
{408281FE-615F-4CBE-BD95-2E86F5ACC6C3} = {C0AE9368-D4E7-450B-9713-929D319DE690}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E6CFB3A7-A7C8-4E82-8F06-F750408F0BA9} SolutionGuid = {E6CFB3A7-A7C8-4E82-8F06-F750408F0BA9}

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@@ -0,0 +1,33 @@
using MediatR;
using Microsoft.Extensions.Logging;
using GozareshgirProgramManager.Domain.CustomerAgg.Events;
using GozareshgirProgramManager.Application._Common.Models;
namespace GozareshgirProgramManager.Application.DomainEventHandlers;
public class CustomerRegisteredHandler : INotificationHandler<DomainEventNotification<CustomerRegistered>>
{
private readonly ILogger<CustomerRegisteredHandler> _logger;
public CustomerRegisteredHandler(ILogger<CustomerRegisteredHandler> logger)
{
_logger = logger;
}
public Task Handle(DomainEventNotification<CustomerRegistered> notification, CancellationToken cancellationToken)
{
var domainEvent = notification.DomainEvent;
_logger.LogInformation(
"Customer registered: {CustomerId}, Name: {Name}, Email: {Email}",
domainEvent.CustomerId,
domainEvent.Name,
domainEvent.Email);
// اینجا می‌توانید email ارسال کنید یا کارهای دیگر انجام دهید
return Task.CompletedTask;
}
}

View File

@@ -0,0 +1,23 @@
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Application.Interfaces;
using GozareshgirProgramManager.Domain.ProjectAgg.Events;
using MediatR;
using Microsoft.Extensions.Logging;
namespace GozareshgirProgramManager.Application.DomainEventHandlers.ProjectSection;
public class ProjectSectionAddedHandler:INotificationHandler<DomainEventNotification<ProjectSectionAddedEvent>>
{
private readonly ILogger<CustomerRegisteredHandler> _logger;
public ProjectSectionAddedHandler(ILogger<CustomerRegisteredHandler> logger)
{
_logger = logger;
}
public Task Handle(DomainEventNotification<ProjectSectionAddedEvent> notification, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}

View File

@@ -0,0 +1,14 @@
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Domain.ProjectAgg.Events;
using MediatR;
namespace GozareshgirProgramManager.Application.DomainEventHandlers.ProjectSection;
public class ProjectSectionAssignedHandler:INotificationHandler<DomainEventNotification<ProjectSectionAssignedEvent>>
{
public Task Handle(DomainEventNotification<ProjectSectionAssignedEvent> notification, CancellationToken cancellationToken)
{
var domainEvent = notification.DomainEvent;
return Task.CompletedTask;
}
}

View File

@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentValidation" Version="12.1.0" />
<PackageReference Include="MediatR" Version="13.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Domain\GozareshgirProgramManager.Domain\GozareshgirProgramManager.Domain.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,6 @@
namespace GozareshgirProgramManager.Application.Interfaces;
public interface IBoardNotificationService
{
Task SendProjectAssignedAsync();
}

View File

@@ -0,0 +1,266 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Application.Modules.Checkouts.Queries.GetUserToGropCreate;
using GozareshgirProgramManager.Domain._Common;
using GozareshgirProgramManager.Domain.CheckoutAgg.Entities;
using GozareshgirProgramManager.Domain.CheckoutAgg.Enums;
using GozareshgirProgramManager.Domain.CheckoutAgg.Repositories;
using GozareshgirProgramManager.Domain.ProjectAgg.Repositories;
using GozareshgirProgramManager.Domain.SalaryPaymentSettingAgg.DTOs;
using GozareshgirProgramManager.Domain.SalaryPaymentSettingAgg.Enums;
using GozareshgirProgramManager.Domain.SalaryPaymentSettingAgg.Repositories;
using GozareshgirProgramManager.Domain.UserAgg.Entities;
using MediatR;
using PersianTools.Core;
using System.Runtime.InteropServices;
using Microsoft.EntityFrameworkCore;
namespace GozareshgirProgramManager.Application.Modules.Checkouts.Commands.CreateCheckout;
public class CreateOrEditCheckoutCommandHandler : IBaseCommandHandler<CreateOrEditCheckoutCommand>
{
private readonly ICheckoutRepository _checkoutRepository;
private readonly ISalaryPaymentSettingRepository _salaryPaymentSettingRepository;
private readonly ITaskSectionActivityRepository _taskSectionActivityRepository;
private readonly IUnitOfWork _unitOfWork;
private readonly IGozareshgirDbContext _gozareshgirDbContext;
public CreateOrEditCheckoutCommandHandler(ICheckoutRepository checkoutRepository, IUnitOfWork unitOfWork, ISalaryPaymentSettingRepository salaryPaymentSettingRepository, ITaskSectionActivityRepository taskSectionActivityRepository, IGozareshgirDbContext gozareshgirDbContext)
{
_checkoutRepository = checkoutRepository;
_unitOfWork = unitOfWork;
_salaryPaymentSettingRepository = salaryPaymentSettingRepository;
_taskSectionActivityRepository = taskSectionActivityRepository;
_gozareshgirDbContext = gozareshgirDbContext;
}
public async Task<OperationResult> Handle(CreateOrEditCheckoutCommand request, CancellationToken cancellationToken)
{
switch (request.TypeOfCheckoutHandler)
{
case TypeOfCheckoutHandler.CreateInGroup:
return await Create(request.Year, request.Month, request.UserIdList);
break;
case TypeOfCheckoutHandler.SingleEdit:
case TypeOfCheckoutHandler.GroupEditing:
return await GroupOrSingleEditing(request.CheckoutIdList);
break;
}
return OperationResult.Failure("نوع متد انتخاب نشده است");
}
/// <summary>
/// ایجاد گروهی فیش حقوقی
/// </summary>
/// <param name="Year"></param>
/// <param name="Month"></param>
/// <param name="UserIdList"></param>
/// <returns></returns>
public async Task<OperationResult> Create(string? Year, string? Month, List<long>? UserIdList)
{
if (string.IsNullOrWhiteSpace(Month))
return OperationResult.Failure("ماه خالی است");
if (string.IsNullOrWhiteSpace(Year))
return OperationResult.Failure("سال خالی است");
if (UserIdList == null)
return OperationResult.Failure("هیچ موردی برای ایجاد انتخاب نشده اشت");
if (UserIdList.Count == 0)
return OperationResult.Failure("هیچ موردی برای ایجاد انتخاب نشده اشت");
var startDateGr = new DateTime();
var EndDateGr = new DateTime();
var persianStart = new PersianDateTime();
int year = 0;
int month = 0;
try
{
year = Convert.ToInt32(Year);
month = Convert.ToInt32(Month);
persianStart = new PersianDateTime(year, month, 1);
var startDateFa = $"{persianStart}";
startDateGr = startDateFa.ToGeorgianDateTime();
var endDateFa = startDateFa.FindeEndOfMonth();
EndDateGr = endDateFa.ToGeorgianDateTime();
}
catch (Exception)
{
return OperationResult<GetUserToGroupCreatingResponse>.Failure(
"خطا در ورود سال و ماه");
}
var totalDays = Convert.ToInt32((EndDateGr - startDateGr).TotalDays + 1);
var getAllSettings = await _salaryPaymentSettingRepository.GetAllSettings(UserIdList);
var get = await _taskSectionActivityRepository.GetTotalTimeSpentPerUserInRangeAsync(startDateGr, EndDateGr);
foreach (var user in getAllSettings)
{
var totalWorked = get.FirstOrDefault(x => x.UserId == user.UserId);
var totalTimeTotalMinutes = (int)totalWorked.TotalTime.TotalMinutes;
var res = await ComputeSalary(user.WorkingHoursListDto, totalTimeTotalMinutes, user.MonthlySalary, startDateGr, EndDateGr, user.HolidayWorking);
var createCheckout = new Checkout(startDateGr, EndDateGr, year, month, user.FullName, user.UserId,
res.MandatoryHours, totalTimeTotalMinutes,
totalDays, res.RemainingHours, user.MonthlySalary, res.MonthlySalaryPay, res.DeductionFromSalary);
await _checkoutRepository.CreateAsync(createCheckout);
}
await _unitOfWork.SaveChangesAsync();
return OperationResult.Success();
}
/// <summary>
/// متد ویراش گروهی و تکی
/// </summary>
/// <param name="CheckoutIdList"></param>
/// <param name="CheckoutId"></param>
/// <param name="TypeOfCheckoutHandler"></param>
/// <returns></returns>
public async Task<OperationResult> GroupOrSingleEditing(List<Guid>? CheckoutIdList)
{
if (CheckoutIdList == null)
return OperationResult.Failure("هیچ موردی برای ویرایش انتخاب نشده اشت");
if (CheckoutIdList.Count == 0)
return OperationResult.Failure("هیچ موردی برای ویرایش انتخاب نشده اشت");
var checkouts = await _checkoutRepository.GetCheckoutListByIds(CheckoutIdList);
if (!checkouts.Any())
return OperationResult.Failure("هیچ موردی برای ویرایش انتخاب نشده اشت");
var UserIdList = checkouts.Select(x => x.UserId).ToList();
var getAllSettings = await _salaryPaymentSettingRepository.GetAllSettings(UserIdList);
if (!getAllSettings.Any())
return OperationResult.Failure("تنظیمات ساعت و حقوق یافت نشد");
foreach (var checkoutId in CheckoutIdList)
{
var checkout = checkouts.FirstOrDefault(x => x.Id == checkoutId);
if (checkout == null)
return OperationResult.Failure("فیش مورد نظر یافت نشد");
var userSetting = getAllSettings.FirstOrDefault(x => x.UserId == checkout.UserId);
var get = await _taskSectionActivityRepository.GetTotalTimeSpentByUserInRangeAsync(checkout.UserId, checkout.CheckoutStartDate, checkout.CheckoutEndDate);
var totalTimeTotalMinutes = (int)get.TotalMinutes;
var totalDays = Convert.ToInt32((checkout.CheckoutEndDate - checkout.CheckoutStartDate).TotalDays + 1);
var res = await ComputeSalary(userSetting.WorkingHoursListDto, totalTimeTotalMinutes, userSetting.MonthlySalary, checkout.CheckoutStartDate, checkout.CheckoutEndDate, userSetting.HolidayWorking);
checkout.Edit(res.MandatoryHours, totalTimeTotalMinutes, totalDays, res.RemainingHours, userSetting.MonthlySalary, res.MonthlySalaryPay, res.DeductionFromSalary);
await _unitOfWork.SaveChangesAsync();
}
return OperationResult.Success();
}
/// <summary>
/// محاسبه حقوق
/// </summary>
/// <returns></returns>
public async Task<ComputeResultDto> ComputeSalary(List<WorkingHoursListDto> workingHoursListDto, int totalHoursWorked, double monthlySalaryDefined, DateTime start, DateTime end, bool holidayWorking)
{
var startDate = start.ToFarsi();
var startYear = Convert.ToInt32(startDate.Substring(0, 4));
var startMonth = Convert.ToInt32(startDate.Substring(5, 2));
var startDay = Convert.ToInt32(startDate.Substring(8, 2));
var persianStart = new PersianDateTime(startYear, startMonth, startDay);
var endDate = end.ToFarsi();
var endYear = Convert.ToInt32(endDate.Substring(0, 4));
var endMonth = Convert.ToInt32(endDate.Substring(5, 2));
var endDay = Convert.ToInt32(endDate.Substring(8, 2));
var persianEnd = new PersianDateTime(endYear, endMonth, endDay);
var holidays = await _gozareshgirDbContext.HolidayItems.Where(x=>x.Holidaydate >= start && x.Holidaydate <= end).ToListAsync();
int mandatoryHours = 0;
for (var currentDay = persianStart; currentDay <= persianEnd; currentDay = currentDay.AddDays(1))
{
var currentDayOfWeek = new DNTPersianUtils.Core.PersianDateTime(currentDay.Year, currentDay.Month, currentDay.Day);
var holidayDate = currentDay.ShamsiDate.ToGeorgianDateTime();
var day = (PersianDayOfWeek)currentDayOfWeek.WeekDayNumber!;
var getDaySetting = workingHoursListDto.FirstOrDefault(x => x.PersianDayOfWeek == day);
if (getDaySetting != null)
{
if (!holidayWorking && holidays.Any(x => x.Holidaydate == holidayDate))
{
}
else
{
mandatoryHours += (int)getDaySetting.ShiftDuration.TotalMinutes;
Console.WriteLine((int)getDaySetting.ShiftDuration.TotalMinutes + " " + currentDay + " - " + day);
}
}
}
//حقوق نهایی
var monthlySalaryPay = (totalHoursWorked * monthlySalaryDefined) / mandatoryHours;
// اگر اضافه کار داشت حقوق تعین شده به عنوان حقوق نهایی در نظر گرفته میشود
monthlySalaryPay = monthlySalaryPay > monthlySalaryDefined ? monthlySalaryDefined : monthlySalaryPay;
//حقوق کسر شده
var deductionFromSalary = monthlySalaryDefined - monthlySalaryPay;
//زمان باقی مانده
var remainingTime = totalHoursWorked - mandatoryHours;
var computeResult = new ComputeResultDto
{
MandatoryHours = mandatoryHours,
MonthlySalaryPay = monthlySalaryPay,
DeductionFromSalary = deductionFromSalary,
RemainingHours = remainingTime
};
Console.WriteLine(mandatoryHours);
return computeResult;
}
}
public record CreateOrEditCheckoutCommand(TypeOfCheckoutHandler TypeOfCheckoutHandler, string? Year, string? Month, List<long>? UserIdList, List<Guid>? CheckoutIdList) : IBaseCommand;
public record ComputeResultDto
{
/// <summary>
/// ساعات باقی مانده
/// کسر کار یا اضافه کار
/// </summary>
public int RemainingHours { get; set; }
/// <summary>
/// حقوق نهایی که به پرسنل داده می شود
/// </summary>
public double MonthlySalaryPay { get; set; }
/// <summary>
/// کسر از حقوق
/// </summary>
public double DeductionFromSalary { get; set; }
/// <summary>
/// ساعت موظفی
/// </summary>
public int MandatoryHours { get; set; }
}

View File

@@ -0,0 +1,130 @@
using GozareshgirProgramManager.Application._Common.Extensions;
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Application.Modules.Checkouts.Queries.GetUserToGropCreate;
using GozareshgirProgramManager.Domain._Common;
using Microsoft.EntityFrameworkCore;
namespace GozareshgirProgramManager.Application.Modules.Checkouts.Queries.GetCheckoutList;
public class GetCheckoutListQueryHandler : IBasePaginationQueryHandler<GetCheckoutListQuery, GetCheckoutListResponse>
{
private readonly IProgramManagerDbContext _programManagerDbContext;
public GetCheckoutListQueryHandler(IProgramManagerDbContext programManagerDbContext)
{
_programManagerDbContext = programManagerDbContext;
}
public async Task<OperationResult<PaginationResult<GetCheckoutListResponse>>> Handle(GetCheckoutListQuery request, CancellationToken cancellationToken)
{
var query = _programManagerDbContext.Checkouts.AsQueryable();
if (!string.IsNullOrWhiteSpace(request.Year))
{
var year = Convert.ToInt32(request.Year);
query = query.Where(x => x.Year == year);
}
if (!string.IsNullOrWhiteSpace(request.Month))
{
var month = Convert.ToInt32(request.Month);
query = query.Where(x => x.Month == month);
}
if (!string.IsNullOrWhiteSpace(request.FullName))
query = query.Where(x => x.FullName.Contains(request.FullName));
var res =await query.Select(x => new GetCheckoutListResponse()
{
CheckoutId = x.Id,
Year = x.Year,
Month = x.PersianMonthName,
FullName = x.FullName,
MandatoryHours = x.MandatoryHours,
TotalHoursWorked = x.TotalHoursWorked,
RemainingHours = x.RemainingHours,
MonthlySalaryDefined = x.MonthlySalaryDefined.ToMoney(),
DeductionFromSalary = x.DeductionFromSalary.ToMoney(),
MonthlySalaryPay = x.MonthlySalaryPay.ToMoney()
}).ApplyPagination(request.PageIndex,request.PageSize).ToListAsync(cancellationToken: cancellationToken);
var response = new PaginationResult<GetCheckoutListResponse>
{
List = res,
TotalCount = query.Count(),
};
return OperationResult<PaginationResult<GetCheckoutListResponse>>.Success(response);
}
}
public record GetCheckoutListQuery(string? Month, string? Year, string? FullName) : PaginationRequest, IBasePaginationQuery<GetCheckoutListResponse>;
public record GetCheckoutListResponse
{
/// <summary>
/// آی دی فیش حقوقی
/// </summary>
public Guid CheckoutId { get; set; }
/// <summary>
/// سال
/// </summary>
public int Year { get; set; }
/// <summary>
/// ماه
/// </summary>
public string Month { get; set; }
/// <summary>
/// نام کامل پرسنل
/// </summary>
public string FullName { get; set; }
/// <summary>
/// ساعت موظفی
/// </summary>
public int MandatoryHours { get; set; }
/// <summary>
/// مجموع ساعات کارکرد پرسنل
/// </summary>
public int TotalHoursWorked { get; set; }
/// <summary>
/// ساعات باقی مانده
/// کسر کار یا اضافه کار
/// </summary>
public int RemainingHours { get; set; }
/// <summary>
/// حقوق ماهانه
/// تعیین شده
/// </summary>
public string MonthlySalaryDefined { get; set; }
/// <summary>
/// کسر از حقوق
/// </summary>
public string DeductionFromSalary { get; set; }
/// <summary>
/// حقوق نهایی که به پرسنل داده می شود
/// </summary>
public string MonthlySalaryPay { get; set; }
}

View File

@@ -0,0 +1,174 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Application.Modules.SalaryPaymentSettings.Queries.GetUserListWhoHaveSettings;
using GozareshgirProgramManager.Domain._Common;
using GozareshgirProgramManager.Domain.CheckoutAgg.Enums;
using Microsoft.EntityFrameworkCore;
using PersianTools.Core;
namespace GozareshgirProgramManager.Application.Modules.Checkouts.Queries.GetUserToGropCreate;
/// <summary>
/// دریافت کاربران برای ایجاد گروهی فیش حقوقی با سال و ماه
/// </summary>
public class GetUserToGroupCreatingQueryHandler : IBaseQueryHandler<GetUserToGroupCreatingQuery, GetUserToGroupCreatingResponse>
{
private readonly IProgramManagerDbContext _context;
public GetUserToGroupCreatingQueryHandler(IProgramManagerDbContext context)
{
_context = context;
}
public async Task<OperationResult<GetUserToGroupCreatingResponse>> Handle(GetUserToGroupCreatingQuery request, CancellationToken cancellationToken)
{
//سال و ماه انتخاب شده از فرانت
var selectedDate = new DateTime();
try
{
int year = Convert.ToInt32(request.Year);
int month = Convert.ToInt32(request.Month);
selectedDate = ($"{new PersianDateTime(year,month,1)}").ToGeorgianDateTime();
}
catch (Exception)
{
return OperationResult<GetUserToGroupCreatingResponse>.Failure(
"خطا در ورود سال و ماه");
}
//آخرین تاریخ مجاز برای ایجاد فیش
var lastMonth = ($"{DateTime.Now.ToFarsi().Substring(0, 8)}01").ToGeorgianDateTime().AddDays(-1);
if (selectedDate > lastMonth)
return OperationResult<GetUserToGroupCreatingResponse>.Failure(
"ایجاد فیش فقط برای ماه های گذشته امکان پذیر است");
var lastMonthStart = lastMonth;
var lastMonthEnd = lastMonth;
var query =
await (from u in _context.Users
// LEFT JOIN
// تنظیمات حقوق
join s in _context.SalaryPaymentSettings
on u.Id equals s.UserId into sJoin
from s in sJoin.DefaultIfEmpty()
// LEFT JOIN
//فیش
join ch in _context.Checkouts
.Where(x => x.CheckoutStartDate < lastMonthStart
&& x.CheckoutEndDate >= lastMonthStart)
on u.Id equals ch.UserId into chJoin
from ch in chJoin.DefaultIfEmpty()
group new { s, ch } by new { u.Id, u.FullName } into g
select new GetUserWhoHaveSettingsAndCheckoutDto
{
UserId = g.Key.Id,
FullName = g.Key.FullName,
HasSalarySettings = g.Any(x => x.s != null),
HasCheckout = g.Any(x => x.ch != null)
})
.ToListAsync(cancellationToken);
var responseList = query.Select(x =>
{
bool validToCreate = x.HasSalarySettings && !x.HasCheckout;
string message = "آماده تنظیم";
CreateCheckoutStatus createCheckoutStatus = CreateCheckoutStatus.ReadyToCreate;
if (x.HasCheckout)
{
message = "موجود است";
createCheckoutStatus = CreateCheckoutStatus.AlreadyCreated;
}
if (!x.HasSalarySettings)
{
message = "فاقد تنظیمات";
createCheckoutStatus = CreateCheckoutStatus.NotSetSalaryPaymentSettings;
}
return new GetUserToGroupCreatingDto
{
UserId = x.UserId,
FullName = x.FullName,
IsValidToCreate = validToCreate,
StatusMessage = message,
CreateCheckoutStatus = createCheckoutStatus
};
}).OrderByDescending(x=>x.IsValidToCreate).ToList();
var response = new GetUserToGroupCreatingResponse(responseList);
return OperationResult<GetUserToGroupCreatingResponse>.Success(response);
}
}
public record GetUserToGroupCreatingQuery(string Year, string Month) : IBaseQuery<GetUserToGroupCreatingResponse>;
public record GetUserToGroupCreatingResponse(List<GetUserToGroupCreatingDto> GetUserToGroupCreatingDtoList);
public record GetUserToGroupCreatingDto
{
/// <summary>
/// آی دی کاربر
/// </summary>
public long UserId { get; set; }
/// <summary>
/// نام کامل پرسنل
/// </summary>
public string FullName { get; set; }
/// <summary>
/// پیام وضعیت ایجاد فیش
/// </summary>
public string StatusMessage { get; set; }
/// <summary>
/// آیا مجاز به ایجاد فیش می باشد
/// </summary>
public bool IsValidToCreate { get; set; }
public CreateCheckoutStatus CreateCheckoutStatus { get; set; }
}
public record GetUserWhoHaveSettingsAndCheckoutDto
{
/// <summary>
/// آی دی کاربر
/// </summary>
public long UserId { get; set; }
/// <summary>
/// نام کامل پرسنل
/// </summary>
public string FullName { get; set; }
/// <summary>
/// داشتن تنظیمات
/// </summary>
public bool HasSalarySettings { get; set; }
public bool HasCheckout { get; set; }
}

View File

@@ -0,0 +1,13 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
namespace GozareshgirProgramManager.Application.Modules.Projects.Commands.AddPhaseToProject;
/// <summary>
/// Command to add a phase to an existing project
/// </summary>
public record AddPhaseToProjectCommand(
Guid ProjectId,
string Name,
string? Description = null,
int OrderIndex = 0
) : IBaseCommand;

View File

@@ -0,0 +1,47 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Domain._Common;
using GozareshgirProgramManager.Domain.ProjectAgg.Repositories;
using MediatR;
namespace GozareshgirProgramManager.Application.Modules.Projects.Commands.AddPhaseToProject;
public class AddPhaseToProjectCommandHandler : IRequestHandler<AddPhaseToProjectCommand, OperationResult>
{
private readonly IProjectRepository _projectRepository;
private readonly IUnitOfWork _unitOfWork;
public AddPhaseToProjectCommandHandler(
IProjectRepository projectRepository,
IUnitOfWork unitOfWork)
{
_projectRepository = projectRepository;
_unitOfWork = unitOfWork;
}
public async Task<OperationResult> Handle(AddPhaseToProjectCommand request, CancellationToken cancellationToken)
{
try
{
// Get project
var project = await _projectRepository.GetByIdAsync(request.ProjectId);
if (project == null)
{
return OperationResult.NotFound("پروژه یافت نشد");
}
// Add phase
var phase = project.AddPhase(request.Name, request.Description);
phase.SetOrderIndex(request.OrderIndex);
// Save changes
await _unitOfWork.SaveChangesAsync(cancellationToken);
return OperationResult.Success();
}
catch (Exception ex)
{
return OperationResult.Failure($"خطا در افزودن فاز: {ex.Message}");
}
}
}

View File

@@ -0,0 +1,16 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Domain.ProjectAgg.Enums;
namespace GozareshgirProgramManager.Application.Modules.Projects.Commands.AddTaskToPhase;
/// <summary>
/// Command to add a task to an existing phase
/// </summary>
public record AddTaskToPhaseCommand(
Guid PhaseId,
string Name,
string? Description = null,
TaskPriority Priority = TaskPriority.Medium,
int OrderIndex = 0,
DateTime? DueDate = null
) : IBaseCommand;

View File

@@ -0,0 +1,53 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Domain._Common;
using GozareshgirProgramManager.Domain.ProjectAgg.Repositories;
using MediatR;
namespace GozareshgirProgramManager.Application.Modules.Projects.Commands.AddTaskToPhase;
public class AddTaskToPhaseCommandHandler : IRequestHandler<AddTaskToPhaseCommand, OperationResult>
{
private readonly IProjectPhaseRepository _phaseRepository;
private readonly IUnitOfWork _unitOfWork;
public AddTaskToPhaseCommandHandler(
IProjectPhaseRepository phaseRepository,
IUnitOfWork unitOfWork)
{
_phaseRepository = phaseRepository;
_unitOfWork = unitOfWork;
}
public async Task<OperationResult> Handle(AddTaskToPhaseCommand request, CancellationToken cancellationToken)
{
try
{
// Get phase
var phase = await _phaseRepository.GetByIdAsync(request.PhaseId);
if (phase == null)
{
return OperationResult.NotFound("فاز یافت نشد");
}
// Add task
var task = phase.AddTask(request.Name, request.Description);
task.SetPriority(request.Priority);
task.SetOrderIndex(request.OrderIndex);
if (request.DueDate.HasValue)
{
task.SetDates(dueDate: request.DueDate);
}
// Save changes
await _unitOfWork.SaveChangesAsync(cancellationToken);
return OperationResult.Success();
}
catch (Exception ex)
{
return OperationResult.Failure($"خطا در افزودن تسک: {ex.Message}");
}
}
}

View File

@@ -0,0 +1,18 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Domain.ProjectAgg.Enums;
namespace GozareshgirProgramManager.Application.Modules.Projects.Commands.AssignProject;
public class AssignProjectCommand:IBaseCommand
{
public List<AssignProjectCommandItem> Items { get; set; }
public Guid Id { get; set; }
public ProjectHierarchyLevel Level { get; set; }
public bool CascadeToChildren { get; set; }
}
public class AssignProjectCommandItem
{
public long UserId { get; set; }
public Guid SkillId { get; set; }
}

View File

@@ -0,0 +1,274 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Domain._Common;
using GozareshgirProgramManager.Domain.ProjectAgg.Entities;
using GozareshgirProgramManager.Domain.ProjectAgg.Enums;
using GozareshgirProgramManager.Domain.ProjectAgg.Repositories;
using GozareshgirProgramManager.Domain.SkillAgg.Repositories;
namespace GozareshgirProgramManager.Application.Modules.Projects.Commands.AssignProject;
public class AssignProjectCommandHandler:IBaseCommandHandler<AssignProjectCommand>
{
private readonly IUnitOfWork _unitOfWork;
private readonly IProjectRepository _projectRepository;
private readonly IProjectPhaseRepository _projectPhaseRepository;
private readonly IProjectTaskRepository _projectTaskRepository;
private readonly ISkillRepository _skillRepository;
private readonly IPhaseSectionRepository _phaseSectionRepository;
private readonly IProjectSectionRepository _projectSectionRepository;
private readonly ITaskSectionRepository _taskSectionRepository;
public AssignProjectCommandHandler(
IProjectRepository projectRepository,
IProjectPhaseRepository projectPhaseRepository,
IProjectTaskRepository projectTaskRepository,
IUnitOfWork unitOfWork,
ISkillRepository skillRepository,
IPhaseSectionRepository phaseSectionRepository,
IProjectSectionRepository projectSectionRepository,
ITaskSectionRepository taskSectionRepository)
{
_projectRepository = projectRepository;
_projectPhaseRepository = projectPhaseRepository;
_projectTaskRepository = projectTaskRepository;
_unitOfWork = unitOfWork;
_skillRepository = skillRepository;
_phaseSectionRepository = phaseSectionRepository;
_projectSectionRepository = projectSectionRepository;
_taskSectionRepository = taskSectionRepository;
}
public async Task<OperationResult> Handle(AssignProjectCommand request, CancellationToken cancellationToken)
{
switch (request.Level)
{
case ProjectHierarchyLevel.Project:
return await AssignProject(request);
case ProjectHierarchyLevel.Phase:
return await AssignProjectPhase(request);
case ProjectHierarchyLevel.Task:
return await AssignProjectTask(request);
default:
return OperationResult.Failure("سطح پروژه نامعتبر است");
}
}
private async Task<OperationResult> AssignProject(AssignProjectCommand request)
{
var project = await _projectRepository.GetWithFullHierarchyAsync(request.Id);
if (project is null)
{
return OperationResult.NotFound("پروژه یافت نشد");
}
// تخصیص در سطح پروژه
foreach (var item in request.Items)
{
var skill = await _skillRepository.GetByIdAsync(item.SkillId);
if (skill is null)
{
return OperationResult.NotFound($"مهارت با شناسه {item.SkillId} یافت نشد");
}
// بررسی و به‌روزرسانی یا اضافه کردن ProjectSection
var existingSection = project.ProjectSections.FirstOrDefault(s => s.SkillId == item.SkillId);
if (existingSection != null)
{
// اگر وجود داشت، فقط userId را به‌روزرسانی کن
existingSection.UpdateUser(item.UserId);
}
else
{
// اگر وجود نداشت، اضافه کن
var newSection = new ProjectSection(project.Id, item.UserId, item.SkillId);
await _projectSectionRepository.CreateAsync(newSection);
}
}
// حالا برای تمام فازها و تسک‌ها cascade کن
foreach (var phase in project.Phases)
{
// اگر CascadeToChildren true است یا فاز override ندارد
if (request.CascadeToChildren || !phase.HasAssignmentOverride)
{
// برای phase هم باید sectionها را به‌روزرسانی کنیم
foreach (var item in request.Items)
{
var existingSection = phase.PhaseSections.FirstOrDefault(s => s.SkillId == item.SkillId);
if (existingSection != null)
{
existingSection.Update(item.UserId, item.SkillId);
}
else
{
var newPhaseSection = new PhaseSection(phase.Id, item.UserId, item.SkillId);
await _phaseSectionRepository.CreateAsync(newPhaseSection);
}
}
foreach (var task in phase.Tasks)
{
// اگر CascadeToChildren true است یا تسک override ندارد
if (request.CascadeToChildren || !task.HasAssignmentOverride)
{
foreach (var item in request.Items)
{
var section = task.Sections.FirstOrDefault(s => s.SkillId == item.SkillId);
if (section != null)
{
// استفاده از TransferToUser
if (section.CurrentAssignedUserId != item.UserId)
{
if (section.CurrentAssignedUserId > 0)
{
section.TransferToUser(section.CurrentAssignedUserId, item.UserId);
}
else
{
section.AssignToUser(item.UserId);
}
}
}
else
{
var newTaskSection = new TaskSection(task.Id, item.SkillId, item.UserId);
await _taskSectionRepository.CreateAsync(newTaskSection);
}
}
}
}
}
}
await _unitOfWork.SaveChangesAsync();
return OperationResult.Success();
}
private async Task<OperationResult> AssignProjectPhase(AssignProjectCommand request)
{
var phase = await _projectPhaseRepository.GetWithTasksAsync(request.Id);
if (phase is null)
{
return OperationResult.NotFound("فاز پروژه یافت نشد");
}
// تخصیص در سطح فاز
foreach (var item in request.Items)
{
var skill = await _skillRepository.GetByIdAsync(item.SkillId);
if (skill is null)
{
return OperationResult.NotFound($"مهارت با شناسه {item.SkillId} یافت نشد");
}
}
// علامت‌گذاری که این فاز نسبت به parent متمایز است
phase.MarkAsOverridden();
// به‌روزرسانی یا اضافه کردن PhaseSection
foreach (var item in request.Items)
{
var existingSection = phase.PhaseSections.FirstOrDefault(s => s.SkillId == item.SkillId);
if (existingSection != null)
{
// اگر وجود داشت، فقط userId را به‌روزرسانی کن
existingSection.Update(item.UserId, item.SkillId);
}
else
{
// اگر وجود نداشت، اضافه کن
var newPhaseSection = new PhaseSection(phase.Id, item.UserId, item.SkillId);
await _phaseSectionRepository.CreateAsync(newPhaseSection);
}
}
// cascade به تمام تسک‌ها
foreach (var task in phase.Tasks)
{
// اگر CascadeToChildren true است یا تسک override ندارد
if (request.CascadeToChildren || !task.HasAssignmentOverride)
{
foreach (var item in request.Items)
{
var section = task.Sections.FirstOrDefault(s => s.SkillId == item.SkillId);
if (section != null)
{
// استفاده از TransferToUser
if (section.CurrentAssignedUserId != item.UserId)
{
if (section.CurrentAssignedUserId > 0)
{
section.TransferToUser(section.CurrentAssignedUserId, item.UserId);
}
else
{
section.AssignToUser(item.UserId);
}
}
}
else
{
var newTaskSection = new TaskSection(task.Id, item.SkillId, item.UserId);
await _taskSectionRepository.CreateAsync(newTaskSection);
}
}
}
}
await _unitOfWork.SaveChangesAsync();
return OperationResult.Success();
}
private async Task<OperationResult> AssignProjectTask(AssignProjectCommand request)
{
var task = await _projectTaskRepository.GetWithSectionsAsync(request.Id);
if (task is null)
{
return OperationResult.NotFound("تسک یافت نشد");
}
foreach (var item in request.Items)
{
var skill = await _skillRepository.GetByIdAsync(item.SkillId);
if (skill is null)
{
return OperationResult.NotFound($"مهارت با شناسه {item.SkillId} یافت نشد");
}
}
// علامت‌گذاری که این تسک نسبت به parent متمایز است
task.MarkAsOverridden();
// به‌روزرسانی یا اضافه کردن TaskSection
foreach (var item in request.Items)
{
var section = task.Sections.FirstOrDefault(s => s.SkillId == item.SkillId);
if (section != null)
{
// اگر وجود داشت، از TransferToUser استفاده کن
if (section.CurrentAssignedUserId != item.UserId)
{
if (section.CurrentAssignedUserId > 0)
{
section.TransferToUser(section.CurrentAssignedUserId, item.UserId);
}
else
{
section.AssignToUser(item.UserId);
}
}
}
else
{
// اگر وجود نداشت، اضافه کن
var newTaskSection = new TaskSection(task.Id, item.SkillId, item.UserId);
await _taskSectionRepository.CreateAsync(newTaskSection);
}
}
await _unitOfWork.SaveChangesAsync();
return OperationResult.Success();
}
}

View File

@@ -0,0 +1,39 @@
using FluentValidation;
namespace GozareshgirProgramManager.Application.Modules.Projects.Commands.AssignProject;
public class AssignProjectCommandValidator : AbstractValidator<AssignProjectCommand>
{
public AssignProjectCommandValidator()
{
RuleFor(x => x.Id)
.NotEmpty()
.NotNull()
.WithMessage("شناسه پروژه نمیتواند خالی باشد");
RuleFor(x => x.CascadeToChildren)
.NotNull()
.WithMessage("مقدار CascadeToChildren نمیتواند خالی باشد");
RuleForEach(x => x.Items)
.SetValidator(new AssignProjectItemValidator());
}
}
public class AssignProjectItemValidator : AbstractValidator<AssignProjectCommandItem>
{
public AssignProjectItemValidator()
{
RuleFor(x => x.UserId)
.NotEmpty()
.NotNull()
.GreaterThan(0)
.WithMessage("شناسه کاربر نمیتواند خالی باشد");
RuleFor(x => x.SkillId)
.NotEmpty()
.NotNull()
.WithMessage("شناسه مهارت نمیتواند خالی باشد");
}
}

View File

@@ -0,0 +1,101 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Domain._Common;
using GozareshgirProgramManager.Domain._Common.Exceptions;
using GozareshgirProgramManager.Domain.ProjectAgg.Enums;
using GozareshgirProgramManager.Domain.ProjectAgg.Repositories;
namespace GozareshgirProgramManager.Application.Modules.Projects.Commands.ChangeStatusSection;
public record ChangeStatusSectionCommand(Guid SectionId, TaskSectionStatus Status) : IBaseCommand;
public class ChangeStatusSectionCommandHandler : IBaseCommandHandler<ChangeStatusSectionCommand>
{
private readonly IUnitOfWork _unitOfWork;
private readonly ITaskSectionRepository _taskSectionRepository;
private readonly IAuthHelper _authHelper;
public ChangeStatusSectionCommandHandler(ITaskSectionRepository taskSectionRepository,
IUnitOfWork unitOfWork, IAuthHelper authHelper)
{
_taskSectionRepository = taskSectionRepository;
_unitOfWork = unitOfWork;
_authHelper = authHelper;
}
public async Task<OperationResult> Handle(ChangeStatusSectionCommand request, CancellationToken cancellationToken)
{
// استفاده از متد مخصوص که Activities رو load می‌کنه
var section = await _taskSectionRepository.GetByIdWithActivitiesAsync(request.SectionId, cancellationToken);
if (section == null)
return OperationResult.NotFound("بخش مورد نظر یافت نشد");
if (section.Status == request.Status)
return OperationResult.Success();
long currentUser = _authHelper.GetCurrentUserId()
?? throw new UnAuthorizedException("کاربر احراز هویت نشده است");
// Validate state transitions
var validationResult = ValidateStateTransition(section.Status, request.Status);
if (!validationResult.IsSuccess)
return validationResult;
// Handle state machine logic
if (section.Status == TaskSectionStatus.InProgress)
{
// Coming FROM InProgress: Stop the active activity
section.StopWork(currentUser, request.Status);
}
else if (request.Status == TaskSectionStatus.InProgress)
{
// Going TO InProgress: Start work and create activity
section.StartWork(currentUser);
}
else
{
// All other transitions: Just update status
section.UpdateStatus(request.Status);
}
await _unitOfWork.SaveChangesAsync(cancellationToken);
return OperationResult.Success();
}
/// <summary>
/// Validates state transitions based on business rules:
/// - ReadyToStart: شروع نشده - Initial state only, cannot return to it once left
/// - InProgress: درحال انجام - Can transition from ReadyToStart, can go to Incomplete or Completed
/// - Incomplete: نیمه کاره - Can come from InProgress or other states
/// - Completed: اتمام رسیده - Can come from InProgress or other states
/// </summary>
private OperationResult ValidateStateTransition(TaskSectionStatus currentStatus, TaskSectionStatus targetStatus)
{
// Cannot transition to ReadyToStart once the section has been started
if (targetStatus == TaskSectionStatus.ReadyToStart)
return OperationResult.ValidationError("بخش نمی‌تواند به وضعیت 'آماده برای شروع' تغییر کند");
// From ReadyToStart, can only go to InProgress
if (currentStatus == TaskSectionStatus.ReadyToStart && targetStatus != TaskSectionStatus.InProgress)
return OperationResult.ValidationError("از وضعیت 'آماده برای شروع' فقط می‌توان به 'درحال انجام' رفت");
// Valid transitions matrix
var validTransitions = new Dictionary<TaskSectionStatus, List<TaskSectionStatus>>
{
{ TaskSectionStatus.ReadyToStart, new List<TaskSectionStatus> { TaskSectionStatus.InProgress } },
{ TaskSectionStatus.InProgress, new List<TaskSectionStatus> { TaskSectionStatus.Incomplete, TaskSectionStatus.Completed } },
{ TaskSectionStatus.Incomplete, new List<TaskSectionStatus> { TaskSectionStatus.InProgress, TaskSectionStatus.Completed } },
{ TaskSectionStatus.Completed, new List<TaskSectionStatus> { TaskSectionStatus.InProgress, TaskSectionStatus.Incomplete } }, // Can return to InProgress or Incomplete
{ TaskSectionStatus.NotAssigned, new List<TaskSectionStatus> { TaskSectionStatus.InProgress, TaskSectionStatus.ReadyToStart } }
};
if (!validTransitions.TryGetValue(currentStatus, out var allowedTargets))
return OperationResult.ValidationError($"وضعیت فعلی '{currentStatus}' نامعتبر است");
if (!allowedTargets.Contains(targetStatus))
return OperationResult.ValidationError(
$"نمی‌توان از وضعیت '{currentStatus}' به '{targetStatus}' رفت");
return OperationResult.Success();
}
}

View File

@@ -0,0 +1,20 @@
using FluentValidation;
namespace GozareshgirProgramManager.Application.Modules.Projects.Commands.ChangeStatusSection;
public class ChangeStatusSectionCommandValidator:AbstractValidator<ChangeStatusSectionCommand>
{
public ChangeStatusSectionCommandValidator()
{
RuleFor(c => c.SectionId)
.NotEmpty()
.NotNull()
.WithMessage("شناسه بخش نمی‌تواند خالی باشد");
RuleFor(c => c.Status)
.IsInEnum()
.NotEmpty()
.NotNull()
.WithMessage("وضعیت بخش نامعتبر است");
}
}

View File

@@ -0,0 +1,7 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Domain.ProjectAgg.Enums;
namespace GozareshgirProgramManager.Application.Modules.Projects.Commands.CreateProject;
public record CreateProjectCommand(string Name,ProjectHierarchyLevel Level,
Guid? ParentId):IBaseCommand;

View File

@@ -0,0 +1,82 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Domain._Common;
using GozareshgirProgramManager.Domain._Common.Exceptions;
using GozareshgirProgramManager.Domain.ProjectAgg.Entities;
using GozareshgirProgramManager.Domain.ProjectAgg.Enums;
using GozareshgirProgramManager.Domain.ProjectAgg.Repositories;
namespace GozareshgirProgramManager.Application.Modules.Projects.Commands.CreateProject;
public class CreateProjectCommandHandler : IBaseCommandHandler<CreateProjectCommand>
{
private readonly IProjectRepository _projectRepository;
private readonly IProjectPhaseRepository _projectPhaseRepository;
private readonly IProjectTaskRepository _projectTaskRepository;
private readonly IUnitOfWork _unitOfWork;
public CreateProjectCommandHandler(IProjectRepository projectRepository, IUnitOfWork unitOfWork, IProjectTaskRepository projectTaskRepository, IProjectPhaseRepository projectPhaseRepository)
{
_projectRepository = projectRepository;
_unitOfWork = unitOfWork;
_projectTaskRepository = projectTaskRepository;
_projectPhaseRepository = projectPhaseRepository;
}
public async Task<OperationResult> Handle(CreateProjectCommand request, CancellationToken cancellationToken)
{
switch (request.Level)
{
case ProjectHierarchyLevel.Project:
await CreateProject(request);
break;
case ProjectHierarchyLevel.Phase:
await CreateProjectPhase(request);
break;
case ProjectHierarchyLevel.Task:
await CreateProjectTask(request);
break;
default:
return OperationResult.Failure("سطح پروژه نامعتبر است");
}
await _unitOfWork.SaveChangesAsync(cancellationToken);
return OperationResult.Success();
}
private async Task CreateProject(CreateProjectCommand request)
{
var project = new Project(request.Name);
await _projectRepository.CreateAsync(project);
}
private async Task CreateProjectPhase(CreateProjectCommand request)
{
if (!request.ParentId.HasValue)
throw new BadRequestException("برای ایجاد فاز، شناسه پروژه الزامی است");
if(!_projectRepository.Exists(x=>x.Id == request.ParentId.Value))
{
throw new BadRequestException("والد پروژه یافت نشد");
}
var projectPhase = new ProjectPhase(request.Name, request.ParentId.Value);
await _projectPhaseRepository.CreateAsync(projectPhase);
}
private async Task CreateProjectTask(CreateProjectCommand request)
{
if (!request.ParentId.HasValue)
throw new BadRequestException("برای ایجاد تسک، شناسه فاز الزامی است");
if(!_projectPhaseRepository.Exists(x=>x.Id == request.ParentId.Value))
{
throw new BadRequestException("والد پروژه یافت نشد");
}
var projectTask = new ProjectTask(request.Name, request.ParentId.Value);
await _projectTaskRepository.CreateAsync(projectTask);
}
}

View File

@@ -0,0 +1,26 @@
using FluentValidation;
using GozareshgirProgramManager.Domain.ProjectAgg.Enums;
namespace GozareshgirProgramManager.Application.Modules.Projects.Commands.CreateProject;
public class CreateProjectCommandValidator:AbstractValidator<CreateProjectCommand>
{
public CreateProjectCommandValidator()
{
RuleFor(x => x.Name)
.NotEmpty()
.NotNull()
.WithMessage("نام نمیتواند خالی باشد");
RuleFor(y => y.Level)
.NotNull()
.IsInEnum();
When(x=>x.Level>ProjectHierarchyLevel.Project,()=>
{
RuleFor(x => x.ParentId)
.NotNull()
.NotEmpty()
.WithMessage("شناسه والد نمیتواند خالی باشد");
});
}
}

View File

@@ -0,0 +1,13 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
namespace GozareshgirProgramManager.Application.Modules.Projects.Commands.CreateProjectWithHierarchy;
/// <summary>
/// Command to create a new project with the new hierarchy structure
/// </summary>
public record CreateProjectWithHierarchyCommand(
string Name,
string? Description = null,
DateTime? PlannedStartDate = null,
DateTime? PlannedEndDate = null
) : IBaseCommand;

View File

@@ -0,0 +1,49 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Domain._Common;
using GozareshgirProgramManager.Domain.ProjectAgg.Entities;
using GozareshgirProgramManager.Domain.ProjectAgg.Repositories;
using MediatR;
namespace GozareshgirProgramManager.Application.Modules.Projects.Commands.CreateProjectWithHierarchy;
public class CreateProjectWithHierarchyCommandHandler : IRequestHandler<CreateProjectWithHierarchyCommand, OperationResult>
{
private readonly IProjectRepository _projectRepository;
private readonly IUnitOfWork _unitOfWork;
public CreateProjectWithHierarchyCommandHandler(
IProjectRepository projectRepository,
IUnitOfWork unitOfWork)
{
_projectRepository = projectRepository;
_unitOfWork = unitOfWork;
}
public async Task<OperationResult> Handle(CreateProjectWithHierarchyCommand request, CancellationToken cancellationToken)
{
try
{
// Create new project
var project = new Project(request.Name, request.Description);
// Set planned dates if provided
if (request.PlannedStartDate.HasValue || request.PlannedEndDate.HasValue)
{
project.SetPlannedDates(request.PlannedStartDate, request.PlannedEndDate);
}
// Add to repository
await _projectRepository.CreateAsync(project);
// Save changes
await _unitOfWork.SaveChangesAsync(cancellationToken);
return OperationResult.Success();
}
catch (Exception ex)
{
return OperationResult.Failure($"خطا در ایجاد پروژه: {ex.Message}");
}
}
}

View File

@@ -0,0 +1,21 @@
using FluentValidation;
namespace GozareshgirProgramManager.Application.Modules.Projects.Commands.CreateProjectWithHierarchy;
public class CreateProjectWithHierarchyCommandValidator : AbstractValidator<CreateProjectWithHierarchyCommand>
{
public CreateProjectWithHierarchyCommandValidator()
{
RuleFor(x => x.Name)
.NotEmpty().WithMessage("نام پروژه نمی‌تواند خالی باشد")
.MaximumLength(200).WithMessage("نام پروژه نمی‌تواند بیش از 200 کاراکتر باشد");
RuleFor(x => x.Description)
.MaximumLength(1000).WithMessage("توضیحات نمی‌تواند بیش از 1000 کاراکتر باشد")
.When(x => !string.IsNullOrEmpty(x.Description));
RuleFor(x => x)
.Must(x => x.PlannedStartDate == null || x.PlannedEndDate == null || x.PlannedStartDate <= x.PlannedEndDate)
.WithMessage("تاریخ شروع برنامه‌ریزی شده نمی‌تواند بعد از تاریخ پایان برنامه‌ریزی شده باشد");
}
}

View File

@@ -0,0 +1,91 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Domain._Common;
using GozareshgirProgramManager.Domain._Common.Exceptions;
using GozareshgirProgramManager.Domain.ProjectAgg.Enums;
using GozareshgirProgramManager.Domain.ProjectAgg.Repositories;
namespace GozareshgirProgramManager.Application.Modules.Projects.Commands.DeleteProject;
public record DeleteProjectCommand(Guid Id,ProjectHierarchyLevel Level) : IBaseCommand;
public class DeleteProjectCommandHandler : IBaseCommandHandler<DeleteProjectCommand>
{
private readonly IUnitOfWork _unitOfWork;
private readonly IProjectRepository _projectRepository;
private readonly IProjectPhaseRepository _projectPhaseRepository;
private readonly IProjectTaskRepository _projectTaskRepository;
public DeleteProjectCommandHandler(
IUnitOfWork unitOfWork,
IProjectRepository projectRepository,
IProjectPhaseRepository projectPhaseRepository,
IProjectTaskRepository projectTaskRepository)
{
_unitOfWork = unitOfWork;
_projectRepository = projectRepository;
_projectPhaseRepository = projectPhaseRepository;
_projectTaskRepository = projectTaskRepository;
}
public async Task<OperationResult> Handle(DeleteProjectCommand request, CancellationToken cancellationToken)
{
switch (request.Level)
{
case ProjectHierarchyLevel.Project:
await DeleteProject(request.Id);
break;
case ProjectHierarchyLevel.Phase:
await DeleteProjectPhase(request.Id);
break;
case ProjectHierarchyLevel.Task:
await DeleteProjectTask(request.Id);
break;
default:
return OperationResult.Failure("سطح پروژه نامعتبر است");
}
await _unitOfWork.SaveChangesAsync(cancellationToken);
return OperationResult.Success();
}
private async Task DeleteProject(Guid projectId)
{
var projectWithPhases = await _projectRepository.GetWithFullHierarchyAsync(projectId);
if (projectWithPhases == null)
throw new NotFoundException("پروژه یافت نشد");
// بررسی اینکه پروژه فاز یا زیرمجموعه دارد یا نه
if (projectWithPhases.Phases != null && projectWithPhases.Phases.Any())
throw new BadRequestException("نمی‌توان پروژه‌ای را حذف کرد که دارای فاز است. ابتدا تمام فازها را حذف کنید.");
_projectRepository.Remove(projectWithPhases);
}
private async Task DeleteProjectPhase(Guid phaseId)
{
var phase = await _projectPhaseRepository.GetByIdAsync(phaseId);
if (phase == null)
throw new NotFoundException("فاز پروژه یافت نشد");
// بررسی اینکه فاز تسک یا زیرمجموعه دارد یا نه
var phaseWithTasks = await _projectPhaseRepository.GetWithTasksAsync(phaseId);
if (phaseWithTasks?.Tasks != null && phaseWithTasks.Tasks.Any())
throw new InvalidOperationException("نمی‌توان فازی را حذف کرد که دارای تسک است. ابتدا تمام تسک‌ها را حذف کنید.");
_projectPhaseRepository.Remove(phase);
}
private async Task DeleteProjectTask(Guid taskId)
{
var task = await _projectTaskRepository.GetByIdAsync(taskId);
if (task == null)
throw new NotFoundException("تسک یافت نشد");
// حذف خود تسک
_projectTaskRepository.Remove(task);
}
}

View File

@@ -0,0 +1,19 @@
using FluentValidation;
namespace GozareshgirProgramManager.Application.Modules.Projects.Commands.DeleteProject;
public class DeleteProjectCommandValidator:AbstractValidator<DeleteProjectCommand>
{
public DeleteProjectCommandValidator()
{
RuleFor(x=>x.Id)
.NotEmpty()
.NotNull()
.WithMessage("شناسه پروژه نمی‌تواند خالی باشد.");
RuleFor(x=>x.Level)
.IsInEnum()
.NotNull()
.WithMessage("سطح حذف پروژه نامعتبر است.");
}
}

View File

@@ -0,0 +1,80 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Domain._Common;
using GozareshgirProgramManager.Domain._Common.Exceptions;
using GozareshgirProgramManager.Domain.ProjectAgg.Enums;
using GozareshgirProgramManager.Domain.ProjectAgg.Repositories;
namespace GozareshgirProgramManager.Application.Modules.Projects.Commands.EditProject;
public record EditProjectCommand(string Name, Guid Id, ProjectHierarchyLevel Level): IBaseCommand;
public class EditProjectCommandHandler: IBaseCommandHandler<EditProjectCommand>
{
private readonly IProjectRepository _projectRepository;
private readonly IProjectPhaseRepository _projectPhaseRepository;
private readonly IProjectTaskRepository _projectTaskRepository;
private readonly IUnitOfWork _unitOfWork;
public EditProjectCommandHandler(
IProjectRepository projectRepository,
IProjectPhaseRepository projectPhaseRepository,
IProjectTaskRepository projectTaskRepository,
IUnitOfWork unitOfWork)
{
_projectRepository = projectRepository;
_projectPhaseRepository = projectPhaseRepository;
_projectTaskRepository = projectTaskRepository;
_unitOfWork = unitOfWork;
}
public async Task<OperationResult> Handle(EditProjectCommand request, CancellationToken cancellationToken)
{
switch (request.Level)
{
case ProjectHierarchyLevel.Project:
await EditProject(request);
break;
case ProjectHierarchyLevel.Phase:
await EditProjectPhase(request);
break;
case ProjectHierarchyLevel.Task:
await EditProjectTask(request);
break;
default:
return OperationResult.Failure("سطح پروژه نامعتبر است");
}
await _unitOfWork.SaveChangesAsync(cancellationToken);
return OperationResult.Success();
}
private async Task EditProject(EditProjectCommand request)
{
var project = await _projectRepository.GetByIdAsync(request.Id);
if (project == null)
throw new NotFoundException("پروژه یافت نشد");
project.UpdateName(request.Name);
}
private async Task EditProjectPhase(EditProjectCommand request)
{
var phase = await _projectPhaseRepository.GetByIdAsync(request.Id);
if (phase == null)
throw new NotFoundException("فاز پروژه یافت نشد");
phase.UpdateName(request.Name);
}
private async Task EditProjectTask(EditProjectCommand request)
{
var task = await _projectTaskRepository.GetByIdAsync(request.Id);
if (task == null)
throw new NotFoundException("تسک یافت نشد");
task.UpdateName(request.Name);
}
}

View File

@@ -0,0 +1,18 @@
using FluentValidation;
namespace GozareshgirProgramManager.Application.Modules.Projects.Commands.EditProject;
public class EditProjectCommandValidator:AbstractValidator<EditProjectCommand>
{
public EditProjectCommandValidator()
{
RuleFor(x => x.Name)
.NotEmpty()
.WithMessage("نام پروژه نمی‌تواند خالی باشد.");
RuleFor(x=>x.Id)
.NotEmpty()
.NotNull().WithMessage("شناسه پروژه نمی‌تواند خالی باشد.");
}
}

View File

@@ -0,0 +1,13 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application.Modules.Projects.DTOs;
using GozareshgirProgramManager.Domain.ProjectAgg.Enums;
namespace GozareshgirProgramManager.Application.Modules.Projects.Commands.SetTimeProject;
public record SetTimeProjectCommand(List<SetTimeProjectSectionItem> SectionItems, Guid Id, ProjectHierarchyLevel Level):IBaseCommand;
public class SetTimeSectionTime
{
public string Description { get; set; }
public int Hours { get; set; }
}

View File

@@ -0,0 +1,164 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Application.Modules.Projects.DTOs;
using GozareshgirProgramManager.Domain._Common;
using GozareshgirProgramManager.Domain.ProjectAgg.Entities;
using GozareshgirProgramManager.Domain.ProjectAgg.Enums;
using GozareshgirProgramManager.Domain.ProjectAgg.Repositories;
namespace GozareshgirProgramManager.Application.Modules.Projects.Commands.SetTimeProject;
public class SetTimeProjectCommandHandler:IBaseCommandHandler<SetTimeProjectCommand>
{
private readonly IProjectRepository _projectRepository;
private readonly IProjectPhaseRepository _projectPhaseRepository;
private readonly IProjectTaskRepository _projectTaskRepository;
private readonly IUnitOfWork _unitOfWork;
private readonly IAuthHelper _authHelper;
private long? _userId;
public SetTimeProjectCommandHandler(
IProjectRepository projectRepository,
IProjectPhaseRepository projectPhaseRepository,
IProjectTaskRepository projectTaskRepository,
IUnitOfWork unitOfWork, IAuthHelper authHelper)
{
_projectRepository = projectRepository;
_projectPhaseRepository = projectPhaseRepository;
_projectTaskRepository = projectTaskRepository;
_unitOfWork = unitOfWork;
_authHelper = authHelper;
_userId = authHelper.GetCurrentUserId();
}
public async Task<OperationResult> Handle(SetTimeProjectCommand request, CancellationToken cancellationToken)
{
switch (request.Level)
{
case ProjectHierarchyLevel.Task:
return await SetTimeForProjectTask(request, cancellationToken);
default:
return OperationResult.Failure("سطح پروژه نامعتبر است");
}
}
private async Task<OperationResult> SetTimeForProject(SetTimeProjectCommand request, CancellationToken cancellationToken)
{
var project = await _projectRepository.GetWithFullHierarchyAsync(request.Id);
if (project == null)
{
return OperationResult.NotFound("پروژه یافت نشد");
return OperationResult.NotFound("<22><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD>");
}
long? addedByUserId = _userId;
// تنظیم زمان برای تمام sections در تمام فازها و تسک‌های پروژه
foreach (var phase in project.Phases)
{
foreach (var task in phase.Tasks)
{
foreach (var section in task.Sections)
{
var sectionItem = request.SectionItems.FirstOrDefault(si => si.SectionId == section.Id);
if (sectionItem != null)
{
SetSectionTime(section, sectionItem, addedByUserId);
}
}
}
}
await _unitOfWork.SaveChangesAsync(cancellationToken);
return OperationResult.Success();
}
private async Task<OperationResult> SetTimeForProjectPhase(SetTimeProjectCommand request, CancellationToken cancellationToken)
{
var phase = await _projectPhaseRepository.GetWithTasksAsync(request.Id);
if (phase == null)
{
return OperationResult.NotFound("فاز پروژه یافت نشد");
return OperationResult.NotFound("<22><><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD>");
}
long? addedByUserId = _userId;
// تنظیم زمان برای تمام sections در تمام تسک‌های این فاز
foreach (var task in phase.Tasks)
{
foreach (var section in task.Sections)
{
var sectionItem = request.SectionItems.FirstOrDefault(si => si.SectionId == section.Id);
if (sectionItem != null)
{
SetSectionTime(section, sectionItem, addedByUserId);
}
}
}
await _unitOfWork.SaveChangesAsync(cancellationToken);
return OperationResult.Success();
}
private async Task<OperationResult> SetTimeForProjectTask(SetTimeProjectCommand request, CancellationToken cancellationToken)
{
var task = await _projectTaskRepository.GetWithSectionsAsync(request.Id);
if (task == null)
{
return OperationResult.NotFound("تسک یافت نشد");
return OperationResult.NotFound("<22>Ә <20><><EFBFBD><EFBFBD> <20><><EFBFBD>");
}
long? addedByUserId = _userId;
// تنظیم زمان مستقیماً برای sections این تسک
foreach (var section in task.Sections)
{
var sectionItem = request.SectionItems.FirstOrDefault(si => si.SectionId == section.Id);
if (sectionItem != null)
{
SetSectionTime(section, sectionItem, addedByUserId);
}
}
await _unitOfWork.SaveChangesAsync(cancellationToken);
return OperationResult.Success();
}
private void SetSectionTime(TaskSection section, SetTimeProjectSectionItem sectionItem, long? addedByUserId)
{
var initData = sectionItem.InitData;
var initialTime = TimeSpan.FromHours(initData.Hours);
// تنظیم زمان اولیه
section.UpdateInitialEstimatedHours(initialTime, initData.Description);
section.ClearAdditionalTimes();
// افزودن زمان‌های اضافی
foreach (var additionalTime in sectionItem.AdditionalTime)
{
var additionalTimeSpan = TimeSpan.FromHours(additionalTime.Hours);
section.AddAdditionalTime(additionalTimeSpan, additionalTime.Description, addedByUserId);
}
}
// private void SetSectionTime(ProjectSection section, SetTimeProjectSectionItem sectionItem, long? addedByUserId)
// {
// var initData = sectionItem.InitData;
// var initialTime = TimeSpan.FromHours(initData.Hours);
//
// // تنظیم زمان اولیه
// section.UpdateInitialEstimatedHours(initialTime, initData.Description);
//
// section.ClearAdditionalTimes();
// // افزودن زمان‌های اضافی
// foreach (var additionalTime in sectionItem.AdditionalTime)
// {
// var additionalTimeSpan = TimeSpan.FromHours(additionalTime.Hours);
// section.AddAdditionalTime(additionalTimeSpan, additionalTime.Description, addedByUserId);
// }
// }
}

View File

@@ -0,0 +1,50 @@
using FluentValidation;
using GozareshgirProgramManager.Application.Modules.Projects.DTOs;
namespace GozareshgirProgramManager.Application.Modules.Projects.Commands.SetTimeProject;
public class SetTimeProjectCommandValidator:AbstractValidator<SetTimeProjectCommand>
{
public SetTimeProjectCommandValidator()
{
RuleFor(x=>x.Id)
.NotEmpty()
.NotNull()
.WithMessage("شناسه پروژه نمی‌تواند خالی باشد.");
RuleForEach(x => x.SectionItems)
.SetValidator(command => new SetTimeProjectSectionItemValidator());
}
}
public class SetTimeProjectSectionItemValidator:AbstractValidator<SetTimeProjectSectionItem>
{
public SetTimeProjectSectionItemValidator()
{
RuleFor(x=>x.SectionId)
.NotEmpty()
.NotNull()
.WithMessage("شناسه بخش نمی‌تواند خالی باشد.");
RuleFor(x=>x.InitData)
.SetValidator(new TimeDataValidator());
RuleForEach(x=>x.AdditionalTime)
.SetValidator(new TimeDataValidator());
}
}
public class TimeDataValidator : AbstractValidator<SetTimeSectionTime>
{
public TimeDataValidator()
{
RuleFor(x => x.Hours)
.GreaterThanOrEqualTo(0)
.WithMessage("ساعت نمی‌تواند منفی باشد.");
RuleFor(x=>x.Description)
.MaximumLength(500)
.WithMessage("توضیحات نمی‌تواند بیشتر از 500 کاراکتر باشد.");
}
}

View File

@@ -0,0 +1,12 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
namespace GozareshgirProgramManager.Application.Modules.Projects.Commands.TransferSection;
public record TransferSectionCommand : IBaseCommand
{
public Guid SectionId { get; set; }
public long FromUserId { get; set; }
public long ToUserId { get; set; }
public string? Notes { get; set; }
}

View File

@@ -0,0 +1,69 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Domain._Common;
using GozareshgirProgramManager.Domain.ProjectAgg.Repositories;
using GozareshgirProgramManager.Domain.UserAgg.Repositories;
namespace GozareshgirProgramManager.Application.Modules.Projects.Commands.TransferSection;
public class TransferSectionCommandHandler : IBaseCommandHandler<TransferSectionCommand>
{
private readonly IUnitOfWork _unitOfWork;
private readonly ITaskSectionRepository _taskSectionRepository;
private readonly IUserRepository _userRepository;
public TransferSectionCommandHandler(
ITaskSectionRepository taskSectionRepository,
IUserRepository userRepository,
IUnitOfWork unitOfWork)
{
_taskSectionRepository = taskSectionRepository;
_userRepository = userRepository;
_unitOfWork = unitOfWork;
}
public async Task<OperationResult> Handle(TransferSectionCommand request, CancellationToken cancellationToken)
{
// دریافت section با activities
var section = await _taskSectionRepository.GetByIdWithActivitiesAsync(request.SectionId, cancellationToken);
if (section == null)
{
return OperationResult.NotFound("بخش پروژه یافت نشد");
}
// بررسی وجود کاربر مبدا
var fromUser = await _userRepository.GetByIdAsync(request.FromUserId);
if (fromUser == null)
{
return OperationResult.NotFound($"کاربر مبدا با شناسه {request.FromUserId} یافت نشد");
}
// بررسی وجود کاربر مقصد
var toUser = await _userRepository.GetByIdAsync(request.ToUserId);
if (toUser == null)
{
return OperationResult.NotFound($"کاربر مقصد با شناسه {request.ToUserId} یافت نشد");
}
// بررسی اینکه کاربر مبدا و مقصد یکسان نباشند
if (request.FromUserId == request.ToUserId)
{
return OperationResult.Failure("کاربر مبدا و مقصد نمی‌توانند یکسان باشند");
}
try
{
// انتقال به کاربر جدید
section.TransferToUser(request.FromUserId, request.ToUserId);
await _unitOfWork.SaveChangesAsync(cancellationToken);
return OperationResult.Success();
}
catch (InvalidOperationException ex)
{
return OperationResult.Failure(ex.Message);
}
}
}

View File

@@ -0,0 +1,28 @@
using FluentValidation;
namespace GozareshgirProgramManager.Application.Modules.Projects.Commands.TransferSection;
public class TransferSectionCommandValidator : AbstractValidator<TransferSectionCommand>
{
public TransferSectionCommandValidator()
{
RuleFor(x => x.SectionId)
.NotEmpty()
.WithMessage("شناسه بخش نمیتواند خالی باشد");
RuleFor(x => x.FromUserId)
.NotEmpty()
.GreaterThan(0)
.WithMessage("شناسه کاربر مبدا نمیتواند خالی یا صفر باشد");
RuleFor(x => x.ToUserId)
.NotEmpty()
.GreaterThan(0)
.WithMessage("شناسه کاربر مقصد نمیتواند خالی یا صفر باشد");
RuleFor(x => x)
.Must(x => x.FromUserId != x.ToUserId)
.WithMessage("کاربر مبدا و مقصد نمی‌توانند یکسان باشند");
}
}

View File

@@ -0,0 +1,127 @@
namespace GozareshgirProgramManager.Application.Modules.Projects.DTOs;
/// <summary>
/// DTO for Project entity
/// </summary>
public class ProjectDto
{
public Guid Id { get; set; }
public string Name { get; set; } = string.Empty;
public string? Description { get; set; }
public DateTime CreationDate { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set; }
public DateTime? PlannedStartDate { get; set; }
public DateTime? PlannedEndDate { get; set; }
public string Status { get; set; } = string.Empty;
public TimeSpan? AllocatedTime { get; set; }
public bool HasTimeOverride { get; set; }
public bool HasAssignmentOverride { get; set; }
public TimeSpan TotalTimeSpent { get; set; }
public TimeSpan TotalEstimatedTime { get; set; }
public List<ProjectPhaseDto> Phases { get; set; } = new();
}
/// <summary>
/// DTO for ProjectPhase entity
/// </summary>
public class ProjectPhaseDto
{
public Guid Id { get; set; }
public string Name { get; set; } = string.Empty;
public string? Description { get; set; }
public DateTime CreationDate { get; set; }
public Guid ProjectId { get; set; }
public string Status { get; set; } = string.Empty;
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set; }
public int OrderIndex { get; set; }
public TimeSpan? AllocatedTime { get; set; }
public bool HasTimeOverride { get; set; }
public bool HasAssignmentOverride { get; set; }
public TimeSpan TotalTimeSpent { get; set; }
public TimeSpan TotalEstimatedTime { get; set; }
public List<ProjectTaskDto> Tasks { get; set; } = new();
}
/// <summary>
/// DTO for ProjectTask entity
/// </summary>
public class ProjectTaskDto
{
public Guid Id { get; set; }
public string Name { get; set; } = string.Empty;
public string? Description { get; set; }
public DateTime CreationDate { get; set; }
public Guid PhaseId { get; set; }
public string Status { get; set; } = string.Empty;
public string Priority { get; set; } = string.Empty;
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set; }
public DateTime? DueDate { get; set; }
public int OrderIndex { get; set; }
public TimeSpan? AllocatedTime { get; set; }
public bool HasTimeOverride { get; set; }
public bool HasAssignmentOverride { get; set; }
public TimeSpan TotalTimeSpent { get; set; }
public TimeSpan TotalEstimatedTime { get; set; }
public List<ProjectSectionDto> Sections { get; set; } = new();
}
/// <summary>
/// DTO for TaskSection entity
/// </summary>
public class ProjectSectionDto
{
public Guid Id { get; set; }
public Guid TaskId { get; set; }
public Guid SkillId { get; set; }
public string SkillName { get; set; } = string.Empty;
public TimeSpan InitialEstimatedHours { get; set; }
public string? InitialDescription { get; set; }
public string Status { get; set; } = string.Empty;
public long CurrentAssignedUserId { get; set; }
public string? CurrentAssignedUserName { get; set; }
public DateTime CreationDate { get; set; }
public TimeSpan FinalEstimatedHours { get; set; }
public TimeSpan TotalTimeSpent { get; set; }
public bool IsCompleted { get; set; }
public bool IsInProgress { get; set; }
public List<TaskSectionActivityDto> Activities { get; set; } = new();
public List<TaskSectionAdditionalTimeDto> AdditionalTimes { get; set; } = new();
}
/// <summary>
/// DTO for ProjectSectionActivity entity
/// </summary>
public class TaskSectionActivityDto
{
public Guid Id { get; set; }
public Guid SectionId { get; set; }
public long UserId { get; set; }
public string? UserName { get; set; }
public DateTime StartDate { get; set; }
public DateTime? EndDate { get; set; }
public string? Notes { get; set; }
public string? EndNotes { get; set; }
public bool IsActive { get; set; }
public TimeSpan TimeSpent { get; set; }
}
/// <summary>
/// DTO for ProjectSectionAdditionalTime entity
/// </summary>
public class TaskSectionAdditionalTimeDto
{
public Guid Id { get; set; }
public TimeSpan Hours { get; set; }
public string? Reason { get; set; }
public long? AddedByUserId { get; set; }
public string? AddedByUserName { get; set; }
public DateTime AddedAt { get; set; }
}

View File

@@ -0,0 +1,10 @@
using GozareshgirProgramManager.Application.Modules.Projects.Commands.SetTimeProject;
namespace GozareshgirProgramManager.Application.Modules.Projects.DTOs;
public class SetTimeProjectSectionItem
{
public Guid SectionId { get; set; }
public SetTimeSectionTime InitData { get; set; }
public List<SetTimeSectionTime> AdditionalTime { get; set; } = [];
}

View File

@@ -0,0 +1,258 @@
using GozareshgirProgramManager.Application.Modules.Projects.DTOs;
using GozareshgirProgramManager.Domain.ProjectAgg.Entities;
using GozareshgirProgramManager.Domain.ProjectAgg.Enums;
namespace GozareshgirProgramManager.Application.Modules.Projects.Extensions;
/// <summary>
/// Mapping extensions for project hierarchy entities to DTOs
/// </summary>
public static class ProjectMappingExtensions
{
#region Project Mappings
public static ProjectDto ToDto(this Project project)
{
return new ProjectDto
{
Id = project.Id,
Name = project.Name,
Description = project.Description,
CreationDate = project.CreationDate,
StartDate = project.StartDate,
EndDate = project.EndDate,
PlannedStartDate = project.PlannedStartDate,
PlannedEndDate = project.PlannedEndDate,
Status = project.Status.ToString(),
HasAssignmentOverride = project.HasAssignmentOverride,
TotalTimeSpent = project.GetTotalTimeSpent(),
TotalEstimatedTime = project.GetTotalEstimatedTime(),
Phases = project.Phases.Select(p => p.ToDto()).ToList()
};
}
public static ProjectDto ToSummaryDto(this Project project)
{
return new ProjectDto
{
Id = project.Id,
Name = project.Name,
Description = project.Description,
CreationDate = project.CreationDate,
StartDate = project.StartDate,
EndDate = project.EndDate,
PlannedStartDate = project.PlannedStartDate,
PlannedEndDate = project.PlannedEndDate,
Status = project.Status.ToString(),
HasAssignmentOverride = project.HasAssignmentOverride,
TotalTimeSpent = project.GetTotalTimeSpent(),
TotalEstimatedTime = project.GetTotalEstimatedTime()
// No phases for summary
};
}
#endregion
#region Phase Mappings
public static ProjectPhaseDto ToDto(this ProjectPhase phase)
{
return new ProjectPhaseDto
{
Id = phase.Id,
Name = phase.Name,
Description = phase.Description,
CreationDate = phase.CreationDate,
ProjectId = phase.ProjectId,
Status = phase.Status.ToString(),
StartDate = phase.StartDate,
EndDate = phase.EndDate,
OrderIndex = phase.OrderIndex,
HasAssignmentOverride = phase.HasAssignmentOverride,
TotalTimeSpent = phase.GetTotalTimeSpent(),
TotalEstimatedTime = phase.GetTotalEstimatedTime(),
Tasks = phase.Tasks.Select(t => t.ToDto()).ToList()
};
}
public static ProjectPhaseDto ToSummaryDto(this ProjectPhase phase)
{
return new ProjectPhaseDto
{
Id = phase.Id,
Name = phase.Name,
Description = phase.Description,
CreationDate = phase.CreationDate,
ProjectId = phase.ProjectId,
Status = phase.Status.ToString(),
StartDate = phase.StartDate,
EndDate = phase.EndDate,
OrderIndex = phase.OrderIndex,
HasAssignmentOverride = phase.HasAssignmentOverride,
TotalTimeSpent = phase.GetTotalTimeSpent(),
TotalEstimatedTime = phase.GetTotalEstimatedTime()
// No tasks for summary
};
}
#endregion
#region Task Mappings
public static ProjectTaskDto ToDto(this ProjectTask task)
{
return new ProjectTaskDto
{
Id = task.Id,
Name = task.Name,
Description = task.Description,
CreationDate = task.CreationDate,
PhaseId = task.PhaseId,
Status = task.Status.ToString(),
Priority = task.Priority.ToString(),
StartDate = task.StartDate,
EndDate = task.EndDate,
DueDate = task.DueDate,
OrderIndex = task.OrderIndex,
AllocatedTime = task.AllocatedTime,
HasTimeOverride = task.HasTimeOverride,
HasAssignmentOverride = task.HasAssignmentOverride,
TotalTimeSpent = task.GetTotalTimeSpent(),
TotalEstimatedTime = task.GetTotalEstimatedTime(),
Sections = task.Sections.Select(s => s.ToDto()).ToList()
};
}
public static ProjectTaskDto ToSummaryDto(this ProjectTask task)
{
return new ProjectTaskDto
{
Id = task.Id,
Name = task.Name,
Description = task.Description,
CreationDate = task.CreationDate,
PhaseId = task.PhaseId,
Status = task.Status.ToString(),
Priority = task.Priority.ToString(),
StartDate = task.StartDate,
EndDate = task.EndDate,
DueDate = task.DueDate,
OrderIndex = task.OrderIndex,
AllocatedTime = task.AllocatedTime,
HasTimeOverride = task.HasTimeOverride,
HasAssignmentOverride = task.HasAssignmentOverride,
TotalTimeSpent = task.GetTotalTimeSpent(),
TotalEstimatedTime = task.GetTotalEstimatedTime()
// No sections for summary
};
}
#endregion
#region Section Mappings
public static ProjectSectionDto ToDto(this TaskSection section)
{
return new ProjectSectionDto
{
Id = section.Id,
TaskId = section.TaskId,
SkillId = section.SkillId,
SkillName = section.Skill?.Name ?? string.Empty,
InitialEstimatedHours = section.InitialEstimatedHours,
InitialDescription = section.InitialDescription,
Status = section.Status.ToString(),
CurrentAssignedUserId = section.CurrentAssignedUserId,
CreationDate = section.CreationDate,
FinalEstimatedHours = section.FinalEstimatedHours,
TotalTimeSpent = section.GetTotalTimeSpent(),
IsCompleted = section.IsCompleted(),
IsInProgress = section.IsInProgress(),
Activities = section.Activities.Select(a => a.ToDto()).ToList(),
AdditionalTimes = section.AdditionalTimes.Select(at => at.ToDto()).ToList()
};
}
public static ProjectSectionDto ToSummaryDto(this TaskSection section)
{
return new ProjectSectionDto
{
Id = section.Id,
TaskId = section.TaskId,
SkillId = section.SkillId,
SkillName = section.Skill?.Name ?? string.Empty,
InitialEstimatedHours = section.InitialEstimatedHours,
InitialDescription = section.InitialDescription,
Status = section.Status.ToString(),
CurrentAssignedUserId = section.CurrentAssignedUserId,
CreationDate = section.CreationDate,
FinalEstimatedHours = section.FinalEstimatedHours,
TotalTimeSpent = section.GetTotalTimeSpent(),
IsCompleted = section.IsCompleted(),
IsInProgress = section.IsInProgress()
// No activities or additional times for summary
};
}
#endregion
#region Activity Mappings
public static TaskSectionActivityDto ToDto(this TaskSectionActivity activity)
{
return new TaskSectionActivityDto
{
Id = activity.Id,
SectionId = activity.SectionId,
UserId = activity.UserId,
StartDate = activity.StartDate,
EndDate = activity.EndDate,
Notes = activity.Notes,
EndNotes = activity.EndNotes,
IsActive = activity.IsActive,
TimeSpent = activity.GetTimeSpent()
};
}
#endregion
#region Additional Time Mappings
public static TaskSectionAdditionalTimeDto ToDto(this TaskSectionAdditionalTime additionalTime)
{
return new TaskSectionAdditionalTimeDto
{
Id = additionalTime.Id,
Hours = additionalTime.Hours,
Reason = additionalTime.Reason,
AddedByUserId = additionalTime.AddedByUserId,
AddedAt = additionalTime.AddedAt
};
}
#endregion
#region Collection Mappings
public static List<ProjectDto> ToSummaryDtos(this IEnumerable<Project> projects)
{
return projects.Select(p => p.ToSummaryDto()).ToList();
}
public static List<ProjectPhaseDto> ToSummaryDtos(this IEnumerable<ProjectPhase> phases)
{
return phases.Select(p => p.ToSummaryDto()).ToList();
}
public static List<ProjectTaskDto> ToSummaryDtos(this IEnumerable<ProjectTask> tasks)
{
return tasks.Select(t => t.ToSummaryDto()).ToList();
}
public static List<ProjectSectionDto> ToSummaryDtos(this IEnumerable<TaskSection> sections)
{
return sections.Select(s => s.ToSummaryDto()).ToList();
}
#endregion
}

View File

@@ -0,0 +1,122 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Domain.ProjectAgg.Enums;
using Microsoft.EntityFrameworkCore;
namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectAssignDetails;
public record GetProjectAssignDetailsResponse(List<GetProjectAssignSectionDetails> Items);
public record GetProjectAssignSectionDetails(string Skill,Guid SkillId,Guid? SectionId,long UserId);
public record GetProjectAssignDetailsQuery(Guid Id,ProjectHierarchyLevel Level) : IBaseQuery<GetProjectAssignDetailsResponse>;
public class GetProjectAssignDetailsQueryHandler:IBaseQueryHandler<GetProjectAssignDetailsQuery,GetProjectAssignDetailsResponse>
{
private readonly IProgramManagerDbContext _dbContext;
public GetProjectAssignDetailsQueryHandler(IProgramManagerDbContext dbContext)
{
_dbContext = dbContext;
}
public async Task<OperationResult<GetProjectAssignDetailsResponse>> Handle(GetProjectAssignDetailsQuery request, CancellationToken cancellationToken)
{
switch (request.Level)
{
case ProjectHierarchyLevel.Project:
return await GetProjectAssignDetails(request.Id, cancellationToken);
case ProjectHierarchyLevel.Phase:
return await GetPhaseAssignDetails(request.Id, cancellationToken);
case ProjectHierarchyLevel.Task:
return await GetTaskAssignDetails(request.Id, cancellationToken);
default:
return OperationResult<GetProjectAssignDetailsResponse>.Failure("سطح پروژه نامعتبر است");
}
}
private async Task<OperationResult<GetProjectAssignDetailsResponse>> GetProjectAssignDetails(Guid projectId, CancellationToken cancellationToken)
{
// گرفتن تمام فازهای پروژه
var skills = _dbContext.Skills.ToList();
var sectionsData =await _dbContext.ProjectSections
.Where(p => p.ProjectId == projectId).ToListAsync(cancellationToken);
if (skills.Count == 0)
{
return OperationResult<GetProjectAssignDetailsResponse>.Success(new GetProjectAssignDetailsResponse(new List<GetProjectAssignSectionDetails>()));
}
var sections = skills.Select(x=>
{
var section = sectionsData.FirstOrDefault(s => s.SkillId == x.Id);
return new GetProjectAssignSectionDetails(
x.Name,
x.Id,
section?.Id,
section?.UserId ?? 0
);
}).ToList();
var response = new GetProjectAssignDetailsResponse(sections);
return OperationResult<GetProjectAssignDetailsResponse>.Success(response);
}
private async Task<OperationResult<GetProjectAssignDetailsResponse>> GetPhaseAssignDetails(Guid phaseId, CancellationToken cancellationToken)
{
var skills = _dbContext.Skills.ToList();
var sectionsData =await _dbContext.PhaseSections
.Where(p => p.PhaseId == phaseId)
.ToListAsync(cancellationToken: cancellationToken);
if (skills.Count == 0)
{
return OperationResult<GetProjectAssignDetailsResponse>.Success(new GetProjectAssignDetailsResponse(new List<GetProjectAssignSectionDetails>()));
}
var sections = skills.Select(x=>
{
var section = sectionsData.FirstOrDefault(s => s.SkillId == x.Id);
return new GetProjectAssignSectionDetails(
x.Name,
x.Id,
section?.Id,
section?.UserId ?? 0
);
}).ToList();
var response = new GetProjectAssignDetailsResponse(sections);
return OperationResult<GetProjectAssignDetailsResponse>.Success(response);
}
private async Task<OperationResult<GetProjectAssignDetailsResponse>> GetTaskAssignDetails(Guid taskId, CancellationToken cancellationToken)
{
var skills = _dbContext.Skills.ToList();
var sectionsData =await _dbContext.TaskSections
.Where(p => p.TaskId == taskId).ToListAsync(cancellationToken);
if (skills.Count == 0)
{
return OperationResult<GetProjectAssignDetailsResponse>.Success(new GetProjectAssignDetailsResponse(new List<GetProjectAssignSectionDetails>()));
}
var sections = skills.Select(x=>
{
var section = sectionsData.FirstOrDefault(s => s.SkillId == x.Id);
return new GetProjectAssignSectionDetails(
x.Name,
x.Id,
section?.Id,
section?.OriginalAssignedUserId ?? 0
);
}).ToList();
var response = new GetProjectAssignDetailsResponse(sections);
return OperationResult<GetProjectAssignDetailsResponse>.Success(response);
}
}

View File

@@ -0,0 +1,18 @@
using GozareshgirProgramManager.Domain.ProjectAgg.Enums;
namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectsList;
public record GetProjectListDto
{
public Guid Id { get; init; }
public string Name { get; init; } = string.Empty;
public int Percentage { get; init; }
public ProjectHierarchyLevel Level { get; init; }
public Guid? ParentId { get; init; }
public bool HasFront { get; set; }
public bool HasBackend { get; set; }
public bool HasDesign { get; set; }
}

View File

@@ -0,0 +1,8 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Domain.ProjectAgg.Enums;
namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectsList;
public record GetProjectsListQuery(ProjectHierarchyLevel HierarchyLevel,
Guid? ParentId) : IBaseQuery<GetProjectsListResponse>;

View File

@@ -0,0 +1,303 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Domain.ProjectAgg.Entities;
using GozareshgirProgramManager.Domain.ProjectAgg.Enums;
using Microsoft.EntityFrameworkCore;
namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectsList;
public class GetProjectsListQueryHandler : IBaseQueryHandler<GetProjectsListQuery, GetProjectsListResponse>
{
private readonly IProgramManagerDbContext _context;
public GetProjectsListQueryHandler(IProgramManagerDbContext context)
{
_context = context;
}
public async Task<OperationResult<GetProjectsListResponse>> Handle(GetProjectsListQuery request, CancellationToken cancellationToken)
{
List<GetProjectListDto> projects;
switch (request.HierarchyLevel)
{
case ProjectHierarchyLevel.Project:
projects = await GetProjects(request.ParentId, cancellationToken);
break;
case ProjectHierarchyLevel.Phase:
projects = await GetPhases(request.ParentId, cancellationToken);
break;
case ProjectHierarchyLevel.Task:
projects = await GetTasks(request.ParentId, cancellationToken);
break;
default:
return OperationResult<GetProjectsListResponse>.Failure("سطح سلسله مراتب نامعتبر است");
}
await SetSkillFlags(projects, cancellationToken);
var response = new GetProjectsListResponse(projects);
return OperationResult<GetProjectsListResponse>.Success(response);
}
private async Task<List<GetProjectListDto>> GetProjects(Guid? parentId, CancellationToken cancellationToken)
{
var query = _context.Projects.AsQueryable();
// پروژه‌ها سطح بالا هستند و parentId ندارند، فقط در صورت null بودن parentId نمایش داده می‌شوند
if (parentId.HasValue)
{
return new List<GetProjectListDto>(); // پروژه‌ها parent ندارند
}
var projects = await query
.OrderByDescending(p => p.CreationDate)
.ToListAsync(cancellationToken);
var result = new List<GetProjectListDto>();
foreach (var project in projects)
{
var percentage = await CalculateProjectPercentage(project, cancellationToken);
result.Add(new GetProjectListDto
{
Id = project.Id,
Name = project.Name,
Level = ProjectHierarchyLevel.Project,
ParentId = null,
Percentage = percentage
});
}
return result;
}
private async Task<List<GetProjectListDto>> GetPhases(Guid? parentId, CancellationToken cancellationToken)
{
var query = _context.ProjectPhases.AsQueryable();
if (parentId.HasValue)
{
query = query.Where(x => x.ProjectId == parentId);
}
var phases = await query
.OrderByDescending(p => p.CreationDate)
.ToListAsync(cancellationToken);
var result = new List<GetProjectListDto>();
foreach (var phase in phases)
{
var percentage = await CalculatePhasePercentage(phase, cancellationToken);
result.Add(new GetProjectListDto
{
Id = phase.Id,
Name = phase.Name,
Level = ProjectHierarchyLevel.Phase,
ParentId = phase.ProjectId,
Percentage = percentage
});
}
return result;
}
private async Task<List<GetProjectListDto>> GetTasks(Guid? parentId, CancellationToken cancellationToken)
{
var query = _context.ProjectTasks.AsQueryable();
if (parentId.HasValue)
{
query = query.Where(x => x.PhaseId == parentId);
}
var tasks = await query
.OrderByDescending(t => t.CreationDate)
.ToListAsync(cancellationToken);
var result = new List<GetProjectListDto>();
foreach (var task in tasks)
{
var percentage = await CalculateTaskPercentage(task, cancellationToken);
result.Add(new GetProjectListDto
{
Id = task.Id,
Name = task.Name,
Level = ProjectHierarchyLevel.Task,
ParentId = task.PhaseId,
Percentage = percentage
});
}
return result;
}
private async Task SetSkillFlags(List<GetProjectListDto> projects, CancellationToken cancellationToken)
{
var projectIds = projects.Select(x => x.Id).ToList();
// تنها تسک‌ها sections دارند، بنابراین برای سطوح مختلف باید متفاوت عمل کنیم
List<Guid> taskIds;
switch (projects.FirstOrDefault()?.Level)
{
case ProjectHierarchyLevel.Project:
// برای پروژه‌ها، باید تمام تسک‌های زیرمجموعه را پیدا کنیم
var phaseIds = await _context.ProjectPhases
.Where(ph => projectIds.Contains(ph.ProjectId))
.Select(ph => ph.Id)
.ToListAsync(cancellationToken);
taskIds = await _context.ProjectTasks
.Where(t => phaseIds.Contains(t.PhaseId))
.Select(t => t.Id)
.ToListAsync(cancellationToken);
break;
case ProjectHierarchyLevel.Phase:
// برای فازها، تمام تسک‌های آن فازها را پیدا کنیم
taskIds = await _context.ProjectTasks
.Where(t => projectIds.Contains(t.PhaseId))
.Select(t => t.Id)
.ToListAsync(cancellationToken);
break;
case ProjectHierarchyLevel.Task:
// برای تسک‌ها، خود آنها taskIds هستند
taskIds = projectIds;
break;
default:
return;
}
if (!taskIds.Any())
return;
var sections = await _context.TaskSections
.Include(x => x.Skill)
.Where(x => taskIds.Contains(x.TaskId))
.ToListAsync(cancellationToken);
foreach (var project in projects)
{
List<Guid> relevantTaskIds;
switch (project.Level)
{
case ProjectHierarchyLevel.Project:
// برای پروژه، تمام تسک‌های زیرمجموعه
var projectPhaseIds = await _context.ProjectPhases
.Where(ph => ph.ProjectId == project.Id)
.Select(ph => ph.Id)
.ToListAsync(cancellationToken);
relevantTaskIds = await _context.ProjectTasks
.Where(t => projectPhaseIds.Contains(t.PhaseId))
.Select(t => t.Id)
.ToListAsync(cancellationToken);
break;
case ProjectHierarchyLevel.Phase:
// برای فاز، تمام تسک‌های آن فاز
relevantTaskIds = await _context.ProjectTasks
.Where(t => t.PhaseId == project.Id)
.Select(t => t.Id)
.ToListAsync(cancellationToken);
break;
case ProjectHierarchyLevel.Task:
// برای تسک، خود آن
relevantTaskIds = new List<Guid> { project.Id };
break;
default:
continue;
}
var projectSections = sections.Where(x => relevantTaskIds.Contains(x.TaskId)).ToList();
project.HasBackend = projectSections.Any(x => x.Skill.Name == "Backend");
project.HasFront = projectSections.Any(x => x.Skill.Name == "Frontend");
project.HasDesign = projectSections.Any(x => x.Skill.Name == "UI/UX Design");
}
}
private async Task<int> CalculateProjectPercentage(Project project, CancellationToken cancellationToken)
{
// گرفتن تمام فازهای پروژه
var phases = await _context.ProjectPhases
.Where(ph => ph.ProjectId == project.Id)
.ToListAsync(cancellationToken);
if (!phases.Any())
return 0;
// محاسبه درصد هر فاز و میانگین‌گیری
var phasePercentages = new List<int>();
foreach (var phase in phases)
{
var phasePercentage = await CalculatePhasePercentage(phase, cancellationToken);
phasePercentages.Add(phasePercentage);
}
return phasePercentages.Any() ? (int)phasePercentages.Average() : 0;
}
private async Task<int> CalculatePhasePercentage(ProjectPhase phase, CancellationToken cancellationToken)
{
// گرفتن تمام تسک‌های فاز
var tasks = await _context.ProjectTasks
.Where(t => t.PhaseId == phase.Id)
.ToListAsync(cancellationToken);
if (!tasks.Any())
return 0;
// محاسبه درصد هر تسک و میانگین‌گیری
var taskPercentages = new List<int>();
foreach (var task in tasks)
{
var taskPercentage = await CalculateTaskPercentage(task, cancellationToken);
taskPercentages.Add(taskPercentage);
}
return taskPercentages.Any() ? (int)taskPercentages.Average() : 0;
}
private async Task<int> CalculateTaskPercentage(ProjectTask task, CancellationToken cancellationToken)
{
// گرفتن تمام سکشن‌های تسک با activities
var sections = await _context.TaskSections
.Include(s => s.Activities)
.Where(s => s.TaskId == task.Id)
.ToListAsync(cancellationToken);
if (!sections.Any())
return 0;
// محاسبه درصد هر سکشن و میانگین‌گیری
var sectionPercentages = new List<int>();
foreach (var section in sections)
{
var sectionPercentage = CalculateSectionPercentage(section);
sectionPercentages.Add(sectionPercentage);
}
return sectionPercentages.Any() ? (int)sectionPercentages.Average() : 0;
}
private static int CalculateSectionPercentage(TaskSection section)
{
// محاسبه کل زمان تخمین زده شده (اولیه + اضافی)
var totalEstimatedHours = section.FinalEstimatedHours.TotalHours;
if (totalEstimatedHours <= 0)
return 0;
// محاسبه کل زمان صرف شده از activities
var totalSpentHours = section.Activities.Sum(a => a.GetTimeSpent().TotalHours);
// محاسبه درصد (حداکثر 100%)
var percentage = (totalSpentHours / totalEstimatedHours) * 100;
return Math.Min((int)Math.Round(percentage), 100);
}
}

View File

@@ -0,0 +1,19 @@
using FluentValidation;
namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectsList;
public class GetProjectsListQueryValidator : AbstractValidator<GetProjectsListQuery>
{
public GetProjectsListQueryValidator()
{
RuleFor(x => x.HierarchyLevel)
.IsInEnum().WithMessage("سطح ارسال شده معتبر نمی‌باشد.");
When(x => x.HierarchyLevel != Domain.ProjectAgg.Enums.ProjectHierarchyLevel.Project, () =>
{
RuleFor(x => x.ParentId)
.NotNull().WithMessage("شناسه والد باید برای سطوح غیر از پروژه ارسال شود.");
});
}
}

View File

@@ -0,0 +1,5 @@
namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectsList;
public record GetProjectsListResponse(
List<GetProjectListDto> Projects);

View File

@@ -0,0 +1,10 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectsList;
using GozareshgirProgramManager.Domain.ProjectAgg.Enums;
namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.ProjectBoardList;
public record ProjectBoardListQuery: IBaseQuery<List<ProjectBoardListResponse>>
{
}

View File

@@ -0,0 +1,98 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using Microsoft.EntityFrameworkCore;
namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.ProjectBoardList;
public class ProjectBoardListQueryHandler : IBaseQueryHandler<ProjectBoardListQuery, List<ProjectBoardListResponse>>
{
private readonly IProgramManagerDbContext _programManagerDbContext;
private readonly IAuthHelper _authHelper;
public ProjectBoardListQueryHandler(IProgramManagerDbContext programManagerDbContext, IAuthHelper authHelper)
{
_programManagerDbContext = programManagerDbContext;
_authHelper = authHelper;
}
public async Task<OperationResult<List<ProjectBoardListResponse>>> Handle(ProjectBoardListQuery request,
CancellationToken cancellationToken)
{
var currentUserId = _authHelper.GetCurrentUserId();
var data = await _programManagerDbContext.TaskSections.AsNoTracking()
.Where(x => x.CurrentAssignedUserId == currentUserId)
.Where(x => x.InitialEstimatedHours > TimeSpan.Zero)
.Include(x => x.Task)
.ThenInclude(x => x.Phase)
.ThenInclude(x => x.Project)
.Include(x => x.Activities)
.Include(x => x.AdditionalTimes)
.ToListAsync(cancellationToken);
var activityUserIds = data.SelectMany(x => x.Activities).Select(a => a.UserId).Distinct().ToList();
var users = await _programManagerDbContext.Users.AsNoTracking()
.Where(x => activityUserIds.Contains(x.Id))
.Select(x => new { x.Id, x.FullName })
.ToDictionaryAsync(x => x.Id, x => x.FullName, cancellationToken);
var result = data.Select(x =>
{
// محاسبه یکبار برای هر Activity و Cache کردن نتیجه
var activityTimeData = x.Activities.Select(a =>
{
var timeSpent = a.GetTimeSpent();
return new
{
Activity = a,
TimeSpent = timeSpent,
TotalSeconds = timeSpent.TotalSeconds,
FormattedTime = timeSpent.ToString(@"hh\:mm")
};
}).ToList();
// ادغام پشت سر هم فعالیت‌های یک کاربر
var mergedHistories = new List<ProjectProgressHistoryDto>();
foreach (var activityData in activityTimeData)
{
var lastHistory = mergedHistories.LastOrDefault();
// اگر آخرین history برای همین کاربر باشد، زمان‌ها را جمع می‌کنیم
if (lastHistory != null && lastHistory.UserId == activityData.Activity.UserId)
{
var totalTimeSpan = lastHistory.WorkedTimeSpan + activityData.TimeSpent;
lastHistory.WorkedTimeSpan = totalTimeSpan;
lastHistory.WorkedTime = totalTimeSpan.ToString(@"hh\:mm");
}
else
{
// در غیر این صورت، یک history جدید اضافه می‌کنیم
mergedHistories.Add(new ProjectProgressHistoryDto()
{
UserId = activityData.Activity.UserId,
IsCurrentUser = activityData.Activity.UserId == currentUserId,
Name = users.GetValueOrDefault(activityData.Activity.UserId, "ناشناس"),
WorkedTime = activityData.FormattedTime,
WorkedTimeSpan = activityData.TimeSpent,
});
}
}
return new ProjectBoardListResponse()
{
Id = x.Id,
PhaseName = x.Task.Phase.Name,
ProjectName = x.Task.Phase.Project.Name,
TaskName = x.Task.Name,
SectionStatus = x.Status,
Progress = new ProjectProgressDto()
{
CompleteSecond = x.FinalEstimatedHours.TotalSeconds,
CurrentSecond = activityTimeData.Sum(a => a.TotalSeconds),
Histories = mergedHistories
}
};
}).ToList();
return OperationResult<List<ProjectBoardListResponse>>.Success(result);
}
}

View File

@@ -0,0 +1,28 @@
using GozareshgirProgramManager.Domain.ProjectAgg.Enums;
namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.ProjectBoardList;
public class ProjectBoardListResponse
{
public Guid Id { get; set; }
public string ProjectName { get; set; }
public string PhaseName { get; set; }
public string TaskName { get; set; }
public ProjectProgressDto Progress { get; set; }
public TaskSectionStatus SectionStatus { get; set; }
}
public class ProjectProgressDto
{
public double CurrentSecond { get; set; }
public double CompleteSecond { get; set; }
public List<ProjectProgressHistoryDto> Histories { get; set; }
}
public class ProjectProgressHistoryDto
{
public string Name { get; set; }
public long UserId { get; set; }
public string WorkedTime { get; set; }
public TimeSpan WorkedTimeSpan { get; set; }
public bool IsCurrentUser { get; set; }
}

View File

@@ -0,0 +1,30 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Domain.ProjectAgg.Enums;
namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.ProjectSetTimeDetails;
public record ProjectSetTimeDetailsQuery(Guid TaskId)
: IBaseQuery<ProjectSetTimeResponse>;
public record ProjectSetTimeResponse(
List<ProjectSetTimeResponseSections> SectionItems,
Guid Id,
ProjectHierarchyLevel Level);
public record ProjectSetTimeResponseSections
{
public string SkillName { get; init; }
public string UserName { get; init; }
public int InitialTime { get; set; }
public string InitialDescription { get; set; }
public int TotalEstimateTime { get; init; }
public int TotalAdditionalTime { get; init; }
public string InitCreationTime { get; init; }
public List<ProjectSetTimeResponseSectionAdditionalTime> AdditionalTimes { get; init; }
public Guid SectionId { get; set; }
}
public class ProjectSetTimeResponseSectionAdditionalTime
{
public int Time { get; init; }
public string Description { get; init; }
}

View File

@@ -0,0 +1,80 @@
using DNTPersianUtils.Core;
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Application.Modules.Projects.Commands.SetTimeProject;
using GozareshgirProgramManager.Application.Modules.Projects.DTOs;
using GozareshgirProgramManager.Domain._Common;
using GozareshgirProgramManager.Domain.ProjectAgg.Enums;
using Microsoft.EntityFrameworkCore;
namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.ProjectSetTimeDetails;
public class ProjectSetTimeDetailsQueryHandler
: IBaseQueryHandler<ProjectSetTimeDetailsQuery, ProjectSetTimeResponse>
{
private readonly IProgramManagerDbContext _context;
public ProjectSetTimeDetailsQueryHandler(IProgramManagerDbContext context)
{
_context = context;
}
public async Task<OperationResult<ProjectSetTimeResponse>> Handle(ProjectSetTimeDetailsQuery request,
CancellationToken cancellationToken)
{
var task = await _context.ProjectTasks
.Where(p => p.Id == request.TaskId)
.Include(x => x.Sections)
.ThenInclude(x => x.AdditionalTimes).AsNoTracking()
.FirstOrDefaultAsync(cancellationToken);
if (task == null)
{
return OperationResult<ProjectSetTimeResponse>.NotFound("Project not found");
}
var userIds = task.Sections.Select(x => x.OriginalAssignedUserId)
.Distinct().ToList();
var users = await _context.Users
.Where(x => userIds.Contains(x.Id))
.AsNoTracking()
.ToListAsync(cancellationToken);
var skillIds = task.Sections.Select(x => x.SkillId)
.Distinct().ToList();
var skills = await _context.Skills
.Where(x => skillIds.Contains(x.Id))
.AsNoTracking()
.ToListAsync(cancellationToken);
var res = new ProjectSetTimeResponse(
task.Sections.Select(ts =>
{
var user = users.FirstOrDefault(x => x.Id == ts.OriginalAssignedUserId);
var skill = skills.FirstOrDefault(x => x.Id == ts.SkillId);
return new ProjectSetTimeResponseSections
{
AdditionalTimes = ts.AdditionalTimes
.Select(x => new ProjectSetTimeResponseSectionAdditionalTime
{
Description = x.Reason ?? "",
Time = (int)x.Hours.TotalHours
}).ToList(),
InitCreationTime = ts.CreationDate.ToFarsi(),
SkillName = skill?.Name ?? "",
TotalAdditionalTime = (int)ts.GetTotalAdditionalTime().TotalHours,
TotalEstimateTime = (int)ts.FinalEstimatedHours.TotalHours,
UserName = user?.UserName ?? "",
SectionId = ts.Id,
InitialDescription = ts.InitialDescription ?? "",
InitialTime = (int)ts.InitialEstimatedHours.TotalHours
};
}).ToList(),
task.Id,
ProjectHierarchyLevel.Task);
return OperationResult<ProjectSetTimeResponse>.Success(res);
}
}

View File

@@ -0,0 +1,13 @@
using FluentValidation;
namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.ProjectSetTimeDetails;
public class ProjectSetTimeDetailsQueryValidator:AbstractValidator<ProjectSetTimeDetailsQuery>
{
public ProjectSetTimeDetailsQueryValidator()
{
RuleFor(x => x.TaskId)
.NotEmpty()
.WithMessage("شناسه پروژه نمی‌تواند خالی باشد.");
}
}

View File

@@ -0,0 +1,44 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Domain._Common;
using GozareshgirProgramManager.Domain.PermissionAgg.Entities;
using GozareshgirProgramManager.Domain.RoleAgg.Entities;
using GozareshgirProgramManager.Domain.RoleAgg.Repositories;
namespace GozareshgirProgramManager.Application.Modules.Roles.Commands.CreateRole;
public class CreateRoleCommandHandler : IBaseCommandHandler<CreateRoleCommand>
{
private readonly IRoleRepository _roleRepository;
private readonly IUnitOfWork _unitOfWork;
public CreateRoleCommandHandler(IRoleRepository roleRepository, IUnitOfWork unitOfWork)
{
_roleRepository = roleRepository;
_unitOfWork = unitOfWork;
}
public async Task<OperationResult> Handle(CreateRoleCommand request, CancellationToken cancellationToken)
{
if(string.IsNullOrWhiteSpace(request.RoleName))
return OperationResult.Failure("نام نقش خالی است");
if(!request.Permissions.Any())
return OperationResult.Failure("هیچ دسترسی داده نشده است");
var permissions = request.Permissions.Where(x => x > 0).Select(x => new Permission(x)).ToList();
var role = new Role(request.RoleName, request.GozareshgirRoleId, permissions);
await _roleRepository.CreateAsync(role);
await _unitOfWork.SaveChangesAsync();
return OperationResult.Success();
}
}
public record CreateRoleCommand : IBaseCommand
{
public string RoleName { get; set; }
public List<int> Permissions { get; set; }
public long? GozareshgirRoleId { get; set; }
}

View File

@@ -0,0 +1,54 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Domain._Common;
using GozareshgirProgramManager.Domain.PermissionAgg.Entities;
using GozareshgirProgramManager.Domain.RoleAgg.Repositories;
namespace GozareshgirProgramManager.Application.Modules.Roles.Commands.EditRole;
public class EditRoleCommandHandler : IBaseCommandHandler<EditRoleCommand>
{
private readonly IRoleRepository _roleRepository;
private readonly IUnitOfWork _unitOfWork;
public EditRoleCommandHandler(IRoleRepository roleRepository, IUnitOfWork unitOfWork)
{
_roleRepository = roleRepository;
_unitOfWork = unitOfWork;
}
public async Task<OperationResult> Handle(EditRoleCommand request, CancellationToken cancellationToken)
{
if (_roleRepository.Exists(x => x.RoleName == request.RoleName && x.GozareshgirRoleId != request.GozareshgirRoleId))
return OperationResult.Failure("نام نقش تکراری است");
if (string.IsNullOrWhiteSpace(request.RoleName))
return OperationResult.Failure("نام نقش خالی است");
if(request.GozareshgirRoleId == null || request.GozareshgirRoleId == 0)
return OperationResult.Failure("آی دی نقش از سمت گزارشگیر خالی است");
var permissions = request.Permissions.Where(x => x > 0).Select(x => new Permission(x)).ToList();
var role =await _roleRepository.GetByGozareshgirRoleIdAsync(request.GozareshgirRoleId);
if (role != null)
{
role?.Edit(request.RoleName, permissions);
await _unitOfWork.SaveChangesAsync();
}
return OperationResult.Success();
}
}
public record EditRoleCommand : IBaseCommand
{
public string RoleName { get; set; }
public List<int> Permissions { get; set; }
public long? GozareshgirRoleId { get; set; }
}

View File

@@ -0,0 +1,76 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using Microsoft.EntityFrameworkCore;
namespace GozareshgirProgramManager.Application.Modules.Roles.Queries.GetRoles;
public class GetRolesQueryHandler : IBaseQueryHandler<GetRolesQuery, GetRolesResponse>
{
private readonly IProgramManagerDbContext _context;
public GetRolesQueryHandler(IProgramManagerDbContext context)
{
_context = context;
}
public async Task<OperationResult<GetRolesResponse>> Handle(GetRolesQuery request, CancellationToken cancellationToken)
{
var query = _context.Roles.AsQueryable();
if (!string.IsNullOrWhiteSpace(request.RoleName))
query = query.Where(x => x.RoleName.Contains(request.RoleName));
if (request.GozareshgirRoleId > 0)
query = query.Where(x => x.GozareshgirRoleId == request.GozareshgirRoleId);
var roles = await query
.Select(p => new GetRolesDto()
{
Id = p.Id,
RoleName = p.RoleName,
GozareshgirRoleId = p.GozareshgirRoleId,
Permissions = p.Permissions.Select(x=>x.Code).ToList()
})
.ToListAsync(cancellationToken);
if(!roles.Any())
return OperationResult<GetRolesResponse>.NotFound("یافت نشد");
var response = new GetRolesResponse(
roles
);
return OperationResult<GetRolesResponse>.Success(response);
}
}
public record GetRolesQuery(string? RoleName, long? GozareshgirRoleId) : IBaseQuery<GetRolesResponse>;
public record GetRolesResponse(List<GetRolesDto> Role);
public record GetRolesDto
{
/// <summary>
/// آی دی نقش
/// </summary>
public long Id { get; set; }
/// <summary>
/// نام نقش
/// </summary>
public string RoleName { get; set; }
/// <summary>
/// آی دی نقش در گزارشگیر
/// </summary>
public long? GozareshgirRoleId { get; set; }
/// <summary>
/// لیست کدهای دسترسی
/// </summary>
public List<int> Permissions { get; set; }
}

View File

@@ -0,0 +1,173 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Domain._Common;
using GozareshgirProgramManager.Domain.SalaryPaymentSettingAgg.Entities;
using GozareshgirProgramManager.Domain.SalaryPaymentSettingAgg.Enums;
using GozareshgirProgramManager.Domain.SalaryPaymentSettingAgg.Repositories;
namespace GozareshgirProgramManager.Application.Modules.SalaryPaymentSettings.Commands.CreateSalarySettings;
public class CreateSalarySettingsCommandHandler : IBaseCommandHandler<CreateSalarySettingsCommand>
{
readonly ISalaryPaymentSettingRepository _salaryPaymentSettingRepository;
readonly IUnitOfWork _unitOfWork;
public CreateSalarySettingsCommandHandler(ISalaryPaymentSettingRepository salaryPaymentSettingRepository, IUnitOfWork unitOfWork)
{
_salaryPaymentSettingRepository = salaryPaymentSettingRepository;
_unitOfWork = unitOfWork;
}
public async Task<OperationResult> Handle(CreateSalarySettingsCommand request, CancellationToken cancellationToken)
{
if(_salaryPaymentSettingRepository.Exists(x=>x.UserId == request.UserId))
return OperationResult.Failure(" برای این پرسنل قبلا تنظیمات ایجاد شده است");
if (string.IsNullOrWhiteSpace(request.MonthlySalary))
return OperationResult.Failure("حقوق ماهانه وارد نشده اشت");
double monthlySalary = 0;
try
{
monthlySalary = request.MonthlySalary.MoneyToDouble();
}
catch (Exception e)
{
return OperationResult.Failure("مبلغ حقوق به درستی وارد نشده است");
}
if (monthlySalary == 0)
return OperationResult.Failure("مبلغ حقوق به درستی وارد نشده است");
var workingHoursList = new List<WorkingHours>();
foreach (var workingHours in request.WorkingHoursList)
{
var startShiftOne = new TimeSpan();
var endShiftOne = new TimeSpan();
var startShiftTwo = new TimeSpan();
var endShiftTwo = new TimeSpan();
var restTime = new TimeSpan();
workingHours.HasShiftOne = false;
workingHours.HasRestTime = false;
workingHours.HasShiftTow = false;
try
{
if (!string.IsNullOrWhiteSpace(workingHours.StartShiftOne) && !string.IsNullOrWhiteSpace(workingHours.EndShiftOne))
{
startShiftOne = TimeSpan.ParseExact(workingHours.StartShiftOne, @"hh\:mm", null);
endShiftOne = TimeSpan.ParseExact(workingHours.EndShiftOne, @"hh\:mm", null);
workingHours.HasShiftOne = true;
if (!string.IsNullOrWhiteSpace(workingHours.RestTime))
{
try
{
restTime = TimeSpan.ParseExact(workingHours.RestTime, @"hh\:mm", null);
workingHours.HasRestTime = true;
}
catch (Exception e)
{
return OperationResult.Failure("فرمت ساعت استراحت اشتباه وارد شده است");
}
}
}
if (!string.IsNullOrWhiteSpace(workingHours.StartShiftTwo) &&
!string.IsNullOrWhiteSpace(workingHours.EndShiftTwo))
{
workingHours.HasRestTime = false;
workingHours.HasShiftTow = true;
startShiftTwo = TimeSpan.ParseExact(workingHours.StartShiftTwo, @"hh\:mm", null);
endShiftTwo = TimeSpan.ParseExact(workingHours.EndShiftTwo, @"hh\:mm", null);
}
}
catch (Exception)
{
return OperationResult.Failure("فرمت ساعت اشتباه وارد شده است");
}
workingHoursList.Add(new WorkingHours(startShiftOne,endShiftOne,startShiftTwo, endShiftTwo,restTime,workingHours.HasShiftOne,workingHours.HasShiftTow,workingHours.HasRestTime, workingHours.PersianDayOfWeek, workingHours.IsActiveDay));
}
if(workingHoursList.Count < 7)
return OperationResult.Failure("خطا در تعداد روز های ارسال شده");
var salarySetting = new SalaryPaymentSetting(request.HolidayWorking, request.UserId,monthlySalary, workingHoursList);
await _salaryPaymentSettingRepository.CreateAsync(salarySetting);
await _unitOfWork.SaveChangesAsync();
return OperationResult.Success();
}
}
public record CreateSalarySettingsCommand(bool HolidayWorking, long UserId, string? MonthlySalary, List<WorkingHoursListDto> WorkingHoursList) : IBaseCommand;
public record WorkingHoursListDto
{
/// <summary>
/// ساعت شروع شیفت کاری
/// </summary>
public string? StartShiftOne { get; set; }
/// <summary>
/// ساعت پایان شیفت کاری
/// </summary>
public string? EndShiftOne { get; set; }
/// <summary>
/// ساعت شروع شیفت دوم کاری
/// </summary>
public string? StartShiftTwo { get; set; }
/// <summary>
/// ساعت پایان شیفت دوم کاری
/// </summary>
public string? EndShiftTwo { get; set; }
/// <summary>
/// مدت استراحت
/// </summary>
public string? RestTime { get; set; }
/// <summary>
/// آیا مقطع مار اول دارد
/// </summary>
public bool HasShiftOne { get; set; }
/// <summary>
/// آیا مقطع کار دوم دارد
/// </summary>
public bool HasShiftTow { get; set; }
/// <summary>
/// آیا ساعت استراحت دارد
/// </summary>
public bool HasRestTime { get; set; }
/// <summary>
/// عدد روز از ماه
/// </summary>
public PersianDayOfWeek PersianDayOfWeek { get; set; }
/// <summary>
/// آیا این روز هفته
/// فعال است
/// </summary>
public bool IsActiveDay { get; set; }
}

View File

@@ -0,0 +1,120 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Application.Modules.SalaryPaymentSettings.Commands.CreateSalarySettings;
using GozareshgirProgramManager.Domain._Common;
using GozareshgirProgramManager.Domain.SalaryPaymentSettingAgg.Entities;
using GozareshgirProgramManager.Domain.SalaryPaymentSettingAgg.Repositories;
namespace GozareshgirProgramManager.Application.Modules.SalaryPaymentSettings.Commands.EditSalarySettings;
public class EditSalarySettingsCommandHandler : IBaseCommandHandler<EditSalarySettingsCommand>
{
private readonly ISalaryPaymentSettingRepository _salaryPaymentSettingRepository;
private readonly IUnitOfWork _unitOfWork;
public EditSalarySettingsCommandHandler(ISalaryPaymentSettingRepository salaryPaymentSettingRepository, IUnitOfWork unitOfWork)
{
_salaryPaymentSettingRepository = salaryPaymentSettingRepository;
_unitOfWork = unitOfWork;
}
public async Task<OperationResult> Handle(EditSalarySettingsCommand request, CancellationToken cancellationToken)
{
var getSettings =await _salaryPaymentSettingRepository.GetSalarySettingByUserId(request.UserId);
if(getSettings == null)
return OperationResult.NotFound("یافت نشد");
if(string.IsNullOrWhiteSpace(request.MonthlySalary))
return OperationResult.Failure("حقوق ماهانه وارد نشده اشت");
double monthlySalary = 0;
try
{
monthlySalary = request.MonthlySalary.MoneyToDouble();
}
catch (Exception e)
{
return OperationResult.Failure("مبلغ حقوق به درستی وارد نشده است");
}
if (monthlySalary == 0)
return OperationResult.Failure("مبلغ حقوق به درستی وارد نشده است");
var workingHoursList = new List<WorkingHours>();
foreach (var workingHours in request.WorkingHoursList)
{
var startShiftOne = new TimeSpan();
var endShiftOne = new TimeSpan();
var startShiftTwo = new TimeSpan();
var endShiftTwo = new TimeSpan();
var restTime = new TimeSpan();
workingHours.HasShiftOne = false;
workingHours.HasRestTime = false;
workingHours.HasShiftTow = false;
try
{
if (!string.IsNullOrWhiteSpace(workingHours.StartShiftOne) && !string.IsNullOrWhiteSpace(workingHours.EndShiftOne))
{
startShiftOne = TimeSpan.ParseExact(workingHours.StartShiftOne, @"hh\:mm", null);
endShiftOne = TimeSpan.ParseExact(workingHours.EndShiftOne, @"hh\:mm", null);
workingHours.HasShiftOne = true;
if (!string.IsNullOrWhiteSpace(workingHours.RestTime))
{
try
{
restTime = TimeSpan.ParseExact(workingHours.RestTime, @"hh\:mm", null);
workingHours.HasRestTime = true;
}
catch (Exception e)
{
return OperationResult.Failure("فرمت ساعت استراحت اشتباه وارد شده است");
}
}
}
if (!string.IsNullOrWhiteSpace(workingHours.StartShiftTwo) &&
!string.IsNullOrWhiteSpace(workingHours.EndShiftTwo))
{
workingHours.HasRestTime = false;
workingHours.HasShiftTow = true;
startShiftTwo = TimeSpan.ParseExact(workingHours.StartShiftTwo, @"hh\:mm", null);
endShiftTwo = TimeSpan.ParseExact(workingHours.EndShiftTwo, @"hh\:mm", null);
}
}
catch (Exception)
{
return OperationResult.Failure("فرمت ساعت اشتباه وارد شده است");
}
workingHoursList.Add(new WorkingHours(startShiftOne, endShiftOne, startShiftTwo, endShiftTwo, restTime, workingHours.HasShiftOne, workingHours.HasShiftTow, workingHours.HasRestTime, workingHours.PersianDayOfWeek, workingHours.IsActiveDay));
}
getSettings.Edit(request.HolidayWorking,monthlySalary, workingHoursList);
await _unitOfWork.SaveChangesAsync();
return OperationResult.Success();
}
}
public record EditSalarySettingsCommand(long UserId, bool HolidayWorking,string? MonthlySalary, List<WorkingHoursListDto> WorkingHoursList) : IBaseCommand;

View File

@@ -0,0 +1,88 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Application.Modules.SalaryPaymentSettings.Commands.CreateSalarySettings;
using GozareshgirProgramManager.Domain._Common;
using Microsoft.EntityFrameworkCore;
using static Microsoft.EntityFrameworkCore.DbLoggerCategory.Database;
namespace GozareshgirProgramManager.Application.Modules.SalaryPaymentSettings.Queries.GetSalarySettingToEdit;
public class GetSalarySettingToEditQueryHandler : IBaseQueryHandler<GetSalarySettingToEditQuery, GetSalarySettingToEditResponse>
{
private readonly IProgramManagerDbContext _context;
public GetSalarySettingToEditQueryHandler(IProgramManagerDbContext context)
{
_context = context;
}
public async Task<OperationResult<GetSalarySettingToEditResponse>> Handle(GetSalarySettingToEditQuery request, CancellationToken cancellationToken)
{
var user =await _context.Users.FirstOrDefaultAsync(x => x.Id == request.UserId);
if(user == null)
return OperationResult<GetSalarySettingToEditResponse>.NotFound("کاربر یافت نشد");
var editSalarySettingsList = await _context.SalaryPaymentSettings
.Select(x => new GetSalarySettingToEdit()
{
Id = x.Id,
HolidayWorking = x.HolidayWorking,
UserId = x.UserId,
MonthlySalary = x.MonthlySalary.ToMoney(),
WorkingHoursList = x.WorkingHoursList.Select(wh => new WorkingHoursListDto
{
StartShiftOne =wh.HasShiftOne ? wh.StartShiftOne.ToString(@"hh\:mm") : null,
EndShiftOne = wh.HasShiftOne ? wh.EndShiftOne.ToString(@"hh\:mm") : null,
StartShiftTwo = wh.HasShiftTow ? wh.StartShiftTwo.ToString(@"hh\:mm") : null,
EndShiftTwo = wh.HasShiftTow ? wh.EndShiftTwo.ToString(@"hh\:mm") :null,
RestTime = wh.HasRestTime ? wh.RestTime.ToString(@"hh\:mm") : null,
HasRestTime = wh.HasRestTime,
HasShiftOne = wh.HasShiftOne,
HasShiftTow = wh.HasShiftTow,
PersianDayOfWeek = wh.PersianDayOfWeek,
IsActiveDay = wh.IsActiveDay
}).OrderBy(wh=>wh.PersianDayOfWeek).ToList(),
}).FirstOrDefaultAsync(x => x.UserId == request.UserId);
var response = new GetSalarySettingToEditResponse(request.UserId,user.FullName,editSalarySettingsList);
return OperationResult<GetSalarySettingToEditResponse>.Success(response);
}
}
public record GetSalarySettingToEditResponse(long UserId, string FullName, GetSalarySettingToEdit EditSalarySettingsList);
public record GetSalarySettingToEditQuery(long UserId) :IBaseQuery<GetSalarySettingToEditResponse>;
public record GetSalarySettingToEdit
{
/// <summary>
/// ای دی کاربر
/// </summary>
public long Id { get; set; }
/// <summary>
/// آی دی کاربر
/// </summary>
public long UserId { get; set; }
/// <summary>
/// کار در تعطیلات رسمی
/// </summary>
public bool HolidayWorking { get; set; }
/// <summary>
/// حقوق ماهانه
/// </summary>
public string MonthlySalary { get; set; }
/// <summary>
/// لیست روزهای هفته و ساعات کاری
/// </summary>
public List<WorkingHoursListDto> WorkingHoursList { get; set; }
}

View File

@@ -0,0 +1,111 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Domain._Common;
using GozareshgirProgramManager.Domain.SalaryPaymentSettingAgg.Enums;
using Microsoft.EntityFrameworkCore;
namespace GozareshgirProgramManager.Application.Modules.SalaryPaymentSettings.Queries.GetUserListWhoHaveSettings;
public class GetUserListWhoHaveSettingsQueryHandler : IBaseQueryHandler<GetUserListWhoHaveSettingsQuery, GetUserListWhoHaveSettingsResponse>
{
private readonly IProgramManagerDbContext _context;
public GetUserListWhoHaveSettingsQueryHandler(IProgramManagerDbContext context)
{
_context = context;
}
public async Task<OperationResult<GetUserListWhoHaveSettingsResponse>> Handle(GetUserListWhoHaveSettingsQuery request, CancellationToken cancellationToken)
{
var query = await (
from u in _context.Users
join s in _context.SalaryPaymentSettings
on u.Id equals s.UserId into settingsGroup
select new GetUserWhoHaveSettingsDto
{
UserId = u.Id,
FullName = u.FullName,
HasSalarySettings = settingsGroup.Any(),
MontlySalary = settingsGroup.Any() ? settingsGroup.FirstOrDefault().MonthlySalary.ToMoney() : "",
WeeklyWorkingTimeAvrageInt = settingsGroup
.SelectMany(x => x.WorkingHoursList)
.Sum(w => (int?)w.ShiftDurationInMinutes) ?? 0
}
).ToListAsync(cancellationToken);
if (!string.IsNullOrWhiteSpace(request.FullName))
query = query.Where(x => x.FullName.Contains(request.FullName)).ToList();
if (request.HasSalarySettings != HasSalarySettings.Default)
{
bool hasSettings = request.HasSalarySettings == HasSalarySettings.HasSettings;
query = query.Where(x => x.HasSalarySettings == hasSettings).ToList();
}
var operationQuery = query.Select(user =>
{
var weeklyWorkingTimeAvrage = user.WeeklyWorkingTimeAvrageInt.ConvertIntDurationToHoursAndMinutes();
return new GetUserWhoHaveSettingsDto
{
UserId = user.UserId,
FullName = user.FullName,
HasSalarySettings = user.HasSalarySettings,
MontlySalary = user.MontlySalary,
WeeklyWorkingTimeAvrageInt = user.WeeklyWorkingTimeAvrageInt,
WeeklyWorkingTimeAvrage = weeklyWorkingTimeAvrage
};
}).ToList();
var response = new GetUserListWhoHaveSettingsResponse(operationQuery);
return OperationResult<GetUserListWhoHaveSettingsResponse>.Success(response);
}
}
public record GetUserListWhoHaveSettingsQuery(string? FullName, HasSalarySettings HasSalarySettings) : IBaseQuery<GetUserListWhoHaveSettingsResponse>;
public record GetUserListWhoHaveSettingsResponse(List<GetUserWhoHaveSettingsDto> UserList);
public record GetUserWhoHaveSettingsDto
{
/// <summary>
/// نام و نام خانوادگی
/// </summary>
public string FullName { get; set; }
/// <summary>
/// آی دی کاربر
/// </summary>
public long UserId { get; set; }
/// <summary>
/// آیا کاربر دارای تنظیمات حقوق است؟
/// </summary>
public bool HasSalarySettings { get; set; }
/// <summary>
/// میانگین ساعت کار
/// عددی مجموع دقایق
/// </summary>
public int WeeklyWorkingTimeAvrageInt { get; set; }
/// <summary>
/// میانگین ساعت کار در هفته
/// رشته ساعت و دقیقه
/// </summary>
public string WeeklyWorkingTimeAvrage { get; set; }
/// <summary>
/// حقوق ماهانه
/// </summary>
public string MontlySalary { get; set; }
}

View File

@@ -0,0 +1,7 @@
namespace GozareshgirProgramManager.Application.Modules.Skills.DTOs;
public class SkillDto
{
public Guid Id { get; set; }
public string Name { get; set; }
}

View File

@@ -0,0 +1,33 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Application.Modules.Skills.DTOs;
using Microsoft.EntityFrameworkCore;
namespace GozareshgirProgramManager.Application.Modules.Skills.Queries.GetSkillList;
public record GetSkillListQuery() : IBaseQuery<GetSkillListResponse>;
public record GetSkillListResponse(List<SkillDto> Skills);
public class GetSkillListCommandHandler:IBaseQueryHandler<GetSkillListQuery,GetSkillListResponse>
{
private readonly IProgramManagerDbContext _dbContext;
public GetSkillListCommandHandler(IProgramManagerDbContext dbContext)
{
_dbContext = dbContext;
}
public async Task<OperationResult<GetSkillListResponse>> Handle(GetSkillListQuery request, CancellationToken cancellationToken)
{
var skills =await _dbContext.Skills
.Select(s => new SkillDto
{
Id = s.Id,
Name = s.Name
})
.ToListAsync(cancellationToken: cancellationToken);
var response = new GetSkillListResponse(skills);
return OperationResult<GetSkillListResponse>.Success(response);
}
}

View File

@@ -0,0 +1,5 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
namespace GozareshgirProgramManager.Application.Modules.Users.Commands.CreateUser;
public record CreateUserCommand(string FullName, string UserName, string Password, string Mobile, string? Email, long? AccountId, List<long> Roles) : IBaseCommand;

View File

@@ -0,0 +1,43 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Domain._Common;
using GozareshgirProgramManager.Domain.RoleUserAgg;
using GozareshgirProgramManager.Domain.UserAgg.Entities;
using GozareshgirProgramManager.Domain.UserAgg.Repositories;
namespace GozareshgirProgramManager.Application.Modules.Users.Commands.CreateUser;
public class CreateUserCommandHandler : IBaseCommandHandler<CreateUserCommand>
{
private readonly IUserRepository _userRepository;
private readonly IUnitOfWork _unitOfWork;
public CreateUserCommandHandler(IUnitOfWork unitOfWork, IUserRepository userRepository)
{
_unitOfWork = unitOfWork;
_userRepository = userRepository;
}
public async Task<OperationResult> Handle(CreateUserCommand request, CancellationToken cancellationToken)
{
#region CustomValidation
if (_userRepository.Exists(x => x.FullName == request.FullName))
return OperationResult.Failure("نام و خانوادگی تکراری است");
if (_userRepository.Exists(x => x.UserName == request.UserName))
return OperationResult.Failure("نام کاربری تکراری است");
if (_userRepository.Exists(x=> !string.IsNullOrWhiteSpace(x.Mobile) && x.Mobile == request.Mobile))
return OperationResult.ValidationError("این شماره همراه قبلا به فرد دیگری اختصاص داده شده است");
if(request.AccountId == 0)
return OperationResult.Failure("آی دی اکانت، از سمت گزارشگیر صفر است");
#endregion
var userRoles = request.Roles.Where(x => x > 0).Select(x => new RoleUser(x)).ToList() ;
var create = new User(request.FullName, request.UserName, request.Password, request.Mobile,
request.Email, request?.AccountId, userRoles);
await _userRepository.CreateAsync(create);
await _unitOfWork.SaveChangesAsync(cancellationToken);
return OperationResult.Success();
}
}

View File

@@ -0,0 +1,24 @@
using FluentValidation;
namespace GozareshgirProgramManager.Application.Modules.Users.Commands.CreateUser;
public class CreateUserCommandValidators : AbstractValidator<CreateUserCommand>
{
public CreateUserCommandValidators()
{
RuleFor(x => x.FullName)
.NotEmpty()
.NotNull()
.WithMessage("نام و نام خانوادگی نمی تواند خالی باشد");
RuleFor(x => x.Mobile)
.NotNull().NotEmpty().WithMessage("شماره همراه نمی تواند خالی باشد");
RuleFor(x=>x.Mobile)
.Length(11).WithMessage("طول شماره همراه می بایست 11 رقم باشد");
RuleFor(x => x.UserName)
.NotEmpty().NotNull().WithMessage("نام کاربری نمیتوان خالی باشد");
}
}

View File

@@ -0,0 +1,34 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Domain._Common;
using GozareshgirProgramManager.Domain.RoleUserAgg;
using GozareshgirProgramManager.Domain.UserAgg.Repositories;
namespace GozareshgirProgramManager.Application.Modules.Users.Commands.EditUser;
public class EditUserCommandHandler :IBaseCommandHandler<EditUserCommand>
{
private readonly IUserRepository _userRepository;
private readonly IUnitOfWork _unitOfWork;
public EditUserCommandHandler(IUserRepository userRepository, IUnitOfWork unitOfWork)
{
_userRepository = userRepository;
_unitOfWork = unitOfWork;
}
public async Task<OperationResult> Handle(EditUserCommand request, CancellationToken cancellationToken)
{
var user = await _userRepository.GetByGozareshgirAccountId(request.AccountId);
if (user != null)
{
var userRoles = request.Roles.Where(x => x > 0).Select(x => new RoleUser(x)).ToList();
user.Edit(request.FullName, request.UserName, request.Mobile, userRoles, request.IsActive);
await _unitOfWork.SaveChangesAsync();
}
return OperationResult.Success();
}
}
public record EditUserCommand(string FullName, string UserName, string Mobile,long AccountId, List<long> Roles, bool IsActive) : IBaseCommand;

View File

@@ -0,0 +1,11 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using MediatR;
namespace GozareshgirProgramManager.Application.Modules.Users.Commands.LoginUser;
/// <summary>
/// دستور ورود کاربر به سیستم
/// </summary>
public record LoginUserCommand(long UserId) : IBaseCommand<LoginResponse>;

View File

@@ -0,0 +1,98 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Domain._Common;
using GozareshgirProgramManager.Domain.UserAgg.Entities;
using GozareshgirProgramManager.Domain.UserAgg.Repositories;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace GozareshgirProgramManager.Application.Modules.Users.Commands.LoginUser;
/// <summary>
/// Handler برای ورود کاربر به سیستم
/// </summary>
public class LoginUserCommandHandler : IRequestHandler<LoginUserCommand, OperationResult<LoginResponse>>
{
private readonly IUserRepository _userRepository;
private readonly IUserRefreshTokenRepository _refreshTokenRepository;
private readonly IAuthHelper _authHelper;
private readonly IUnitOfWork _unitOfWork;
public LoginUserCommandHandler(
IUserRepository userRepository,
IAuthHelper authHelper,
IUnitOfWork unitOfWork, IUserRefreshTokenRepository refreshTokenRepository)
{
_userRepository = userRepository;
_authHelper = authHelper;
_unitOfWork = unitOfWork;
_refreshTokenRepository = refreshTokenRepository;
}
public async Task<OperationResult<LoginResponse>> Handle(LoginUserCommand request, CancellationToken cancellationToken)
{
// اعتبارسنجی
if (request.UserId <= 0)
{
return OperationResult<LoginResponse>.Failure("شناسه کاربری معتبر نیست", ErrorType.BadRequest);
}
// یافتن کاربر
var user = await _userRepository.GetUserWithRolesByIdAsync(request.UserId, cancellationToken);
if (user == null)
{
return OperationResult<LoginResponse>.Failure("کاربر یافت نشد", ErrorType.NotFound);
}
// بررسی فعال بودن کاربر
if (!user.IsActive)
{
return OperationResult<LoginResponse>.Failure("حساب کاربری غیرفعال است", ErrorType.Unauthorized);
}
// تولید توکن‌ها با استفاده از AuthHelper
var roles = user.RoleUser
.Select(r => r.RoleId.ToString()).ToList();
var session = _authHelper.SignIn(
user.Id,
user.UserName,
user.FullName,
user.AccountId??0,
roles);
// دریافت اطلاعات درخواست با استفاده از AuthHelper
var ipAddress = _authHelper.GetClientIpAddress();
var userAgent = _authHelper.GetUserAgent();
// ذخیره Refresh Token در دیتابیس
//user.AddRefreshToken(refreshToken, refreshTokenExpiration, ipAddress, userAgent);
var refreshTokenEntity = new UserRefreshToken(
user.Id,
session.RefreshToken,
session.RefreshTokenExpiration,
ipAddress,
userAgent);
await _refreshTokenRepository.CreateAsync(refreshTokenEntity);
await _unitOfWork.SaveChangesAsync(cancellationToken);
// ساخت پاسخ (RefreshToken به فرانت داده نمی‌شود)
var response = new LoginResponse
{
AccessToken = session.AccessToken,
ExpiresAt = session.AccessTokenExpiration,
UserId = user.Id,
FullName = user.FullName,
UserName = user.UserName,
Roles = user.RoleUser.Select(r => r.RoleId).ToList()
};
return OperationResult<LoginResponse>.Success(response);
}
}

View File

@@ -0,0 +1,11 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using MediatR;
namespace GozareshgirProgramManager.Application.Modules.Users.Commands.RefreshUserToken;
/// <summary>
/// دستور تازه‌سازی توکن دسترسی کاربر
/// </summary>
public record RefreshUserTokenCommand() : IBaseCommand;

View File

@@ -0,0 +1,86 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Domain._Common;
using Microsoft.EntityFrameworkCore;
using MediatR;
namespace GozareshgirProgramManager.Application.Modules.Users.Commands.RefreshUserToken;
/// <summary>
/// Handler برای تازه‌سازی توکن دسترسی
/// </summary>
public class RefreshUserTokenCommandHandler : IBaseCommandHandler<RefreshUserTokenCommand>
{
private readonly IAuthHelper _authHelper;
private readonly IProgramManagerDbContext _context;
public RefreshUserTokenCommandHandler(
IAuthHelper authHelper,
IProgramManagerDbContext context)
{
_authHelper = authHelper;
_context = context;
}
public async Task<OperationResult> Handle(RefreshUserTokenCommand request, CancellationToken cancellationToken)
{
var refreshToken = _authHelper.GetRefreshTokenFromCookie();
// یافتن کاربر و Refresh Token فعال از دیتابیس
var user = await _context.Users
.Include(u => u.RefreshTokens)
.Include(u => u.RoleUser)
.FirstOrDefaultAsync(u => u.RefreshTokens.Any(r=>r.Token ==refreshToken), cancellationToken);
if (user == null)
{
return OperationResult<AccessTokenResponse>.Failure("کاربر یافت نشد", ErrorType.NotFound);
}
// بررسی فعال بودن کاربر
if (!user.IsActive)
{
return OperationResult<AccessTokenResponse>.Failure("حساب کاربری غیرفعال است", ErrorType.Unauthorized);
}
// پیدا کردن Refresh Token فعال
var activeRefreshToken = user.RefreshTokens
.FirstOrDefault(rt => rt.Token == refreshToken && rt.IsActive);
if (activeRefreshToken == null)
{
return OperationResult<AccessTokenResponse>.Failure(
"نشست شما منقضی شده است. لطفاً دوباره وارد شوید",
ErrorType.Unauthorized);
}
if (!activeRefreshToken.IsActive|| activeRefreshToken.IsRevoked||activeRefreshToken.IsExpired)
{
return OperationResult<AccessTokenResponse>.Failure(
"نشست شما منقضی شده است. لطفاً دوباره وارد شوید",
ErrorType.Unauthorized);
}
// تولید Access Token جدید با استفاده از AuthHelper
var roles = user.RoleUser.Select(r => r.RoleId.ToString()).ToList();
var newAccessToken = _authHelper.GenerateAccessToken(
user.Id,
user.UserName,
user.FullName,
user.AccountId,
roles);
var response = new AccessTokenResponse
{
AccessToken = newAccessToken,
ExpiresAt = DateTime.UtcNow.AddMinutes(30),
UserId = user.Id,
FullName = user.FullName,
UserName = user.UserName
};
return OperationResult<AccessTokenResponse>.Success(response);
}
}

View File

@@ -0,0 +1,11 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using MediatR;
namespace GozareshgirProgramManager.Application.Modules.Users.Commands.SignOutUser;
/// <summary>
/// دستور خروج کاربر از سیستم
/// </summary>
public record SignOutUserCommand(string RefreshToken) : IBaseCommand;

View File

@@ -0,0 +1,68 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Domain._Common;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace GozareshgirProgramManager.Application.Modules.Users.Commands.SignOutUser;
/// <summary>
/// Handler برای خروج کاربر از سیستم
/// </summary>
public class SignOutUserCommandHandler : IBaseCommandHandler<SignOutUserCommand>
{
private readonly IAuthHelper _authHelper;
private readonly IProgramManagerDbContext _context;
private readonly IUnitOfWork _unitOfWork;
public SignOutUserCommandHandler(
IAuthHelper _authHelper,
IProgramManagerDbContext context,
IUnitOfWork unitOfWork)
{
this._authHelper = _authHelper;
_context = context;
_unitOfWork = unitOfWork;
}
public async Task<OperationResult> Handle(SignOutUserCommand request, CancellationToken cancellationToken)
{
// دریافت UserId از Claims با استفاده از AuthHelper
var userId = _authHelper.GetCurrentUserId();
if (!userId.HasValue)
{
return OperationResult.Failure("کاربر احراز هویت نشده است", ErrorType.Unauthorized);
}
if (string.IsNullOrEmpty(request.RefreshToken))
{
return OperationResult.Failure("توکن تازه‌سازی یافت نشد", ErrorType.BadRequest);
}
// یافتن کاربر
var user = await _context.Users
.Include(u => u.RefreshTokens)
.FirstOrDefaultAsync(u => u.Id == userId.Value, cancellationToken);
if (user == null)
{
return OperationResult.Failure("کاربر یافت نشد", ErrorType.NotFound);
}
try
{
// لغو Refresh Token
user.RevokeRefreshToken(request.RefreshToken);
await _unitOfWork.SaveChangesAsync(cancellationToken);
_authHelper.SignOut();
return OperationResult.Success();
}
catch (InvalidOperationException ex)
{
return OperationResult.Failure(ex.Message, ErrorType.BadRequest);
}
}
}

View File

@@ -0,0 +1,10 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
namespace GozareshgirProgramManager.Application.Modules.Users.Commands.SsoLogin;
/// <summary>
/// دستور ورود از طریق SSO با استفاده از توکن JWT
/// </summary>
public record SsoLoginCommand(string Token) : IBaseCommand<LoginResponse>;

View File

@@ -0,0 +1,115 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Domain._Common;
using GozareshgirProgramManager.Domain.UserAgg.Entities;
using GozareshgirProgramManager.Domain.UserAgg.Repositories;
using MediatR;
namespace GozareshgirProgramManager.Application.Modules.Users.Commands.SsoLogin;
/// <summary>
/// Handler برای ورود از طریق SSO با استفاده از JWT Token
/// </summary>
public class SsoLoginCommandHandler : IRequestHandler<SsoLoginCommand, OperationResult<LoginResponse>>
{
private readonly IUserRepository _userRepository;
private readonly IUserRefreshTokenRepository _refreshTokenRepository;
private readonly IAuthHelper _authHelper;
private readonly IUnitOfWork _unitOfWork;
public SsoLoginCommandHandler(
IUserRepository userRepository,
IAuthHelper authHelper,
IUnitOfWork unitOfWork,
IUserRefreshTokenRepository refreshTokenRepository)
{
_userRepository = userRepository;
_authHelper = authHelper;
_unitOfWork = unitOfWork;
_refreshTokenRepository = refreshTokenRepository;
}
public async Task<OperationResult<LoginResponse>> Handle(SsoLoginCommand request, CancellationToken cancellationToken)
{
// اعتبارسنجی
if (string.IsNullOrWhiteSpace(request.Token))
{
return OperationResult<LoginResponse>.Failure("توکن SSO معتبر نیست", ErrorType.BadRequest);
}
// اعتبارسنجی توکن و استخراج Claims
var principal = _authHelper.ValidateToken(request.Token);
if (principal == null)
{
return OperationResult<LoginResponse>.Failure("توکن SSO نامعتبر یا منقضی شده است", ErrorType.Unauthorized);
}
// استخراج AccountId از Claims
var accountIdClaim = principal.FindFirst("AccountId")?.Value;
if (string.IsNullOrEmpty(accountIdClaim) || !long.TryParse(accountIdClaim, out var accountId))
{
return OperationResult<LoginResponse>.Failure("AccountId در توکن یافت نشد", ErrorType.BadRequest);
}
// یافتن کاربر بر اساس AccountId
var user = await _userRepository.GetByGozareshgirAccountId(accountId);
if (user == null)
{
return OperationResult<LoginResponse>.Failure("کاربر با AccountId مشخص شده یافت نشد", ErrorType.NotFound);
}
// بررسی فعال بودن کاربر
if (!user.IsActive)
{
return OperationResult<LoginResponse>.Failure("حساب کاربری غیرفعال است", ErrorType.Unauthorized);
}
// بارگذاری نقش‌های کاربر
user = await _userRepository.GetUserWithRolesByIdAsync(user.Id, cancellationToken);
if (user == null)
{
return OperationResult<LoginResponse>.Failure("خطا در بارگذاری اطلاعات کاربر", ErrorType.InternalServerError);
}
// تولید توکن‌های جدید برای کاربر
var roles = user.RoleUser
.Select(r => r.RoleId.ToString()).ToList();
var session = _authHelper.SignIn(
user.Id,
user.UserName,
user.FullName,
user.AccountId ?? 0,
roles);
// دریافت اطلاعات درخواست
var ipAddress = _authHelper.GetClientIpAddress();
var userAgent = _authHelper.GetUserAgent();
// ذخیره Refresh Token در دیتابیس
var refreshTokenEntity = new UserRefreshToken(
user.Id,
session.RefreshToken,
session.RefreshTokenExpiration,
ipAddress,
userAgent);
await _refreshTokenRepository.CreateAsync(refreshTokenEntity);
await _unitOfWork.SaveChangesAsync(cancellationToken);
// ساخت پاسخ
var response = new LoginResponse
{
AccessToken = session.AccessToken,
ExpiresAt = session.AccessTokenExpiration,
UserId = user.Id,
FullName = user.FullName,
UserName = user.UserName,
Roles = user.RoleUser.Select(r => r.RoleId).ToList()
};
return OperationResult<LoginResponse>.Success(response);
}
}

View File

@@ -0,0 +1,124 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using Microsoft.EntityFrameworkCore;
namespace GozareshgirProgramManager.Application.Modules.Users.Queries.GetSingleUser;
public class GetSingleUserQueryHandler : IBaseQueryHandler<GetSingleUserQuery, GetSingleUserResponse>
{
private readonly IProgramManagerDbContext _context;
public GetSingleUserQueryHandler(IProgramManagerDbContext context)
{
_context = context;
}
public async Task<OperationResult<GetSingleUserResponse>> Handle(GetSingleUserQuery request, CancellationToken cancellationToken)
{
if (!string.IsNullOrWhiteSpace(request.accountId))
{
long accountId = 0;
try
{
accountId = Convert.ToInt64(request.accountId);
}
catch (Exception e)
{
return (OperationResult<GetSingleUserResponse>)OperationResult.Failure("فقط عدد وارد کنید");
}
if (accountId > 0)
{
var user = await _context.Users
.FirstOrDefaultAsync(x => x.AccountId == accountId);
if(user != null)
{
List<long> roles = user.RoleUser.Select(x => x.RoleId).ToList();
var response = new GetSingleUserResponse
{
FullName = user.FullName,
UserName = user.UserName,
ProfilePhotoPath = user.ProfilePhotoPath,
Mobile = user.Mobile,
IsActive = user.IsActive,
AccountId = user.AccountId,
Roles = roles,
RoleListDto = await _context.Roles.Where(x => roles.Contains(x.Id)).Select(x=> new RoleListDto()
{
RoleName = x.RoleName,
RoleId = x.Id,
Permissions = x.Permissions.Select(x=>x.Code).ToList()
}).ToListAsync(),
};
return OperationResult<GetSingleUserResponse>.Success(response);
}
else
{
return (OperationResult<GetSingleUserResponse>)OperationResult.NotFound("کاربر یافت نشد");
}
}
}
return (OperationResult<GetSingleUserResponse>)OperationResult.Failure("آی دی اکانت گزارشگیر پر نشده است");
}
}
public record GetSingleUserResponse
{
/// <summary>
/// نام و نام خانوادگی
/// </summary>
public string FullName { get; set; }
/// <summary>
/// نام کاربری
/// </summary>
public string UserName { get; set; }
/// <summary>
/// مسیر عکس پروفایل
/// </summary>
public string ProfilePhotoPath { get; set; }
/// <summary>
/// شماره موبایل
/// </summary>
public string Mobile { get; set; }
/// <summary>
/// فعال/غیر فعال بودن یوزر
/// </summary>
public bool IsActive { get; set; }
/// <summary>
/// آی دی کاربر در گزارشگیر
/// </summary>
public long? AccountId { get; set; }
/// <summary>
/// نقش ها
/// </summary>
public List<long> Roles { get; set; }
public List<RoleListDto> RoleListDto { get; set; }
};
public record RoleListDto
{
public string RoleName { get; set; }
public long RoleId { get; set; }
public List<int> Permissions { get; set; }
}
public record GetSingleUserQuery(string? accountId) : IBaseQuery<GetSingleUserResponse>;

View File

@@ -0,0 +1,48 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Application.Modules.Users.Queries.GetSingleUser;
using Microsoft.EntityFrameworkCore;
namespace GozareshgirProgramManager.Application.Modules.Users.Queries.GetUserSelectList;
public class GetUserSelectListQueryHandler : IBaseQueryHandler<GetUserSelectListQuery, GetUserSelectListResponse>
{
private readonly IProgramManagerDbContext _context;
public GetUserSelectListQueryHandler(IProgramManagerDbContext context)
{
_context = context;
}
public async Task<OperationResult<GetUserSelectListResponse>> Handle(GetUserSelectListQuery request, CancellationToken cancellationToken)
{
var query = await _context.Users.Select(x => new GetUserSelectListDto()
{
FullName = x.FullName,
Id = x.Id
}).ToListAsync();
var response = new GetUserSelectListResponse(query);
return OperationResult<GetUserSelectListResponse>.Success(response);
}
}
public record GetUserSelectListResponse(List<GetUserSelectListDto>? GetUserSelectListDto);
public record GetUserSelectListDto
{
/// <summary>
/// نام و نام خانوادگی
/// </summary>
public string FullName { get; set; }
/// <summary>
/// آی دی کاربر
/// </summary>
public long Id { get; set; }
}
public record GetUserSelectListQuery() : IBaseQuery<GetUserSelectListResponse>;

View File

@@ -0,0 +1,5 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
namespace GozareshgirProgramManager.Application.Modules.Users.Queries.GetUsers;
public record GetUsersQuery(string? FullName, string? UserName, string? Mobile) : IBaseQuery<GetUsersResponse>;

View File

@@ -0,0 +1,49 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using Microsoft.EntityFrameworkCore;
namespace GozareshgirProgramManager.Application.Modules.Users.Queries.GetUsers;
public class GetUsersQueryHandler :IBaseQueryHandler<GetUsersQuery, GetUsersResponse>
{
private readonly IProgramManagerDbContext _context;
public GetUsersQueryHandler(IProgramManagerDbContext context)
{
_context = context;
}
public async Task<OperationResult<GetUsersResponse>> Handle(GetUsersQuery request, CancellationToken cancellationToken)
{
var query = _context.Users.AsQueryable();
//if (request.ParentId != null)
//{
// query = query.Where(x => x.ParentId == request.ParentId);
//}
var users = await query
.Select(p => new GetUserDto()
{
FullName = p.FullName,
Mobile = p.Mobile,
UserName = p.UserName,
AccountId = p.AccountId,
IsActive = p.IsActive,
ProfilePhotoPath = p.ProfilePhotoPath,
})
.ToListAsync(cancellationToken);
var response = new GetUsersResponse(
users
);
return OperationResult<GetUsersResponse>.Success(response);
}
}

View File

@@ -0,0 +1,46 @@
namespace GozareshgirProgramManager.Application.Modules.Users.Queries.GetUsers;
public record GetUsersResponse(List<GetUserDto> User);
public record GetUserDto
{
/// <summary>
/// نام و نام خانوادگی
/// </summary>
public string FullName { get; set; }
/// <summary>
/// نام کاربری
/// </summary>
public string UserName { get; set; }
/// <summary>
/// مسیر عکس پروفایل
/// </summary>
public string ProfilePhotoPath { get; set; }
/// <summary>
/// شماره موبایل
/// </summary>
public string Mobile { get; set; }
/// <summary>
/// فعال/غیر فعال بودن یوزر
/// </summary>
public bool IsActive { get; set; }
/// <summary>
/// آی دی کاربر در گزارشگیر
/// </summary>
public long? AccountId { get; set; }
/// <summary>
/// نقش ها
/// </summary>
public List<long> Roles { get; set; }
}

View File

@@ -0,0 +1,22 @@
using System.Reflection;
using GozareshgirProgramManager.Application.Interfaces;
using Microsoft.Extensions.DependencyInjection;
namespace GozareshgirProgramManager.Application._Bootstrapper;
public static class DependencyInjection
{
public static IServiceCollection AddProgramManagerApplication(this IServiceCollection services)
{
services.AddMediatR(cfg =>
{
cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly());
cfg.LicenseKey =
"eyJhbGciOiJSUzI1NiIsImtpZCI6Ikx1Y2t5UGVubnlTb2Z0d2FyZUxpY2Vuc2VLZXkvYmJiMTNhY2I1OTkwNGQ4OWI0Y2IxYzg1ZjA4OGNjZjkiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2x1Y2t5cGVubnlzb2Z0d2FyZS5jb20iLCJhdWQiOiJMdWNreVBlbm55U29mdHdhcmUiLCJleHAiOiIxNzk2MDgzMjAwIiwiaWF0IjoiMTc2NDU5NTE2OSIsImFjY291bnRfaWQiOiIwMTlhZGExMDA0MzA3M2Y5ODhhNWY0MmJmZDdlYTE3OCIsImN1c3RvbWVyX2lkIjoiY3RtXzAxa2JkMTJ4eXNnMHB6MjR0YnhqeXA0a2p3Iiwic3ViX2lkIjoiLSIsImVkaXRpb24iOiIwIiwidHlwZSI6IjIifQ.Gl0FB9XoQMJQgzdCh9gplRirYvmkDJYXD8tfLiWkRAeQJ2zlFdoHw7btxGrUVisI_ZfSxK2kkUB_LLty2eArbeZb5Ja1_xexgzTv3CJ4TnacT0FoVOc8eLeGCbJmmXtSt6CW89XwzvV_taiSSkcdsnATNTH0MEBLqCkKw4FMpSXqrPxCgakXB-pcyeqeTO9a0DD5XjBVITGaslUUFgnBGirsLdHRgL9AYVty3EzWTBH9WBcc4dHyZ5YUDMzsIab5elc-pmOLCXAJviamG9Afsaq4N88IMjsYvq6ihw_EAsmQH1K8WNunFMN8VjwO5csHmgKJ6wajONH7kUaMWNxRrQ";
});
return services;
}
}

View File

@@ -0,0 +1,42 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace GozareshgirProgramManager.Application._Common.Behaviors;
public class TransactionBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IBaseCommand
{
private readonly IProgramManagerDbContext _dbContext;
public TransactionBehavior(IProgramManagerDbContext dbContext)
{
_dbContext = dbContext;
}
public async Task<TResponse> Handle(
TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken)
{
// اگر تراکنش فعال است، ادامه بده
if (_dbContext is not DbContext efContext)
return await next();
if (efContext.Database.CurrentTransaction != null)
return await next();
await using var transaction = await efContext.Database.BeginTransactionAsync(cancellationToken);
try
{
var response = await next();
await _dbContext.SaveChangesAsync(cancellationToken);
await transaction.CommitAsync(cancellationToken);
return response;
}
catch
{
await transaction.RollbackAsync(cancellationToken);
throw;
}
}
}

View File

@@ -0,0 +1,40 @@
using FluentValidation;
using GozareshgirProgramManager.Application._Common.Interfaces;
using MediatR;
namespace GozareshgirProgramManager.Application._Common.Behaviors;
public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IBaseCommand
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public async Task<TResponse> Handle(
TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken)
{
if (!_validators.Any())
return await next();
var context = new ValidationContext<TRequest>(request);
var validationResults = await Task.WhenAll(
_validators.Select(v => v.ValidateAsync(context, cancellationToken)));
var failures = validationResults
.SelectMany(r => r.Errors)
.Where(f => f != null)
.ToList();
if (failures.Count != 0)
throw new ValidationException(failures);
return await next();
}
}

View File

@@ -0,0 +1,19 @@
namespace GozareshgirProgramManager.Application._Common.Extensions;
public static class QueryableExtensions
{
public static IQueryable<T> ApplyPagination<T>(this IQueryable<T> query, int page, int pageSize = 30)
{
if (page <= 0) page = 1;
if (pageSize <= 0) pageSize = 10;
return query.Skip((page - 1) * pageSize).Take(pageSize);
}
public static IEnumerable<T> ApplyPagination<T>(this IEnumerable<T> source, int page, int pageSize = 30)
{
if (page <= 0) page = 1;
if (pageSize <= 0) pageSize = 10;
return source.Skip((page - 1) * pageSize).Take(pageSize);
}
}

View File

@@ -0,0 +1,114 @@
using System.Security.Claims;
namespace GozareshgirProgramManager.Application._Common.Interfaces;
/// <summary>
/// رابط کمکی برای کار با JWT و HttpContext
/// این interface فقط متدهای helper دارد و هیچ عملیات دیتابیسی ندارد
/// </summary>
public interface IAuthHelper
{
// ==================== Token Generation ====================
LoginSession SignIn(long userId, string userName, string fullName, long accountId, List<string> roles);
/// <summary>
/// تولید Access Token
/// </summary>
string GenerateAccessToken(long userId, string userName, string fullName, long? accountId, List<string> roles);
/// <summary>
/// تولید Refresh Token
/// </summary>
string GenerateRefreshToken();
/// <summary>
/// دریافت تاریخ انقضای Refresh Token
/// </summary>
DateTime GetRefreshTokenExpiration();
// ==================== Token Validation ====================
/// <summary>
/// اعتبارسنجی توکن و استخراج Claims
/// </summary>
ClaimsPrincipal? ValidateToken(string token);
/// <summary>
/// اعتبارسنجی توکن منقضی شده (بدون چک زمان انقضا)
/// </summary>
ClaimsPrincipal? ValidateExpiredToken(string token);
/// <summary>
/// استخراج UserId از توکن (حتی اگر منقضی شده باشد)
/// </summary>
long? GetUserIdFromToken(string token);
// ==================== HttpContext Helpers ====================
/// <summary>
/// دریافت IP Address کاربر جاری
/// </summary>
string? GetClientIpAddress();
/// <summary>
/// دریافت User Agent کاربر جاری
/// </summary>
string? GetUserAgent();
/// <summary>
/// دریافت Refresh Token از Cookie
/// </summary>
string? GetRefreshTokenFromCookie();
// ==================== Current User Claims ====================
/// <summary>
/// بررسی احراز هویت کاربر جاری
/// </summary>
bool IsAuthenticated();
/// <summary>
/// دریافت شناسه کاربر جاری از Claims
/// </summary>
long? GetCurrentUserId();
/// <summary>
/// دریافت نام کاربری جاری از Claims
/// </summary>
string? GetCurrentUserName();
/// <summary>
/// دریافت نام کامل کاربر جاری از Claims
/// </summary>
string? GetCurrentFullName();
/// <summary>
/// دریافت AccountId کاربر جاری از Claims
/// </summary>
long? GetCurrentAccountId();
/// <summary>
/// دریافت نقش‌های کاربر جاری از Claims
/// </summary>
List<string> GetCurrentUserRoles();
// ==================== Role Checking ====================
/// <summary>
/// بررسی دسترسی کاربر به نقش خاص
/// </summary>
bool HasRole(string roleName);
/// <summary>
/// بررسی دسترسی کاربر به یکی از نقش‌ها
/// </summary>
bool HasAnyRole(params string[] roleNames);
void SignOut();
}
public class LoginSession
{
public string AccessToken { get; set; }
public string RefreshToken { get; set; }
public DateTime RefreshTokenExpiration { get; set; }
public DateTime AccessTokenExpiration { get; set; }
}

Some files were not shown because too many files have changed in this diff Show More