Add new domain models and interfaces for project management features
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<RootNamespace>_0_Framework</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<AssemblyName>BackgroundInstitutionContract.Task</AssemblyName>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -88,6 +88,22 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CompanyManagement.Infrastru
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BackgroundInstitutionContract.Task", "BackgroundInstitutionContract\BackgroundInstitutionContract.Task\BackgroundInstitutionContract.Task.csproj", "{F78FBB92-294B-88BA-168D-F0C578B0D7D6}"
|
||||
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
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
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}.Release|Any CPU.ActiveCfg = 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
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -234,6 +262,13 @@ Global
|
||||
{BF98173C-42AF-4897-A7CB-4CACEB2B52A2} = {86921E1B-2AFA-4B8A-9403-EE16D58B5B26}
|
||||
{97E148FA-3C36-40DD-B121-D90C1C0F3B47} = {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
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {E6CFB3A7-A7C8-4E82-8F06-F750408F0BA9}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace GozareshgirProgramManager.Application.Interfaces;
|
||||
|
||||
public interface IBoardNotificationService
|
||||
{
|
||||
Task SendProjectAssignedAsync();
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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("شناسه مهارت نمیتواند خالی باشد");
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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("وضعیت بخش نامعتبر است");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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("شناسه والد نمیتواند خالی باشد");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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("تاریخ شروع برنامهریزی شده نمیتواند بعد از تاریخ پایان برنامهریزی شده باشد");
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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("سطح حذف پروژه نامعتبر است.");
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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("شناسه پروژه نمیتواند خالی باشد.");
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
@@ -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 کاراکتر باشد.");
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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("کاربر مبدا و مقصد نمیتوانند یکسان باشند");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; } = [];
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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("شناسه والد باید برای سطوح غیر از پروژه ارسال شود.");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectsList;
|
||||
|
||||
public record GetProjectsListResponse(
|
||||
List<GetProjectListDto> Projects);
|
||||
|
||||
@@ -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>>
|
||||
{
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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("شناسه پروژه نمیتواند خالی باشد.");
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
|
||||
@@ -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; }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace GozareshgirProgramManager.Application.Modules.Skills.DTOs;
|
||||
|
||||
public class SkillDto
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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("نام کاربری نمیتوان خالی باشد");
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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>;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
@@ -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>;
|
||||
@@ -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>;
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -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; }
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user