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