Compare commits
76 Commits
Feature/in
...
Feature/pr
| Author | SHA1 | Date | |
|---|---|---|---|
| 73e6681baa | |||
| 80a58f8cdc | |||
| c8dddabdff | |||
| 95d66c2d89 | |||
| 609daf4353 | |||
| a81e01ce2b | |||
| 2cd838a5e3 | |||
| 34bd7ba444 | |||
| 43b124664e | |||
| d2dd67343b | |||
| 3d2b5ff6bd | |||
| 209aa5912d | |||
| 340685a06c | |||
| b20a56df26 | |||
| 8d93fa4fc6 | |||
| 8f10f7057c | |||
| 00b5066f6f | |||
| abd221cb55 | |||
| 33833a408c | |||
| c2fca9f9eb | |||
| a16c20440b | |||
| 1f365f3642 | |||
| 0bfcde6a3f | |||
| 4ada29a98a | |||
| 6f64ee1ce4 | |||
| 3340edcc17 | |||
| 385a885c93 | |||
| f99f199a77 | |||
| 287b31e356 | |||
| 5f8232809a | |||
| 3c72311096 | |||
| 250d17eba2 | |||
| 5db8e7d319 | |||
|
|
3300f60845 | ||
|
|
7537cfe5b8 | ||
|
|
3c1bf7dff0 | ||
| 6909fcf715 | |||
| fe66ff5aa3 | |||
| ce305edac4 | |||
| a49b825ce9 | |||
| fb62523a23 | |||
|
|
e171a4749c | ||
| 9b6c0d4cc4 | |||
| f8126b4000 | |||
|
|
cf62d75f0e | ||
|
|
f93e59b77c | ||
|
|
8f37d9f388 | ||
| 8e72b56758 | |||
| a6e1251445 | |||
| 490a1a69d5 | |||
| 7e3ea39d5b | |||
| 66a6c411d6 | |||
|
|
b03a806dfb | ||
| 14ff0a2e59 | |||
| 58f695fe95 | |||
| b4ccacd37e | |||
| 1fef8e355a | |||
|
|
4d326b1983 | ||
| aa37ca4b28 | |||
| 45615684ed | |||
| eb9a3e52fe | |||
| 94955ea1b4 | |||
| 16c1ae04a9 | |||
| 656bb49fab | |||
| cf3f0564f9 | |||
| fb5b98bf25 | |||
| 1e733f3f20 | |||
| d855684cd7 | |||
| 9e5e8d8e5d | |||
| 9eefdd8fd1 | |||
| 2159901614 | |||
| 7ce7854091 | |||
| 4c638cbdae | |||
| 39a5918a11 | |||
| b9e271de1a | |||
| e661bc2dcb |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -362,3 +362,7 @@ MigrationBackup/
|
||||
# # Fody - auto-generated XML schema
|
||||
# FodyWeavers.xsd
|
||||
.idea
|
||||
|
||||
# Storage folder - ignore all uploaded files, thumbnails, and temporary files
|
||||
ServiceHost/Storage
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace _0_Framework.Application.PaymentGateway;
|
||||
@@ -12,18 +13,24 @@ public class SepehrPaymentGateway:IPaymentGateway
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private const long TerminalId = 99213700;
|
||||
private readonly ILogger<SepehrPaymentGateway> _logger;
|
||||
|
||||
public SepehrPaymentGateway(IHttpClientFactory httpClient)
|
||||
public SepehrPaymentGateway(IHttpClientFactory httpClient, ILogger<SepehrPaymentGateway> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_httpClient = httpClient.CreateClient();
|
||||
_httpClient.BaseAddress = new Uri("https://sepehr.shaparak.ir/Rest/V1/PeymentApi/");
|
||||
}
|
||||
|
||||
public async Task<PaymentGatewayResponse> Create(CreatePaymentGatewayRequest command, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_logger.LogInformation("Create payment started. TransactionId: {TransactionId}, Amount: {Amount}", command.TransactionId, command.Amount);
|
||||
command.ExtraData ??= new Dictionary<string, object>();
|
||||
_logger.LogInformation("Initializing extra data with FinancialInvoiceId: {FinancialInvoiceId}", command.FinancialInvoiceId);
|
||||
command.ExtraData.Add("financialInvoiceId", command.FinancialInvoiceId);
|
||||
var extraData = JsonConvert.SerializeObject(command.ExtraData);
|
||||
_logger.LogInformation("Serialized extra data payload: {Payload}", extraData);
|
||||
|
||||
var res = await _httpClient.PostAsJsonAsync("GetToken", new
|
||||
{
|
||||
TerminalID = TerminalId,
|
||||
@@ -32,21 +39,25 @@ public class SepehrPaymentGateway:IPaymentGateway
|
||||
callbackURL = command.CallBackUrl,
|
||||
payload = extraData
|
||||
}, cancellationToken: cancellationToken);
|
||||
_logger.LogInformation("Create payment request sent. StatusCode: {StatusCode}", res.StatusCode);
|
||||
// خواندن محتوای پاسخ
|
||||
var content = await res.Content.ReadAsStringAsync(cancellationToken);
|
||||
|
||||
_logger.LogInformation("Create payment response content: {Content}", content);
|
||||
|
||||
// تبدیل پاسخ JSON به آبجکت داتنت
|
||||
var json = System.Text.Json.JsonDocument.Parse(content);
|
||||
_logger.LogInformation("Create payment JSON parsed successfully.");
|
||||
|
||||
// گرفتن مقدار AccessToken
|
||||
var accessToken = json.RootElement.GetProperty("Accesstoken").ToString();
|
||||
var status = json.RootElement.GetProperty("Status").ToString();
|
||||
|
||||
_logger.LogInformation("Create payment parsed values. Status: {Status}, AccessToken: {AccessToken}", status, accessToken);
|
||||
|
||||
return new PaymentGatewayResponse
|
||||
{
|
||||
Status = status,
|
||||
IsSuccess = status == "0",
|
||||
Token = accessToken
|
||||
Token = accessToken,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -55,21 +66,24 @@ public class SepehrPaymentGateway:IPaymentGateway
|
||||
|
||||
public async Task<PaymentGatewayResponse> Verify(VerifyPaymentGateWayRequest command, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_logger.LogInformation("Verify payment started. DigitalReceipt: {DigitalReceipt}", command.DigitalReceipt);
|
||||
var res = await _httpClient.PostAsJsonAsync("Advice", new
|
||||
{
|
||||
digitalreceipt = command.DigitalReceipt,
|
||||
Tid = TerminalId,
|
||||
}, cancellationToken: cancellationToken);
|
||||
|
||||
_logger.LogInformation("Verify payment request sent. StatusCode: {StatusCode}", res.StatusCode);
|
||||
// خواندن محتوای پاسخ
|
||||
var content = await res.Content.ReadAsStringAsync(cancellationToken);
|
||||
|
||||
_logger.LogInformation("Verify payment response content: {Content}", content);
|
||||
|
||||
// تبدیل پاسخ JSON به آبجکت داتنت
|
||||
var json = System.Text.Json.JsonDocument.Parse(content);
|
||||
|
||||
|
||||
_logger.LogInformation("Verify payment JSON parsed successfully.");
|
||||
|
||||
var message = json.RootElement.GetProperty("Message").GetString();
|
||||
var status = json.RootElement.GetProperty("Status").GetString();
|
||||
_logger.LogInformation("Verify payment parsed values. Status: {Status}, Message: {Message}", status, message);
|
||||
return new PaymentGatewayResponse
|
||||
{
|
||||
Status = status,
|
||||
|
||||
@@ -64,6 +64,7 @@ public interface ISmsService
|
||||
|
||||
/// <summary>
|
||||
/// پیامک مسدودی طرف حساب
|
||||
/// قراردادهای قدیم
|
||||
/// </summary>
|
||||
/// <param name="number"></param>
|
||||
/// <param name="fullname"></param>
|
||||
@@ -74,6 +75,19 @@ public interface ISmsService
|
||||
/// <returns></returns>
|
||||
Task<(byte status, string message, int messaeId, bool isSucceded)> BlockMessage(string number, string fullname, string amount, string accountType, string id, string aprove);
|
||||
|
||||
/// <summary>
|
||||
/// پیامک مسدودی طرف حساب
|
||||
/// قرارداد های جدید
|
||||
/// </summary>
|
||||
/// <param name="number"></param>
|
||||
/// <param name="fullname"></param>
|
||||
/// <param name="amount"></param>
|
||||
/// <param name="code1"></param>
|
||||
/// <param name="code2"></param>
|
||||
/// <returns></returns>
|
||||
Task<(byte status, string message, int messaeId, bool isSucceded)> BlockMessageForElectronicContract(string number,
|
||||
string fullname,
|
||||
string amount, string code1, string code2);
|
||||
#endregion
|
||||
|
||||
#region AlarmMessage
|
||||
|
||||
@@ -75,7 +75,6 @@ public interface IAccountApplication
|
||||
void CameraLogin(CameraLoginRequest request);
|
||||
|
||||
Task<GetPmUserDto> GetPmUserAsync(long accountId);
|
||||
|
||||
}
|
||||
|
||||
public class CameraLoginRequest
|
||||
|
||||
@@ -18,7 +18,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Shared.Contracts.PmUser.Commands;
|
||||
using Shared.Contracts.PmUser.Queries;
|
||||
|
||||
@@ -48,7 +47,13 @@ public class AccountApplication : IAccountApplication
|
||||
private readonly IPmUserCommandService _pmUserCommandService;
|
||||
|
||||
public AccountApplication(IAccountRepository accountRepository, IPasswordHasher passwordHasher,
|
||||
IFileUploader fileUploader, IAuthHelper authHelper, IRoleRepository roleRepository, IWorker worker, ISmsService smsService, ICameraAccountRepository cameraAccountRepository, IPositionRepository positionRepository, IAccountLeftworkRepository accountLeftworkRepository, IWorkshopRepository workshopRepository, ISubAccountRepository subAccountRepository, ISubAccountRoleRepository subAccountRoleRepository, IWorkshopSubAccountRepository workshopSubAccountRepository, ISubAccountPermissionSubtitle1Repository accountPermissionSubtitle1Repository, IUnitOfWork unitOfWork, IPmUserQueryService pmUserQueryService, IPmUserCommandService pmUserCommandService)
|
||||
IFileUploader fileUploader, IAuthHelper authHelper, IRoleRepository roleRepository, IWorker worker,
|
||||
ISmsService smsService, ICameraAccountRepository cameraAccountRepository,
|
||||
IPositionRepository positionRepository, IAccountLeftworkRepository accountLeftworkRepository,
|
||||
IWorkshopRepository workshopRepository, ISubAccountRepository subAccountRepository,
|
||||
ISubAccountRoleRepository subAccountRoleRepository, IWorkshopSubAccountRepository workshopSubAccountRepository,
|
||||
ISubAccountPermissionSubtitle1Repository accountPermissionSubtitle1Repository, IUnitOfWork unitOfWork,
|
||||
IPmUserQueryService pmUserQueryService, IPmUserCommandService pmUserCommandService)
|
||||
{
|
||||
_authHelper = authHelper;
|
||||
_roleRepository = roleRepository;
|
||||
@@ -68,7 +73,6 @@ public class AccountApplication : IAccountApplication
|
||||
_fileUploader = fileUploader;
|
||||
_passwordHasher = passwordHasher;
|
||||
_accountRepository = accountRepository;
|
||||
|
||||
}
|
||||
|
||||
public OperationResult EditClient(EditClientAccount command)
|
||||
@@ -89,7 +93,8 @@ public class AccountApplication : IAccountApplication
|
||||
(x.Mobile == command.Mobile && x.id != command.Id)))
|
||||
return opreation.Failed("شماره موبایل تکراری است");
|
||||
if (_accountRepository.Exists(x =>
|
||||
(x.NationalCode == command.NationalCode && !string.IsNullOrWhiteSpace(x.NationalCode) && x.id != command.Id)))
|
||||
(x.NationalCode == command.NationalCode && !string.IsNullOrWhiteSpace(x.NationalCode) &&
|
||||
x.id != command.Id)))
|
||||
return opreation.Failed("کد ملی تکراری است");
|
||||
if (_accountRepository.Exists(x =>
|
||||
(x.Email == command.Email && !string.IsNullOrWhiteSpace(x.Email) && x.id != command.Id)))
|
||||
@@ -97,7 +102,8 @@ public class AccountApplication : IAccountApplication
|
||||
|
||||
var path = $"profilePhotos";
|
||||
var picturePath = _fileUploader.Upload(command.ProfilePhoto, path);
|
||||
editAccount.EditClient(command.Fullname, command.Username, command.Mobile, picturePath, command.Email, command.NationalCode);
|
||||
editAccount.EditClient(command.Fullname, command.Username, command.Mobile, picturePath, command.Email,
|
||||
command.NationalCode);
|
||||
_accountRepository.SaveChanges();
|
||||
return opreation.Succcedded();
|
||||
}
|
||||
@@ -142,8 +148,8 @@ public class AccountApplication : IAccountApplication
|
||||
if (_fileUploader != null)
|
||||
{
|
||||
picturePath = _fileUploader.Upload(command.ProfilePhoto, path);
|
||||
|
||||
}
|
||||
|
||||
var account = new Account(command.Fullname, command.Username, password, command.Mobile, command.RoleId,
|
||||
picturePath, roleName.Name, "true", "false");
|
||||
|
||||
@@ -158,7 +164,8 @@ public class AccountApplication : IAccountApplication
|
||||
if (command.UserRoles == null)
|
||||
return operation.Failed("حداقل یک نقش برای کاربر مدیریت پروژه لازم است");
|
||||
var pmUserRoles = command.UserRoles.Where(x => x > 0).ToList();
|
||||
var createPm = await _pmUserCommandService.Create(new CreatePmUserDto(command.Fullname, command.Username, account.Password, command.Mobile,
|
||||
var createPm = await _pmUserCommandService.Create(new CreatePmUserDto(command.Fullname, command.Username,
|
||||
account.Password, command.Mobile,
|
||||
null, account.id, pmUserRoles));
|
||||
if (!createPm.isSuccess)
|
||||
{
|
||||
@@ -167,7 +174,6 @@ public class AccountApplication : IAccountApplication
|
||||
}
|
||||
|
||||
|
||||
|
||||
//var url = "api/user/create";
|
||||
//var key = SecretKeys.ProgramManagerInternalApi;
|
||||
|
||||
@@ -252,29 +258,30 @@ public class AccountApplication : IAccountApplication
|
||||
// $"api/user/{account.id}",
|
||||
// key
|
||||
//);
|
||||
var userResult =await _pmUserQueryService.GetPmUserDataByAccountId(account.id);
|
||||
var userResult = await _pmUserQueryService.GetPmUserDataByAccountId(account.id);
|
||||
|
||||
if (command.UserRoles == null)
|
||||
return operation.Failed("حداقل یک نقش برای کاربر مدیریت پروژه لازم است");
|
||||
var pmUserRoles = command.UserRoles.Where(x => x > 0).ToList();
|
||||
|
||||
//اگر کاربر در پروگرام منیجر قبلا ایجاد شده
|
||||
if (userResult.Id >0)
|
||||
if (userResult.Id > 0)
|
||||
{
|
||||
if (!command.UserRoles.Any())
|
||||
{
|
||||
_unitOfWork.RollbackAccountContext();
|
||||
return operation.Failed("حداقل یک نقش باید انتخاب شود");
|
||||
}
|
||||
|
||||
var editPm =await _pmUserCommandService.Edit(new EditPmUserDto(command.Fullname, command.Username, command.Mobile, account.id, pmUserRoles,
|
||||
|
||||
var editPm = await _pmUserCommandService.Edit(new EditPmUserDto(command.Fullname, command.Username,
|
||||
command.Mobile, account.id, pmUserRoles,
|
||||
command.IsProgramManagerUser));
|
||||
if (!editPm.isSuccess)
|
||||
{
|
||||
_unitOfWork.RollbackAccountContext();
|
||||
return operation.Failed("خطا در ویرایش کاربر پروگرام منیجر");
|
||||
}
|
||||
|
||||
|
||||
//var parameters = new EditUserCommand(
|
||||
// command.Fullname,
|
||||
// command.Username,
|
||||
@@ -302,7 +309,6 @@ public class AccountApplication : IAccountApplication
|
||||
// _unitOfWork.RollbackAccountContext();
|
||||
// return operation.Failed(response.Error);
|
||||
//}
|
||||
|
||||
}
|
||||
else //اگر کاربر قبلا ایجاد نشده
|
||||
{
|
||||
@@ -315,19 +321,15 @@ public class AccountApplication : IAccountApplication
|
||||
return operation.Failed("حداقل یک نقش باید انتخاب شود");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
var createPm = await _pmUserCommandService.Create(new CreatePmUserDto(command.Fullname, command.Username, account.Password, command.Mobile,
|
||||
var createPm = await _pmUserCommandService.Create(new CreatePmUserDto(command.Fullname,
|
||||
command.Username, account.Password, command.Mobile,
|
||||
null, account.id, pmUserRoles));
|
||||
if (!createPm.isSuccess)
|
||||
{
|
||||
_unitOfWork.RollbackAccountContext();
|
||||
return operation.Failed("خطا در ویرایش کاربر پروگرام منیجر");
|
||||
}
|
||||
|
||||
|
||||
|
||||
//var parameters = new CreateProgramManagerUser(
|
||||
@@ -362,7 +364,6 @@ public class AccountApplication : IAccountApplication
|
||||
// return operation.Failed(response.Error);
|
||||
//}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
_unitOfWork.CommitAccountContext();
|
||||
@@ -376,7 +377,6 @@ public class AccountApplication : IAccountApplication
|
||||
|
||||
public OperationResult Login(Login command)
|
||||
{
|
||||
|
||||
long idAutoriz = 0;
|
||||
var operation = new OperationResult();
|
||||
if (string.IsNullOrWhiteSpace(command.Password))
|
||||
@@ -401,21 +401,27 @@ public class AccountApplication : IAccountApplication
|
||||
.Select(x => x.Code)
|
||||
.ToList();
|
||||
//PmPermission
|
||||
var PmUserData = _pmUserQueryService.GetPmUserDataByAccountId(account.id).GetAwaiter().GetResult();
|
||||
if (PmUserData.AccountId > 0 && PmUserData.IsActive)
|
||||
var PmUserData = _pmUserQueryService.GetPmUserDataByAccountId(account.id)
|
||||
.GetAwaiter().GetResult();
|
||||
long? pmUserId = null;
|
||||
if (PmUserData != null)
|
||||
{
|
||||
|
||||
var pmUserPermissions =
|
||||
PmUserData.RoleListDto != null
|
||||
? PmUserData.RoleListDto
|
||||
.SelectMany(x => x.Permissions)
|
||||
.Where(p => p != 99)
|
||||
.Distinct()
|
||||
.ToList()
|
||||
: new List<int>();
|
||||
permissions.AddRange(pmUserPermissions);
|
||||
if (PmUserData.AccountId > 0 && PmUserData.IsActive)
|
||||
{
|
||||
var pmUserPermissions =
|
||||
PmUserData.RoleListDto != null
|
||||
? PmUserData.RoleListDto
|
||||
.SelectMany(x => x.Permissions)
|
||||
.Where(p => p != 99)
|
||||
.Distinct()
|
||||
.ToList()
|
||||
: new List<int>();
|
||||
permissions.AddRange(pmUserPermissions);
|
||||
}
|
||||
|
||||
pmUserId = PmUserData.Id > 0 ? PmUserData.Id : null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
int? positionValue;
|
||||
if (account.PositionId != null)
|
||||
@@ -426,24 +432,25 @@ public class AccountApplication : IAccountApplication
|
||||
{
|
||||
positionValue = null;
|
||||
}
|
||||
var pmUserId = PmUserData.AccountId > 0 ? PmUserData.AccountId : null;
|
||||
|
||||
var authViewModel = new AuthViewModel(account.id, account.RoleId, account.Fullname
|
||||
, account.Username, account.Mobile, account.ProfilePhoto,
|
||||
permissions, account.RoleName, account.AdminAreaPermission,
|
||||
account.ClientAriaPermission, positionValue,0,pmUserId);
|
||||
permissions, account.RoleName, account.AdminAreaPermission,
|
||||
account.ClientAriaPermission, positionValue, 0, pmUserId);
|
||||
|
||||
if (account.ClientAriaPermission == "true" && account.AdminAreaPermission == "false" &&
|
||||
account.IsActiveString == "true")
|
||||
{
|
||||
var clientPermissions = _accountPermissionSubtitle1Repository.GetAllPermissionCodes();
|
||||
authViewModel.Permissions = clientPermissions;
|
||||
var workshopList = _workshopRepository.GetWorkshopsByClientAccountId(account.id).Select(x => new WorkshopClaim
|
||||
{
|
||||
PersonnelCount = x.PersonnelCount,
|
||||
Id = x.Id,
|
||||
Name = x.WorkshopFullName,
|
||||
Slug = _passwordHasher.SlugHasher(x.Id)
|
||||
}).OrderByDescending(x => x.PersonnelCount).ToList();
|
||||
var workshopList = _workshopRepository.GetWorkshopsByClientAccountId(account.id).Select(x =>
|
||||
new WorkshopClaim
|
||||
{
|
||||
PersonnelCount = x.PersonnelCount,
|
||||
Id = x.Id,
|
||||
Name = x.WorkshopFullName,
|
||||
Slug = _passwordHasher.SlugHasher(x.Id)
|
||||
}).OrderByDescending(x => x.PersonnelCount).ToList();
|
||||
authViewModel.WorkshopList = workshopList;
|
||||
if (workshopList.Any())
|
||||
{
|
||||
@@ -456,10 +463,14 @@ public class AccountApplication : IAccountApplication
|
||||
|
||||
_authHelper.Signin(authViewModel);
|
||||
|
||||
if ((account.AdminAreaPermission == "true" && account.ClientAriaPermission == "true" && account.IsActiveString == "true") || (account.AdminAreaPermission == "true" && account.ClientAriaPermission == "false" && account.IsActiveString == "true"))
|
||||
if ((account.AdminAreaPermission == "true" && account.ClientAriaPermission == "true" &&
|
||||
account.IsActiveString == "true") || (account.AdminAreaPermission == "true" &&
|
||||
account.ClientAriaPermission == "false" &&
|
||||
account.IsActiveString == "true"))
|
||||
idAutoriz = 1;
|
||||
|
||||
if (account.ClientAriaPermission == "true" && account.AdminAreaPermission == "false" && account.IsActiveString == "true")
|
||||
if (account.ClientAriaPermission == "true" && account.AdminAreaPermission == "false" &&
|
||||
account.IsActiveString == "true")
|
||||
idAutoriz = 2;
|
||||
}
|
||||
|
||||
@@ -471,7 +482,8 @@ public class AccountApplication : IAccountApplication
|
||||
|
||||
var mobile = string.IsNullOrWhiteSpace(cameraAccount.Mobile) ? " " : cameraAccount.Mobile;
|
||||
var authViewModel = new CameraAuthViewModel(cameraAccount.id, cameraAccount.WorkshopId,
|
||||
cameraAccount.Username, mobile, cameraAccount.WorkshopName, cameraAccount.AccountId, cameraAccount.IsActiveSting);
|
||||
cameraAccount.Username, mobile, cameraAccount.WorkshopName, cameraAccount.AccountId,
|
||||
cameraAccount.IsActiveSting);
|
||||
if (cameraAccount.IsActiveSting == "true")
|
||||
{
|
||||
_authHelper.CameraSignIn(authViewModel);
|
||||
@@ -481,7 +493,6 @@ public class AccountApplication : IAccountApplication
|
||||
{
|
||||
idAutoriz = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (subAccount != null)
|
||||
@@ -511,12 +522,14 @@ public class AccountApplication : IAccountApplication
|
||||
authViewModel.WorkshopSlug = _passwordHasher.SlugHasher(workshop.WorkshopId);
|
||||
authViewModel.WorkshopId = workshop.WorkshopId;
|
||||
}
|
||||
|
||||
_authHelper.Signin(authViewModel);
|
||||
idAutoriz = 2;
|
||||
}
|
||||
|
||||
return operation.Succcedded(idAutoriz);
|
||||
}
|
||||
|
||||
public OperationResult LoginWithMobile(long id)
|
||||
{
|
||||
var operation = new OperationResult();
|
||||
@@ -525,7 +538,6 @@ public class AccountApplication : IAccountApplication
|
||||
return operation.Failed(ApplicationMessages.WrongUserPass);
|
||||
|
||||
|
||||
|
||||
var permissions = _roleRepository.Get(account.RoleId)
|
||||
.Permissions
|
||||
.Select(x => x.Code)
|
||||
@@ -541,20 +553,22 @@ public class AccountApplication : IAccountApplication
|
||||
}
|
||||
|
||||
var authViewModel = new AuthViewModel(account.id, account.RoleId, account.Fullname
|
||||
, account.Username, account.Mobile, account.ProfilePhoto, permissions, account.RoleName, account.AdminAreaPermission, account.ClientAriaPermission, positionValue);
|
||||
, account.Username, account.Mobile, account.ProfilePhoto, permissions, account.RoleName,
|
||||
account.AdminAreaPermission, account.ClientAriaPermission, positionValue);
|
||||
|
||||
if (account.ClientAriaPermission == "true" && account.AdminAreaPermission == "false" &&
|
||||
account.IsActiveString == "true")
|
||||
{
|
||||
var clientPermissions = _accountPermissionSubtitle1Repository.GetAllPermissionCodes();
|
||||
authViewModel.Permissions = clientPermissions;
|
||||
var workshopList = _workshopRepository.GetWorkshopsByClientAccountId(account.id).Select(x => new WorkshopClaim
|
||||
{
|
||||
PersonnelCount = x.PersonnelCount,
|
||||
Id = x.Id,
|
||||
Name = x.WorkshopFullName,
|
||||
Slug = _passwordHasher.SlugHasher(x.Id)
|
||||
}).OrderByDescending(x => x.PersonnelCount).ToList();
|
||||
var workshopList = _workshopRepository.GetWorkshopsByClientAccountId(account.id).Select(x =>
|
||||
new WorkshopClaim
|
||||
{
|
||||
PersonnelCount = x.PersonnelCount,
|
||||
Id = x.Id,
|
||||
Name = x.WorkshopFullName,
|
||||
Slug = _passwordHasher.SlugHasher(x.Id)
|
||||
}).OrderByDescending(x => x.PersonnelCount).ToList();
|
||||
authViewModel.WorkshopList = workshopList;
|
||||
if (workshopList.Any())
|
||||
{
|
||||
@@ -567,13 +581,15 @@ public class AccountApplication : IAccountApplication
|
||||
|
||||
_authHelper.Signin(authViewModel);
|
||||
long idAutoriz = 0;
|
||||
if (account.AdminAreaPermission == "true" && account.ClientAriaPermission == "true" || account.AdminAreaPermission == "true" && account.ClientAriaPermission == "false")
|
||||
if (account.AdminAreaPermission == "true" && account.ClientAriaPermission == "true" ||
|
||||
account.AdminAreaPermission == "true" && account.ClientAriaPermission == "false")
|
||||
idAutoriz = 1;
|
||||
|
||||
if (account.ClientAriaPermission == "true" && account.AdminAreaPermission == "false")
|
||||
idAutoriz = 2;
|
||||
return operation.Succcedded(idAutoriz);
|
||||
}
|
||||
|
||||
public void Logout()
|
||||
{
|
||||
_authHelper.SignOut();
|
||||
@@ -609,6 +625,7 @@ public class AccountApplication : IAccountApplication
|
||||
_accountRepository.SaveChanges();
|
||||
return operation.Succcedded();
|
||||
}
|
||||
|
||||
public EditAccount GetByVerifyCode(string code, string phone)
|
||||
{
|
||||
return _accountRepository.GetByVerifyCode(code, phone);
|
||||
@@ -637,7 +654,6 @@ public class AccountApplication : IAccountApplication
|
||||
await _accountRepository.RemoveCode(id);
|
||||
|
||||
return operation.Succcedded();
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -682,7 +698,6 @@ public class AccountApplication : IAccountApplication
|
||||
return operation.Failed("این اکانت وجود ندارد");
|
||||
|
||||
|
||||
|
||||
var permissions = _roleRepository.Get(account.RoleId)
|
||||
.Permissions
|
||||
.Select(x => x.Code)
|
||||
@@ -691,7 +706,8 @@ public class AccountApplication : IAccountApplication
|
||||
|
||||
_authHelper.SignOut();
|
||||
var authViewModel = new AuthViewModel(account.id, account.RoleId, account.Fullname
|
||||
, account.Username, account.Mobile, account.ProfilePhoto, permissions, account.RoleName, "false", "true", null);
|
||||
, account.Username, account.Mobile, account.ProfilePhoto, permissions, account.RoleName, "false", "true",
|
||||
null);
|
||||
var workshopList = _workshopRepository.GetWorkshopsByClientAccountId(account.id).Select(x => new WorkshopClaim
|
||||
{
|
||||
PersonnelCount = x.PersonnelCount,
|
||||
@@ -711,9 +727,11 @@ public class AccountApplication : IAccountApplication
|
||||
authViewModel.WorkshopName = workshop.Name;
|
||||
authViewModel.WorkshopId = workshop.Id;
|
||||
}
|
||||
|
||||
_authHelper.Signin(authViewModel);
|
||||
return operation.Succcedded(2);
|
||||
}
|
||||
|
||||
public OperationResult DirectCameraLogin(long cameraAccountId)
|
||||
{
|
||||
var prAcc = _authHelper.CurrentAccountInfo();
|
||||
@@ -723,24 +741,22 @@ public class AccountApplication : IAccountApplication
|
||||
return operation.Failed("این اکانت وجود ندارد");
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_authHelper.SignOut();
|
||||
|
||||
|
||||
var mobile = string.IsNullOrWhiteSpace(cameraAccount.Mobile) ? " " : cameraAccount.Mobile;
|
||||
var authViewModel = new CameraAuthViewModel(cameraAccount.id, cameraAccount.WorkshopId,
|
||||
cameraAccount.Username, mobile, cameraAccount.WorkshopName, cameraAccount.AccountId, cameraAccount.IsActiveSting);
|
||||
cameraAccount.Username, mobile, cameraAccount.WorkshopName, cameraAccount.AccountId,
|
||||
cameraAccount.IsActiveSting);
|
||||
if (cameraAccount.IsActiveSting == "true")
|
||||
{
|
||||
_authHelper.CameraSignIn(authViewModel);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
return operation.Failed("این اکانت غیر فعال شده است");
|
||||
}
|
||||
|
||||
return operation.Succcedded(2);
|
||||
}
|
||||
|
||||
@@ -773,10 +789,12 @@ public class AccountApplication : IAccountApplication
|
||||
{
|
||||
return this._accountLeftworkRepository.SaveWorkshopAccount(workshopAccountList, startDate, leftDate, accountId);
|
||||
}
|
||||
|
||||
public OperationResult CreateNewWorkshopAccount(long currentAccountId, long newAccountId)
|
||||
{
|
||||
return this._accountLeftworkRepository.CopyWorkshopToNewAccount(currentAccountId, newAccountId);
|
||||
}
|
||||
|
||||
#region Mahan
|
||||
|
||||
public List<AccountViewModel> AccountsForAssign(long taskId)
|
||||
@@ -790,6 +808,7 @@ public class AccountApplication : IAccountApplication
|
||||
{
|
||||
return new List<AccountViewModel>();
|
||||
}
|
||||
|
||||
return _accountRepository.GetAccountsByPositionId(positionId);
|
||||
}
|
||||
|
||||
@@ -807,7 +826,6 @@ public class AccountApplication : IAccountApplication
|
||||
return operation.Failed("این اکانت وجود ندارد");
|
||||
|
||||
|
||||
|
||||
var permissions = _roleRepository.Get(account.RoleId)
|
||||
.Permissions
|
||||
.Select(x => x.Code)
|
||||
@@ -816,10 +834,10 @@ public class AccountApplication : IAccountApplication
|
||||
|
||||
_authHelper.SignOut();
|
||||
var authViewModel = new AuthViewModel(account.id, account.RoleId, account.Fullname
|
||||
, account.Username, account.Mobile, account.ProfilePhoto, permissions, account.RoleName, account.AdminAreaPermission, account.ClientAriaPermission, account.Position.PositionValue);
|
||||
, account.Username, account.Mobile, account.ProfilePhoto, permissions, account.RoleName,
|
||||
account.AdminAreaPermission, account.ClientAriaPermission, account.Position.PositionValue);
|
||||
_authHelper.Signin(authViewModel);
|
||||
return operation.Succcedded(2);
|
||||
|
||||
}
|
||||
|
||||
public async Task<List<AccountSelectListViewModel>> GetAdminSelectList()
|
||||
@@ -828,8 +846,11 @@ public class AccountApplication : IAccountApplication
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Pooya
|
||||
public OperationResult IsPhoneNumberAndPasswordValid(long accountId, string phoneNumber, string password, string rePassword)
|
||||
|
||||
public OperationResult IsPhoneNumberAndPasswordValid(long accountId, string phoneNumber, string password,
|
||||
string rePassword)
|
||||
{
|
||||
OperationResult op = new();
|
||||
|
||||
@@ -847,7 +868,8 @@ public class AccountApplication : IAccountApplication
|
||||
return op.Failed("رمز عبور نمی تواند کمتر از 8 کاراکتر باشد");
|
||||
}
|
||||
|
||||
if ((string.IsNullOrWhiteSpace(phoneNumber) || entity.Mobile == phoneNumber) && string.IsNullOrWhiteSpace(rePassword))
|
||||
if ((string.IsNullOrWhiteSpace(phoneNumber) || entity.Mobile == phoneNumber) &&
|
||||
string.IsNullOrWhiteSpace(rePassword))
|
||||
return op.Failed("چیزی برای تغییر وجود ندارد");
|
||||
|
||||
|
||||
@@ -873,20 +895,22 @@ public class AccountApplication : IAccountApplication
|
||||
var entity = _accountRepository.Get(command.AccountId);
|
||||
if (entity == null)
|
||||
return op.Failed(ApplicationMessages.RecordNotFound);
|
||||
var validationResult = IsPhoneNumberAndPasswordValid(command.AccountId, command.PhoneNumber, command.Password, command.RePassword);
|
||||
var validationResult = IsPhoneNumberAndPasswordValid(command.AccountId, command.PhoneNumber, command.Password,
|
||||
command.RePassword);
|
||||
if (validationResult.IsSuccedded == false)
|
||||
return validationResult;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(command.RePassword))
|
||||
{
|
||||
|
||||
entity.ChangePassword(_passwordHasher.Hash(command.Password));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(command.PhoneNumber))
|
||||
{
|
||||
entity.Edit(entity.Fullname, entity.Username, command.PhoneNumber, entity.RoleId, entity.ProfilePhoto, entity.RoleName);
|
||||
entity.Edit(entity.Fullname, entity.Username, command.PhoneNumber, entity.RoleId, entity.ProfilePhoto,
|
||||
entity.RoleName);
|
||||
}
|
||||
|
||||
_accountRepository.SaveChanges();
|
||||
return op.Succcedded();
|
||||
}
|
||||
@@ -982,6 +1006,7 @@ public class AccountApplication : IAccountApplication
|
||||
|
||||
// return claimsResponse.Failed(ApplicationMessages.WrongUserPass);
|
||||
//}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
@@ -57,6 +57,18 @@ public class JobSchedulerRegistrator
|
||||
() => SendInstitutionContractConfirmSms(),
|
||||
"*/1 * * * *" // هر 1 دقیقه یکبار چک کن
|
||||
);
|
||||
|
||||
//RecurringJob.AddOrUpdate(
|
||||
// "InstitutionContract.SendWarningSms",
|
||||
// () => SendWarningSms(),
|
||||
// "*/1 * * * *" // هر 1 دقیقه یکبار چک کن
|
||||
//);
|
||||
|
||||
//RecurringJob.AddOrUpdate(
|
||||
// "InstitutionContract.SendLegalActionSms",
|
||||
// () => SendLegalActionSms(),
|
||||
// "*/1 * * * *" // هر 1 دقیقه یکبار چک کن
|
||||
//);
|
||||
}
|
||||
|
||||
|
||||
@@ -170,4 +182,22 @@ public class JobSchedulerRegistrator
|
||||
await _institutionContractRepository.SendInstitutionContractConfirmSmsTask();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ارسال پیامک هشدار
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[DisableConcurrentExecution(timeoutInSeconds: 100)]
|
||||
public async System.Threading.Tasks.Task SendWarningSms()
|
||||
{
|
||||
_logger.LogInformation("SendWarningSms job run");
|
||||
await _institutionContractRepository.SendWarningSmsTask();
|
||||
}
|
||||
|
||||
[DisableConcurrentExecution(timeoutInSeconds: 100)]
|
||||
public async System.Threading.Tasks.Task SendLegalActionSms()
|
||||
{
|
||||
_logger.LogInformation("SendWarningSms job run");
|
||||
await _institutionContractRepository.SendLegalActionSmsTask();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using CompanyManagment.App.Contracts.PersonalContractingParty;
|
||||
using System;
|
||||
using CompanyManagment.App.Contracts.PersonalContractingParty;
|
||||
using System.Collections.Generic;
|
||||
using _0_Framework.Application;
|
||||
using _0_Framework.Domain;
|
||||
@@ -32,7 +33,9 @@ public interface IPersonalContractingPartyRepository :IRepository<long, Personal
|
||||
List<PersonalContractingPartyViewModel> SearchForMain(PersonalContractingPartySearchModel searchModel2);
|
||||
OperationResult DeletePersonalContractingParties(long id);
|
||||
bool GetHasContract(long id);
|
||||
[Obsolete("از متدهای async استفاده کنید")]
|
||||
OperationResult DeActiveAll(long id);
|
||||
[Obsolete("از متدهای async استفاده کنید")]
|
||||
OperationResult ActiveAll(long id);
|
||||
|
||||
#endregion
|
||||
@@ -76,4 +79,9 @@ public interface IPersonalContractingPartyRepository :IRepository<long, Personal
|
||||
|
||||
Task<PersonalContractingParty> GetByNationalCode(string nationalCode);
|
||||
Task<PersonalContractingParty> GetByNationalId(string registerId);
|
||||
|
||||
Task<OperationResult> DeActiveAllAsync(long id);
|
||||
Task<OperationResult> ActiveAllAsync(long id);
|
||||
|
||||
|
||||
}
|
||||
@@ -10,4 +10,6 @@ public interface IFinancialInvoiceRepository : IRepository<long, FinancialInvoic
|
||||
EditFinancialInvoice GetDetails(long id);
|
||||
List<FinancialInvoiceViewModel> Search(FinancialInvoiceSearchModel searchModel);
|
||||
Task<FinancialInvoice> GetUnPaidByEntityId(long entityId, FinancialInvoiceItemType financialInvoiceItemType);
|
||||
Task<FinancialInvoice> GetUnPaidFinancialInvoiceByContractingPartyIdAndAmount(long contractingPartyId,
|
||||
double amount);
|
||||
}
|
||||
@@ -13,7 +13,7 @@ namespace Company.Domain.InstitutionContractAgg;
|
||||
|
||||
public interface IInstitutionContractRepository : IRepository<long, InstitutionContract>
|
||||
{
|
||||
|
||||
|
||||
EditInstitutionContract GetDetails(long id);
|
||||
EditInstitutionContract GetFirstContract(long contractingPartyId, string typeOfContract);
|
||||
List<InstitutionContractViewModel> InstitutionContractsWithoutAccount();
|
||||
@@ -56,12 +56,12 @@ public interface IInstitutionContractRepository : IRepository<long, InstitutionC
|
||||
void UpdateStatusIfNeeded(long institutionContractId);
|
||||
Task<GetInstitutionVerificationDetailsViewModel> GetVerificationDetails(Guid id);
|
||||
Task<InstitutionContract> GetByPublicIdAsync(Guid id);
|
||||
InstitutionContractDiscountResponse CalculateDiscount(InstitutionContractSetDiscountRequest request,string contractStart = null);
|
||||
InstitutionContractDiscountResponse CalculateDiscount(InstitutionContractSetDiscountRequest request, string contractStart = null);
|
||||
InstitutionContractDiscountResponse ResetDiscountCreate(InstitutionContractResetDiscountForCreateRequest request);
|
||||
|
||||
#region Creation
|
||||
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Extension
|
||||
@@ -73,19 +73,19 @@ public interface IInstitutionContractRepository : IRepository<long, InstitutionC
|
||||
Task<InstitutionContractDiscountResponse> SetDiscountForExtension(
|
||||
InstitutionContractSetDiscountForExtensionRequest request);
|
||||
Task<InstitutionContractDiscountResponse> ResetDiscountForExtension(InstitutionContractResetDiscountForExtensionRequest request);
|
||||
|
||||
|
||||
Task<OperationResult> ExtensionComplete(InstitutionContractExtensionCompleteRequest request);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Upgrade(Amendment)
|
||||
|
||||
Task<InstitutionContractAmendmentWorkshopsResponse> GetAmendmentWorkshops(long institutionContractId);
|
||||
Task<InsitutionContractAmendmentPaymentResponse> GetAmendmentPaymentDetails(InsitutionContractAmendmentPaymentRequest request);
|
||||
Task<InstitutionContractAmendmentWorkshopsResponse> GetAmendmentWorkshops(long institutionContractId);
|
||||
Task<InsitutionContractAmendmentPaymentResponse> GetAmendmentPaymentDetails(InsitutionContractAmendmentPaymentRequest request);
|
||||
|
||||
Task<InsertAmendmentTempWorkshopResponse> InsertAmendmentTempWorkshops(InstitutionContractAmendmentTempWorkshopViewModel request);
|
||||
Task RemoveAmendmentWorkshops(Guid workshopTempId);
|
||||
#endregion
|
||||
Task<InsertAmendmentTempWorkshopResponse> InsertAmendmentTempWorkshops(InstitutionContractAmendmentTempWorkshopViewModel request);
|
||||
Task RemoveAmendmentWorkshops(Guid workshopTempId);
|
||||
#endregion
|
||||
|
||||
Task<List<InstitutionContractSelectListViewModel>> GetInstitutionContractSelectList(string search, string selected);
|
||||
Task<List<InstitutionContractPrintViewModel>> PrintAllAsync(List<long> ids);
|
||||
@@ -160,6 +160,25 @@ public interface IInstitutionContractRepository : IRepository<long, InstitutionC
|
||||
Task CreateTransactionForInstitutionContracts(DateTime endOfMonthGr, string endOfMonthFa, string description);
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region WarningSms
|
||||
/// <summary>
|
||||
/// پیامک های هشدار
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task SendWarningSmsTask();
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region legalAction
|
||||
/// <summary>
|
||||
/// پیامک اقدام قضائی
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task SendLegalActionSmsTask();
|
||||
|
||||
#endregion
|
||||
|
||||
Task<long> GetIdByInstallmentId(long installmentId);
|
||||
@@ -171,4 +190,5 @@ public interface IInstitutionContractRepository : IRepository<long, InstitutionC
|
||||
Task<InstitutionContractDiscountResponse> SetDiscountForCreation(InstitutionContractSetDiscountForCreationRequest request);
|
||||
Task<InstitutionContractDiscountResponse> ResetDiscountForCreation(InstitutionContractResetDiscountForExtensionRequest request);
|
||||
Task<OperationResult> CreationComplete(InstitutionContractExtensionCompleteRequest request);
|
||||
Task<InstitutionContract> GetIncludeInstallments(long id);
|
||||
}
|
||||
@@ -2,7 +2,7 @@ using System.Collections.Generic;
|
||||
using _0_Framework.Application;
|
||||
using Company.Application.Contracts.AuthorizedBankDetails;
|
||||
|
||||
namespace Company.Application.Contracts.AuthorizedBankDetails
|
||||
namespace CompanyManagment.App.Contracts.AuthorizedBankDetails
|
||||
{
|
||||
public interface IAuthorizedBankDetailsApplication
|
||||
{
|
||||
|
||||
@@ -8,7 +8,7 @@ public class CreateFinancialInvoice
|
||||
public double Amount { get; set; }
|
||||
public long ContractingPartyId { get; set; }
|
||||
public string Description { get; set; }
|
||||
public List<CreateFinancialInvoiceItem>? Items { get; set; }
|
||||
public List<CreateFinancialInvoiceItem> Items { get; set; }
|
||||
}
|
||||
|
||||
public class CreateFinancialInvoiceItem
|
||||
|
||||
@@ -10,7 +10,8 @@ public class EditFinancialInvoice
|
||||
public double Amount { get; set; }
|
||||
public FinancialInvoiceStatus Status { get; set; }
|
||||
|
||||
public List<EditFinancialInvoiceItem>? Items { get; set; }
|
||||
public List<EditFinancialInvoiceItem> Items { get; set; }
|
||||
public long ContractingPartyId { get; set; }
|
||||
}
|
||||
|
||||
public class EditFinancialInvoiceItem
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using CompanyManagment.App.Contracts.SepehrPaymentGateway;
|
||||
|
||||
namespace CompanyManagment.App.Contracts.FinancialStatment;
|
||||
|
||||
@@ -62,7 +63,17 @@ public interface IFinancialStatmentApplication
|
||||
/// <returns></returns>
|
||||
Task<FinancialStatmentDetailsByContractingPartyViewModel> GetDetailsByContractingParty(long contractingPartyId,
|
||||
FinancialStatementSearchModel searchModel);
|
||||
|
||||
/// <summary>
|
||||
/// پردازش شارژ حساب از طریق درگاه پرداخت سپهر
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="gateWayCallBackUrl">مسیر برگشت درگاه پرداخت</param>
|
||||
///
|
||||
Task<OperationResult<CreateSepehrPaymentGatewayResponse>> CreatePaymentGateWayAndCreateInvoice(
|
||||
CreateFinancialPayRequest request, string gateWayCallBackUrl);
|
||||
}
|
||||
public record CreateFinancialPayRequest(long Id, string BaseUrl);
|
||||
|
||||
public class FinancialStatmentDetailsByContractingPartyViewModel
|
||||
{
|
||||
|
||||
@@ -94,4 +94,29 @@ public class BlockSmsListData
|
||||
/// آی دی صورت حساب مالی
|
||||
/// </summary>
|
||||
public string AproveId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// آیا قرداد مالی قدیمی است
|
||||
/// </summary>
|
||||
public bool IsElectronicContract { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// پابلیک آی دی بخش یک
|
||||
/// </summary>
|
||||
public string Code1 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// پابلیک آی دی بخش دو
|
||||
/// </summary>
|
||||
public string Code2 { get; set; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// لیست قراداد های آبی
|
||||
/// جهت ارسال هشدار یا اقدام قضائی
|
||||
/// </summary>
|
||||
public class BlueWarningSmsData
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using _0_Framework.Application;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CompanyManagment.App.Contracts.PaymentCallback;
|
||||
|
||||
/// <summary>
|
||||
/// رابط برای مدیریت Callback درگاههای پرداخت
|
||||
/// </summary>
|
||||
public interface IPaymentCallbackHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// تأیید و پردازش callback درگاه پرداخت سپهر
|
||||
/// </summary>
|
||||
/// <param name="command">دادههای callback درگاه</param>
|
||||
/// <param name="cancellationToken">توکن لغو عملیات</param>
|
||||
/// <returns>نتیجه عملیات</returns>
|
||||
Task<OperationResult> VerifySepehrPaymentCallback(VerifyPaymentCallbackCommand command,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
namespace CompanyManagment.App.Contracts.PaymentCallback;
|
||||
|
||||
/// <summary>
|
||||
/// دستور تأیید callback درگاه پرداخت
|
||||
/// </summary>
|
||||
public class VerifyPaymentCallbackCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// کد پاسخ درگاه (0 = موفق)
|
||||
/// </summary>
|
||||
public int ResponseCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// شناسه فاکتور/تراکنش
|
||||
/// </summary>
|
||||
public long InvoiceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// دادههای اضافی JSON
|
||||
/// </summary>
|
||||
public string Payload { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// مبلغ تراکنش
|
||||
/// </summary>
|
||||
public long Amount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// شماره پیگیری درگاه
|
||||
/// </summary>
|
||||
public long TraceNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// شماره سند بانکی (RRN)
|
||||
/// </summary>
|
||||
public long Rrn { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// رسید دیجیتال
|
||||
/// </summary>
|
||||
public string DigitalReceipt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// بانک صادر کننده کارت
|
||||
/// </summary>
|
||||
public string IssuerBank { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// شماره کارت
|
||||
/// </summary>
|
||||
public string CardNumber { get; set; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace CompanyManagment.App.Contracts.SepehrPaymentGateway;
|
||||
|
||||
/// <summary>
|
||||
/// پاسخ ایجاد درگاه پرداخت سپهر
|
||||
/// </summary>
|
||||
public class CreateSepehrPaymentGatewayResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// توکن درگاه
|
||||
/// </summary>
|
||||
public string Token { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// شناسه تراکنش
|
||||
/// </summary>
|
||||
public long TransactionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// URL درگاه پرداخت
|
||||
/// </summary>
|
||||
public string PaymentUrl { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using _0_Framework.Application;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CompanyManagment.App.Contracts.SepehrPaymentGateway;
|
||||
|
||||
/// <summary>
|
||||
/// رابط برای سرویس مشترک ایجاد درگاه پرداخت سپهر
|
||||
/// </summary>
|
||||
public interface ISepehrPaymentGatewayService
|
||||
{
|
||||
/// <summary>
|
||||
/// ایجاد درگاه پرداخت سپهر برای یک تراکنش
|
||||
/// </summary>
|
||||
/// <param name="amount">مبلغ</param>
|
||||
/// <param name="contractingPartyId">شناسه طرف قرارداد</param>
|
||||
/// <param name="frontCallbackUrl">آدرس بازگشتی به فرانت برای نمایش نتیجه</param>
|
||||
/// <param name="gatewayCallbackUrl">آدرس بازگشتی درگاه پرداخت</param>
|
||||
/// <param name="financialInvoiceId">شناسه فاکتور مالی (اختیاری) - این پارامتر مستقیماً به درگاه فرستاده میشود</param>
|
||||
/// <param name="extraData">دادههای اضافی سفارشی برای payload (اختیاری)</param>
|
||||
/// <param name="cancellationToken">توکن لغو</param>
|
||||
/// <returns>شامل Token درگاه یا OperationResult با خطا</returns>
|
||||
Task<OperationResult<CreateSepehrPaymentGatewayResponse>> CreateSepehrPaymentGateway(
|
||||
double amount,
|
||||
long contractingPartyId,
|
||||
long financialInvoiceId,
|
||||
string gatewayCallbackUrl,
|
||||
string frontCallbackUrl="https://client.gozareshgir.ir",
|
||||
Dictionary<string, object> extraData = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using _0_Framework.Application;
|
||||
using Company.Application.Contracts.AuthorizedBankDetails;
|
||||
using Company.Domain.AuthorizedBankDetailsAgg;
|
||||
using CompanyManagment.App.Contracts.AuthorizedBankDetails;
|
||||
|
||||
namespace CompanyManagment.Application
|
||||
{
|
||||
|
||||
@@ -9,15 +9,13 @@ using CompanyManagment.EFCore;
|
||||
|
||||
namespace CompanyManagment.Application;
|
||||
|
||||
public class FinancialInvoiceApplication : RepositoryBase<long, FinancialInvoice>, IFinancialInvoiceApplication
|
||||
public class FinancialInvoiceApplication : IFinancialInvoiceApplication
|
||||
{
|
||||
private readonly IFinancialInvoiceRepository _financialInvoiceRepository;
|
||||
private readonly CompanyContext _context;
|
||||
|
||||
public FinancialInvoiceApplication(IFinancialInvoiceRepository financialInvoiceRepository, CompanyContext context) : base(context)
|
||||
public FinancialInvoiceApplication(IFinancialInvoiceRepository financialInvoiceRepository)
|
||||
{
|
||||
_financialInvoiceRepository = financialInvoiceRepository;
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public OperationResult Create(CreateFinancialInvoice command)
|
||||
@@ -185,6 +183,7 @@ public class FinancialInvoiceApplication : RepositoryBase<long, FinancialInvoice
|
||||
{
|
||||
return _financialInvoiceRepository.Search(searchModel);
|
||||
}
|
||||
|
||||
|
||||
//public OperationResult Remove(long id)
|
||||
//{
|
||||
|
||||
@@ -3,9 +3,12 @@ using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using _0_Framework.Application;
|
||||
using Company.Domain.ContarctingPartyAgg;
|
||||
using Company.Domain.FinancialInvoiceAgg;
|
||||
using Company.Domain.FinancialStatmentAgg;
|
||||
using CompanyManagment.App.Contracts.FinancialInvoice;
|
||||
using CompanyManagment.App.Contracts.FinancialStatment;
|
||||
using CompanyManagment.App.Contracts.FinancilTransaction;
|
||||
using CompanyManagment.App.Contracts.SepehrPaymentGateway;
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
@@ -16,12 +19,20 @@ public class FinancialStatmentApplication : IFinancialStatmentApplication
|
||||
private readonly IFinancialStatmentRepository _financialStatmentRepository;
|
||||
private readonly IFinancialTransactionApplication _financialTransactionApplication;
|
||||
private readonly IPersonalContractingPartyRepository _contractingPartyRepository;
|
||||
private readonly IFinancialInvoiceRepository _financialInvoiceRepository;
|
||||
private readonly ISepehrPaymentGatewayService _sepehrPaymentGatewayService;
|
||||
|
||||
public FinancialStatmentApplication(IFinancialStatmentRepository financialStatmentRepository, IFinancialTransactionApplication financialTransactionApplication, IPersonalContractingPartyRepository contractingPartyRepository)
|
||||
public FinancialStatmentApplication(IFinancialStatmentRepository financialStatmentRepository,
|
||||
IFinancialTransactionApplication financialTransactionApplication,
|
||||
IPersonalContractingPartyRepository contractingPartyRepository,
|
||||
IFinancialInvoiceRepository financialInvoiceRepository,
|
||||
ISepehrPaymentGatewayService sepehrPaymentGatewayService)
|
||||
{
|
||||
_financialStatmentRepository = financialStatmentRepository;
|
||||
_financialTransactionApplication = financialTransactionApplication;
|
||||
_contractingPartyRepository = contractingPartyRepository;
|
||||
_financialInvoiceRepository = financialInvoiceRepository;
|
||||
_sepehrPaymentGatewayService = sepehrPaymentGatewayService;
|
||||
}
|
||||
|
||||
public OperationResult CreateFromBankGateway(CreateFinancialStatment command)
|
||||
@@ -51,7 +62,6 @@ public class FinancialStatmentApplication : IFinancialStatmentApplication
|
||||
if (createTransaction.IsSuccedded)
|
||||
return op.Succcedded();
|
||||
return op.Failed("خطا در انجام عملیات");
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -71,8 +81,6 @@ public class FinancialStatmentApplication : IFinancialStatmentApplication
|
||||
Balance = 0,
|
||||
TypeOfTransaction = command.TypeOfTransaction,
|
||||
DescriptionOption = command.DescriptionOption
|
||||
|
||||
|
||||
};
|
||||
var createTransaction = _financialTransactionApplication.Create(transaction);
|
||||
if (createTransaction.IsSuccedded)
|
||||
@@ -98,22 +106,22 @@ public class FinancialStatmentApplication : IFinancialStatmentApplication
|
||||
{
|
||||
debtor = 0;
|
||||
creditor = command.CreditorString.MoneyToDouble();
|
||||
|
||||
}
|
||||
else if (command.TypeOfTransaction == "debt")
|
||||
{
|
||||
creditor = 0;
|
||||
debtor = command.DeptorString.MoneyToDouble();
|
||||
|
||||
}
|
||||
|
||||
if (!command.TdateFa.TryToGeorgianDateTime(out var tDateGr))
|
||||
{
|
||||
return op.Failed("تاریخ وارد شده صحیح نمی باشد");
|
||||
}
|
||||
|
||||
if (_financialStatmentRepository.Exists(x => x.ContractingPartyId == command.ContractingPartyId))
|
||||
{
|
||||
var financialStatment = _financialStatmentRepository.GetDetailsByContractingPartyId(command.ContractingPartyId);
|
||||
var financialStatment =
|
||||
_financialStatmentRepository.GetDetailsByContractingPartyId(command.ContractingPartyId);
|
||||
var transaction = new CreateFinancialTransaction()
|
||||
{
|
||||
FinancialStatementId = financialStatment.Id,
|
||||
@@ -124,20 +132,15 @@ public class FinancialStatmentApplication : IFinancialStatmentApplication
|
||||
Creditor = creditor,
|
||||
TypeOfTransaction = command.TypeOfTransaction,
|
||||
DescriptionOption = command.DescriptionOption
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
var createTransaction = _financialTransactionApplication.Create(transaction);
|
||||
|
||||
var createTransaction = _financialTransactionApplication.Create(transaction);
|
||||
if (createTransaction.IsSuccedded)
|
||||
return op.Succcedded();
|
||||
return op.Failed("خطا در انجام عملیات");
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
var statement = new FinancialStatment(command.ContractingPartyId, command.ContractingPartyName);
|
||||
_financialStatmentRepository.Create(statement);
|
||||
_financialStatmentRepository.SaveChanges();
|
||||
@@ -153,20 +156,14 @@ public class FinancialStatmentApplication : IFinancialStatmentApplication
|
||||
Balance = 0,
|
||||
TypeOfTransaction = command.TypeOfTransaction,
|
||||
DescriptionOption = command.DescriptionOption
|
||||
|
||||
|
||||
};
|
||||
var createTransaction = _financialTransactionApplication.Create(transaction);
|
||||
if (createTransaction.IsSuccedded)
|
||||
return op.Succcedded();
|
||||
return op.Failed("خطا در انجام عملیات");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public List<FinancialStatmentViewModel> Search(FinancialStatmentSearchModel searchModel)
|
||||
{
|
||||
@@ -203,6 +200,45 @@ public class FinancialStatmentApplication : IFinancialStatmentApplication
|
||||
public async Task<FinancialStatmentDetailsByContractingPartyViewModel> GetDetailsByContractingParty(
|
||||
long contractingPartyId, FinancialStatementSearchModel searchModel)
|
||||
{
|
||||
return await _financialStatmentRepository.GetDetailsByContractingParty(contractingPartyId,searchModel);
|
||||
return await _financialStatmentRepository.GetDetailsByContractingParty(contractingPartyId, searchModel);
|
||||
}
|
||||
|
||||
public async Task<OperationResult<CreateSepehrPaymentGatewayResponse>> CreatePaymentGateWayAndCreateInvoice(
|
||||
CreateFinancialPayRequest request, string gateWayCallBackUrl)
|
||||
{
|
||||
var op = new OperationResult<CreateSepehrPaymentGatewayResponse>();
|
||||
// گام 1: دریافت موجودی حساب
|
||||
var balanceAmount = await GetBalanceAmount(request.Id);
|
||||
if (balanceAmount.Amount <= 0)
|
||||
{
|
||||
return op.Failed("موجودی حساب شما صفر است");
|
||||
}
|
||||
// گام 2: ایجاد درگاه پرداخت سپهر
|
||||
|
||||
var financialInvoice = await _financialInvoiceRepository
|
||||
.GetUnPaidFinancialInvoiceByContractingPartyIdAndAmount(balanceAmount.ContractingPartyId,
|
||||
balanceAmount.Amount);
|
||||
|
||||
if (financialInvoice == null)
|
||||
{
|
||||
financialInvoice = new FinancialInvoice(balanceAmount.Amount, balanceAmount.ContractingPartyId,
|
||||
"پرداخت بدهی صورت حساب مالی");
|
||||
|
||||
var items = new FinancialInvoiceItem("پرداخت بدهی صورت حساب مالی", balanceAmount.Amount,
|
||||
financialInvoice.id, FinancialInvoiceItemType.PreviousDebt, 0);
|
||||
financialInvoice.AddItem(items);
|
||||
await _financialInvoiceRepository.CreateAsync(financialInvoice);
|
||||
await _financialInvoiceRepository.SaveChangesAsync();
|
||||
}
|
||||
|
||||
var gatewayResult = await _sepehrPaymentGatewayService.CreateSepehrPaymentGateway(
|
||||
amount: balanceAmount.Amount,
|
||||
contractingPartyId: balanceAmount.ContractingPartyId,
|
||||
frontCallbackUrl: request.BaseUrl,
|
||||
gatewayCallbackUrl: gateWayCallBackUrl,
|
||||
financialInvoiceId: financialInvoice.id,
|
||||
extraData: null);
|
||||
|
||||
return gatewayResult;
|
||||
}
|
||||
}
|
||||
@@ -1,40 +1,32 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using _0_Framework.Application;
|
||||
using _0_Framework.Application.Enums;
|
||||
using _0_Framework.Application.PaymentGateway;
|
||||
using _0_Framework.Application.Sms;
|
||||
using _0_Framework.Application.UID;
|
||||
using _0_Framework.Exceptions;
|
||||
using AccountManagement.Application.Contracts.Account;
|
||||
using Company.Domain.ContarctingPartyAgg;
|
||||
using Company.Domain.EmployeeAgg;
|
||||
using Company.Domain.empolyerAgg;
|
||||
using Company.Domain.FinancialInvoiceAgg;
|
||||
using Company.Domain.FinancialStatmentAgg;
|
||||
using Company.Domain.FinancialTransactionAgg;
|
||||
using Company.Domain.InstitutionContractAgg;
|
||||
using Company.Domain.LeftWorkAgg;
|
||||
using Company.Domain.PaymentTransactionAgg;
|
||||
using Company.Domain.RepresentativeAgg;
|
||||
using Company.Domain.RollCallServiceAgg;
|
||||
using Company.Domain.TemporaryClientRegistrationAgg;
|
||||
using Company.Domain.WorkshopAgg;
|
||||
using CompanyManagment.App.Contracts.FinancialInvoice;
|
||||
using CompanyManagment.App.Contracts.FinancialStatment;
|
||||
using CompanyManagment.App.Contracts.InstitutionContract;
|
||||
using CompanyManagment.App.Contracts.InstitutionContractContactinfo;
|
||||
using CompanyManagment.App.Contracts.PaymentTransaction;
|
||||
using CompanyManagment.App.Contracts.PersonalContractingParty;
|
||||
using CompanyManagment.App.Contracts.SepehrPaymentGateway;
|
||||
using CompanyManagment.App.Contracts.Workshop;
|
||||
using CompanyManagment.EFCore.Migrations;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using OfficeOpenXml.Packaging.Ionic.Zip;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using PersianTools.Core;
|
||||
using ConnectedPersonnelViewModel = CompanyManagment.App.Contracts.Workshop.ConnectedPersonnelViewModel;
|
||||
using FinancialStatment = Company.Domain.FinancialStatmentAgg.FinancialStatment;
|
||||
@@ -49,49 +41,45 @@ public class InstitutionContractApplication : IInstitutionContractApplication
|
||||
private readonly IFinancialStatmentApplication _financialStatmentApplication;
|
||||
private readonly IEmployerRepository _employerRepository;
|
||||
private readonly IWorkshopRepository _workshopRepository;
|
||||
private readonly ILeftWorkRepository _leftWorkRepository;
|
||||
private readonly IWorkshopApplication _workshopApplication;
|
||||
private readonly IContractingPartyTempRepository _contractingPartyTempRepository;
|
||||
private readonly IFinancialStatmentRepository _financialStatmentRepository;
|
||||
private readonly IContactInfoApplication _contactInfoApplication;
|
||||
private readonly IAccountApplication _accountApplication;
|
||||
private readonly ISmsService _smsService;
|
||||
private readonly IUidService _uidService;
|
||||
private readonly IFinancialInvoiceRepository _financialInvoiceRepository;
|
||||
private readonly IPaymentGateway _paymentGateway;
|
||||
private readonly IPaymentTransactionRepository _paymentTransactionRepository;
|
||||
private readonly IRollCallServiceRepository _rollCallServiceRepository;
|
||||
private readonly ISepehrPaymentGatewayService _sepehrPaymentGatewayService;
|
||||
|
||||
|
||||
public InstitutionContractApplication(IInstitutionContractRepository institutionContractRepository,
|
||||
IPersonalContractingPartyRepository contractingPartyRepository,
|
||||
IRepresentativeRepository representativeRepository, IEmployerRepository employerRepository,
|
||||
IWorkshopRepository workshopRepository, ILeftWorkRepository leftWorkRepository,
|
||||
IWorkshopRepository workshopRepository,
|
||||
IFinancialStatmentApplication financialStatmentApplication, IWorkshopApplication workshopApplication,
|
||||
IContractingPartyTempRepository contractingPartyTempRepository,
|
||||
IFinancialStatmentRepository financialStatmentRepository, IContactInfoApplication contactInfoApplication,
|
||||
IAccountApplication accountApplication, ISmsService smsService, IUidService uidService,
|
||||
IAccountApplication accountApplication, ISmsService smsService,
|
||||
IFinancialInvoiceRepository financialInvoiceRepository, IHttpClientFactory httpClientFactory,
|
||||
IPaymentTransactionRepository paymentTransactionRepository, IRollCallServiceRepository rollCallServiceRepository)
|
||||
IPaymentTransactionRepository paymentTransactionRepository, IRollCallServiceRepository rollCallServiceRepository,
|
||||
ISepehrPaymentGatewayService sepehrPaymentGatewayService,ILogger<SepehrPaymentGateway> sepehrGatewayLogger)
|
||||
{
|
||||
_institutionContractRepository = institutionContractRepository;
|
||||
_contractingPartyRepository = contractingPartyRepository;
|
||||
_representativeRepository = representativeRepository;
|
||||
_employerRepository = employerRepository;
|
||||
_workshopRepository = workshopRepository;
|
||||
_leftWorkRepository = leftWorkRepository;
|
||||
_financialStatmentApplication = financialStatmentApplication;
|
||||
_workshopApplication = workshopApplication;
|
||||
_contractingPartyTempRepository = contractingPartyTempRepository;
|
||||
_financialStatmentRepository = financialStatmentRepository;
|
||||
_contactInfoApplication = contactInfoApplication;
|
||||
_accountApplication = accountApplication;
|
||||
_smsService = smsService;
|
||||
_uidService = uidService;
|
||||
_financialInvoiceRepository = financialInvoiceRepository;
|
||||
_paymentTransactionRepository = paymentTransactionRepository;
|
||||
_rollCallServiceRepository = rollCallServiceRepository;
|
||||
_paymentGateway = new SepehrPaymentGateway(httpClientFactory);
|
||||
_sepehrPaymentGatewayService = sepehrPaymentGatewayService;
|
||||
_paymentGateway = new SepehrPaymentGateway(httpClientFactory,sepehrGatewayLogger);
|
||||
}
|
||||
|
||||
public OperationResult Create(CreateInstitutionContract command)
|
||||
@@ -817,20 +805,11 @@ public class InstitutionContractApplication : IInstitutionContractApplication
|
||||
var contractingParty = _contractingPartyRepository.Get(institutionContract.ContractingPartyId);
|
||||
if (contractingParty != null)
|
||||
{
|
||||
contractingParty.DeActive();
|
||||
_contractingPartyRepository.SaveChanges();
|
||||
var employers =
|
||||
_employerRepository.GetEmployerByContracrtingPartyID(institutionContract.ContractingPartyId);
|
||||
//var employersIdList = employers.Select(x => x.Id).ToList();
|
||||
//var workshops = _workshopApplication.GetWorkshopsByEmployerId(employersIdList);
|
||||
//foreach (var workshop in workshops)
|
||||
//{
|
||||
// var res = _workshopApplication.DeActive(workshop.Id);
|
||||
//}
|
||||
foreach (var employer in employers)
|
||||
{
|
||||
var res = _employerRepository.DeActiveAll(employer.Id);
|
||||
}
|
||||
var accountsDeActiveRes = _contractingPartyRepository.DeActiveAllAsync(contractingParty.id)
|
||||
.GetAwaiter().GetResult();
|
||||
|
||||
if (!accountsDeActiveRes.IsSuccedded)
|
||||
return opration.Failed(accountsDeActiveRes.Message);
|
||||
}
|
||||
|
||||
return opration.Succcedded();
|
||||
@@ -847,20 +826,10 @@ public class InstitutionContractApplication : IInstitutionContractApplication
|
||||
var contractingParty = _contractingPartyRepository.Get(institutionContract.ContractingPartyId);
|
||||
if (contractingParty != null)
|
||||
{
|
||||
contractingParty.Active();
|
||||
_contractingPartyRepository.SaveChanges();
|
||||
var employers =
|
||||
_employerRepository.GetEmployerByContracrtingPartyID(institutionContract.ContractingPartyId);
|
||||
//var employersIdList = employers.Select(x => x.Id).ToList();
|
||||
//var workshops = _workshopApplication.GetWorkshopsByEmployerId(employersIdList);
|
||||
//foreach (var workshop in workshops)
|
||||
//{
|
||||
// var res = _workshopApplication.DeActive(workshop.Id);
|
||||
//}
|
||||
foreach (var employer in employers)
|
||||
{
|
||||
var res = _employerRepository.ActiveAll(employer.Id);
|
||||
}
|
||||
var activeRes = _contractingPartyRepository.ActiveAllAsync(contractingParty.id).GetAwaiter()
|
||||
.GetResult();
|
||||
if (!activeRes.IsSuccedded)
|
||||
return opration.Failed(activeRes.Message);
|
||||
}
|
||||
|
||||
return opration.Succcedded();
|
||||
@@ -1294,119 +1263,87 @@ public class InstitutionContractApplication : IInstitutionContractApplication
|
||||
if (contractingParty == null)
|
||||
throw new NotFoundException("طرف قرارداد یافت نشد");
|
||||
|
||||
if (institutionContract.VerifyCode != code)
|
||||
return op.Failed("کد وارد شده صحیح نمی باشد");
|
||||
|
||||
var financialStatement =await _financialStatmentRepository.GetByContractingPartyId(contractingParty.id);
|
||||
if (institutionContract.VerifyCode != code)
|
||||
return op.Failed("کد وارد شده صحیح نمی باشد");
|
||||
|
||||
var dbTransaction = await _institutionContractRepository.BeginTransactionAsync();
|
||||
FinancialInvoice financialInvoice;
|
||||
FinancialInvoiceItem financialInvoiceItem;
|
||||
var today = DateTime.Today;
|
||||
double invoiceAmount = 0;
|
||||
string invoiceItemDescription = string.Empty;
|
||||
FinancialInvoiceItemType invoiceItemType = FinancialInvoiceItemType.BuyInstitutionContract;
|
||||
long invoiceItemEntityId = 0;
|
||||
var financialStatement = await _financialStatmentRepository.GetByContractingPartyId(contractingParty.id);
|
||||
|
||||
if (institutionContract.IsInstallment)
|
||||
{
|
||||
var firstInstallment = institutionContract.Installments.First();
|
||||
var firstInstallmentAmount = firstInstallment.Amount;
|
||||
var dbTransaction = await _institutionContractRepository.BeginTransactionAsync();
|
||||
FinancialInvoice financialInvoice;
|
||||
FinancialInvoiceItem financialInvoiceItem;
|
||||
var today = DateTime.Today;
|
||||
double invoiceAmount = 0;
|
||||
string invoiceItemDescription = string.Empty;
|
||||
FinancialInvoiceItemType invoiceItemType = FinancialInvoiceItemType.BuyInstitutionContract;
|
||||
long invoiceItemEntityId = 0;
|
||||
|
||||
financialInvoice = await _financialInvoiceRepository.GetUnPaidByEntityId(firstInstallment.Id, FinancialInvoiceItemType.BuyInstitutionContractInstallment);
|
||||
if (financialInvoice == null)
|
||||
{
|
||||
var financialTransaction = new FinancialTransaction(0, today, today.ToFarsi(),
|
||||
"قسط اول سرویس", "debt", "بابت خدمات", firstInstallmentAmount, 0, 0);
|
||||
financialStatement.AddFinancialTransaction(financialTransaction);
|
||||
invoiceAmount = firstInstallmentAmount;
|
||||
invoiceItemDescription = $"پرداخت قسط اول قرارداد شماره {institutionContract.ContractNo}";
|
||||
invoiceItemType = FinancialInvoiceItemType.BuyInstitutionContractInstallment;
|
||||
invoiceItemEntityId = firstInstallment.Id;
|
||||
}
|
||||
else
|
||||
{
|
||||
invoiceAmount = financialInvoice.Amount;
|
||||
invoiceItemDescription = financialInvoice.Items.First().Description;
|
||||
invoiceItemType = financialInvoice.Items.First().Type;
|
||||
invoiceItemEntityId = financialInvoice.Items.First().EntityId;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
financialInvoice = await _financialInvoiceRepository.GetUnPaidByEntityId(institutionContract.id, FinancialInvoiceItemType.BuyInstitutionContract);
|
||||
if (financialInvoice == null)
|
||||
{
|
||||
var financialTransaction = new FinancialTransaction(0, today, today.ToFarsi(),
|
||||
"پرداخت کل سرویس", "debt", "بابت خدمات", institutionContract.TotalAmount, 0, 0);
|
||||
financialStatement.AddFinancialTransaction(financialTransaction);
|
||||
invoiceAmount = institutionContract.TotalAmount;
|
||||
invoiceItemDescription = $"پرداخت کل قرارداد شماره {institutionContract.ContractNo}";
|
||||
invoiceItemType = FinancialInvoiceItemType.BuyInstitutionContract;
|
||||
invoiceItemEntityId = institutionContract.id;
|
||||
}
|
||||
else
|
||||
{
|
||||
invoiceAmount = financialInvoice.Amount;
|
||||
invoiceItemDescription = financialInvoice.Items.First().Description;
|
||||
invoiceItemType = financialInvoice.Items.First().Type;
|
||||
invoiceItemEntityId = financialInvoice.Items.First().EntityId;
|
||||
}
|
||||
}
|
||||
if (institutionContract.IsInstallment)
|
||||
{
|
||||
var firstInstallment = institutionContract.Installments.First();
|
||||
var firstInstallmentAmount = firstInstallment.Amount;
|
||||
|
||||
if (financialInvoice == null)
|
||||
{
|
||||
financialInvoice = new FinancialInvoice(invoiceAmount, contractingParty.id, $"خرید قرارداد مالی شماره {institutionContract.ContractNo}");
|
||||
financialInvoiceItem = new FinancialInvoiceItem(invoiceItemDescription, invoiceAmount, 0, invoiceItemType, invoiceItemEntityId);
|
||||
financialInvoice.AddItem(financialInvoiceItem);
|
||||
await _financialInvoiceRepository.CreateAsync(financialInvoice);
|
||||
}
|
||||
financialInvoice = await _financialInvoiceRepository.GetUnPaidByEntityId(firstInstallment.Id, FinancialInvoiceItemType.BuyInstitutionContractInstallment);
|
||||
if (financialInvoice == null)
|
||||
{
|
||||
invoiceAmount = firstInstallmentAmount;
|
||||
invoiceItemDescription = $"پرداخت قسط اول قرارداد شماره {institutionContract.ContractNo}";
|
||||
invoiceItemType = FinancialInvoiceItemType.BuyInstitutionContractInstallment;
|
||||
invoiceItemEntityId = firstInstallment.Id;
|
||||
}
|
||||
else
|
||||
{
|
||||
invoiceAmount = financialInvoice.Amount;
|
||||
invoiceItemDescription = financialInvoice.Items.First().Description;
|
||||
invoiceItemType = financialInvoice.Items.First().Type;
|
||||
invoiceItemEntityId = financialInvoice.Items.First().EntityId;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
financialInvoice = await _financialInvoiceRepository.GetUnPaidByEntityId(institutionContract.id, FinancialInvoiceItemType.BuyInstitutionContract);
|
||||
if (financialInvoice == null)
|
||||
{
|
||||
invoiceAmount = institutionContract.TotalAmount;
|
||||
invoiceItemDescription = $"پرداخت کل قرارداد شماره {institutionContract.ContractNo}";
|
||||
invoiceItemType = FinancialInvoiceItemType.BuyInstitutionContract;
|
||||
invoiceItemEntityId = institutionContract.id;
|
||||
}
|
||||
else
|
||||
{
|
||||
invoiceAmount = financialInvoice.Amount;
|
||||
invoiceItemDescription = financialInvoice.Items.First().Description;
|
||||
invoiceItemType = financialInvoice.Items.First().Type;
|
||||
invoiceItemEntityId = financialInvoice.Items.First().EntityId;
|
||||
}
|
||||
}
|
||||
|
||||
await _financialInvoiceRepository.SaveChangesAsync();
|
||||
if (financialInvoice == null)
|
||||
{
|
||||
financialInvoice = new FinancialInvoice(invoiceAmount, contractingParty.id, $"خرید قرارداد مالی شماره {institutionContract.ContractNo}");
|
||||
financialInvoiceItem = new FinancialInvoiceItem(invoiceItemDescription, invoiceAmount, 0, invoiceItemType, invoiceItemEntityId);
|
||||
financialInvoice.AddItem(financialInvoiceItem);
|
||||
await _financialInvoiceRepository.CreateAsync(financialInvoice);
|
||||
}
|
||||
|
||||
var transaction = new PaymentTransaction(institutionContract.ContractingPartyId, invoiceAmount,
|
||||
institutionContract.ContractingPartyName, "https://client.gozareshgir.ir",
|
||||
PaymentTransactionGateWay.SepehrPay);
|
||||
await _paymentTransactionRepository.CreateAsync(transaction);
|
||||
await _financialInvoiceRepository.SaveChangesAsync();
|
||||
await _financialInvoiceRepository.SaveChangesAsync();
|
||||
|
||||
var createPayment = new CreatePaymentGatewayRequest()
|
||||
{
|
||||
Amount = invoiceAmount,
|
||||
TransactionId = transaction.id.ToString(),
|
||||
CallBackUrl = callbackUrl,
|
||||
FinancialInvoiceId = financialInvoice.id,
|
||||
};
|
||||
var gatewayResponse = await _paymentGateway.Create(createPayment);
|
||||
if (!gatewayResponse.IsSuccess)
|
||||
return op.Failed("خطا در ایجاد درگاه پرداخت: " + gatewayResponse.Message + gatewayResponse.ErrorCode);
|
||||
// استفاده از سرویس مشترک برای ایجاد درگاه پرداخت
|
||||
var gatewayResult = await _sepehrPaymentGatewayService.CreateSepehrPaymentGateway(
|
||||
amount: (long)invoiceAmount,
|
||||
contractingPartyId: institutionContract.ContractingPartyId,
|
||||
gatewayCallbackUrl: callbackUrl,
|
||||
financialInvoiceId: financialInvoice.id,
|
||||
extraData: null);
|
||||
|
||||
|
||||
// institutionContract.SetPendingWorkflow();
|
||||
//
|
||||
// var phone = institutionContract.ContactInfoList.FirstOrDefault(x =>
|
||||
// x.SendSms && x.Position == "طرف قرارداد" && x.PhoneType == "شماره همراه");
|
||||
// if (phone !=null)
|
||||
// {
|
||||
// var userPass = contractingParty.IsLegal == "حقیقی"
|
||||
// ? contractingParty.Nationalcode
|
||||
// : contractingParty.NationalId;
|
||||
// var createAcc = new RegisterAccount
|
||||
// {
|
||||
// Fullname = contractingParty.LName,
|
||||
// Username = userPass,
|
||||
// Password = userPass,
|
||||
// Mobile = phone.PhoneNumber,
|
||||
// NationalCode = userPass
|
||||
// };
|
||||
// var res = _accountApplication.RegisterClient(createAcc);
|
||||
// if (res.IsSuccedded)
|
||||
// CreateContractingPartyAccount(contractingParty.id, res.SendId);
|
||||
// }
|
||||
if (!gatewayResult.IsSuccedded)
|
||||
{
|
||||
await dbTransaction.RollbackAsync();
|
||||
return op.Failed(gatewayResult.Message);
|
||||
}
|
||||
|
||||
await dbTransaction.CommitAsync();
|
||||
await _institutionContractRepository.SaveChangesAsync();
|
||||
return op.Succcedded(gatewayResponse.Token);
|
||||
return op.Succcedded(gatewayResult.Data.Token);
|
||||
}
|
||||
|
||||
public async Task<InstitutionContractWorkshopDetailViewModel> GetWorkshopInitialDetails(long workshopDetailsId)
|
||||
|
||||
239
CompanyManagment.Application/PaymentCallbackHandler.cs
Normal file
239
CompanyManagment.Application/PaymentCallbackHandler.cs
Normal file
@@ -0,0 +1,239 @@
|
||||
using _0_Framework.Application;
|
||||
using Company.Domain.PaymentTransactionAgg;
|
||||
using CompanyManagment.App.Contracts.FinancialInvoice;
|
||||
using CompanyManagment.App.Contracts.FinancialStatment;
|
||||
using CompanyManagment.App.Contracts.InstitutionContract;
|
||||
using CompanyManagment.App.Contracts.PaymentCallback;
|
||||
using CompanyManagment.App.Contracts.PaymentTransaction;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Transactions;
|
||||
using _0_Framework.Application.PaymentGateway;
|
||||
using Company.Domain.FinancialStatmentAgg;
|
||||
using Company.Domain.FinancialTransactionAgg;
|
||||
using Company.Domain.InstitutionContractAgg;
|
||||
using CompanyManagment.EFCore.Migrations;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CompanyManagment.Application;
|
||||
|
||||
public class PaymentCallbackHandler : IPaymentCallbackHandler
|
||||
{
|
||||
private readonly IPaymentTransactionApplication _paymentTransactionApplication;
|
||||
private readonly IFinancialStatmentApplication _financialStatmentApplication;
|
||||
private readonly IFinancialStatmentRepository _financialStatmentRepository;
|
||||
private readonly IFinancialInvoiceApplication _financialInvoiceApplication;
|
||||
private readonly IInstitutionContractApplication _institutionContractApplication;
|
||||
private readonly IInstitutionContractRepository _institutionContractRepository;
|
||||
private readonly IPaymentGateway _paymentGateway;
|
||||
|
||||
public PaymentCallbackHandler(
|
||||
IPaymentTransactionApplication paymentTransactionApplication,
|
||||
IFinancialStatmentApplication financialStatmentApplication,
|
||||
IFinancialInvoiceApplication financialInvoiceApplication,
|
||||
IInstitutionContractApplication institutionContractApplication,
|
||||
IHttpClientFactory httpClientFactory, IInstitutionContractRepository institutionContractRepository,
|
||||
IFinancialStatmentRepository financialStatmentRepository,
|
||||
ILogger<SepehrPaymentGateway> sepehrGatewayLogger)
|
||||
{
|
||||
_paymentTransactionApplication = paymentTransactionApplication;
|
||||
_financialStatmentApplication = financialStatmentApplication;
|
||||
_financialInvoiceApplication = financialInvoiceApplication;
|
||||
_institutionContractApplication = institutionContractApplication;
|
||||
_institutionContractRepository = institutionContractRepository;
|
||||
_financialStatmentRepository = financialStatmentRepository;
|
||||
_paymentGateway = new SepehrPaymentGateway(httpClientFactory, sepehrGatewayLogger);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// تأیید و پردازش callback درگاه پرداخت سپهر
|
||||
/// </summary>
|
||||
public async Task<OperationResult> VerifySepehrPaymentCallback(VerifyPaymentCallbackCommand command,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var operation = new OperationResult();
|
||||
|
||||
try
|
||||
{
|
||||
await using var transactionScope =await _financialStatmentRepository.BeginTransactionAsync();
|
||||
|
||||
// گام 1: دریافت اطلاعات تراکنش
|
||||
var transaction = await _paymentTransactionApplication.GetDetails(command.InvoiceId);
|
||||
|
||||
if (transaction == null)
|
||||
return operation.Failed("تراکنش مورد نظر یافت نشد");
|
||||
|
||||
// گام 2: بررسی وضعیت قبلی تراکنش
|
||||
if (transaction.Status != PaymentTransactionStatus.Pending)
|
||||
return operation.Failed("این تراکنش قبلا پرداخت شده است");
|
||||
|
||||
// گام 3: بررسی کد پاسخ درگاه
|
||||
if (command.ResponseCode != 0)
|
||||
{
|
||||
var failResult = _paymentTransactionApplication.SetFailed(command.InvoiceId);
|
||||
return failResult.IsSuccedded
|
||||
? operation.Failed("تراکنش توسط درگاه رد شد")
|
||||
: operation.Failed("خطا در بهروزرسانی وضعیت تراکنش");
|
||||
}
|
||||
|
||||
// گام 4: استخراج اطلاعات فاکتور مالی
|
||||
var extraData = JsonConvert.DeserializeObject<IDictionary<string, object>>(command.Payload ?? "{}");
|
||||
|
||||
extraData.TryGetValue("financialInvoiceId", out var financialInvoiceIdObj);
|
||||
|
||||
if (financialInvoiceIdObj == null ||
|
||||
!long.TryParse(financialInvoiceIdObj.ToString(), out var financialInvoiceId))
|
||||
return operation.Failed("فاکتور مالی نامعتبر است");
|
||||
|
||||
// گام 5: دریافت اطلاعات فاکتور مالی
|
||||
var financialInvoice = _financialInvoiceApplication.GetDetails(financialInvoiceId);
|
||||
|
||||
if (financialInvoice == null)
|
||||
return operation.Failed("فاکتور مالی نامعتبر است");
|
||||
|
||||
if (financialInvoice.Status != FinancialInvoiceStatus.Unpaid)
|
||||
return operation.Failed("فاکتور مالی نامعتبر است");
|
||||
|
||||
// گام 6: بررسی تطابق مبلغ
|
||||
if ((long)financialInvoice.Amount != command.Amount)
|
||||
{
|
||||
var failResult = _paymentTransactionApplication.SetFailed(command.InvoiceId);
|
||||
return operation.Failed("مبلغ تراکنش با مبلغ فاکتور مطابقت ندارد");
|
||||
}
|
||||
|
||||
// گام 7: بهروزرسانی فاکتور مالی
|
||||
var setPaidResult = _financialInvoiceApplication.SetPaid(financialInvoiceId, DateTime.Now);
|
||||
if (!setPaidResult.IsSuccedded)
|
||||
{
|
||||
var failResult = _paymentTransactionApplication.SetFailed(command.InvoiceId);
|
||||
return operation.Failed("خطا در بهروزرسانی فاکتور مالی");
|
||||
}
|
||||
|
||||
|
||||
|
||||
// گام 8: بهروزرسانی وضعیت تراکنش
|
||||
var setSuccessResult = _paymentTransactionApplication.SetSuccess(
|
||||
command.InvoiceId,
|
||||
command.CardNumber,
|
||||
command.IssuerBank,
|
||||
command.Rrn.ToString(),
|
||||
command.DigitalReceipt);
|
||||
|
||||
if (!setSuccessResult.IsSuccedded)
|
||||
{
|
||||
return operation.Failed("خطا در بهروزرسانی وضعیت تراکنش");
|
||||
}
|
||||
|
||||
// گام 9: بهروزرسانی وضعیت قراردادهای نهادی (اگر وجود داشته باشند)
|
||||
var institutionContractItems = financialInvoice.Items.Where(x =>
|
||||
x.Type is FinancialInvoiceItemType.BuyInstitutionContract
|
||||
or FinancialInvoiceItemType.BuyInstitutionContractInstallment).ToList();
|
||||
|
||||
if (institutionContractItems.Any())
|
||||
{
|
||||
await HandleInstitutionContractItems(financialInvoice);
|
||||
}
|
||||
|
||||
|
||||
// گام 10: ایجاد سند مالی (Financial Statement)
|
||||
var createCreditStatementCommand = new CreateFinancialStatment()
|
||||
{
|
||||
ContractingPartyId = transaction.ContractingPartyId,
|
||||
Deptor = 0,
|
||||
Creditor = command.Amount,
|
||||
DeptorString = "0",
|
||||
TypeOfTransaction = "credit",
|
||||
DescriptionOption = (financialInvoice.Description ?? "") + " شماره فاکتور: " +
|
||||
(financialInvoice.InvoiceNumber ?? ""),
|
||||
Description = "درگاه بانکی",
|
||||
};
|
||||
|
||||
var statementResult = _financialStatmentApplication.CreateFromBankGateway(createCreditStatementCommand);
|
||||
if (!statementResult.IsSuccedded)
|
||||
{
|
||||
_paymentTransactionApplication.SetFailed(command.InvoiceId);
|
||||
return operation.Failed("خطا در ایجاد سند مالی");
|
||||
}
|
||||
// گام 11: تأیید نهایی با درگاه پرداخت
|
||||
var verifyCommand = new VerifyPaymentGateWayRequest()
|
||||
{
|
||||
Amount = transaction.Amount,
|
||||
TransactionId = command.InvoiceId.ToString(),
|
||||
DigitalReceipt = command.DigitalReceipt
|
||||
};
|
||||
|
||||
var verifyRes = await _paymentGateway.Verify(verifyCommand, cancellationToken);
|
||||
#if DEBUG
|
||||
verifyRes.IsSuccess = true;
|
||||
#endif
|
||||
if (!verifyRes.IsSuccess)
|
||||
{
|
||||
return operation.Failed("خطا در تایید پرداخت از درگاه");
|
||||
}
|
||||
|
||||
// تمام عملیات موفق - تایید transaction
|
||||
await transactionScope.CommitAsync(cancellationToken);
|
||||
return operation.Succcedded();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// در صورت بروز هرگونه خطا، transaction خودکار rollback میشود
|
||||
return operation.Failed($"خطا در پردازش callback: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// مدیریت آپدیت قراردادهای نهادی
|
||||
/// </summary>
|
||||
private async Task HandleInstitutionContractItems(EditFinancialInvoice financialInvoice)
|
||||
{
|
||||
// قراردادهای خریداری مستقیم
|
||||
var directContractItems = financialInvoice.Items
|
||||
.Where(x => x.Type == FinancialInvoiceItemType.BuyInstitutionContract);
|
||||
var financialStatement =
|
||||
await _financialStatmentRepository.GetByContractingPartyId(financialInvoice.ContractingPartyId);
|
||||
|
||||
var today = DateTime.Now;
|
||||
foreach (var item in directContractItems)
|
||||
{
|
||||
var institutionContract = _institutionContractRepository.Get(item.EntityId);
|
||||
|
||||
await _institutionContractApplication.SetPendingWorkflow(item.EntityId,
|
||||
InstitutionContractSigningType.OtpBased);
|
||||
|
||||
var financialTransaction = new FinancialTransaction(0, today, today.ToFarsi(),
|
||||
"پرداخت کل سرویس", "debt", "بابت خدمات", institutionContract.TotalAmount, 0, 0);
|
||||
|
||||
financialStatement.AddFinancialTransaction(financialTransaction);
|
||||
}
|
||||
|
||||
// قراردادهای خریداری با اقساط
|
||||
var installmentItems = financialInvoice.Items
|
||||
.Where(x => x.Type == FinancialInvoiceItemType.BuyInstitutionContractInstallment);
|
||||
|
||||
foreach (var item in installmentItems)
|
||||
{
|
||||
var institutionContractId =await _institutionContractRepository.GetIdByInstallmentId(item.EntityId);
|
||||
var institutionContract = _institutionContractRepository.Get(institutionContractId);
|
||||
|
||||
|
||||
await _institutionContractApplication.SetPendingWorkflow(institutionContractId,
|
||||
InstitutionContractSigningType.OtpBased);
|
||||
|
||||
var firstInstallment = institutionContract.Installments.First();
|
||||
|
||||
var firstInstallmentAmount = firstInstallment.Amount;
|
||||
|
||||
var financialTransaction = new FinancialTransaction(0, today, today.ToFarsi(),
|
||||
"قسط اول سرویس", "debt", "بابت خدمات", firstInstallmentAmount, 0, 0);
|
||||
|
||||
financialStatement.AddFinancialTransaction(financialTransaction);
|
||||
}
|
||||
await _financialStatmentRepository.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
119
CompanyManagment.Application/SepehrPaymentGatewayService.cs
Normal file
119
CompanyManagment.Application/SepehrPaymentGatewayService.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using _0_Framework.Application;
|
||||
using _0_Framework.Application.PaymentGateway;
|
||||
using CompanyManagment.App.Contracts.PaymentTransaction;
|
||||
using CompanyManagment.App.Contracts.SepehrPaymentGateway;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CompanyManagment.Application;
|
||||
|
||||
/// <summary>
|
||||
/// سرویس مشترک برای ایجاد درگاه پرداخت سپهر
|
||||
/// </summary>
|
||||
public class SepehrPaymentGatewayService : ISepehrPaymentGatewayService
|
||||
{
|
||||
private readonly IPaymentGateway _paymentGateway;
|
||||
private readonly IPaymentTransactionApplication _paymentTransactionApplication;
|
||||
|
||||
public SepehrPaymentGatewayService(
|
||||
IPaymentTransactionApplication paymentTransactionApplication,
|
||||
IHttpClientFactory httpClientFactory,
|
||||
ILogger<SepehrPaymentGateway> sepehrGatewayLogger)
|
||||
{
|
||||
_paymentGateway = new SepehrPaymentGateway(httpClientFactory, sepehrGatewayLogger);
|
||||
_paymentTransactionApplication = paymentTransactionApplication;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ایجاد درگاه پرداخت سپهر برای یک تراکنش
|
||||
/// </summary>
|
||||
/// <param name="amount">مبلغ</param>
|
||||
/// <param name="contractingPartyId">شناسه طرف قرارداد</param>
|
||||
/// <param name="frontCallbackUrl">آدرس بازگشتی به فرانت برای نمایش نتیجه</param>
|
||||
/// <param name="gatewayCallbackUrl">آدرس بازگشتی درگاه پرداخت</param>
|
||||
/// <param name="financialInvoiceId">شناسه فاکتور مالی (اختیاری)</param>
|
||||
/// <param name="extraData">دادههای اضافی (اختیاری)</param>
|
||||
/// <param name="cancellationToken">توکن لغو</param>
|
||||
/// <returns>شامل Token درگاه یا OperationResult با خطا</returns>
|
||||
public async Task<OperationResult<CreateSepehrPaymentGatewayResponse>> CreateSepehrPaymentGateway(
|
||||
double amount,
|
||||
long contractingPartyId,
|
||||
long financialInvoiceId,
|
||||
string gatewayCallbackUrl,
|
||||
string frontCallbackUrl="https://client.gozareshgir.ir",
|
||||
Dictionary<string, object> extraData = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var op = new OperationResult<CreateSepehrPaymentGatewayResponse>();
|
||||
|
||||
try
|
||||
{
|
||||
// گام 1: ایجاد تراکنش پرداخت
|
||||
var transactionCommand = new CreatePaymentTransaction()
|
||||
{
|
||||
Amount = amount,
|
||||
ContractingPartyId = contractingPartyId,
|
||||
CallBackUrl = frontCallbackUrl,
|
||||
Gateway = PaymentTransactionGateWay.SepehrPay
|
||||
};
|
||||
|
||||
var transactionResult = await _paymentTransactionApplication.Create(transactionCommand);
|
||||
|
||||
if (!transactionResult.IsSuccedded)
|
||||
{
|
||||
return op.Failed(transactionResult.Message);
|
||||
}
|
||||
|
||||
// گام 2: ایجاد درخواست درگاه پرداخت
|
||||
extraData ??= new Dictionary<string, object>();
|
||||
|
||||
var createPaymentCommand = new CreatePaymentGatewayRequest()
|
||||
{
|
||||
Amount = amount,
|
||||
TransactionId = transactionResult.SendId.ToString(),
|
||||
CallBackUrl = gatewayCallbackUrl,
|
||||
FinancialInvoiceId = financialInvoiceId,
|
||||
ExtraData = extraData
|
||||
};
|
||||
|
||||
// گام 3: ارسال درخواست به درگاه سپهر
|
||||
var gatewayResponse = await _paymentGateway.Create(createPaymentCommand, cancellationToken);
|
||||
|
||||
#if DEBUG
|
||||
gatewayResponse.IsSuccess = true;
|
||||
#endif
|
||||
if (!gatewayResponse.IsSuccess)
|
||||
{
|
||||
return op.Failed($"خطا در ایجاد درگاه پرداخت: {gatewayResponse.Message ?? gatewayResponse.ErrorCode?.ToString()}");
|
||||
}
|
||||
|
||||
// گام 4: ذخیره Token در تراکنش
|
||||
var setTokenResult = await _paymentTransactionApplication.SetTransactionId(
|
||||
transactionResult.SendId,
|
||||
gatewayResponse.Token);
|
||||
|
||||
if (!setTokenResult.IsSuccedded)
|
||||
{
|
||||
return op.Failed("خطا در ذخیره Token درگاه");
|
||||
}
|
||||
|
||||
// گام 5: بازگشت اطلاعات درگاه پرداخت
|
||||
var response = new CreateSepehrPaymentGatewayResponse
|
||||
{
|
||||
Token = gatewayResponse.Token,
|
||||
TransactionId = transactionResult.SendId,
|
||||
PaymentUrl = _paymentGateway.GetStartPayUrl(gatewayResponse.Token)
|
||||
};
|
||||
|
||||
return op.Succcedded(response);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return op.Failed($"خطا در ایجاد درگاه پرداخت: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -407,6 +407,10 @@ public class WorkshopAppliction : IWorkshopApplication
|
||||
public EditWorkshop GetDetails(long id)
|
||||
{
|
||||
var workshop = _workshopRepository.GetDetails(id);
|
||||
if (workshop == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (workshop.IsClassified)
|
||||
{
|
||||
workshop.CreatePlan = _workshopPlanApplication.GetWorkshopPlanByWorkshopId(id);
|
||||
|
||||
@@ -34,6 +34,7 @@ public class FinancialInvoiceRepository : RepositoryBase<long, FinancialInvoice>
|
||||
Amount = financialInvoice.Amount,
|
||||
Status = financialInvoice.Status,
|
||||
InvoiceNumber = financialInvoice.InvoiceNumber,
|
||||
ContractingPartyId = financialInvoice.ContractingPartyId,
|
||||
Items = financialInvoice.Items?.Select(x => new EditFinancialInvoiceItem
|
||||
{
|
||||
Id = x.id,
|
||||
@@ -100,4 +101,12 @@ public class FinancialInvoiceRepository : RepositoryBase<long, FinancialInvoice>
|
||||
.Where(x => x.Status == FinancialInvoiceStatus.Unpaid).FirstOrDefaultAsync(x => x.Items
|
||||
.Any(y => y.Type == financialInvoiceItemType && y.EntityId == entityId));
|
||||
}
|
||||
|
||||
public async Task<FinancialInvoice> GetUnPaidFinancialInvoiceByContractingPartyIdAndAmount(long contractingPartyId,
|
||||
double amount)
|
||||
{
|
||||
return await _context.FinancialInvoices.FirstOrDefaultAsync(x=>x.ContractingPartyId == contractingPartyId &&
|
||||
x.Amount == amount &&
|
||||
x.Status == FinancialInvoiceStatus.Unpaid);
|
||||
}
|
||||
}
|
||||
@@ -210,6 +210,7 @@ public class FinancialStatmentRepository : RepositoryBase<long, FinancialStatmen
|
||||
}
|
||||
return new FinancialTransactionDetailViewModel()
|
||||
{
|
||||
Id = t.id,
|
||||
DateTimeGr = t.TdateGr,
|
||||
DateFa = t.TdateGr.ToFarsi(),
|
||||
TimeFa = $"{t.TdateGr:HH:mm}",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -773,6 +773,137 @@ public class PersonalContractingPartyRepository : RepositoryBase<long, PersonalC
|
||||
return await _context.PersonalContractingParties.FirstOrDefaultAsync(x => x.NationalId == nationalId);
|
||||
}
|
||||
|
||||
public async Task<OperationResult> DeActiveAllAsync(long id)
|
||||
{
|
||||
OperationResult result = new OperationResult();
|
||||
await using var transaction = await _context.Database.BeginTransactionAsync();
|
||||
await using var accountTransaction = await _accountContext.Database.BeginTransactionAsync();
|
||||
try
|
||||
{
|
||||
var contractingParty = _context.PersonalContractingParties
|
||||
.FirstOrDefault(x => x.id == id);
|
||||
|
||||
if (contractingParty == null)
|
||||
return result.Failed("طرف حساب یافت نشد");
|
||||
|
||||
contractingParty.DeActive();
|
||||
|
||||
var employers = _context.Employers
|
||||
.Where(x => x.ContractingPartyId == id).ToList();
|
||||
employers.ForEach(x => x.DeActive());
|
||||
|
||||
var employerIds = employers.Select(x => x.id).ToList();
|
||||
var workshopIds = _context.WorkshopEmployers
|
||||
.Where(x => employerIds.Contains(x.EmployerId))
|
||||
.Select(x => x.WorkshopId).ToList();
|
||||
|
||||
var workshops = _context.Workshops
|
||||
.Where(x => workshopIds.Contains(x.id)).ToList();
|
||||
workshops.ForEach(x => x.DeActive(x.ArchiveCode));
|
||||
|
||||
var contracts = _context.Contracts
|
||||
.Where(x => workshopIds.Contains(x.WorkshopIds)).ToList();
|
||||
contracts.ForEach(x => x.DeActive());
|
||||
|
||||
var contractIds = contracts.Select(x => x.id).ToList();
|
||||
var checkouts = _context.CheckoutSet
|
||||
.Where(x => contractIds.Contains(x.ContractId)).ToList();
|
||||
checkouts.ForEach(x => x.DeActive());
|
||||
|
||||
var contractingPartyAccount =await _context.ContractingPartyAccounts
|
||||
.FirstOrDefaultAsync(x => x.PersonalContractingPartyId == id);
|
||||
if (contractingPartyAccount != null)
|
||||
{
|
||||
var account = await _accountContext.Accounts
|
||||
.FirstOrDefaultAsync(x => x.id == contractingPartyAccount.AccountId);
|
||||
|
||||
account?.DeActive();
|
||||
|
||||
var cameraAccount =await _accountContext.CameraAccounts
|
||||
.FirstOrDefaultAsync(x=>x.AccountId==account.id);
|
||||
|
||||
cameraAccount?.DeActive();
|
||||
|
||||
await _accountContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
await transaction.CommitAsync();
|
||||
await accountTransaction.CommitAsync();
|
||||
result.Succcedded();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
result.Failed("غیرفعال کردن طرف حساب با خطا مواجه شد");
|
||||
await transaction.RollbackAsync();
|
||||
await accountTransaction.RollbackAsync();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<OperationResult> ActiveAllAsync(long id)
|
||||
{
|
||||
OperationResult result = new OperationResult();
|
||||
await using var transaction =await _context.Database.BeginTransactionAsync();
|
||||
await using var accountTransaction = await _accountContext.Database.BeginTransactionAsync();
|
||||
try
|
||||
{
|
||||
var personel = _context.PersonalContractingParties
|
||||
.FirstOrDefault(x => x.id == id);
|
||||
if (personel == null)
|
||||
return result.Failed("طرف حساب یافت نشد");
|
||||
|
||||
personel.Active();
|
||||
|
||||
var employers = _context.Employers.Where(x => x.ContractingPartyId == id).ToList();
|
||||
employers.ForEach(x => x.Active());
|
||||
|
||||
var employerIds = employers.Select(x => x.id).ToList();
|
||||
var workshopIds = _context.WorkshopEmployers.Where(x => employerIds.Contains(x.EmployerId))
|
||||
.Select(x => x.WorkshopId).ToList();
|
||||
var workshops = _context.Workshops.Where(x => workshopIds.Contains(x.id)).ToList();
|
||||
workshops.ForEach(x => x.Active(x.ArchiveCode));
|
||||
|
||||
var contracts = _context.Contracts.Where(x => workshopIds.Contains(x.WorkshopIds)).ToList();
|
||||
contracts.ForEach(x => x.Active());
|
||||
|
||||
var contractIds = contracts.Select(x => x.id).ToList();
|
||||
var checkouts = _context.CheckoutSet.Where(x => contractIds.Contains(x.ContractId)).ToList();
|
||||
checkouts.ForEach(x => x.Active());
|
||||
|
||||
var contractingPartyAccount =await _context.ContractingPartyAccounts
|
||||
.FirstOrDefaultAsync(x => x.PersonalContractingPartyId == id);
|
||||
if (contractingPartyAccount != null)
|
||||
{
|
||||
var account = await _accountContext.Accounts
|
||||
.FirstOrDefaultAsync(x => x.id == contractingPartyAccount.AccountId);
|
||||
|
||||
account?.Active();
|
||||
|
||||
var cameraAccount =await _accountContext.CameraAccounts
|
||||
.FirstOrDefaultAsync(x=>x.AccountId==account.id);
|
||||
|
||||
cameraAccount?.Active();
|
||||
|
||||
await _accountContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
await transaction.CommitAsync();
|
||||
await accountTransaction.CommitAsync();
|
||||
result.Succcedded();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
result.Failed("فعال کردن طرف حساب با خطا مواجه شد");
|
||||
await transaction.RollbackAsync();
|
||||
await accountTransaction.RollbackAsync();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
||||
@@ -518,6 +518,31 @@ public class SmsService : ISmsService
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public async Task<(byte status, string message, int messaeId, bool isSucceded)> BlockMessageForElectronicContract(string number, string fullname, string amount,string code1, string code2)
|
||||
{
|
||||
var tamplateId = 117685;
|
||||
var result = new ValueTuple<byte, string, int, bool>();
|
||||
var smsIr = new SmsIr("Og5M562igmzJRhQPnq0GdtieYdLgtfikjzxOmeQBPxJjZtyge5Klc046Lfw1mxSa");
|
||||
|
||||
var sendResult = await smsIr.VerifySendAsync(number, tamplateId,
|
||||
new VerifySendParameter[]
|
||||
{
|
||||
new("FULLNAME", fullname), new("AMOUNT", amount), new("CODE1", code1), new("CODE2", code2)
|
||||
});
|
||||
Thread.Sleep(500);
|
||||
|
||||
|
||||
if (sendResult.Message == "موفق")
|
||||
{
|
||||
|
||||
result = (sendResult.Status, sendResult.Message, sendResult.Data.MessageId, true);
|
||||
return result;
|
||||
}
|
||||
|
||||
result = (sendResult.Status, sendResult.Message, sendResult.Data.MessageId, false);
|
||||
return result;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ using CompanyManagment.App.Contracts.AuthorizedPerson;
|
||||
using _0_Framework.Application;
|
||||
using _0_Framework.Application.UID;
|
||||
using Company.Application.Contracts.AuthorizedBankDetails;
|
||||
using CompanyManagment.App.Contracts.AuthorizedBankDetails;
|
||||
|
||||
namespace CompanyManagment.EFCore.Services;
|
||||
|
||||
|
||||
@@ -89,6 +89,9 @@ EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BackgroundInstitutionContract.Task", "BackgroundInstitutionContract\BackgroundInstitutionContract.Task\BackgroundInstitutionContract.Task.csproj", "{F78FBB92-294B-88BA-168D-F0C578B0D7D6}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ProgramManager", "ProgramManager", "{67AFF7B6-4C4F-464C-A90D-9BDB644D83A9}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
ProgramManager\appsettings.FileStorage.json = ProgramManager\appsettings.FileStorage.json
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{48F6F6A5-7340-42F8-9216-BEB7A4B7D5A1}"
|
||||
EndProject
|
||||
|
||||
@@ -229,153 +229,16 @@ using CompanyManagment.Application;
|
||||
using CompanyManagment.EFCore;
|
||||
using CompanyManagment.EFCore._common;
|
||||
using CompanyManagment.EFCore.Repository;
|
||||
using CompanyManagment.EFCore.Repository;
|
||||
using File.EfCore.Repository;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using P_TextManager.Domin.TextManagerAgg;
|
||||
using CompanyManagment.App.Contracts.CrossJobItems;
|
||||
using Company.Domain.CrossJobItemsAgg;
|
||||
using Company.Domain.DateSalaryAgg;
|
||||
using Company.Domain.DateSalaryItemAgg;
|
||||
using Company.Domain.FinancialStatmentAgg;
|
||||
using Company.Domain.FinancialTransactionAgg;
|
||||
using Company.Domain.GroupPlanAgg;
|
||||
using Company.Domain.GroupPlanJobItemAgg;
|
||||
using Company.Domain.InstitutionContractAgg;
|
||||
using Company.Domain.InstitutionContractContactInfoAgg;
|
||||
using CompanyManagment.App.Contracts.Insurance;
|
||||
using Company.Domain.InsuranceAgg;
|
||||
using Company.Domain.InsuranceEmployeeInfoAgg;
|
||||
using Company.Domain.InsuranceJobItemAgg;
|
||||
using Company.Domain.InsuranceListAgg;
|
||||
using Company.Domain.InsurancJobAgg;
|
||||
using Company.Domain.InsurancWorkshopInfoAgg;
|
||||
using Company.Domain.LeftWorkInsuranceAgg;
|
||||
using Company.Domain.PaymentToEmployeeAgg;
|
||||
using Company.Domain.PaymentToEmployeeItemAgg;
|
||||
using Company.Domain.PercentageAgg;
|
||||
using Company.Domain.PersonnelCodeAgg;
|
||||
using Company.Domain.SmsResultAgg;
|
||||
using Company.Domain.WorkingHoursTempAgg;
|
||||
using Company.Domain.WorkingHoursTempItemAgg;
|
||||
using Company.Domain.WorkshopPlanAgg;
|
||||
using Company.Domain.WorkshopPlanEmployeeAgg;
|
||||
using Company.Domain.ZoneAgg;
|
||||
using CompanyManagment.App.Contracts.ClassifiedSalary;
|
||||
using CompanyManagment.App.Contracts.DateSalary;
|
||||
using CompanyManagment.App.Contracts.DateSalaryItem;
|
||||
using CompanyManagment.App.Contracts.EmployeeInsurancListData;
|
||||
using CompanyManagment.App.Contracts.FinancialStatment;
|
||||
using CompanyManagment.App.Contracts.FinancilTransaction;
|
||||
using CompanyManagment.App.Contracts.InstitutionContract;
|
||||
using CompanyManagment.App.Contracts.InstitutionContractContactinfo;
|
||||
using CompanyManagment.App.Contracts.InsuranceEmployeeInfo;
|
||||
using CompanyManagment.App.Contracts.InsuranceJob;
|
||||
using CompanyManagment.App.Contracts.InsuranceList;
|
||||
using CompanyManagment.App.Contracts.InsuranceWorkshopInfo;
|
||||
using CompanyManagment.App.Contracts.LeftWorkInsurance;
|
||||
using CompanyManagment.App.Contracts.PaymentToEmployee;
|
||||
using CompanyManagment.App.Contracts.Percentage;
|
||||
using CompanyManagment.App.Contracts.PersonnleCode;
|
||||
using CompanyManagment.App.Contracts.SmsResult;
|
||||
using CompanyManagment.App.Contracts.WorkingHoursTemp;
|
||||
using CompanyManagment.App.Contracts.WorkingHoursTempItem;
|
||||
using CompanyManagment.App.Contracts.WorkshopPlan;
|
||||
using CompanyManagment.App.Contracts.Zone;
|
||||
using CompanyManagment.App.Contracts.EmployeeComputeOptions;
|
||||
using Company.Domain.EmployeeComputeOptionsAgg;
|
||||
using Company.Domain.InsuranceYearlySalaryAgg;
|
||||
using Company.Domain.ReportAgg;
|
||||
using Company.Domain.RollCallAgg;
|
||||
using Company.Domain.RollCallEmployeeAgg;
|
||||
using Company.Domain.RollCallPlanAgg;
|
||||
using Company.Domain.RollCallServiceAgg;
|
||||
using CompanyManagment.App.Contracts.InsuranceYearlySalary;
|
||||
using CompanyManagment.App.Contracts.Report;
|
||||
using CompanyManagment.App.Contracts.RollCall;
|
||||
using CompanyManagment.App.Contracts.RollCallEmployee;
|
||||
using CompanyManagment.App.Contracts.RollCallService;
|
||||
using CompanyManagment.App.Contracts.RollCallPlan;
|
||||
using Company.Domain.ReportClientAgg;
|
||||
using Company.Domain.TaxJobCategoryAgg;
|
||||
using Company.Domain.WorkshopAccountAgg;
|
||||
using CompanyManagment.App.Contracts.ReportClient;
|
||||
using CompanyManagment.App.Contracts.TaxJobCategory;
|
||||
using Company.Domain.RollCallEmployeeStatusAgg;
|
||||
using CompanyManagment.App.Contracts.RollCallEmployeeStatus;
|
||||
using Company.Domain.CustomizeWorkshopEmployeeSettingsAgg;
|
||||
using Company.Domain.CustomizeWorkshopGroupSettingsAgg;
|
||||
using Company.Domain.CustomizeWorkshopSettingsAgg;
|
||||
using Company.Domain.FineAgg;
|
||||
using Company.Domain.LoanAgg;
|
||||
using Company.Domain.RewardAgg;
|
||||
using Company.Domain.SalaryAidAgg;
|
||||
using CompanyManagment.App.Contracts.CustomizeWorkshopSettings;
|
||||
using CompanyManagment.App.Contracts.Fine;
|
||||
using CompanyManagment.App.Contracts.Loan;
|
||||
using CompanyManagment.App.Contracts.Reward;
|
||||
using CompanyManagment.App.Contracts.SalaryAid;
|
||||
using Company.Domain.AndroidApkVersionAgg;
|
||||
using Company.Domain.BankAgg;
|
||||
using CompanyManagment.App.Contracts.AndroidApkVersion;
|
||||
using Company.Domain.FineSubjectAgg;
|
||||
using CompanyManagment.App.Contracts.FineSubject;
|
||||
using Company.Domain.CustomizeCheckoutAgg;
|
||||
using CompanyManagment.App.Contracts.CustomizeCheckout;
|
||||
using Company.Domain.WorkshopSubAccountAgg;
|
||||
using Company.Domain.CustomizeCheckoutTempAgg;
|
||||
using Company.Domain.EmployeeBankInformationAgg;
|
||||
using Company.Domain.RollCallAgg.DomainService;
|
||||
using CompanyManagment.App.Contracts.Bank;
|
||||
using CompanyManagment.App.Contracts.EmployeeBankInformation;
|
||||
using Company.Domain.EmployeeDocumentItemAgg;
|
||||
using Company.Domain.EmployeeDocumentsAdminSelectionAgg;
|
||||
using Company.Domain.EmployeeDocumentsAgg;
|
||||
using CompanyManagement.Infrastructure.Excel.SalaryAid;
|
||||
using CompanyManagment.App.Contracts.EmployeeDocuments;
|
||||
using CompanyManagment.App.Contracts.EmployeeDocumentsAdminSelection;
|
||||
using Company.Domain.EmployeeClientTempAgg;
|
||||
using Company.Domain.InstitutionPlanAgg;
|
||||
using Company.Domain.LeftWorkTempAgg;
|
||||
using Company.Domain.TemporaryClientRegistrationAgg;
|
||||
using CompanyManagment.App.Contracts.EmployeeClientTemp;
|
||||
using CompanyManagment.App.Contracts.InstitutionPlan;
|
||||
using CompanyManagment.App.Contracts.LeftWorkTemp;
|
||||
using CompanyManagment.App.Contracts.TemporaryClientRegistration;
|
||||
using Company.Domain.ContactUsAgg;
|
||||
using CompanyManagment.App.Contracts.ContactUs;
|
||||
using Company.Domain.EmployeeAuthorizeTempAgg;
|
||||
using Company.Domain.AdminMonthlyOverviewAgg;
|
||||
using Company.Domain.AuthorizedBankDetailsAgg;
|
||||
using Company.Domain.ContractingPartyBankAccountsAgg;
|
||||
using Company.Domain.PaymentInstrumentAgg;
|
||||
using Company.Domain.PaymentTransactionAgg;
|
||||
using Company.Domain.FinancialInvoiceAgg;
|
||||
using CompanyManagment.App.Contracts.AdminMonthlyOverview;
|
||||
using CompanyManagment.App.Contracts.ContractingPartyBankAccounts;
|
||||
using CompanyManagment.App.Contracts.PaymentInstrument;
|
||||
using CompanyManagment.App.Contracts.PaymentTransaction;
|
||||
using CompanyManagment.App.Contracts.AuthorizedPerson;
|
||||
using Company.Domain.AuthorizedPersonAgg;
|
||||
using Company.Domain.EmployeeFaceEmbeddingAgg;
|
||||
using Company.Domain.InstitutionContractExtensionTempAgg;
|
||||
using Company.Domain.LawAgg;
|
||||
using CompanyManagement.Infrastructure.Mongo.EmployeeFaceEmbeddingRepo;
|
||||
using CompanyManagement.Infrastructure.Mongo.InstitutionContractInsertTempRepo;
|
||||
using CompanyManagment.App.Contracts.EmployeeFaceEmbedding;
|
||||
using CompanyManagment.App.Contracts.Law;
|
||||
using CompanyManagment.EFCore.Repository;
|
||||
using CompanyManagment.App.Contracts.FinancialInvoice;
|
||||
using _0_Framework.Application.FaceEmbedding;
|
||||
using _0_Framework.Infrastructure;
|
||||
using _0_Framework.InfraStructure;
|
||||
using CompanyManagment.App.Contracts.PaymentCallback;
|
||||
using CompanyManagment.App.Contracts.SepehrPaymentGateway;
|
||||
using Company.Domain.CameraBugReportAgg;
|
||||
using CompanyManagment.App.Contracts.AuthorizedBankDetails;
|
||||
using CompanyManagment.App.Contracts.CameraBugReport;
|
||||
using CompanyManagement.Infrastructure.Mongo.CameraBugReportRepo;
|
||||
using CameraBugReportRepository = CompanyManagement.Infrastructure.Mongo.CameraBugReportRepo.CameraBugReportRepository;
|
||||
using Company.Domain._common;
|
||||
using CompanyManagment.EFCore._common;
|
||||
using CompanyManagment.EFCore.Services;
|
||||
using Shared.Contracts.Holidays;
|
||||
|
||||
@@ -622,6 +485,8 @@ public class PersonalBootstrapper
|
||||
|
||||
services.AddTransient<IPaymentTransactionRepository, PaymentTransactionRepository>();
|
||||
services.AddTransient<IPaymentTransactionApplication, PaymentTransactionApplication>();
|
||||
services.AddTransient<IPaymentCallbackHandler, PaymentCallbackHandler>();
|
||||
services.AddTransient<ISepehrPaymentGatewayService, SepehrPaymentGatewayService>();
|
||||
|
||||
services.AddTransient<IContractingPartyBankAccountsApplication, ContractingPartyBankAccountsApplication>();
|
||||
services.AddTransient<IContractingPartyBankAccountsRepository, ContractingPartyBankAccountsRepository>();
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentValidation" Version="12.1.1" />
|
||||
<PackageReference Include="MediatR" Version="14.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.3.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.1" />
|
||||
</ItemGroup>
|
||||
@@ -18,4 +19,10 @@
|
||||
<ProjectReference Include="..\..\Domain\GozareshgirProgramManager.Domain\GozareshgirProgramManager.Domain.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.AspNetCore.Http.Features">
|
||||
<HintPath>C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App\10.0.1\Microsoft.AspNetCore.Http.Features.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
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.ApproveTaskSectionCompletion;
|
||||
|
||||
public record ApproveTaskSectionCompletionCommand(Guid TaskSectionId, bool IsApproved) : IBaseCommand;
|
||||
|
||||
public class ApproveTaskSectionCompletionCommandHandler : IBaseCommandHandler<ApproveTaskSectionCompletionCommand>
|
||||
{
|
||||
private readonly ITaskSectionRepository _taskSectionRepository;
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
private readonly IAuthHelper _authHelper;
|
||||
|
||||
public ApproveTaskSectionCompletionCommandHandler(
|
||||
ITaskSectionRepository taskSectionRepository,
|
||||
IUnitOfWork unitOfWork,
|
||||
IAuthHelper authHelper)
|
||||
{
|
||||
_taskSectionRepository = taskSectionRepository;
|
||||
_unitOfWork = unitOfWork;
|
||||
_authHelper = authHelper;
|
||||
}
|
||||
|
||||
public async Task<OperationResult> Handle(ApproveTaskSectionCompletionCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var currentUserId = _authHelper.GetCurrentUserId()
|
||||
?? throw new UnAuthorizedException("˜ÇÑÈÑ ÇÍÑÇÒ åæ?Ê äÔÏå ÇÓÊ");
|
||||
|
||||
var section = await _taskSectionRepository.GetByIdAsync(request.TaskSectionId, cancellationToken);
|
||||
if (section == null)
|
||||
{
|
||||
return OperationResult.NotFound("ÈÎÔ ãæÑÏ äÙÑ ?ÇÝÊ äÔÏ");
|
||||
}
|
||||
|
||||
if (section.Status != TaskSectionStatus.PendingForCompletion)
|
||||
{
|
||||
return OperationResult.Failure("ÝÞØ ÈÎÔ<C38E>åÇ?? ˜å ÏÑ ÇäÊÙÇÑ Ê˜ã?á åÓÊäÏ ÞÇÈá ÊÇ??Ï ?Ç ÑÏ åÓÊäÏ");
|
||||
}
|
||||
|
||||
if (request.IsApproved)
|
||||
{
|
||||
section.UpdateStatus(TaskSectionStatus.Completed);
|
||||
}
|
||||
else
|
||||
{
|
||||
section.UpdateStatus(TaskSectionStatus.Incomplete);
|
||||
}
|
||||
|
||||
await _unitOfWork.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return OperationResult.Success();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using FluentValidation;
|
||||
|
||||
namespace GozareshgirProgramManager.Application.Modules.Projects.Commands.ApproveTaskSectionCompletion;
|
||||
|
||||
public class ApproveTaskSectionCompletionCommandValidator : AbstractValidator<ApproveTaskSectionCompletionCommand>
|
||||
{
|
||||
public ApproveTaskSectionCompletionCommandValidator()
|
||||
{
|
||||
RuleFor(c => c.TaskSectionId)
|
||||
.NotEmpty()
|
||||
.NotNull()
|
||||
.WithMessage("ÔäÇÓå ÈÎÔ äã?<3F>ÊæÇäÏ ÎÇá? ÈÇÔÏ");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using System.Linq;
|
||||
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.Commands.AutoUpdateDeployStatus;
|
||||
|
||||
public record AutoUpdateDeployStatusCommand : IBaseCommand;
|
||||
|
||||
public class AutoUpdateDeployStatusCommandHandler : IBaseCommandHandler<AutoUpdateDeployStatusCommand>
|
||||
{
|
||||
private readonly IProgramManagerDbContext _dbContext;
|
||||
|
||||
public AutoUpdateDeployStatusCommandHandler(IProgramManagerDbContext dbContext)
|
||||
{
|
||||
_dbContext = dbContext;
|
||||
}
|
||||
|
||||
public async Task<OperationResult> Handle(AutoUpdateDeployStatusCommand request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// Fetch all sections whose phase is still marked as not completed
|
||||
var sections = await _dbContext.TaskSections
|
||||
.Include(ts => ts.Task)
|
||||
.ThenInclude(t => t.Phase)
|
||||
.Where(ts => ts.Task.Phase.DeployStatus == ProjectDeployStatus.NotCompleted)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
if (sections.Count == 0)
|
||||
return OperationResult.Success();
|
||||
|
||||
var phasesToUpdate = sections
|
||||
.GroupBy(ts => ts.Task.PhaseId)
|
||||
.Where(g => g.All(s => s.Status == TaskSectionStatus.Completed))
|
||||
.Select(g => g.First().Task.Phase)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
if (phasesToUpdate.Count == 0)
|
||||
return OperationResult.Success();
|
||||
|
||||
foreach (var phase in phasesToUpdate)
|
||||
{
|
||||
phase.UpdateDeployStatus(ProjectDeployStatus.PendingDevDeploy);
|
||||
}
|
||||
|
||||
await _dbContext.SaveChangesAsync(cancellationToken);
|
||||
return OperationResult.Success();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using GozareshgirProgramManager.Application._Common.Interfaces;
|
||||
using GozareshgirProgramManager.Application._Common.Models;
|
||||
using GozareshgirProgramManager.Domain._Common;
|
||||
using GozareshgirProgramManager.Domain.ProjectAgg.Entities;
|
||||
using GozareshgirProgramManager.Domain.ProjectAgg.Repositories;
|
||||
|
||||
namespace GozareshgirProgramManager.Application.Modules.Projects.Commands.ChangeDeployStatusProject;
|
||||
|
||||
public record ChangeDeployStatusProjectCommand(Guid PhaseId, ProjectDeployStatus Status):IBaseCommand;
|
||||
|
||||
public class ChangeDeployStatusProjectCommandHandler : IBaseCommandHandler<ChangeDeployStatusProjectCommand>
|
||||
{
|
||||
private readonly IProjectRepository _projectRepository;
|
||||
private readonly IProjectPhaseRepository _projectPhaseRepository;
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
|
||||
public ChangeDeployStatusProjectCommandHandler(IProjectRepository projectRepository, IUnitOfWork unitOfWork, IProjectPhaseRepository projectPhaseRepository)
|
||||
{
|
||||
_projectRepository = projectRepository;
|
||||
_unitOfWork = unitOfWork;
|
||||
_projectPhaseRepository = projectPhaseRepository;
|
||||
}
|
||||
|
||||
public async Task<OperationResult> Handle(ChangeDeployStatusProjectCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var project = await _projectPhaseRepository.GetByIdAsync(request.PhaseId, cancellationToken);
|
||||
if (project == null)
|
||||
return OperationResult.NotFound("بخش مورد نظر یافت نشد");
|
||||
|
||||
if (project.DeployStatus == ProjectDeployStatus.NotCompleted)
|
||||
{
|
||||
return OperationResult.Failure("وضعیت استقرار نمیتواند از حالت 'تایید نشده' تغییر کند.");
|
||||
}
|
||||
|
||||
if (request.Status == ProjectDeployStatus.NotCompleted)
|
||||
{
|
||||
return OperationResult.Failure("وضعیت استقرار نمیتواند به حالت 'تایید نشده' تغییر کند.");
|
||||
}
|
||||
project.UpdateDeployStatus(request.Status);
|
||||
|
||||
await _unitOfWork.SaveChangesAsync(cancellationToken);
|
||||
return OperationResult.Success();
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,10 @@ public class ChangeStatusSectionCommandHandler : IBaseCommandHandler<ChangeStatu
|
||||
// Going TO InProgress: Check if section has remaining time, then start work
|
||||
if (!section.HasRemainingTime())
|
||||
return OperationResult.ValidationError("زمان این بخش به پایان رسیده است");
|
||||
|
||||
if (await _taskSectionRepository.HasUserAnyInProgressSectionAsync(section.CurrentAssignedUserId, cancellationToken))
|
||||
{
|
||||
return OperationResult.ValidationError("کاربر مورد نظر در حال حاضر بخش دیگری را در وضعیت 'درحال انجام' دارد");
|
||||
}
|
||||
section.StartWork();
|
||||
}
|
||||
else
|
||||
@@ -86,9 +89,9 @@ public class ChangeStatusSectionCommandHandler : IBaseCommandHandler<ChangeStatu
|
||||
var validTransitions = new Dictionary<TaskSectionStatus, List<TaskSectionStatus>>
|
||||
{
|
||||
{ TaskSectionStatus.ReadyToStart, [TaskSectionStatus.InProgress] },
|
||||
{ TaskSectionStatus.InProgress, [TaskSectionStatus.Incomplete, TaskSectionStatus.Completed] },
|
||||
{ TaskSectionStatus.Incomplete, [TaskSectionStatus.InProgress, TaskSectionStatus.Completed] },
|
||||
{ TaskSectionStatus.Completed, [TaskSectionStatus.InProgress, TaskSectionStatus.Incomplete] }, // Can return to InProgress or Incomplete
|
||||
{ TaskSectionStatus.InProgress, [TaskSectionStatus.Incomplete, TaskSectionStatus.PendingForCompletion] },
|
||||
{ TaskSectionStatus.Incomplete, [TaskSectionStatus.InProgress, TaskSectionStatus.PendingForCompletion] },
|
||||
{ TaskSectionStatus.PendingForCompletion, [TaskSectionStatus.InProgress, TaskSectionStatus.Incomplete] }, // Can return to InProgress or Incomplete
|
||||
{ TaskSectionStatus.NotAssigned, [TaskSectionStatus.InProgress, TaskSectionStatus.ReadyToStart] }
|
||||
};
|
||||
|
||||
|
||||
@@ -4,10 +4,15 @@ using GozareshgirProgramManager.Domain.ProjectAgg.Enums;
|
||||
|
||||
namespace GozareshgirProgramManager.Application.Modules.Projects.Commands.SetTimeProject;
|
||||
|
||||
public record SetTimeProjectCommand(List<SetTimeProjectSectionItem> SectionItems, Guid Id, ProjectHierarchyLevel Level):IBaseCommand;
|
||||
public record SetTimeProjectCommand(
|
||||
List<SetTimeProjectSkillItem> SkillItems,
|
||||
Guid Id,
|
||||
ProjectHierarchyLevel Level,
|
||||
bool CascadeToChildren) : IBaseCommand;
|
||||
|
||||
public class SetTimeSectionTime
|
||||
{
|
||||
public string Description { get; set; }
|
||||
public int Hours { get; set; }
|
||||
public int Minutes { get; set; }
|
||||
}
|
||||
@@ -6,6 +6,8 @@ using GozareshgirProgramManager.Domain._Common.Exceptions;
|
||||
using GozareshgirProgramManager.Domain.ProjectAgg.Entities;
|
||||
using GozareshgirProgramManager.Domain.ProjectAgg.Enums;
|
||||
using GozareshgirProgramManager.Domain.ProjectAgg.Repositories;
|
||||
using GozareshgirProgramManager.Domain.SkillAgg.Repositories;
|
||||
using GozareshgirProgramManager.Domain.UserAgg.Repositories;
|
||||
|
||||
namespace GozareshgirProgramManager.Application.Modules.Projects.Commands.SetTimeProject;
|
||||
|
||||
@@ -15,21 +17,33 @@ public class SetTimeProjectCommandHandler : IBaseCommandHandler<SetTimeProjectCo
|
||||
private readonly IProjectPhaseRepository _projectPhaseRepository;
|
||||
private readonly IProjectTaskRepository _projectTaskRepository;
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
private readonly IAuthHelper _authHelper;
|
||||
private readonly IUserRepository _userRepository;
|
||||
private readonly ISkillRepository _skillRepository;
|
||||
private readonly IPhaseSectionRepository _phaseSectionRepository;
|
||||
private readonly IProjectSectionRepository _projectSectionRepository;
|
||||
private long? _userId;
|
||||
private readonly ITaskSectionRepository _taskSectionRepository;
|
||||
|
||||
|
||||
public SetTimeProjectCommandHandler(
|
||||
IProjectRepository projectRepository,
|
||||
IProjectPhaseRepository projectPhaseRepository,
|
||||
IProjectTaskRepository projectTaskRepository,
|
||||
IUnitOfWork unitOfWork, IAuthHelper authHelper)
|
||||
IUnitOfWork unitOfWork, IAuthHelper authHelper,
|
||||
IUserRepository userRepository, ISkillRepository skillRepository,
|
||||
IPhaseSectionRepository phaseSectionRepository,
|
||||
IProjectSectionRepository projectSectionRepository,
|
||||
ITaskSectionRepository taskSectionRepository)
|
||||
{
|
||||
_projectRepository = projectRepository;
|
||||
_projectPhaseRepository = projectPhaseRepository;
|
||||
_projectTaskRepository = projectTaskRepository;
|
||||
_unitOfWork = unitOfWork;
|
||||
_authHelper = authHelper;
|
||||
_userRepository = userRepository;
|
||||
_skillRepository = skillRepository;
|
||||
_phaseSectionRepository = phaseSectionRepository;
|
||||
_projectSectionRepository = projectSectionRepository;
|
||||
_taskSectionRepository = taskSectionRepository;
|
||||
_userId = authHelper.GetCurrentUserId();
|
||||
}
|
||||
|
||||
@@ -37,6 +51,10 @@ public class SetTimeProjectCommandHandler : IBaseCommandHandler<SetTimeProjectCo
|
||||
{
|
||||
switch (request.Level)
|
||||
{
|
||||
case ProjectHierarchyLevel.Project:
|
||||
return await AssignProject(request);
|
||||
case ProjectHierarchyLevel.Phase:
|
||||
return await AssignProjectPhase(request);
|
||||
case ProjectHierarchyLevel.Task:
|
||||
return await SetTimeForProjectTask(request, cancellationToken);
|
||||
default:
|
||||
@@ -44,67 +62,229 @@ public class SetTimeProjectCommandHandler : IBaseCommandHandler<SetTimeProjectCo
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<OperationResult> SetTimeForProject(SetTimeProjectCommand request,
|
||||
CancellationToken cancellationToken)
|
||||
private async Task<OperationResult> AssignProject(SetTimeProjectCommand request)
|
||||
{
|
||||
var project = await _projectRepository.GetWithFullHierarchyAsync(request.Id);
|
||||
if (project == null)
|
||||
if (project is null)
|
||||
{
|
||||
return OperationResult.NotFound("پروژه یافت نشد");
|
||||
return OperationResult.NotFound("<22><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD>");
|
||||
}
|
||||
|
||||
long? addedByUserId = _userId;
|
||||
var skillItems = request.SkillItems.Where(x=>x.UserId is > 0).ToList();
|
||||
|
||||
// حذف ProjectSections که در validSkills نیستند
|
||||
var validSkillIds = skillItems.Select(x => x.SkillId).ToList();
|
||||
var sectionsToRemove = project.ProjectSections
|
||||
.Where(s => !validSkillIds.Contains(s.SkillId))
|
||||
.ToList();
|
||||
|
||||
foreach (var section in sectionsToRemove)
|
||||
{
|
||||
project.RemoveProjectSection(section.SkillId);
|
||||
}
|
||||
|
||||
// تخصیص در سطح پروژه
|
||||
foreach (var item in skillItems)
|
||||
{
|
||||
var skill = await _skillRepository.GetByIdAsync(item.SkillId);
|
||||
if (skill is null)
|
||||
{
|
||||
return OperationResult.NotFound($"مهارت با شناسه {item.SkillId} یافت نشد");
|
||||
}
|
||||
|
||||
// تنظیم زمان برای تمام sections در تمام فازها و تسکهای پروژه
|
||||
// بررسی و بهروزرسانی یا اضافه کردن ProjectSection
|
||||
var existingSection = project.ProjectSections.FirstOrDefault(s => s.SkillId == item.SkillId);
|
||||
if (existingSection != null)
|
||||
{
|
||||
// اگر وجود داشت، فقط userId را بهروزرسانی کن
|
||||
existingSection.UpdateUser(item.UserId.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
// اگر وجود نداشت، اضافه کن
|
||||
var newSection = new ProjectSection(project.Id, item.UserId.Value, item.SkillId);
|
||||
await _projectSectionRepository.CreateAsync(newSection);
|
||||
}
|
||||
}
|
||||
|
||||
// حالا برای تمام فازها و تسکها cascade کن
|
||||
foreach (var phase in project.Phases)
|
||||
{
|
||||
foreach (var task in phase.Tasks)
|
||||
// اگر CascadeToChildren true است یا فاز override ندارد
|
||||
if (request.CascadeToChildren || !phase.HasAssignmentOverride)
|
||||
{
|
||||
foreach (var section in task.Sections)
|
||||
// حذف PhaseSections که در validSkills نیستند
|
||||
var phaseSectionsToRemove = phase.PhaseSections
|
||||
.Where(s => !validSkillIds.Contains(s.SkillId))
|
||||
.ToList();
|
||||
|
||||
foreach (var section in phaseSectionsToRemove)
|
||||
{
|
||||
var sectionItem = request.SectionItems.FirstOrDefault(si => si.SectionId == section.Id);
|
||||
if (sectionItem != null)
|
||||
phase.RemovePhaseSection(section.SkillId);
|
||||
}
|
||||
|
||||
// برای phase هم باید sectionها را بهروزرسانی کنیم
|
||||
foreach (var item in skillItems )
|
||||
{
|
||||
var existingSection = phase.PhaseSections.FirstOrDefault(s => s.SkillId == item.SkillId);
|
||||
if (existingSection != null)
|
||||
{
|
||||
SetSectionTime(section, sectionItem, addedByUserId);
|
||||
existingSection.Update(item.UserId.Value, item.SkillId);
|
||||
}
|
||||
else
|
||||
{
|
||||
var newPhaseSection = new PhaseSection(phase.Id, item.UserId.Value, item.SkillId);
|
||||
await _phaseSectionRepository.CreateAsync(newPhaseSection);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var task in phase.Tasks)
|
||||
{
|
||||
// اگر CascadeToChildren true است یا تسک override ندارد
|
||||
if (request.CascadeToChildren || !task.HasAssignmentOverride)
|
||||
{
|
||||
// حذف TaskSections که در validSkills نیستند
|
||||
var taskSectionsToRemove = task.Sections
|
||||
.Where(s => !validSkillIds.Contains(s.SkillId))
|
||||
.ToList();
|
||||
|
||||
foreach (var section in taskSectionsToRemove)
|
||||
{
|
||||
task.RemoveSection(section.Id);
|
||||
}
|
||||
|
||||
foreach (var item in skillItems)
|
||||
{
|
||||
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.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
section.AssignToUser(item.UserId.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var newTaskSection = new TaskSection(task.Id, item.SkillId, item.UserId.Value);
|
||||
await _taskSectionRepository.CreateAsync(newTaskSection);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await _unitOfWork.SaveChangesAsync(cancellationToken);
|
||||
await _unitOfWork.SaveChangesAsync();
|
||||
return OperationResult.Success();
|
||||
}
|
||||
|
||||
private async Task<OperationResult> SetTimeForProjectPhase(SetTimeProjectCommand request,
|
||||
CancellationToken cancellationToken)
|
||||
private async Task<OperationResult> AssignProjectPhase(SetTimeProjectCommand request)
|
||||
{
|
||||
var phase = await _projectPhaseRepository.GetWithTasksAsync(request.Id);
|
||||
if (phase == null)
|
||||
if (phase is null)
|
||||
{
|
||||
return OperationResult.NotFound("فاز پروژه یافت نشد");
|
||||
return OperationResult.NotFound("<22><><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD>");
|
||||
}
|
||||
|
||||
long? addedByUserId = _userId;
|
||||
// تخصیص در سطح فاز
|
||||
foreach (var item in request.SkillItems)
|
||||
{
|
||||
var skill = await _skillRepository.GetByIdAsync(item.SkillId);
|
||||
if (skill is null)
|
||||
{
|
||||
return OperationResult.NotFound($"مهارت با شناسه {item.SkillId} یافت نشد");
|
||||
}
|
||||
}
|
||||
|
||||
// تنظیم زمان برای تمام sections در تمام تسکهای این فاز
|
||||
// علامتگذاری که این فاز نسبت به parent متمایز است
|
||||
phase.MarkAsOverridden();
|
||||
|
||||
var skillItems = request.SkillItems.Where(x=>x.UserId is > 0).ToList();
|
||||
|
||||
// حذف PhaseSections که در validSkills نیستند
|
||||
var validSkillIds = skillItems.Select(x => x.SkillId).ToList();
|
||||
var sectionsToRemove = phase.PhaseSections
|
||||
.Where(s => !validSkillIds.Contains(s.SkillId))
|
||||
.ToList();
|
||||
|
||||
foreach (var section in sectionsToRemove)
|
||||
{
|
||||
phase.RemovePhaseSection(section.SkillId);
|
||||
}
|
||||
|
||||
// بهروزرسانی یا اضافه کردن PhaseSection
|
||||
foreach (var item in skillItems)
|
||||
{
|
||||
var existingSection = phase.PhaseSections.FirstOrDefault(s => s.SkillId == item.SkillId);
|
||||
if (existingSection != null)
|
||||
{
|
||||
// اگر وجود داشت، فقط userId را بهروزرسانی کن
|
||||
existingSection.Update(item.UserId!.Value, item.SkillId);
|
||||
}
|
||||
else
|
||||
{
|
||||
// اگر وجود نداشت، اضافه کن
|
||||
var newPhaseSection = new PhaseSection(phase.Id, item.UserId!.Value, item.SkillId);
|
||||
await _phaseSectionRepository.CreateAsync(newPhaseSection);
|
||||
}
|
||||
}
|
||||
|
||||
// cascade به تمام تسکها
|
||||
foreach (var task in phase.Tasks)
|
||||
{
|
||||
foreach (var section in task.Sections)
|
||||
// اگر CascadeToChildren true است یا تسک override ندارد
|
||||
if (request.CascadeToChildren || !task.HasAssignmentOverride)
|
||||
{
|
||||
var sectionItem = request.SectionItems.FirstOrDefault(si => si.SectionId == section.Id);
|
||||
if (sectionItem != null)
|
||||
// حذف TaskSections که در validSkills نیستند
|
||||
var taskSectionsToRemove = task.Sections
|
||||
.Where(s => !validSkillIds.Contains(s.SkillId))
|
||||
.ToList();
|
||||
|
||||
foreach (var section in taskSectionsToRemove)
|
||||
{
|
||||
SetSectionTime(section, sectionItem, addedByUserId);
|
||||
task.RemoveSection(section.Id);
|
||||
}
|
||||
|
||||
foreach (var item in skillItems)
|
||||
{
|
||||
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!.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
section.AssignToUser(item.UserId!.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var newTaskSection = new TaskSection(task.Id, item.SkillId, item.UserId!.Value);
|
||||
await _taskSectionRepository.CreateAsync(newTaskSection);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await _unitOfWork.SaveChangesAsync(cancellationToken);
|
||||
await _unitOfWork.SaveChangesAsync();
|
||||
return OperationResult.Success();
|
||||
}
|
||||
|
||||
|
||||
private async Task<OperationResult> SetTimeForProjectTask(SetTimeProjectCommand request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -116,21 +296,60 @@ public class SetTimeProjectCommandHandler : IBaseCommandHandler<SetTimeProjectCo
|
||||
|
||||
long? addedByUserId = _userId;
|
||||
|
||||
// تنظیم زمان مستقیماً برای sections این تسک
|
||||
foreach (var section in task.Sections)
|
||||
var validSkills = request.SkillItems
|
||||
.Where(x=>x.UserId is > 0).ToList();
|
||||
|
||||
// حذف سکشنهایی که در validSkills نیستند
|
||||
var validSkillIds = validSkills.Select(x => x.SkillId).ToList();
|
||||
var sectionsToRemove = task.Sections
|
||||
.Where(s => !validSkillIds.Contains(s.SkillId))
|
||||
.ToList();
|
||||
|
||||
foreach (var sectionToRemove in sectionsToRemove)
|
||||
{
|
||||
var sectionItem = request.SectionItems.FirstOrDefault(si => si.SectionId == section.Id);
|
||||
if (sectionItem != null)
|
||||
{
|
||||
SetSectionTime(section, sectionItem, addedByUserId);
|
||||
}
|
||||
task.RemoveSection(sectionToRemove.Id);
|
||||
}
|
||||
|
||||
foreach (var skillItem in validSkills)
|
||||
{
|
||||
var section = task.Sections.FirstOrDefault(s => s.SkillId == skillItem.SkillId);
|
||||
|
||||
if (!_userRepository.Exists(x=>x.Id == skillItem.UserId!.Value))
|
||||
{
|
||||
throw new BadRequestException("کاربر با شناسه یافت نشد.");
|
||||
}
|
||||
|
||||
if (section == null)
|
||||
{
|
||||
var taskSection = new TaskSection(task.Id,
|
||||
skillItem.SkillId, skillItem.UserId!.Value);
|
||||
|
||||
task.AddSection(taskSection);
|
||||
section = taskSection;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (section.CurrentAssignedUserId != skillItem.UserId)
|
||||
{
|
||||
if (section.CurrentAssignedUserId > 0)
|
||||
{
|
||||
section.TransferToUser(section.CurrentAssignedUserId, skillItem.UserId!.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
section.AssignToUser(skillItem.UserId!.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SetSectionTime(section, skillItem, addedByUserId);
|
||||
|
||||
}
|
||||
await _unitOfWork.SaveChangesAsync(cancellationToken);
|
||||
return OperationResult.Success();
|
||||
}
|
||||
|
||||
private void SetSectionTime(TaskSection section, SetTimeProjectSectionItem sectionItem, long? addedByUserId)
|
||||
private void SetSectionTime(TaskSection section, SetTimeProjectSkillItem sectionItem, long? addedByUserId)
|
||||
{
|
||||
var initData = sectionItem.InitData;
|
||||
var initialTime = TimeSpan.FromHours(initData.Hours);
|
||||
@@ -147,7 +366,7 @@ public class SetTimeProjectCommandHandler : IBaseCommandHandler<SetTimeProjectCo
|
||||
// افزودن زمانهای اضافی
|
||||
foreach (var additionalTime in sectionItem.AdditionalTime)
|
||||
{
|
||||
var additionalTimeSpan = TimeSpan.FromHours(additionalTime.Hours);
|
||||
var additionalTimeSpan = TimeSpan.FromHours(additionalTime.Hours).Add(TimeSpan.FromMinutes(additionalTime.Minutes));
|
||||
section.AddAdditionalTime(additionalTimeSpan, additionalTime.Description, addedByUserId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,19 +13,15 @@ public class SetTimeProjectCommandValidator:AbstractValidator<SetTimeProjectComm
|
||||
.NotNull()
|
||||
.WithMessage("شناسه پروژه نمیتواند خالی باشد.");
|
||||
|
||||
RuleForEach(x => x.SectionItems)
|
||||
.SetValidator(command => new SetTimeProjectSectionItemValidator());
|
||||
|
||||
RuleFor(x => x.SectionItems)
|
||||
.Must(sectionItems => sectionItems.Any(si => si.InitData?.Hours > 0))
|
||||
.WithMessage("حداقل یکی از بخشها باید مقدار ساعت معتبری داشته باشد.");
|
||||
RuleForEach(x => x.SkillItems)
|
||||
.SetValidator(command => new SetTimeProjectSkillItemValidator());
|
||||
}
|
||||
}
|
||||
public class SetTimeProjectSectionItemValidator:AbstractValidator<SetTimeProjectSectionItem>
|
||||
public class SetTimeProjectSkillItemValidator:AbstractValidator<SetTimeProjectSkillItem>
|
||||
{
|
||||
public SetTimeProjectSectionItemValidator()
|
||||
public SetTimeProjectSkillItemValidator()
|
||||
{
|
||||
RuleFor(x=>x.SectionId)
|
||||
RuleFor(x=>x.SkillId)
|
||||
.NotEmpty()
|
||||
.NotNull()
|
||||
.WithMessage("شناسه بخش نمیتواند خالی باشد.");
|
||||
@@ -47,6 +43,18 @@ public class AdditionalTimeDataValidator: AbstractValidator<SetTimeSectionTime>
|
||||
.GreaterThanOrEqualTo(0)
|
||||
.WithMessage("ساعت نمیتواند منفی باشد.");
|
||||
|
||||
RuleFor(x => x.Hours)
|
||||
.LessThan(1_000)
|
||||
.WithMessage("ساعت باید کمتر از 1000 باشد.");
|
||||
|
||||
RuleFor(x => x.Minutes)
|
||||
.GreaterThanOrEqualTo(0)
|
||||
.WithMessage("دقیقه نمیتواند منفی باشد.");
|
||||
|
||||
RuleFor(x => x.Minutes)
|
||||
.LessThan(60)
|
||||
.WithMessage("دقیقه باید بین 0 تا 59 باشد.");
|
||||
|
||||
RuleFor(x=>x.Description)
|
||||
.MaximumLength(500)
|
||||
.WithMessage("توضیحات نمیتواند بیشتر از 500 کاراکتر باشد.");
|
||||
|
||||
@@ -2,9 +2,10 @@ using GozareshgirProgramManager.Application.Modules.Projects.Commands.SetTimePro
|
||||
|
||||
namespace GozareshgirProgramManager.Application.Modules.Projects.DTOs;
|
||||
|
||||
public class SetTimeProjectSectionItem
|
||||
public class SetTimeProjectSkillItem
|
||||
{
|
||||
public Guid SectionId { get; set; }
|
||||
public Guid SkillId { get; set; }
|
||||
public long? UserId { get; set; }
|
||||
public SetTimeSectionTime InitData { get; set; }
|
||||
public List<SetTimeSectionTime> AdditionalTime { get; set; } = [];
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using GozareshgirProgramManager.Application._Common.Interfaces;
|
||||
|
||||
namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectHierarchySearch;
|
||||
|
||||
/// <summary>
|
||||
/// درخواست جستجو در سراسر سلسلهمراتب پروژه (پروژه، فاز، تسک).
|
||||
/// نتایج با اطلاعات مسیر سلسلهمراتب برای پشتیبانی از ناوبری درخت در رابط کاربری بازگردانده میشود.
|
||||
/// </summary>
|
||||
public record GetProjectSearchQuery(
|
||||
string SearchQuery) : IBaseQuery<GetProjectSearchResponse>;
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
using GozareshgirProgramManager.Application._Common.Interfaces;
|
||||
using GozareshgirProgramManager.Application._Common.Models;
|
||||
using GozareshgirProgramManager.Domain.ProjectAgg.Enums;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectHierarchySearch;
|
||||
|
||||
/// <summary>
|
||||
/// Handler برای درخواست جستجوی سراسری در سلسلهمراتب پروژه.
|
||||
/// این handler در تمام سطحهای پروژه، فاز و تسک جستجو میکند و از تمام فیلدهای متنی (نام، توضیحات) استفاده میکند.
|
||||
/// همچنین در زیرمجموعههای هر سطح (ProjectSections، PhaseSections، TaskSections) جستجو میکند.
|
||||
/// </summary>
|
||||
public class GetProjectSearchQueryHandler : IBaseQueryHandler<GetProjectSearchQuery, GetProjectSearchResponse>
|
||||
{
|
||||
private readonly IProgramManagerDbContext _context;
|
||||
private const int MaxResults = 50;
|
||||
|
||||
public GetProjectSearchQueryHandler(IProgramManagerDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<OperationResult<GetProjectSearchResponse>> Handle(
|
||||
GetProjectSearchQuery request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var searchQuery = request.SearchQuery.ToLower();
|
||||
var results = new List<ProjectHierarchySearchResultDto>();
|
||||
|
||||
// جستجو در پروژهها و ProjectSections
|
||||
var projects = await SearchProjects(searchQuery, cancellationToken);
|
||||
results.AddRange(projects);
|
||||
|
||||
// جستجو در فازها و PhaseSections
|
||||
var phases = await SearchPhases(searchQuery, cancellationToken);
|
||||
results.AddRange(phases);
|
||||
|
||||
// جستجو در تسکها و TaskSections
|
||||
var tasks = await SearchTasks(searchQuery, cancellationToken);
|
||||
results.AddRange(tasks);
|
||||
|
||||
// مرتبسازی نتایج: ابتدا بر اساس سطح سلسلهمراتب (پروژه → فاز → تسک)، سپس بر اساس نام
|
||||
var sortedResults = results
|
||||
.OrderBy(r => r.Level)
|
||||
.ThenBy(r => r.Title)
|
||||
.Take(MaxResults)
|
||||
.ToList();
|
||||
|
||||
var response = new GetProjectSearchResponse(sortedResults);
|
||||
return OperationResult<GetProjectSearchResponse>.Success(response);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// جستجو در جدول پروژهها (نام، توضیحات) و ProjectSections (نام مهارت، توضیحات اولیه)
|
||||
/// </summary>
|
||||
private async Task<List<ProjectHierarchySearchResultDto>> SearchProjects(
|
||||
string searchQuery,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var projects = await _context.Projects
|
||||
.Where(p =>
|
||||
p.Name.ToLower().Contains(searchQuery) ||
|
||||
(p.Description != null && p.Description.ToLower().Contains(searchQuery)))
|
||||
.Select(p => new ProjectHierarchySearchResultDto
|
||||
{
|
||||
Id = p.Id,
|
||||
Title = p.Name,
|
||||
Level = ProjectHierarchyLevel.Project,
|
||||
ProjectId = null,
|
||||
PhaseId = null
|
||||
})
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return projects;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// جستجو در جدول فازهای پروژه (نام، توضیحات) و PhaseSections
|
||||
/// </summary>
|
||||
private async Task<List<ProjectHierarchySearchResultDto>> SearchPhases(
|
||||
string searchQuery,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var phases = await _context.ProjectPhases
|
||||
.Where(ph =>
|
||||
ph.Name.ToLower().Contains(searchQuery) ||
|
||||
(ph.Description != null && ph.Description.ToLower().Contains(searchQuery)))
|
||||
.Select(ph => new ProjectHierarchySearchResultDto
|
||||
{
|
||||
Id = ph.Id,
|
||||
Title = ph.Name,
|
||||
Level = ProjectHierarchyLevel.Phase,
|
||||
ProjectId = ph.ProjectId,
|
||||
PhaseId = null
|
||||
})
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return phases;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// جستجو در جدول تسکهای پروژه (نام، توضیحات) و TaskSections (نام مهارت، توضیح اولیه، اطلاعات اضافی)
|
||||
/// </summary>
|
||||
private async Task<List<ProjectHierarchySearchResultDto>> SearchTasks(
|
||||
string searchQuery,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var tasks = await _context.ProjectTasks
|
||||
.Include(t => t.Sections)
|
||||
.Include(t => t.Phase)
|
||||
.Where(t =>
|
||||
t.Name.ToLower().Contains(searchQuery) ||
|
||||
(t.Description != null && t.Description.ToLower().Contains(searchQuery)) ||
|
||||
t.Sections.Any(s =>
|
||||
(s.InitialDescription != null && s.InitialDescription.ToLower().Contains(searchQuery)) ||
|
||||
s.AdditionalTimes.Any(at => at.Reason != null && at.Reason.ToLower().Contains(searchQuery))))
|
||||
.Select(t => new ProjectHierarchySearchResultDto
|
||||
{
|
||||
Id = t.Id,
|
||||
Title = t.Name,
|
||||
Level = ProjectHierarchyLevel.Task,
|
||||
ProjectId = t.Phase.ProjectId,
|
||||
PhaseId = t.PhaseId
|
||||
})
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return tasks;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
using FluentValidation;
|
||||
|
||||
namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectHierarchySearch;
|
||||
|
||||
/// <summary>
|
||||
/// اعتبارسنج برای درخواست جستجوی سراسری
|
||||
/// </summary>
|
||||
public class GetProjectSearchQueryValidator : AbstractValidator<GetProjectSearchQuery>
|
||||
{
|
||||
public GetProjectSearchQueryValidator()
|
||||
{
|
||||
RuleFor(x => x.SearchQuery)
|
||||
.NotEmpty().WithMessage("متن جستجو نمیتواند خالی باشد.")
|
||||
.MinimumLength(2).WithMessage("متن جستجو باید حداقل 2 حرف باشد.")
|
||||
.MaximumLength(500).WithMessage("متن جستجو نمیتواند بیش از 500 حرف باشد.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectHierarchySearch;
|
||||
|
||||
/// <summary>
|
||||
/// پوستهی پاسخ برای نتایج جستجوی سراسری
|
||||
/// </summary>
|
||||
public record GetProjectSearchResponse(
|
||||
List<ProjectHierarchySearchResultDto> Results);
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
using GozareshgirProgramManager.Domain.ProjectAgg.Enums;
|
||||
|
||||
namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectHierarchySearch;
|
||||
|
||||
/// <summary>
|
||||
/// DTO برای نتایج جستجوی سراسری در سلسلهمراتب پروژه.
|
||||
/// حاوی اطلاعات کافی برای بازسازی مسیر سلسلهمراتب و بسط درخت در رابط کاربری است.
|
||||
/// </summary>
|
||||
public record ProjectHierarchySearchResultDto
|
||||
{
|
||||
/// <summary>
|
||||
/// شناسه آیتم (پروژه، فاز یا تسک)
|
||||
/// </summary>
|
||||
public Guid Id { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// نام/عنوان آیتم
|
||||
/// </summary>
|
||||
public string Title { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// سطح سلسلهمراتب این آیتم
|
||||
/// </summary>
|
||||
public ProjectHierarchyLevel Level { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// شناسه پروژه - همیشه برای فاز و تسک پر شده است، برای پروژه با شناسه خود پر میشود
|
||||
/// </summary>
|
||||
public Guid? ProjectId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// شناسه فاز - فقط برای تسک پر شده است، برای پروژه و فاز خالی است
|
||||
/// </summary>
|
||||
public Guid? PhaseId { get; init; }
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ public record GetProjectListDto
|
||||
public bool HasFront { get; set; }
|
||||
public bool HasBackend { get; set; }
|
||||
public bool HasDesign { get; set; }
|
||||
public int TotalHours { get; set; }
|
||||
public int Minutes { get; set; }
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -53,17 +53,19 @@ public class GetProjectsListQueryHandler : IBaseQueryHandler<GetProjectsListQuer
|
||||
.OrderByDescending(p => p.CreationDate)
|
||||
.ToListAsync(cancellationToken);
|
||||
var result = new List<GetProjectListDto>();
|
||||
|
||||
|
||||
foreach (var project in projects)
|
||||
{
|
||||
var percentage = await CalculateProjectPercentage(project, cancellationToken);
|
||||
var (percentage, totalTime) = await CalculateProjectPercentage(project, cancellationToken);
|
||||
result.Add(new GetProjectListDto
|
||||
{
|
||||
Id = project.Id,
|
||||
Name = project.Name,
|
||||
Level = ProjectHierarchyLevel.Project,
|
||||
ParentId = null,
|
||||
Percentage = percentage
|
||||
Percentage = percentage,
|
||||
TotalHours = (int)totalTime.TotalHours,
|
||||
Minutes = totalTime.Minutes,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -86,14 +88,16 @@ public class GetProjectsListQueryHandler : IBaseQueryHandler<GetProjectsListQuer
|
||||
|
||||
foreach (var phase in phases)
|
||||
{
|
||||
var percentage = await CalculatePhasePercentage(phase, cancellationToken);
|
||||
var (percentage, totalTime) = await CalculatePhasePercentage(phase, cancellationToken);
|
||||
result.Add(new GetProjectListDto
|
||||
{
|
||||
Id = phase.Id,
|
||||
Name = phase.Name,
|
||||
Level = ProjectHierarchyLevel.Phase,
|
||||
ParentId = phase.ProjectId,
|
||||
Percentage = percentage
|
||||
Percentage = percentage,
|
||||
TotalHours = (int)totalTime.TotalHours,
|
||||
Minutes = totalTime.Minutes,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -116,14 +120,16 @@ public class GetProjectsListQueryHandler : IBaseQueryHandler<GetProjectsListQuer
|
||||
|
||||
foreach (var task in tasks)
|
||||
{
|
||||
var percentage = await CalculateTaskPercentage(task, cancellationToken);
|
||||
var (percentage, totalTime) = await CalculateTaskPercentage(task, cancellationToken);
|
||||
result.Add(new GetProjectListDto
|
||||
{
|
||||
Id = task.Id,
|
||||
Name = task.Name,
|
||||
Level = ProjectHierarchyLevel.Task,
|
||||
ParentId = task.PhaseId,
|
||||
Percentage = percentage
|
||||
Percentage = percentage,
|
||||
TotalHours = (int)totalTime.TotalHours,
|
||||
Minutes = totalTime.Minutes
|
||||
});
|
||||
}
|
||||
|
||||
@@ -211,7 +217,7 @@ public class GetProjectsListQueryHandler : IBaseQueryHandler<GetProjectsListQuer
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<int> CalculateProjectPercentage(Project project, CancellationToken cancellationToken)
|
||||
private async Task<(int Percentage, TimeSpan TotalTime)> CalculateProjectPercentage(Project project, CancellationToken cancellationToken)
|
||||
{
|
||||
// گرفتن تمام فازهای پروژه
|
||||
var phases = await _context.ProjectPhases
|
||||
@@ -219,20 +225,24 @@ public class GetProjectsListQueryHandler : IBaseQueryHandler<GetProjectsListQuer
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
if (!phases.Any())
|
||||
return 0;
|
||||
return (0, TimeSpan.Zero);
|
||||
|
||||
// محاسبه درصد هر فاز و میانگینگیری
|
||||
var phasePercentages = new List<int>();
|
||||
var totalTime = TimeSpan.Zero;
|
||||
|
||||
foreach (var phase in phases)
|
||||
{
|
||||
var phasePercentage = await CalculatePhasePercentage(phase, cancellationToken);
|
||||
var (phasePercentage, phaseTime) = await CalculatePhasePercentage(phase, cancellationToken);
|
||||
phasePercentages.Add(phasePercentage);
|
||||
totalTime += phaseTime;
|
||||
}
|
||||
|
||||
return phasePercentages.Any() ? (int)phasePercentages.Average() : 0;
|
||||
var averagePercentage = phasePercentages.Any() ? (int)phasePercentages.Average() : 0;
|
||||
return (averagePercentage, totalTime);
|
||||
}
|
||||
|
||||
private async Task<int> CalculatePhasePercentage(ProjectPhase phase, CancellationToken cancellationToken)
|
||||
private async Task<(int Percentage, TimeSpan TotalTime)> CalculatePhasePercentage(ProjectPhase phase, CancellationToken cancellationToken)
|
||||
{
|
||||
// گرفتن تمام تسکهای فاز
|
||||
var tasks = await _context.ProjectTasks
|
||||
@@ -240,55 +250,66 @@ public class GetProjectsListQueryHandler : IBaseQueryHandler<GetProjectsListQuer
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
if (!tasks.Any())
|
||||
return 0;
|
||||
return (0, TimeSpan.Zero);
|
||||
|
||||
// محاسبه درصد هر تسک و میانگینگیری
|
||||
var taskPercentages = new List<int>();
|
||||
var totalTime = TimeSpan.Zero;
|
||||
|
||||
foreach (var task in tasks)
|
||||
{
|
||||
var taskPercentage = await CalculateTaskPercentage(task, cancellationToken);
|
||||
var (taskPercentage, taskTime) = await CalculateTaskPercentage(task, cancellationToken);
|
||||
taskPercentages.Add(taskPercentage);
|
||||
totalTime += taskTime;
|
||||
}
|
||||
|
||||
return taskPercentages.Any() ? (int)taskPercentages.Average() : 0;
|
||||
var averagePercentage = taskPercentages.Any() ? (int)taskPercentages.Average() : 0;
|
||||
return (averagePercentage, totalTime);
|
||||
}
|
||||
|
||||
private async Task<int> CalculateTaskPercentage(ProjectTask task, CancellationToken cancellationToken)
|
||||
private async Task<(int Percentage, TimeSpan TotalTime)> CalculateTaskPercentage(ProjectTask task, CancellationToken cancellationToken)
|
||||
{
|
||||
// گرفتن تمام سکشنهای تسک با activities
|
||||
var sections = await _context.TaskSections
|
||||
.Include(s => s.Activities)
|
||||
.Include(x=>x.AdditionalTimes)
|
||||
.Where(s => s.TaskId == task.Id)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
if (!sections.Any())
|
||||
return 0;
|
||||
return (0, TimeSpan.Zero);
|
||||
|
||||
// محاسبه درصد هر سکشن و میانگینگیری
|
||||
var sectionPercentages = new List<int>();
|
||||
var totalTime = TimeSpan.Zero;
|
||||
|
||||
foreach (var section in sections)
|
||||
{
|
||||
var sectionPercentage = CalculateSectionPercentage(section);
|
||||
var (sectionPercentage, sectionTime) = CalculateSectionPercentage(section);
|
||||
sectionPercentages.Add(sectionPercentage);
|
||||
totalTime += sectionTime;
|
||||
}
|
||||
|
||||
return sectionPercentages.Any() ? (int)sectionPercentages.Average() : 0;
|
||||
var averagePercentage = sectionPercentages.Any() ? (int)sectionPercentages.Average() : 0;
|
||||
return (averagePercentage, totalTime);
|
||||
}
|
||||
|
||||
private static int CalculateSectionPercentage(TaskSection section)
|
||||
private static (int Percentage, TimeSpan TotalTime) CalculateSectionPercentage(TaskSection section)
|
||||
{
|
||||
// محاسبه کل زمان تخمین زده شده (اولیه + اضافی)
|
||||
var totalEstimatedHours = section.FinalEstimatedHours.TotalHours;
|
||||
|
||||
if (totalEstimatedHours <= 0)
|
||||
return 0;
|
||||
|
||||
// محاسبه کل زمان صرف شده از activities
|
||||
var totalSpentHours = section.Activities.Sum(a => a.GetTimeSpent().TotalHours);
|
||||
var totalSpentTime = TimeSpan.FromHours(section.Activities.Sum(a => a.GetTimeSpent().TotalHours));
|
||||
|
||||
if (totalEstimatedHours <= 0)
|
||||
return (0, section.FinalEstimatedHours);
|
||||
|
||||
var totalSpentHours = totalSpentTime.TotalHours;
|
||||
|
||||
// محاسبه درصد (حداکثر 100%)
|
||||
var percentage = (totalSpentHours / totalEstimatedHours) * 100;
|
||||
return Math.Min((int)Math.Round(percentage), 100);
|
||||
return (Math.Min((int)Math.Round(percentage), 100), section.FinalEstimatedHours);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,8 +22,9 @@ public class ProjectBoardListQueryHandler : IBaseQueryHandler<ProjectBoardListQu
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var currentUserId = _authHelper.GetCurrentUserId();
|
||||
|
||||
var queryable = _programManagerDbContext.TaskSections.AsNoTracking()
|
||||
.Where(x => x.InitialEstimatedHours > TimeSpan.Zero)
|
||||
.Where(x => x.InitialEstimatedHours > TimeSpan.Zero && x.Status != TaskSectionStatus.Completed)
|
||||
.Include(x => x.Task)
|
||||
.ThenInclude(x => x.Phase)
|
||||
.ThenInclude(x => x.Project)
|
||||
@@ -52,67 +53,82 @@ public class ProjectBoardListQueryHandler : IBaseQueryHandler<ProjectBoardListQu
|
||||
.ToDictionaryAsync(x => x.Id, x => x.FullName, cancellationToken);
|
||||
|
||||
|
||||
var result = data.Select(x =>
|
||||
{
|
||||
// محاسبه یکبار برای هر Activity و Cache کردن نتیجه
|
||||
var activityTimeData = x.Activities.Select(a =>
|
||||
var result = data
|
||||
.Select(x =>
|
||||
{
|
||||
var timeSpent = a.GetTimeSpent();
|
||||
return new
|
||||
// محاسبه یکبار برای هر Activity و Cache کردن نتیجه
|
||||
var activityTimeData = x.Activities.Select(a =>
|
||||
{
|
||||
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()
|
||||
var timeSpent = a.GetTimeSpent();
|
||||
return new
|
||||
{
|
||||
UserId = activityData.Activity.UserId,
|
||||
IsCurrentUser = activityData.Activity.UserId == currentUserId,
|
||||
Name = users.GetValueOrDefault(activityData.Activity.UserId, "ناشناس"),
|
||||
WorkedTime = activityData.FormattedTime,
|
||||
WorkedTimeSpan = activityData.TimeSpent,
|
||||
});
|
||||
}
|
||||
}
|
||||
Activity = a,
|
||||
TimeSpent = timeSpent,
|
||||
TotalSeconds = timeSpent.TotalSeconds,
|
||||
FormattedTime = timeSpent.ToString(@"hh\:mm")
|
||||
};
|
||||
}).ToList();
|
||||
|
||||
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()
|
||||
// ادغام پشت سر هم فعالیتهای یک کاربر
|
||||
var mergedHistories = new List<ProjectProgressHistoryDto>();
|
||||
foreach (var activityData in activityTimeData)
|
||||
{
|
||||
CompleteSecond = x.FinalEstimatedHours.TotalSeconds,
|
||||
CurrentSecond = activityTimeData.Sum(a => a.TotalSeconds),
|
||||
Histories = mergedHistories
|
||||
},
|
||||
OriginalUser = users.GetValueOrDefault(x.OriginalAssignedUserId, "ناشناس"),
|
||||
AssignedUser = x.CurrentAssignedUserId == x.OriginalAssignedUserId ? null
|
||||
: users.GetValueOrDefault(x.CurrentAssignedUserId, "ناشناس"),
|
||||
SkillName = x.Skill?.Name??"-",
|
||||
};
|
||||
}).ToList();
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
mergedHistories = mergedHistories.OrderByDescending(h => h.IsCurrentUser).ToList();
|
||||
|
||||
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
|
||||
},
|
||||
OriginalUser = users.GetValueOrDefault(x.OriginalAssignedUserId, "ناشناس"),
|
||||
AssignedUser = x.CurrentAssignedUserId == x.OriginalAssignedUserId ? null
|
||||
: users.GetValueOrDefault(x.CurrentAssignedUserId, "ناشناس"),
|
||||
SkillName = x.Skill?.Name??"-",
|
||||
TaskId = x.TaskId
|
||||
};
|
||||
})
|
||||
.OrderByDescending(r =>
|
||||
{
|
||||
// اگر AssignedUser null نباشد، بررسی کن که برابر current user هست یا نه
|
||||
if (r.AssignedUser != null)
|
||||
{
|
||||
return users.FirstOrDefault(u => u.Value == r.AssignedUser).Key == currentUserId;
|
||||
}
|
||||
// اگر AssignedUser null بود، از OriginalUser بررسی کن
|
||||
return users.FirstOrDefault(u => u.Value == r.OriginalUser).Key == currentUserId;
|
||||
})
|
||||
.ToList();
|
||||
|
||||
return OperationResult<List<ProjectBoardListResponse>>.Success(result);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ public class ProjectBoardListResponse
|
||||
public string? AssignedUser { get; set; }
|
||||
public string OriginalUser { get; set; }
|
||||
public string SkillName { get; set; }
|
||||
|
||||
public Guid TaskId { get; set; }
|
||||
|
||||
}
|
||||
public class ProjectProgressDto
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
using System.Security.AccessControl;
|
||||
using GozareshgirProgramManager.Application._Common.Interfaces;
|
||||
using GozareshgirProgramManager.Application._Common.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.ProjectDeployBoardDetail;
|
||||
|
||||
public record ProjectDeployBoardDetailsResponse(
|
||||
ProjectDeployBoardDetailPhaseItem Phase,
|
||||
List<ProjectDeployBoardDetailTaskItem> Tasks);
|
||||
|
||||
public record ProjectDeployBoardDetailPhaseItem(
|
||||
string Name,
|
||||
TimeSpan TotalTimeSpan,
|
||||
TimeSpan DoneTimeSpan);
|
||||
|
||||
public record ProjectDeployBoardDetailTaskItem(
|
||||
string Name,
|
||||
TimeSpan TotalTimeSpan,
|
||||
TimeSpan DoneTimeSpan,
|
||||
List<ProjectDeployBoardDetailItemSkill> Skills)
|
||||
: ProjectDeployBoardDetailPhaseItem(Name, TotalTimeSpan, DoneTimeSpan);
|
||||
|
||||
public record ProjectDeployBoardDetailItemSkill(string OriginalUserFullName, string SkillName, int TimePercentage);
|
||||
|
||||
public record ProjectDeployBoardDetailsQuery(Guid PhaseId) : IBaseQuery<ProjectDeployBoardDetailsResponse>;
|
||||
|
||||
public class
|
||||
ProjectDeployBoardDetailsQueryHandler : IBaseQueryHandler<ProjectDeployBoardDetailsQuery,
|
||||
ProjectDeployBoardDetailsResponse>
|
||||
{
|
||||
private readonly IProgramManagerDbContext _dbContext;
|
||||
|
||||
public ProjectDeployBoardDetailsQueryHandler(IProgramManagerDbContext dbContext)
|
||||
{
|
||||
_dbContext = dbContext;
|
||||
}
|
||||
|
||||
public async Task<OperationResult<ProjectDeployBoardDetailsResponse>> Handle(ProjectDeployBoardDetailsQuery request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var phase = await _dbContext.ProjectPhases
|
||||
.Include(x => x.Tasks)
|
||||
.ThenInclude(x => x.Sections)
|
||||
.ThenInclude(x => x.Activities)
|
||||
.Include(x => x.Tasks)
|
||||
.ThenInclude(x => x.Sections)
|
||||
.ThenInclude(x => x.AdditionalTimes)
|
||||
.Include(x => x.Tasks)
|
||||
.ThenInclude(x => x.Sections)
|
||||
.ThenInclude(x => x.Skill)
|
||||
.FirstOrDefaultAsync(x => x.Id == request.PhaseId, cancellationToken);
|
||||
|
||||
if (phase == null)
|
||||
return OperationResult<ProjectDeployBoardDetailsResponse>.NotFound("بخش اصلی مورد نظر یافت نشد");
|
||||
|
||||
var userIds = phase.Tasks
|
||||
.SelectMany(t => t.Sections)
|
||||
.Select(s => s.OriginalAssignedUserId)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
var usersDict = await _dbContext.Users
|
||||
.Where(x => userIds.Contains(x.Id))
|
||||
.ToDictionaryAsync(x => x.Id, x => x.FullName, cancellationToken);
|
||||
|
||||
var tasksRes = phase.Tasks.Select(t =>
|
||||
{
|
||||
var totalTime = t.Sections.Select(s => s.FinalEstimatedHours)
|
||||
.Aggregate(TimeSpan.Zero, (sum, next) => sum.Add(next));
|
||||
|
||||
var doneTime = t.Sections.Aggregate(TimeSpan.Zero,
|
||||
(sum, next) => sum.Add(next.GetTotalTimeSpent()));
|
||||
var skills = t.Sections
|
||||
.Select(s =>
|
||||
{
|
||||
var originalUserFullName = usersDict.GetValueOrDefault(s.OriginalAssignedUserId,
|
||||
"کاربر ناشناس");
|
||||
|
||||
var skillName = s.Skill?.Name ?? "بدون مهارت";
|
||||
|
||||
var totalTimeSpent = s.GetTotalTimeSpent();
|
||||
|
||||
var timePercentage = s.FinalEstimatedHours.Ticks > 0
|
||||
? (int)((totalTimeSpent.Ticks / (double)s.FinalEstimatedHours.Ticks) * 100)
|
||||
: 0;
|
||||
|
||||
return new ProjectDeployBoardDetailItemSkill(
|
||||
originalUserFullName,
|
||||
skillName,
|
||||
timePercentage);
|
||||
}).ToList();
|
||||
|
||||
return new ProjectDeployBoardDetailTaskItem(
|
||||
t.Name,
|
||||
totalTime,
|
||||
doneTime,
|
||||
skills);
|
||||
}).ToList();
|
||||
|
||||
var totalTimeSpan = tasksRes.Aggregate(TimeSpan.Zero,
|
||||
(sum, next) => sum.Add(next.TotalTimeSpan));
|
||||
|
||||
var doneTimeSpan = tasksRes.Aggregate(TimeSpan.Zero,
|
||||
(sum, next) => sum.Add(next.DoneTimeSpan));
|
||||
|
||||
var phaseRes = new ProjectDeployBoardDetailPhaseItem(phase.Name, totalTimeSpan, doneTimeSpan);
|
||||
|
||||
var res = new ProjectDeployBoardDetailsResponse(phaseRes, tasksRes);
|
||||
|
||||
return OperationResult<ProjectDeployBoardDetailsResponse>.Success(res);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using FluentValidation;
|
||||
|
||||
namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.ProjectDeployBoardDetail;
|
||||
|
||||
public class ProjectDeployBoardDetailsQueryValidator:AbstractValidator<ProjectDeployBoardDetailsQuery>
|
||||
{
|
||||
public ProjectDeployBoardDetailsQueryValidator()
|
||||
{
|
||||
RuleFor(x=>x.PhaseId).NotNull().WithMessage("شناسه بخش اصلی نمیتواند خالی باشد");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
using System.Xml.Schema;
|
||||
using GozareshgirProgramManager.Application._Common.Interfaces;
|
||||
using GozareshgirProgramManager.Application._Common.Models;
|
||||
using GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectsList;
|
||||
using GozareshgirProgramManager.Domain.ProjectAgg.Entities;
|
||||
using GozareshgirProgramManager.Domain.ProjectAgg.Enums;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.ProjectDeployBoardList;
|
||||
|
||||
public record ProjectDeployBoardListItem()
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string ProjectName { get; set; }
|
||||
public string PhaseName { get; set; }
|
||||
public int TotalTasks { get; set; }
|
||||
public int DoneTasks { get; set; }
|
||||
public TimeSpan TotalTimeSpan { get; set; }
|
||||
public TimeSpan DoneTimeSpan { get; set; }
|
||||
public ProjectDeployStatus DeployStatus { get; set; }
|
||||
}
|
||||
public record GetProjectsDeployBoardListResponse(List<ProjectDeployBoardListItem> Items);
|
||||
|
||||
|
||||
public record GetProjectDeployBoardListQuery():IBaseQuery<GetProjectsDeployBoardListResponse>;
|
||||
|
||||
public class ProjectDeployBoardListQueryHandler:IBaseQueryHandler<GetProjectDeployBoardListQuery, GetProjectsDeployBoardListResponse>
|
||||
{
|
||||
private readonly IProgramManagerDbContext _dbContext;
|
||||
private readonly IAuthHelper _authHelper;
|
||||
|
||||
|
||||
public ProjectDeployBoardListQueryHandler(IProgramManagerDbContext dbContext, IAuthHelper authHelper)
|
||||
{
|
||||
_dbContext = dbContext;
|
||||
_authHelper = authHelper;
|
||||
}
|
||||
|
||||
public async Task<OperationResult<GetProjectsDeployBoardListResponse>> Handle(GetProjectDeployBoardListQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var userId = _authHelper.GetCurrentUserId();
|
||||
if (userId == null)
|
||||
{
|
||||
return OperationResult<GetProjectsDeployBoardListResponse>.NotFound("کاربر یافت نشد");
|
||||
}
|
||||
|
||||
var query =await _dbContext.TaskSections
|
||||
.Include(x=>x.Activities)
|
||||
.Include(x=>x.Task)
|
||||
.ThenInclude(x => x.Phase)
|
||||
.ThenInclude(x => x.Project)
|
||||
.AsNoTracking()
|
||||
.Where(x => x.Status == TaskSectionStatus.Completed
|
||||
|| x.Status == TaskSectionStatus.PendingForCompletion
|
||||
|| (x.Task.Phase.DeployStatus != ProjectDeployStatus.NotCompleted
|
||||
&& x.InitialEstimatedHours>TimeSpan.Zero))
|
||||
.GroupBy(x=>x.Task.PhaseId).ToListAsync(cancellationToken: cancellationToken);
|
||||
|
||||
var list = query.Select(g => new ProjectDeployBoardListItem
|
||||
{
|
||||
Id = g.Key,
|
||||
ProjectName = g.First().Task.Phase.Project.Name,
|
||||
PhaseName = g.First().Task.Phase.Name,
|
||||
TotalTasks = g.Select(x => x.TaskId).Distinct().Count(),
|
||||
DoneTasks = g.Where(x => x.Status == TaskSectionStatus.Completed)
|
||||
.Select(x => x.TaskId).Distinct().Count(),
|
||||
TotalTimeSpan = TimeSpan.FromTicks(g.Sum(x => x.InitialEstimatedHours.Ticks)),
|
||||
DoneTimeSpan = TimeSpan.FromTicks(g.Sum(x=>x.GetTotalTimeSpent().Ticks)),
|
||||
DeployStatus = g.First().Task.Phase.DeployStatus
|
||||
}).ToList();
|
||||
var response = new GetProjectsDeployBoardListResponse(list);
|
||||
return OperationResult<GetProjectsDeployBoardListResponse>.Success(response);
|
||||
}
|
||||
}
|
||||
@@ -3,21 +3,22 @@ using GozareshgirProgramManager.Domain.ProjectAgg.Enums;
|
||||
|
||||
namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.ProjectSetTimeDetails;
|
||||
|
||||
public record ProjectSetTimeDetailsQuery(Guid TaskId)
|
||||
public record ProjectSetTimeDetailsQuery(Guid Id, ProjectHierarchyLevel Level)
|
||||
: IBaseQuery<ProjectSetTimeResponse>;
|
||||
public record ProjectSetTimeResponse(
|
||||
List<ProjectSetTimeResponseSections> SectionItems,
|
||||
List<ProjectSetTimeResponseSkill> SkillItems,
|
||||
Guid Id,
|
||||
ProjectHierarchyLevel Level);
|
||||
|
||||
public record ProjectSetTimeResponseSections
|
||||
public record ProjectSetTimeResponseSkill
|
||||
{
|
||||
public Guid SkillId { get; init; }
|
||||
public string SkillName { get; init; }
|
||||
public string UserName { get; init; }
|
||||
public int InitialTime { get; set; }
|
||||
public long UserId { get; set; }
|
||||
public string UserFullName { get; init; }
|
||||
public int InitialHours { get; set; }
|
||||
public int InitialMinutes { 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; }
|
||||
@@ -25,6 +26,8 @@ public record ProjectSetTimeResponseSections
|
||||
|
||||
public class ProjectSetTimeResponseSectionAdditionalTime
|
||||
{
|
||||
public int Time { get; init; }
|
||||
public int Hours { get; init; }
|
||||
public int Minutes { get; init; }
|
||||
public string Description { get; init; }
|
||||
public string CreationDate { get; set; }
|
||||
}
|
||||
|
||||
@@ -22,17 +22,30 @@ public class ProjectSetTimeDetailsQueryHandler
|
||||
|
||||
public async Task<OperationResult<ProjectSetTimeResponse>> Handle(ProjectSetTimeDetailsQuery request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return request.Level switch
|
||||
{
|
||||
ProjectHierarchyLevel.Task => await GetTaskSetTimeDetails(request.Id, request.Level, cancellationToken),
|
||||
ProjectHierarchyLevel.Phase => await GetPhaseSetTimeDetails(request.Id, request.Level, cancellationToken),
|
||||
ProjectHierarchyLevel.Project => await GetProjectSetTimeDetails(request.Id, request.Level, cancellationToken),
|
||||
_ => OperationResult<ProjectSetTimeResponse>.Failure("سطح معادل نامعتبر است")
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<OperationResult<ProjectSetTimeResponse>> GetTaskSetTimeDetails(Guid id, ProjectHierarchyLevel level,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var task = await _context.ProjectTasks
|
||||
.Where(p => p.Id == request.TaskId)
|
||||
.Where(p => p.Id == id)
|
||||
.Include(x => x.Sections)
|
||||
.ThenInclude(x => x.AdditionalTimes).AsNoTracking()
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
if (task == null)
|
||||
{
|
||||
return OperationResult<ProjectSetTimeResponse>.NotFound("Project not found");
|
||||
return OperationResult<ProjectSetTimeResponse>.NotFound("تسک یافت نشد");
|
||||
}
|
||||
|
||||
var userIds = task.Sections.Select(x => x.OriginalAssignedUserId)
|
||||
.Distinct().ToList();
|
||||
|
||||
@@ -40,40 +53,142 @@ public class ProjectSetTimeDetailsQueryHandler
|
||||
.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))
|
||||
var skills = await _context.Skills
|
||||
.AsNoTracking()
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var res = new ProjectSetTimeResponse(
|
||||
task.Sections.Select(ts =>
|
||||
skills.Select(skill =>
|
||||
{
|
||||
var section = task.Sections
|
||||
.FirstOrDefault(x => x.SkillId == skill.Id);
|
||||
var user = users.FirstOrDefault(x => x.Id == section?.OriginalAssignedUserId);
|
||||
return new ProjectSetTimeResponseSkill
|
||||
{
|
||||
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);
|
||||
AdditionalTimes = section?.AdditionalTimes
|
||||
.Select(x => new ProjectSetTimeResponseSectionAdditionalTime
|
||||
{
|
||||
Description = x.Reason ?? "",
|
||||
Hours = (int)x.Hours.TotalHours,
|
||||
Minutes = x.Hours.Minutes,
|
||||
CreationDate = x.CreationDate.ToFarsi()
|
||||
}).OrderBy(x => x.CreationDate).ToList() ?? [],
|
||||
InitCreationTime = section?.CreationDate.ToFarsi() ?? "",
|
||||
SkillName = skill.Name ?? "",
|
||||
UserFullName = user?.FullName ?? "",
|
||||
SectionId = section?.Id ?? Guid.Empty,
|
||||
InitialDescription = section?.InitialDescription ?? "",
|
||||
InitialHours = (int)(section?.InitialEstimatedHours.TotalHours ?? 0),
|
||||
InitialMinutes = section?.InitialEstimatedHours.Minutes ?? 0,
|
||||
UserId = section?.OriginalAssignedUserId ?? 0,
|
||||
SkillId = skill.Id,
|
||||
};
|
||||
}).OrderBy(x => x.SkillId).ToList(),
|
||||
task.Id,
|
||||
level);
|
||||
|
||||
return OperationResult<ProjectSetTimeResponse>.Success(res);
|
||||
}
|
||||
|
||||
private async Task<OperationResult<ProjectSetTimeResponse>> GetPhaseSetTimeDetails(Guid id, ProjectHierarchyLevel level,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var phase = await _context.ProjectPhases
|
||||
.Where(p => p.Id == id)
|
||||
.Include(x => x.PhaseSections).AsNoTracking()
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
if (phase == null)
|
||||
{
|
||||
return OperationResult<ProjectSetTimeResponse>.NotFound("فاز یافت نشد");
|
||||
}
|
||||
|
||||
var userIds = phase.PhaseSections.Select(x => x.UserId)
|
||||
.Distinct().ToList();
|
||||
|
||||
var users = await _context.Users
|
||||
.Where(x => userIds.Contains(x.Id))
|
||||
.AsNoTracking()
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var skills = await _context.Skills
|
||||
.AsNoTracking()
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var res = new ProjectSetTimeResponse(
|
||||
skills.Select(skill =>
|
||||
{
|
||||
var section = phase.PhaseSections
|
||||
.FirstOrDefault(x => x.SkillId == skill.Id);
|
||||
var user = users.FirstOrDefault(x => x.Id == section?.UserId);
|
||||
return new ProjectSetTimeResponseSkill
|
||||
{
|
||||
AdditionalTimes = [],
|
||||
InitCreationTime = "",
|
||||
SkillName = skill.Name ?? "",
|
||||
UserFullName = user?.FullName ?? "",
|
||||
SectionId = section?.Id ?? Guid.Empty,
|
||||
InitialDescription = "",
|
||||
InitialHours = 0,
|
||||
InitialMinutes = 0,
|
||||
UserId = section?.UserId ?? 0,
|
||||
SkillId = skill.Id,
|
||||
};
|
||||
}).OrderBy(x => x.SkillId).ToList(),
|
||||
phase.Id,
|
||||
level);
|
||||
|
||||
return OperationResult<ProjectSetTimeResponse>.Success(res);
|
||||
}
|
||||
|
||||
private async Task<OperationResult<ProjectSetTimeResponse>> GetProjectSetTimeDetails(Guid id, ProjectHierarchyLevel level,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var project = await _context.Projects
|
||||
.Where(p => p.Id == id)
|
||||
.Include(x => x.ProjectSections).AsNoTracking()
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
if (project == null)
|
||||
{
|
||||
return OperationResult<ProjectSetTimeResponse>.NotFound("پروژه یافت نشد");
|
||||
}
|
||||
|
||||
var userIds = project.ProjectSections.Select(x => x.UserId)
|
||||
.Distinct().ToList();
|
||||
|
||||
var users = await _context.Users
|
||||
.Where(x => userIds.Contains(x.Id))
|
||||
.AsNoTracking()
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var skills = await _context.Skills
|
||||
.AsNoTracking()
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var res = new ProjectSetTimeResponse(
|
||||
skills.Select(skill =>
|
||||
{
|
||||
var section = project.ProjectSections
|
||||
.FirstOrDefault(x => x.SkillId == skill.Id);
|
||||
var user = users.FirstOrDefault(x => x.Id == section?.UserId);
|
||||
return new ProjectSetTimeResponseSkill
|
||||
{
|
||||
AdditionalTimes = [],
|
||||
InitCreationTime = "",
|
||||
SkillName = skill.Name ?? "",
|
||||
UserFullName = user?.FullName ?? "",
|
||||
SectionId = section?.Id ?? Guid.Empty,
|
||||
InitialDescription = "",
|
||||
InitialHours = 0,
|
||||
InitialMinutes = 0,
|
||||
UserId = section?.UserId ?? 0,
|
||||
SkillId = skill.Id,
|
||||
};
|
||||
}).OrderBy(x => x.SkillId).ToList(),
|
||||
project.Id,
|
||||
level);
|
||||
|
||||
return OperationResult<ProjectSetTimeResponse>.Success(res);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
using FluentValidation;
|
||||
using GozareshgirProgramManager.Domain.ProjectAgg.Enums;
|
||||
|
||||
namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.ProjectSetTimeDetails;
|
||||
|
||||
public class ProjectSetTimeDetailsQueryValidator:AbstractValidator<ProjectSetTimeDetailsQuery>
|
||||
public class ProjectSetTimeDetailsQueryValidator : AbstractValidator<ProjectSetTimeDetailsQuery>
|
||||
{
|
||||
public ProjectSetTimeDetailsQueryValidator()
|
||||
{
|
||||
RuleFor(x => x.TaskId)
|
||||
RuleFor(x => x.Id)
|
||||
.NotEmpty()
|
||||
.WithMessage("شناسه پروژه نمیتواند خالی باشد.");
|
||||
.WithMessage("شناسه نمیتواند خالی باشد.");
|
||||
|
||||
RuleFor(x => x.Level)
|
||||
.IsInEnum()
|
||||
.WithMessage("سطح معادل نامعتبر است.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using GozareshgirProgramManager.Application._Common.Models;
|
||||
using GozareshgirProgramManager.Application._Common.Interfaces;
|
||||
using GozareshgirProgramManager.Domain._Common.Exceptions;
|
||||
using GozareshgirProgramManager.Domain.TaskChatAgg.Repositories;
|
||||
|
||||
namespace GozareshgirProgramManager.Application.Modules.TaskChat.Commands.DeleteMessage;
|
||||
|
||||
public record DeleteMessageCommand(Guid MessageId) : IBaseCommand;
|
||||
|
||||
public class DeleteMessageCommandHandler : IBaseCommandHandler<DeleteMessageCommand>
|
||||
{
|
||||
private readonly ITaskChatMessageRepository _repository;
|
||||
private readonly IAuthHelper _authHelper;
|
||||
|
||||
public DeleteMessageCommandHandler(ITaskChatMessageRepository repository, IAuthHelper authHelper)
|
||||
{
|
||||
_repository = repository;
|
||||
_authHelper = authHelper;
|
||||
}
|
||||
|
||||
public async Task<OperationResult> Handle(DeleteMessageCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var currentUserId = _authHelper.GetCurrentUserId()??
|
||||
throw new UnAuthorizedException("کاربر احراز هویت نشده است");
|
||||
|
||||
var message = await _repository.GetByIdAsync(request.MessageId);
|
||||
if (message == null)
|
||||
{
|
||||
return OperationResult.NotFound("پیام یافت نشد");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
message.DeleteMessage(currentUserId);
|
||||
await _repository.UpdateAsync(message);
|
||||
await _repository.SaveChangesAsync();
|
||||
|
||||
// TODO: SignalR notification
|
||||
|
||||
return OperationResult.Success();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return OperationResult.ValidationError(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using GozareshgirProgramManager.Application._Common.Interfaces;
|
||||
using GozareshgirProgramManager.Application._Common.Models;
|
||||
using GozareshgirProgramManager.Domain._Common.Exceptions;
|
||||
using GozareshgirProgramManager.Domain.TaskChatAgg.Repositories;
|
||||
using MediatR;
|
||||
|
||||
namespace GozareshgirProgramManager.Application.Modules.TaskChat.Commands.EditMessage;
|
||||
|
||||
public record EditMessageCommand(
|
||||
Guid MessageId,
|
||||
string NewTextContent
|
||||
) : IBaseCommand;
|
||||
|
||||
public class EditMessageCommandHandler : IBaseCommandHandler<EditMessageCommand>
|
||||
{
|
||||
private readonly ITaskChatMessageRepository _repository;
|
||||
private readonly IAuthHelper _authHelper;
|
||||
|
||||
public EditMessageCommandHandler(ITaskChatMessageRepository repository, IAuthHelper authHelper)
|
||||
{
|
||||
_repository = repository;
|
||||
_authHelper = authHelper;
|
||||
}
|
||||
|
||||
public async Task<OperationResult> Handle(EditMessageCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var currentUserId = _authHelper.GetCurrentUserId()??
|
||||
throw new UnAuthorizedException("کاربر احراز هویت نشده است");
|
||||
|
||||
var message = await _repository.GetByIdAsync(request.MessageId);
|
||||
if (message == null)
|
||||
{
|
||||
return OperationResult.NotFound("پیام یافت نشد");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
message.EditMessage(request.NewTextContent, currentUserId);
|
||||
await _repository.UpdateAsync(message);
|
||||
await _repository.SaveChangesAsync();
|
||||
|
||||
// TODO: SignalR notification
|
||||
|
||||
return OperationResult.Success();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return OperationResult.ValidationError(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using GozareshgirProgramManager.Application._Common.Interfaces;
|
||||
using GozareshgirProgramManager.Application._Common.Models;
|
||||
using GozareshgirProgramManager.Domain._Common.Exceptions;
|
||||
using GozareshgirProgramManager.Domain.TaskChatAgg.Repositories;
|
||||
using MediatR;
|
||||
|
||||
namespace GozareshgirProgramManager.Application.Modules.TaskChat.Commands.PinMessage;
|
||||
|
||||
public record PinMessageCommand(Guid MessageId) : IBaseCommand;
|
||||
|
||||
public class PinMessageCommandHandler : IBaseCommandHandler<PinMessageCommand>
|
||||
{
|
||||
private readonly ITaskChatMessageRepository _repository;
|
||||
private readonly IAuthHelper _authHelper;
|
||||
|
||||
public PinMessageCommandHandler(ITaskChatMessageRepository repository, IAuthHelper authHelper)
|
||||
{
|
||||
_repository = repository;
|
||||
_authHelper = authHelper;
|
||||
}
|
||||
|
||||
public async Task<OperationResult> Handle(PinMessageCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var currentUserId = _authHelper.GetCurrentUserId()??
|
||||
throw new UnAuthorizedException("کاربر احراز هویت نشده است");
|
||||
|
||||
var message = await _repository.GetByIdAsync(request.MessageId);
|
||||
if (message == null)
|
||||
{
|
||||
return OperationResult.NotFound("پیام یافت نشد");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
message.PinMessage(currentUserId);
|
||||
await _repository.UpdateAsync(message);
|
||||
await _repository.SaveChangesAsync();
|
||||
|
||||
return OperationResult.Success();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return OperationResult.ValidationError(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
using GozareshgirProgramManager.Application._Common.Interfaces;
|
||||
using GozareshgirProgramManager.Application._Common.Models;
|
||||
using GozareshgirProgramManager.Application.Modules.TaskChat.DTOs;
|
||||
using GozareshgirProgramManager.Application.Services.FileManagement;
|
||||
using GozareshgirProgramManager.Domain._Common.Exceptions;
|
||||
using GozareshgirProgramManager.Domain.TaskChatAgg.Entities;
|
||||
using GozareshgirProgramManager.Domain.TaskChatAgg.Repositories;
|
||||
using GozareshgirProgramManager.Domain.TaskChatAgg.Enums;
|
||||
using GozareshgirProgramManager.Domain.FileManagementAgg.Entities;
|
||||
using GozareshgirProgramManager.Domain.FileManagementAgg.Repositories;
|
||||
using GozareshgirProgramManager.Domain.FileManagementAgg.Enums;
|
||||
using GozareshgirProgramManager.Domain.ProjectAgg.Repositories;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace GozareshgirProgramManager.Application.Modules.TaskChat.Commands.SendMessage;
|
||||
|
||||
public record SendMessageCommand(
|
||||
Guid TaskId,
|
||||
MessageType MessageType,
|
||||
string? TextContent,
|
||||
IFormFile? File,
|
||||
Guid? ReplyToMessageId
|
||||
) : IBaseCommand<MessageDto>;
|
||||
|
||||
public class SendMessageCommandHandler : IBaseCommandHandler<SendMessageCommand, MessageDto>
|
||||
{
|
||||
private readonly ITaskChatMessageRepository _messageRepository;
|
||||
private readonly IUploadedFileRepository _fileRepository;
|
||||
private readonly IProjectTaskRepository _taskRepository;
|
||||
private readonly IFileStorageService _fileStorageService;
|
||||
private readonly IThumbnailGeneratorService _thumbnailService;
|
||||
private readonly IAuthHelper _authHelper;
|
||||
|
||||
public SendMessageCommandHandler(
|
||||
ITaskChatMessageRepository messageRepository,
|
||||
IUploadedFileRepository fileRepository,
|
||||
IProjectTaskRepository taskRepository,
|
||||
IFileStorageService fileStorageService,
|
||||
IThumbnailGeneratorService thumbnailService, IAuthHelper authHelper)
|
||||
{
|
||||
_messageRepository = messageRepository;
|
||||
_fileRepository = fileRepository;
|
||||
_taskRepository = taskRepository;
|
||||
_fileStorageService = fileStorageService;
|
||||
_thumbnailService = thumbnailService;
|
||||
_authHelper = authHelper;
|
||||
}
|
||||
|
||||
public async Task<OperationResult<MessageDto>> Handle(SendMessageCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var currentUserId = _authHelper.GetCurrentUserId()
|
||||
?? throw new UnAuthorizedException("کاربر احراز هویت نشده است");
|
||||
|
||||
var task = await _taskRepository.GetByIdAsync(request.TaskId, cancellationToken);
|
||||
if (task == null)
|
||||
{
|
||||
return OperationResult<MessageDto>.NotFound("تسک یافت نشد");
|
||||
}
|
||||
|
||||
Guid? uploadedFileId = null;
|
||||
if (request.File != null)
|
||||
{
|
||||
if (request.File.Length == 0)
|
||||
{
|
||||
return OperationResult<MessageDto>.ValidationError("فایل خالی است");
|
||||
}
|
||||
|
||||
const long maxFileSize = 100 * 1024 * 1024;
|
||||
if (request.File.Length > maxFileSize)
|
||||
{
|
||||
return OperationResult<MessageDto>.ValidationError("حجم فایل بیش از حد مجاز است (حداکثر 100MB)");
|
||||
}
|
||||
|
||||
var fileType = DetectFileType(request.File.ContentType, Path.GetExtension(request.File.FileName));
|
||||
|
||||
var uploadedFile = new UploadedFile(
|
||||
originalFileName: request.File.FileName,
|
||||
fileSizeBytes: request.File.Length,
|
||||
mimeType: request.File.ContentType,
|
||||
fileType: fileType,
|
||||
category: FileCategory.TaskChatMessage,
|
||||
uploadedByUserId: currentUserId,
|
||||
storageProvider: StorageProvider.LocalFileSystem
|
||||
);
|
||||
|
||||
await _fileRepository.AddAsync(uploadedFile);
|
||||
await _fileRepository.SaveChangesAsync();
|
||||
|
||||
try
|
||||
{
|
||||
using var stream = request.File.OpenReadStream();
|
||||
var uploadResult = await _fileStorageService.UploadAsync(
|
||||
stream,
|
||||
uploadedFile.UniqueFileName,
|
||||
"TaskChatMessage"
|
||||
);
|
||||
|
||||
uploadedFile.CompleteUpload(uploadResult.StoragePath, uploadResult.StorageUrl);
|
||||
|
||||
if (fileType == FileType.Image)
|
||||
{
|
||||
var dimensions = await _thumbnailService.GetImageDimensionsAsync(uploadResult.StoragePath);
|
||||
if (dimensions.HasValue)
|
||||
{
|
||||
uploadedFile.SetImageDimensions(dimensions.Value.Width, dimensions.Value.Height);
|
||||
}
|
||||
|
||||
var thumbnail = await _thumbnailService
|
||||
.GenerateImageThumbnailAsync(uploadResult.StoragePath, category: "TaskChatMessage");
|
||||
if (thumbnail.HasValue)
|
||||
{
|
||||
uploadedFile.SetThumbnail(thumbnail.Value.ThumbnailUrl);
|
||||
}
|
||||
}
|
||||
|
||||
await _fileRepository.UpdateAsync(uploadedFile);
|
||||
await _fileRepository.SaveChangesAsync();
|
||||
|
||||
uploadedFileId = uploadedFile.Id;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await _fileRepository.DeleteAsync(uploadedFile);
|
||||
await _fileRepository.SaveChangesAsync();
|
||||
|
||||
return OperationResult<MessageDto>.ValidationError($"خطا در آپلود فایل: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
var message = new TaskChatMessage(
|
||||
taskId: request.TaskId,
|
||||
senderUserId: currentUserId,
|
||||
messageType: request.MessageType,
|
||||
textContent: request.TextContent,
|
||||
uploadedFileId
|
||||
);
|
||||
|
||||
if (request.ReplyToMessageId.HasValue)
|
||||
{
|
||||
message.SetReplyTo(request.ReplyToMessageId.Value);
|
||||
}
|
||||
|
||||
await _messageRepository.AddAsync(message);
|
||||
await _messageRepository.SaveChangesAsync();
|
||||
|
||||
if (uploadedFileId.HasValue)
|
||||
{
|
||||
var file = await _fileRepository.GetByIdAsync(uploadedFileId.Value);
|
||||
if (file != null)
|
||||
{
|
||||
file.SetReference("TaskChatMessage", message.Id.ToString());
|
||||
await _fileRepository.UpdateAsync(file);
|
||||
await _fileRepository.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
var dto = new MessageDto
|
||||
{
|
||||
Id = message.Id,
|
||||
TaskId = message.TaskId,
|
||||
SenderUserId = message.SenderUserId,
|
||||
SenderName = "کاربر",
|
||||
MessageType = message.MessageType.ToString(),
|
||||
TextContent = message.TextContent,
|
||||
ReplyToMessageId = message.ReplyToMessageId,
|
||||
IsEdited = message.IsEdited,
|
||||
IsPinned = message.IsPinned,
|
||||
CreationDate = message.CreationDate,
|
||||
IsMine = true
|
||||
};
|
||||
|
||||
if (uploadedFileId.HasValue)
|
||||
{
|
||||
var file = await _fileRepository.GetByIdAsync(uploadedFileId.Value);
|
||||
if (file != null)
|
||||
{
|
||||
dto.File = new MessageFileDto
|
||||
{
|
||||
Id = file.Id,
|
||||
FileName = file.OriginalFileName,
|
||||
FileUrl = file.StorageUrl ?? "",
|
||||
FileSizeBytes = file.FileSizeBytes,
|
||||
FileType = file.FileType.ToString(),
|
||||
ThumbnailUrl = file.ThumbnailUrl,
|
||||
ImageWidth = file.ImageWidth,
|
||||
ImageHeight = file.ImageHeight,
|
||||
DurationSeconds = file.DurationSeconds
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return OperationResult<MessageDto>.Success(dto);
|
||||
}
|
||||
|
||||
private FileType DetectFileType(string mimeType, string extension)
|
||||
{
|
||||
if (mimeType.StartsWith("image/", StringComparison.OrdinalIgnoreCase))
|
||||
return FileType.Image;
|
||||
|
||||
if (mimeType.StartsWith("video/", StringComparison.OrdinalIgnoreCase))
|
||||
return FileType.Video;
|
||||
|
||||
if (mimeType.StartsWith("audio/", StringComparison.OrdinalIgnoreCase))
|
||||
return FileType.Audio;
|
||||
|
||||
if (new[] { ".zip", ".rar", ".7z", ".tar", ".gz" }.Contains(extension.ToLower()))
|
||||
return FileType.Archive;
|
||||
|
||||
return FileType.Document;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using GozareshgirProgramManager.Application._Common.Interfaces;
|
||||
using GozareshgirProgramManager.Application._Common.Models;
|
||||
using GozareshgirProgramManager.Domain._Common.Exceptions;
|
||||
using GozareshgirProgramManager.Domain.TaskChatAgg.Repositories;
|
||||
using MediatR;
|
||||
|
||||
namespace GozareshgirProgramManager.Application.Modules.TaskChat.Commands.UnpinMessage;
|
||||
|
||||
public record UnpinMessageCommand(Guid MessageId) : IBaseCommand;
|
||||
|
||||
public class UnpinMessageCommandHandler : IBaseCommandHandler<UnpinMessageCommand>
|
||||
{
|
||||
private readonly ITaskChatMessageRepository _repository;
|
||||
private readonly IAuthHelper _authHelper;
|
||||
|
||||
public UnpinMessageCommandHandler(ITaskChatMessageRepository repository, IAuthHelper authHelper)
|
||||
{
|
||||
_repository = repository;
|
||||
_authHelper = authHelper;
|
||||
}
|
||||
|
||||
public async Task<OperationResult> Handle(UnpinMessageCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var currentUserId = _authHelper.GetCurrentUserId()??
|
||||
throw new UnAuthorizedException("کاربر احراز هویت نشده است");
|
||||
|
||||
var message = await _repository.GetByIdAsync(request.MessageId);
|
||||
if (message == null)
|
||||
{
|
||||
return OperationResult.NotFound("پیام یافت نشد");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
message.UnpinMessage(currentUserId);
|
||||
await _repository.UpdateAsync(message);
|
||||
await _repository.SaveChangesAsync();
|
||||
|
||||
return OperationResult.Success();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return OperationResult.ValidationError(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
namespace GozareshgirProgramManager.Application.Modules.TaskChat.DTOs;
|
||||
|
||||
public class SendMessageDto
|
||||
{
|
||||
public Guid TaskId { get; set; }
|
||||
public string MessageType { get; set; } = string.Empty; // "Text", "File", "Image", "Voice", "Video"
|
||||
public string? TextContent { get; set; }
|
||||
public Guid? FileId { get; set; }
|
||||
public Guid? ReplyToMessageId { get; set; }
|
||||
}
|
||||
|
||||
public class MessageDto
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid TaskId { get; set; }
|
||||
public long SenderUserId { get; set; }
|
||||
public string SenderName { get; set; } = string.Empty;
|
||||
public string MessageType { get; set; } = string.Empty;
|
||||
public string? TextContent { get; set; }
|
||||
public MessageFileDto? File { get; set; }
|
||||
public Guid? ReplyToMessageId { get; set; }
|
||||
public MessageDto? ReplyToMessage { get; set; }
|
||||
public bool IsEdited { get; set; }
|
||||
public DateTime? EditedDate { get; set; }
|
||||
public bool IsPinned { get; set; }
|
||||
public DateTime? PinnedDate { get; set; }
|
||||
public long? PinnedByUserId { get; set; }
|
||||
public DateTime CreationDate { get; set; }
|
||||
public bool IsMine { get; set; }
|
||||
}
|
||||
|
||||
public class MessageFileDto
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string FileName { get; set; } = string.Empty;
|
||||
public string FileUrl { get; set; } = string.Empty;
|
||||
public long FileSizeBytes { get; set; }
|
||||
public string FileType { get; set; } = string.Empty;
|
||||
public string? ThumbnailUrl { get; set; }
|
||||
public int? ImageWidth { get; set; }
|
||||
public int? ImageHeight { get; set; }
|
||||
public int? DurationSeconds { get; set; }
|
||||
|
||||
public string FileSizeFormatted
|
||||
{
|
||||
get
|
||||
{
|
||||
const long kb = 1024;
|
||||
const long mb = kb * 1024;
|
||||
const long gb = mb * 1024;
|
||||
|
||||
if (FileSizeBytes >= gb)
|
||||
return $"{FileSizeBytes / (double)gb:F2} GB";
|
||||
if (FileSizeBytes >= mb)
|
||||
return $"{FileSizeBytes / (double)mb:F2} MB";
|
||||
if (FileSizeBytes >= kb)
|
||||
return $"{FileSizeBytes / (double)kb:F2} KB";
|
||||
|
||||
return $"{FileSizeBytes} Bytes";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
using GozareshgirProgramManager.Application._Common.Interfaces;
|
||||
using GozareshgirProgramManager.Application._Common.Models;
|
||||
using GozareshgirProgramManager.Application.Modules.TaskChat.DTOs;
|
||||
using GozareshgirProgramManager.Domain.TaskChatAgg.Enums;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace GozareshgirProgramManager.Application.Modules.TaskChat.Queries.GetMessages;
|
||||
|
||||
public record GetMessagesQuery(
|
||||
Guid TaskId,
|
||||
MessageType? MessageType,
|
||||
int Page = 1,
|
||||
int PageSize = 50
|
||||
) : IBaseQuery<PaginationResult<MessageDto>>;
|
||||
|
||||
|
||||
|
||||
public class GetMessagesQueryHandler : IBaseQueryHandler<GetMessagesQuery, PaginationResult<MessageDto>>
|
||||
{
|
||||
private readonly IProgramManagerDbContext _context;
|
||||
private readonly IAuthHelper _authHelper;
|
||||
|
||||
public GetMessagesQueryHandler(IProgramManagerDbContext context, IAuthHelper authHelper)
|
||||
{
|
||||
_context = context;
|
||||
_authHelper = authHelper;
|
||||
}
|
||||
|
||||
public async Task<OperationResult<PaginationResult<MessageDto>>> Handle(GetMessagesQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var currentUserId = _authHelper.GetCurrentUserId();
|
||||
|
||||
var skip = (request.Page - 1) * request.PageSize;
|
||||
|
||||
var query = _context.TaskChatMessages
|
||||
.Where(m => m.TaskId == request.TaskId && !m.IsDeleted)
|
||||
.Include(m => m.ReplyToMessage)
|
||||
.OrderBy(m => m.CreationDate).AsQueryable();
|
||||
|
||||
if (request.MessageType.HasValue)
|
||||
{
|
||||
query = query.Where(m => m.MessageType == request.MessageType.Value);
|
||||
}
|
||||
|
||||
var totalCount = await query.CountAsync(cancellationToken);
|
||||
|
||||
var messages = await query
|
||||
.Skip(skip)
|
||||
.Take(request.PageSize)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
// ✅ گرفتن تمامی کاربران برای نمایش نام کامل فرستنده به جای "کاربر"
|
||||
// این بخش تمام UserId هایی که در پیامها استفاده شده را جمعآوری میکند
|
||||
// و یک Dictionary ایجاد میکند که UserId را به FullName نگاشت میکند
|
||||
var senderUserIds = messages.Select(m => m.SenderUserId).Distinct().ToList();
|
||||
var users = await _context.Users
|
||||
.Where(u => senderUserIds.Contains(u.Id))
|
||||
.ToDictionaryAsync(u => u.Id, u => u.FullName, cancellationToken);
|
||||
|
||||
// ✅ گرفتن تمامی زمانهای اضافی (Additional Times) برای نمایش به صورت نوت
|
||||
// در اینجا تمامی TaskSections مربوط به این تسک را میگیریم
|
||||
// و برای هر کدام تمام AdditionalTimes آن را بارگذاری میکنیم
|
||||
var taskSections = await _context.TaskSections
|
||||
.Where(ts => ts.TaskId == request.TaskId)
|
||||
.Include(ts => ts.AdditionalTimes)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var messageDtos = new List<MessageDto>();
|
||||
|
||||
foreach (var message in messages)
|
||||
{
|
||||
// ✅ نام فرستنده را از Dictionary Users بگیر، در صورت عدم وجود "کاربر ناشناس" نمایش بده
|
||||
var senderName = users.ContainsKey(message.SenderUserId)
|
||||
? users[message.SenderUserId]
|
||||
: "کاربر ناشناس";
|
||||
|
||||
var dto = new MessageDto
|
||||
{
|
||||
Id = message.Id,
|
||||
TaskId = message.TaskId,
|
||||
SenderUserId = message.SenderUserId,
|
||||
SenderName = senderName, // ✅ از User واقعی استفاده میکنیم
|
||||
MessageType = message.MessageType.ToString(),
|
||||
TextContent = message.TextContent,
|
||||
ReplyToMessageId = message.ReplyToMessageId,
|
||||
IsEdited = message.IsEdited,
|
||||
EditedDate = message.EditedDate,
|
||||
IsPinned = message.IsPinned,
|
||||
PinnedDate = message.PinnedDate,
|
||||
PinnedByUserId = message.PinnedByUserId,
|
||||
CreationDate = message.CreationDate,
|
||||
IsMine = message.SenderUserId == currentUserId
|
||||
};
|
||||
|
||||
if (message.ReplyToMessage != null)
|
||||
{
|
||||
// ✅ برای پیامهای Reply نیز نام فرستنده را درست نمایش بده
|
||||
var replySenderName = users.ContainsKey(message.ReplyToMessage.SenderUserId)
|
||||
? users[message.ReplyToMessage.SenderUserId]
|
||||
: "کاربر ناشناس";
|
||||
|
||||
dto.ReplyToMessage = new MessageDto
|
||||
{
|
||||
Id = message.ReplyToMessage.Id,
|
||||
SenderUserId = message.ReplyToMessage.SenderUserId,
|
||||
SenderName = replySenderName,
|
||||
TextContent = message.ReplyToMessage.TextContent,
|
||||
CreationDate = message.ReplyToMessage.CreationDate
|
||||
};
|
||||
}
|
||||
|
||||
if (message.FileId.HasValue)
|
||||
{
|
||||
var file = await _context.UploadedFiles.FirstOrDefaultAsync(f => f.Id == message.FileId.Value, cancellationToken);
|
||||
if (file != null)
|
||||
{
|
||||
dto.File = new MessageFileDto
|
||||
{
|
||||
Id = file.Id,
|
||||
FileName = file.OriginalFileName,
|
||||
FileUrl = file.StorageUrl ?? "",
|
||||
FileSizeBytes = file.FileSizeBytes,
|
||||
FileType = file.FileType.ToString(),
|
||||
ThumbnailUrl = file.ThumbnailUrl,
|
||||
ImageWidth = file.ImageWidth,
|
||||
ImageHeight = file.ImageHeight,
|
||||
DurationSeconds = file.DurationSeconds
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
messageDtos.Add(dto);
|
||||
|
||||
// ✅ اینجا بخش جدید است: نوتهای زمان اضافی را بین پیامها اضافه کن
|
||||
// این بخش تمام AdditionalTimes را که بعد از این پیام اضافه شدهاند را پیدا میکند
|
||||
var additionalTimesAfterMessage = taskSections
|
||||
.SelectMany(ts => ts.AdditionalTimes)
|
||||
.Where(at => at.AddedAt > message.CreationDate) // ✅ تغییر به AddedAt (زمان واقعی اضافه شدن)
|
||||
.OrderBy(at => at.AddedAt)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (additionalTimesAfterMessage != null)
|
||||
{
|
||||
// ✅ تمام AdditionalTimes بین این پیام و پیام قبلی را بگیر
|
||||
var additionalTimesByDate = taskSections
|
||||
.SelectMany(ts => ts.AdditionalTimes)
|
||||
.Where(at => at.AddedAt <= message.CreationDate &&
|
||||
(messageDtos.Count == 1 || at.AddedAt > messageDtos[messageDtos.Count - 2].CreationDate))
|
||||
.OrderBy(at => at.AddedAt)
|
||||
.ToList();
|
||||
|
||||
foreach (var additionalTime in additionalTimesByDate)
|
||||
{
|
||||
// ✅ نام کاربری که این زمان اضافی را اضافه کرد
|
||||
var addedByUserName = additionalTime.AddedByUserId.HasValue && users.TryGetValue(additionalTime.AddedByUserId.Value, out var user)
|
||||
? user
|
||||
: "سیستم";
|
||||
|
||||
// ✅ محتوای نوت را با اطلاعات کامل ایجاد کن
|
||||
// نمایش میدهد: مقدار زمان + علت + نام کسی که اضافه کرد
|
||||
var noteContent = $"⏱️ زمان اضافی: {additionalTime.Hours.TotalHours:F2} ساعت - {(string.IsNullOrWhiteSpace(additionalTime.Reason) ? "بدون علت" : additionalTime.Reason)} - توسط {addedByUserName}";
|
||||
|
||||
// ✅ نوت را به عنوان MessageDto خاصی ایجاد کن
|
||||
var noteDto = new MessageDto
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
TaskId = request.TaskId,
|
||||
SenderUserId = 0, // ✅ سیستم برای نشان دادن اینکه یک پیام خودکار است
|
||||
SenderName = "سیستم",
|
||||
MessageType = "Note", // ✅ نوع پیام: Note (یادداشت سیستم)
|
||||
TextContent = noteContent,
|
||||
CreationDate = additionalTime.AddedAt, // ✅ تاریخ اضافه شدن زمان اضافی
|
||||
IsMine = false
|
||||
};
|
||||
|
||||
messageDtos.Add(noteDto);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ مرتب کردن نهایی تمام پیامها (معمولی + نوتها) بر اساس زمان ایجاد
|
||||
// اینطور که نوتهای زمان اضافی در جای درست خود قرار میگیرند
|
||||
messageDtos = messageDtos.OrderBy(m => m.CreationDate).ToList();
|
||||
|
||||
var response = new PaginationResult<MessageDto>()
|
||||
{
|
||||
List = messageDtos,
|
||||
TotalCount = totalCount,
|
||||
};
|
||||
|
||||
return OperationResult<PaginationResult<MessageDto>>.Success(response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
using GozareshgirProgramManager.Application._Common.Interfaces;
|
||||
using GozareshgirProgramManager.Application._Common.Models;
|
||||
using GozareshgirProgramManager.Application.Modules.TaskChat.DTOs;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace GozareshgirProgramManager.Application.Modules.TaskChat.Queries.GetPinnedMessages;
|
||||
|
||||
public record GetPinnedMessagesQuery(Guid TaskId) : IBaseQuery<List<MessageDto>>;
|
||||
|
||||
public class GetPinnedMessagesQueryHandler : IBaseQueryHandler<GetPinnedMessagesQuery, List<MessageDto>>
|
||||
{
|
||||
private readonly IProgramManagerDbContext _context;
|
||||
private readonly IAuthHelper _authHelper;
|
||||
|
||||
public GetPinnedMessagesQueryHandler(IProgramManagerDbContext context, IAuthHelper authHelper)
|
||||
{
|
||||
_context = context;
|
||||
_authHelper = authHelper;
|
||||
}
|
||||
|
||||
public async Task<OperationResult<List<MessageDto>>> Handle(GetPinnedMessagesQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var currentUserId = _authHelper.GetCurrentUserId();
|
||||
|
||||
var messages = await _context.TaskChatMessages
|
||||
.Where(m => m.TaskId == request.TaskId && m.IsPinned && !m.IsDeleted)
|
||||
.Include(m => m.ReplyToMessage)
|
||||
.OrderByDescending(m => m.PinnedDate)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
// ✅ گرفتن تمامی کاربران برای نمایش نام کامل فرستنده
|
||||
var senderUserIds = messages.Select(m => m.SenderUserId).Distinct().ToList();
|
||||
var users = await _context.Users
|
||||
.Where(u => senderUserIds.Contains(u.Id))
|
||||
.ToDictionaryAsync(u => u.Id, u => u.FullName, cancellationToken);
|
||||
|
||||
var messageDtos = new List<MessageDto>();
|
||||
|
||||
foreach (var message in messages)
|
||||
{
|
||||
// ✅ نام فرستنده را از User واقعی بگیر (به جای "کاربر" ثابت)
|
||||
var senderName = users.GetValueOrDefault(message.SenderUserId, "کاربر ناشناس");
|
||||
|
||||
var dto = new MessageDto
|
||||
{
|
||||
Id = message.Id,
|
||||
TaskId = message.TaskId,
|
||||
SenderUserId = message.SenderUserId,
|
||||
SenderName = senderName,
|
||||
MessageType = message.MessageType.ToString(),
|
||||
TextContent = message.TextContent,
|
||||
IsPinned = message.IsPinned,
|
||||
PinnedDate = message.PinnedDate,
|
||||
PinnedByUserId = message.PinnedByUserId,
|
||||
CreationDate = message.CreationDate,
|
||||
IsMine = message.SenderUserId == currentUserId
|
||||
};
|
||||
|
||||
if (message.FileId.HasValue)
|
||||
{
|
||||
var file = await _context.UploadedFiles.FirstOrDefaultAsync(f => f.Id == message.FileId.Value, cancellationToken);
|
||||
if (file != null)
|
||||
{
|
||||
dto.File = new MessageFileDto
|
||||
{
|
||||
Id = file.Id,
|
||||
FileName = file.OriginalFileName,
|
||||
FileUrl = file.StorageUrl ?? "",
|
||||
FileSizeBytes = file.FileSizeBytes,
|
||||
FileType = file.FileType.ToString(),
|
||||
ThumbnailUrl = file.ThumbnailUrl
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
messageDtos.Add(dto);
|
||||
}
|
||||
|
||||
return OperationResult<List<MessageDto>>.Success(messageDtos);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
using GozareshgirProgramManager.Application._Common.Interfaces;
|
||||
using GozareshgirProgramManager.Application._Common.Models;
|
||||
using GozareshgirProgramManager.Application.Modules.TaskChat.DTOs;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace GozareshgirProgramManager.Application.Modules.TaskChat.Queries.SearchMessages;
|
||||
|
||||
public record SearchMessagesQuery(
|
||||
Guid TaskId,
|
||||
string SearchText,
|
||||
int Page = 1,
|
||||
int PageSize = 20
|
||||
) : IBaseQuery<List<MessageDto>>;
|
||||
|
||||
public class SearchMessagesQueryHandler : IBaseQueryHandler<SearchMessagesQuery, List<MessageDto>>
|
||||
{
|
||||
private readonly IProgramManagerDbContext _context;
|
||||
private readonly IAuthHelper _authHelper;
|
||||
|
||||
public SearchMessagesQueryHandler(IProgramManagerDbContext context, IAuthHelper authHelper)
|
||||
{
|
||||
_context = context;
|
||||
_authHelper = authHelper;
|
||||
}
|
||||
|
||||
public async Task<OperationResult<List<MessageDto>>> Handle(SearchMessagesQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var currentUserId = _authHelper.GetCurrentUserId();
|
||||
var skip = (request.Page - 1) * request.PageSize;
|
||||
|
||||
var messages = await _context.TaskChatMessages
|
||||
.Where(m => m.TaskId == request.TaskId &&
|
||||
m.TextContent != null &&
|
||||
m.TextContent.Contains(request.SearchText) &&
|
||||
!m.IsDeleted)
|
||||
.Include(m => m.ReplyToMessage)
|
||||
.OrderByDescending(m => m.CreationDate)
|
||||
.Skip(skip)
|
||||
.Take(request.PageSize)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
// ✅ گرفتن تمامی کاربران برای نمایش نام کامل فرستنده
|
||||
var senderUserIds = messages.Select(m => m.SenderUserId).Distinct().ToList();
|
||||
var users = await _context.Users
|
||||
.Where(u => senderUserIds.Contains(u.Id))
|
||||
.ToDictionaryAsync(u => u.Id, u => u.FullName, cancellationToken);
|
||||
|
||||
var messageDtos = new List<MessageDto>();
|
||||
foreach (var message in messages)
|
||||
{
|
||||
// ✅ نام فرستنده را از User واقعی بگیر
|
||||
var senderName = users.GetValueOrDefault(message.SenderUserId, "کاربر ناشناس");
|
||||
|
||||
var dto = new MessageDto
|
||||
{
|
||||
Id = message.Id,
|
||||
TaskId = message.TaskId,
|
||||
SenderUserId = message.SenderUserId,
|
||||
SenderName = senderName,
|
||||
MessageType = message.MessageType.ToString(),
|
||||
TextContent = message.TextContent,
|
||||
CreationDate = message.CreationDate,
|
||||
IsMine = message.SenderUserId == currentUserId
|
||||
};
|
||||
|
||||
messageDtos.Add(dto);
|
||||
}
|
||||
|
||||
return OperationResult<List<MessageDto>>.Success(messageDtos);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using GozareshgirProgramManager.Domain.FileManagementAgg.Entities;
|
||||
|
||||
namespace GozareshgirProgramManager.Application.Services.FileManagement;
|
||||
|
||||
/// <summary>
|
||||
/// سرویس ذخیرهسازی فایل
|
||||
/// </summary>
|
||||
public interface IFileStorageService
|
||||
{
|
||||
/// <summary>
|
||||
/// آپلود فایل
|
||||
/// </summary>
|
||||
Task<(string StoragePath, string StorageUrl)> UploadAsync(
|
||||
Stream fileStream,
|
||||
string uniqueFileName,
|
||||
string category);
|
||||
|
||||
/// <summary>
|
||||
/// حذف فایل
|
||||
/// </summary>
|
||||
Task DeleteAsync(string storagePath);
|
||||
|
||||
/// <summary>
|
||||
/// دریافت فایل
|
||||
/// </summary>
|
||||
Task<Stream?> GetFileStreamAsync(string storagePath);
|
||||
|
||||
/// <summary>
|
||||
/// بررسی وجود فایل
|
||||
/// </summary>
|
||||
Task<bool> ExistsAsync(string storagePath);
|
||||
|
||||
/// <summary>
|
||||
/// دریافت URL فایل
|
||||
/// </summary>
|
||||
string GetFileUrl(string storagePath);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
namespace GozareshgirProgramManager.Application.Services.FileManagement;
|
||||
|
||||
/// <summary>
|
||||
/// سرویس تولید thumbnail برای تصاویر و ویدیوها
|
||||
/// </summary>
|
||||
public interface IThumbnailGeneratorService
|
||||
{
|
||||
/// <summary>
|
||||
/// تولید thumbnail برای تصویر
|
||||
/// </summary>
|
||||
Task<(string ThumbnailPath, string ThumbnailUrl)?> GenerateImageThumbnailAsync(
|
||||
string imagePath,
|
||||
string category,
|
||||
int width = 200,
|
||||
int height = 200);
|
||||
|
||||
/// <summary>
|
||||
/// تولید thumbnail برای ویدیو
|
||||
/// </summary>
|
||||
Task<(string ThumbnailPath, string ThumbnailUrl)?> GenerateVideoThumbnailAsync(
|
||||
string videoPath,
|
||||
string category);
|
||||
|
||||
/// <summary>
|
||||
/// حذف thumbnail
|
||||
/// </summary>
|
||||
Task DeleteThumbnailAsync(string thumbnailPath);
|
||||
|
||||
/// <summary>
|
||||
/// دریافت ابعاد تصویر
|
||||
/// </summary>
|
||||
Task<(int Width, int Height)?> GetImageDimensionsAsync(string imagePath);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ using GozareshgirProgramManager.Domain.SalaryPaymentSettingAgg.Entities;
|
||||
using GozareshgirProgramManager.Domain.SkillAgg.Entities;
|
||||
using GozareshgirProgramManager.Domain.UserAgg.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using GozareshgirProgramManager.Domain.TaskChatAgg.Entities;
|
||||
using GozareshgirProgramManager.Domain.FileManagementAgg.Entities;
|
||||
|
||||
namespace GozareshgirProgramManager.Application._Common.Interfaces;
|
||||
|
||||
@@ -26,6 +28,9 @@ public interface IProgramManagerDbContext
|
||||
|
||||
DbSet<ProjectTask> ProjectTasks { get; set; }
|
||||
|
||||
DbSet<TaskChatMessage> TaskChatMessages { get; set; }
|
||||
DbSet<UploadedFile> UploadedFiles { get; set; }
|
||||
|
||||
DbSet<Skill> Skills { get; set; }
|
||||
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
using GozareshgirProgramManager.Domain._Common;
|
||||
using GozareshgirProgramManager.Domain._Common.Exceptions;
|
||||
using GozareshgirProgramManager.Domain.FileManagementAgg.Enums;
|
||||
using GozareshgirProgramManager.Domain.FileManagementAgg.Events;
|
||||
using FileType = GozareshgirProgramManager.Domain.FileManagementAgg.Enums.FileType;
|
||||
|
||||
namespace GozareshgirProgramManager.Domain.FileManagementAgg.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// فایل آپلود شده - Aggregate Root
|
||||
/// مدیریت مرکزی تمام فایلهای سیستم
|
||||
/// </summary>
|
||||
public class UploadedFile : EntityBase<Guid>
|
||||
{
|
||||
private UploadedFile()
|
||||
{
|
||||
}
|
||||
|
||||
public UploadedFile(
|
||||
string originalFileName,
|
||||
long fileSizeBytes,
|
||||
string mimeType,
|
||||
FileType fileType,
|
||||
FileCategory category,
|
||||
long uploadedByUserId,
|
||||
StorageProvider storageProvider = StorageProvider.LocalFileSystem)
|
||||
{
|
||||
OriginalFileName = originalFileName;
|
||||
FileSizeBytes = fileSizeBytes;
|
||||
MimeType = mimeType;
|
||||
FileType = fileType;
|
||||
Category = category;
|
||||
UploadedByUserId = uploadedByUserId;
|
||||
UploadDate = DateTime.Now;
|
||||
StorageProvider = storageProvider;
|
||||
Status = FileStatus.Uploading;
|
||||
|
||||
// Generate unique file name
|
||||
FileExtension = Path.GetExtension(originalFileName);
|
||||
UniqueFileName = $"{Guid.NewGuid()}{FileExtension}";
|
||||
|
||||
ValidateFile();
|
||||
AddDomainEvent(new FileUploadStartedEvent(Id, originalFileName, uploadedByUserId));
|
||||
}
|
||||
|
||||
// اطلاعات فایل
|
||||
public string OriginalFileName { get; private set; } = string.Empty;
|
||||
public string UniqueFileName { get; private set; } = string.Empty;
|
||||
public string FileExtension { get; private set; } = string.Empty;
|
||||
public long FileSizeBytes { get; private set; }
|
||||
public string MimeType { get; private set; } = string.Empty;
|
||||
public FileType FileType { get; private set; }
|
||||
public FileCategory Category { get; private set; }
|
||||
|
||||
// ذخیرهسازی
|
||||
public StorageProvider StorageProvider { get; private set; }
|
||||
public string? StoragePath { get; private set; }
|
||||
public string? StorageUrl { get; private set; }
|
||||
public string? ThumbnailUrl { get; private set; }
|
||||
|
||||
// متادیتا
|
||||
public long UploadedByUserId { get; private set; }
|
||||
public DateTime UploadDate { get; private set; }
|
||||
public FileStatus Status { get; private set; }
|
||||
|
||||
// اطلاعات تصویر (اختیاری - برای Image)
|
||||
public int? ImageWidth { get; private set; }
|
||||
public int? ImageHeight { get; private set; }
|
||||
|
||||
// اطلاعات صوت/ویدیو (اختیاری)
|
||||
public int? DurationSeconds { get; private set; }
|
||||
|
||||
// امنیت
|
||||
public DateTime? VirusScanDate { get; private set; }
|
||||
public bool? IsVirusScanPassed { get; private set; }
|
||||
public string? VirusScanResult { get; private set; }
|
||||
|
||||
// Soft Delete
|
||||
public bool IsDeleted { get; private set; }
|
||||
public DateTime? DeletedDate { get; private set; }
|
||||
public long? DeletedByUserId { get; private set; }
|
||||
|
||||
// Reference tracking (چه entityهایی از این فایل استفاده میکنند)
|
||||
public string? ReferenceEntityType { get; private set; }
|
||||
public string? ReferenceEntityId { get; private set; }
|
||||
|
||||
private void ValidateFile()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(OriginalFileName))
|
||||
{
|
||||
throw new BadRequestException("نام فایل نمیتواند خالی باشد");
|
||||
}
|
||||
|
||||
if (FileSizeBytes <= 0)
|
||||
{
|
||||
throw new BadRequestException("حجم فایل باید بیشتر از صفر باشد");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(MimeType))
|
||||
{
|
||||
throw new BadRequestException("نوع MIME فایل باید مشخص شود");
|
||||
}
|
||||
|
||||
// محدودیت حجم (مثلاً 100MB)
|
||||
const long maxSizeBytes = 100 * 1024 * 1024; // 100MB
|
||||
if (FileSizeBytes > maxSizeBytes)
|
||||
{
|
||||
throw new BadRequestException($"حجم فایل نباید بیشتر از {maxSizeBytes / (1024 * 1024)} مگابایت باشد");
|
||||
}
|
||||
}
|
||||
|
||||
public void CompleteUpload(string storagePath, string storageUrl)
|
||||
{
|
||||
if (Status != FileStatus.Uploading)
|
||||
{
|
||||
throw new BadRequestException("فایل قبلاً آپلود شده است");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(storagePath))
|
||||
{
|
||||
throw new BadRequestException("مسیر ذخیرهسازی نمیتواند خالی باشد");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(storageUrl))
|
||||
{
|
||||
throw new BadRequestException("URL فایل نمیتواند خالی باشد");
|
||||
}
|
||||
|
||||
StoragePath = storagePath;
|
||||
StorageUrl = storageUrl;
|
||||
Status = FileStatus.Active;
|
||||
|
||||
AddDomainEvent(new FileUploadCompletedEvent(Id, OriginalFileName, StorageUrl, UploadedByUserId));
|
||||
}
|
||||
|
||||
public void SetThumbnail(string thumbnailUrl)
|
||||
{
|
||||
if (FileType != FileType.Image && FileType != FileType.Video)
|
||||
{
|
||||
throw new BadRequestException("فقط میتوان برای تصاویر و ویدیوها thumbnail تنظیم کرد");
|
||||
}
|
||||
|
||||
ThumbnailUrl = thumbnailUrl;
|
||||
}
|
||||
|
||||
public void SetImageDimensions(int width, int height)
|
||||
{
|
||||
if (FileType != FileType.Image)
|
||||
{
|
||||
throw new BadRequestException("فقط میتوان برای تصاویر ابعاد تنظیم کرد");
|
||||
}
|
||||
|
||||
if (width <= 0 || height <= 0)
|
||||
{
|
||||
throw new BadRequestException("ابعاد تصویر باید بیشتر از صفر باشد");
|
||||
}
|
||||
|
||||
ImageWidth = width;
|
||||
ImageHeight = height;
|
||||
}
|
||||
|
||||
public void SetDuration(int durationSeconds)
|
||||
{
|
||||
if (FileType != FileType.Audio && FileType != FileType.Video)
|
||||
{
|
||||
throw new BadRequestException("فقط میتوان برای فایلهای صوتی و تصویری مدت زمان تنظیم کرد");
|
||||
}
|
||||
|
||||
if (durationSeconds <= 0)
|
||||
{
|
||||
throw new BadRequestException("مدت زمان باید بیشتر از صفر باشد");
|
||||
}
|
||||
|
||||
DurationSeconds = durationSeconds;
|
||||
}
|
||||
|
||||
public void MarkAsDeleted(long deletedByUserId)
|
||||
{
|
||||
if (IsDeleted)
|
||||
{
|
||||
throw new BadRequestException("فایل قبلاً حذف شده است");
|
||||
}
|
||||
|
||||
IsDeleted = true;
|
||||
DeletedDate = DateTime.Now;
|
||||
DeletedByUserId = deletedByUserId;
|
||||
Status = FileStatus.Deleted;
|
||||
|
||||
AddDomainEvent(new FileDeletedEvent(Id, OriginalFileName, deletedByUserId));
|
||||
}
|
||||
|
||||
public void SetReference(string entityType, string entityId)
|
||||
{
|
||||
ReferenceEntityType = entityType;
|
||||
ReferenceEntityId = entityId;
|
||||
}
|
||||
|
||||
public bool IsImage()
|
||||
{
|
||||
return FileType == FileType.Image;
|
||||
}
|
||||
|
||||
public bool IsVideo()
|
||||
{
|
||||
return FileType == FileType.Video;
|
||||
}
|
||||
|
||||
public bool IsAudio()
|
||||
{
|
||||
return FileType == FileType.Audio;
|
||||
}
|
||||
|
||||
public bool IsDocument()
|
||||
{
|
||||
return FileType == FileType.Document;
|
||||
}
|
||||
|
||||
public bool IsUploadedBy(long userId)
|
||||
{
|
||||
return UploadedByUserId == userId;
|
||||
}
|
||||
|
||||
public bool IsActive()
|
||||
{
|
||||
return Status == FileStatus.Active && !IsDeleted;
|
||||
}
|
||||
|
||||
public string GetFileSizeFormatted()
|
||||
{
|
||||
const long kb = 1024;
|
||||
const long mb = kb * 1024;
|
||||
const long gb = mb * 1024;
|
||||
|
||||
if (FileSizeBytes >= gb)
|
||||
return $"{FileSizeBytes / (double)gb:F2} GB";
|
||||
if (FileSizeBytes >= mb)
|
||||
return $"{FileSizeBytes / (double)mb:F2} MB";
|
||||
if (FileSizeBytes >= kb)
|
||||
return $"{FileSizeBytes / (double)kb:F2} KB";
|
||||
|
||||
return $"{FileSizeBytes} Bytes";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace GozareshgirProgramManager.Domain.FileManagementAgg.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// دستهبندی فایل - مشخص میکند فایل در کجا استفاده شده
|
||||
/// </summary>
|
||||
public enum FileCategory
|
||||
{
|
||||
TaskChatMessage = 1, // پیام چت تسک
|
||||
TaskAttachment = 2, // ضمیمه تسک
|
||||
ProjectDocument = 3, // مستندات پروژه
|
||||
UserProfilePhoto = 4, // عکس پروفایل کاربر
|
||||
Report = 5, // گزارش
|
||||
Other = 6 // سایر
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace GozareshgirProgramManager.Domain.FileManagementAgg.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// وضعیت فایل
|
||||
/// </summary>
|
||||
public enum FileStatus
|
||||
{
|
||||
Uploading = 1, // در حال آپلود
|
||||
Active = 2, // فعال و قابل استفاده
|
||||
Deleted = 5, // حذف شده (Soft Delete)
|
||||
Archived = 6 // آرشیو شده
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace GozareshgirProgramManager.Domain.FileManagementAgg.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// نوع فایل
|
||||
/// </summary>
|
||||
public enum FileType
|
||||
{
|
||||
Document = 1, // اسناد (PDF, Word, Excel, etc.)
|
||||
Image = 2, // تصویر
|
||||
Video = 3, // ویدیو
|
||||
Audio = 4, // صوت
|
||||
Archive = 5, // فایل فشرده (ZIP, RAR)
|
||||
Other = 6 // سایر
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace GozareshgirProgramManager.Domain.FileManagementAgg.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// نوع ذخیرهساز فایل
|
||||
/// </summary>
|
||||
public enum StorageProvider
|
||||
{
|
||||
LocalFileSystem = 1, // دیسک محلی سرور
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
using GozareshgirProgramManager.Domain._Common;
|
||||
|
||||
namespace GozareshgirProgramManager.Domain.FileManagementAgg.Events;
|
||||
|
||||
// File Upload Events
|
||||
public record FileUploadStartedEvent(Guid FileId, string FileName, long UploadedByUserId) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
public record FileUploadCompletedEvent(Guid FileId, string FileName, string StorageUrl, long UploadedByUserId) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
public record FileDeletedEvent(Guid FileId, string FileName, long DeletedByUserId) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
// Virus Scan Events
|
||||
public record FileQuarantinedEvent(Guid FileId, string FileName) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
public record FileVirusScanPassedEvent(Guid FileId, string FileName) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
public record FileInfectedEvent(Guid FileId, string FileName, string ScanResult) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
using GozareshgirProgramManager.Domain.FileManagementAgg.Entities;
|
||||
using GozareshgirProgramManager.Domain.FileManagementAgg.Enums;
|
||||
|
||||
namespace GozareshgirProgramManager.Domain.FileManagementAgg.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Repository برای مدیریت فایلهای آپلود شده
|
||||
/// </summary>
|
||||
public interface IUploadedFileRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// دریافت فایل بر اساس شناسه
|
||||
/// </summary>
|
||||
Task<UploadedFile?> GetByIdAsync(Guid fileId);
|
||||
|
||||
/// <summary>
|
||||
/// دریافت فایل بر اساس نام یکتا
|
||||
/// </summary>
|
||||
Task<UploadedFile?> GetByUniqueFileNameAsync(string uniqueFileName);
|
||||
|
||||
/// <summary>
|
||||
/// دریافت لیست فایلهای یک کاربر
|
||||
/// </summary>
|
||||
Task<List<UploadedFile>> GetUserFilesAsync(long userId, int pageNumber, int pageSize);
|
||||
|
||||
/// <summary>
|
||||
/// دریافت فایلهای یک دسته خاص
|
||||
/// </summary>
|
||||
Task<List<UploadedFile>> GetByCategoryAsync(FileCategory category, int pageNumber, int pageSize);
|
||||
|
||||
/// <summary>
|
||||
/// دریافت فایلهای با وضعیت خاص
|
||||
/// </summary>
|
||||
Task<List<UploadedFile>> GetByStatusAsync(FileStatus status, int pageNumber, int pageSize);
|
||||
|
||||
/// <summary>
|
||||
/// دریافت فایلهای یک Reference خاص
|
||||
/// </summary>
|
||||
Task<List<UploadedFile>> GetByReferenceAsync(string entityType, string entityId);
|
||||
|
||||
/// <summary>
|
||||
/// جستجو در فایلها بر اساس نام
|
||||
/// </summary>
|
||||
Task<List<UploadedFile>> SearchByNameAsync(string searchTerm, int pageNumber, int pageSize);
|
||||
|
||||
/// <summary>
|
||||
/// دریافت تعداد کل فایلهای یک کاربر
|
||||
/// </summary>
|
||||
Task<int> GetUserFilesCountAsync(long userId);
|
||||
|
||||
/// <summary>
|
||||
/// دریافت مجموع حجم فایلهای یک کاربر (به بایت)
|
||||
/// </summary>
|
||||
Task<long> GetUserTotalFileSizeAsync(long userId);
|
||||
|
||||
/// <summary>
|
||||
/// دریافت فایلهای منقضی شده برای پاکسازی
|
||||
/// </summary>
|
||||
Task<List<UploadedFile>> GetExpiredFilesAsync(DateTime olderThan);
|
||||
|
||||
/// <summary>
|
||||
/// اضافه کردن فایل جدید
|
||||
/// </summary>
|
||||
Task<UploadedFile> AddAsync(UploadedFile file);
|
||||
|
||||
/// <summary>
|
||||
/// بهروزرسانی فایل
|
||||
/// </summary>
|
||||
Task UpdateAsync(UploadedFile file);
|
||||
|
||||
/// <summary>
|
||||
/// حذف فیزیکی فایل (فقط برای cleanup)
|
||||
/// </summary>
|
||||
Task DeleteAsync(UploadedFile file);
|
||||
|
||||
/// <summary>
|
||||
/// ذخیره تغییرات
|
||||
/// </summary>
|
||||
Task<int> SaveChangesAsync();
|
||||
|
||||
/// <summary>
|
||||
/// بررسی وجود فایل
|
||||
/// </summary>
|
||||
Task<bool> ExistsAsync(Guid fileId);
|
||||
|
||||
/// <summary>
|
||||
/// بررسی وجود فایل با نام یکتا
|
||||
/// </summary>
|
||||
Task<bool> ExistsByUniqueFileNameAsync(string uniqueFileName);
|
||||
}
|
||||
|
||||
@@ -74,6 +74,15 @@ public class Project : ProjectHierarchyNode
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveProjectSection(Guid skillId)
|
||||
{
|
||||
var section = _projectSections.FirstOrDefault(s => s.SkillId == skillId);
|
||||
if (section != null)
|
||||
{
|
||||
_projectSections.Remove(section);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearProjectSections()
|
||||
{
|
||||
_projectSections.Clear();
|
||||
|
||||
@@ -23,6 +23,7 @@ public class ProjectPhase : ProjectHierarchyNode
|
||||
ProjectId = projectId;
|
||||
_tasks = new List<ProjectTask>();
|
||||
_phaseSections = new List<PhaseSection>();
|
||||
DeployStatus = ProjectDeployStatus.NotCompleted;
|
||||
AddDomainEvent(new PhaseCreatedEvent(Id, projectId, name));
|
||||
}
|
||||
|
||||
@@ -36,6 +37,8 @@ public class ProjectPhase : ProjectHierarchyNode
|
||||
public DateTime? StartDate { get; private set; }
|
||||
public DateTime? EndDate { get; private set; }
|
||||
public int OrderIndex { get; private set; }
|
||||
public bool IsArchived { get; set; }
|
||||
public ProjectDeployStatus DeployStatus { get; set; }
|
||||
|
||||
#region Task Management
|
||||
|
||||
@@ -84,6 +87,15 @@ public class ProjectPhase : ProjectHierarchyNode
|
||||
}
|
||||
}
|
||||
|
||||
public void RemovePhaseSection(Guid skillId)
|
||||
{
|
||||
var section = _phaseSections.FirstOrDefault(s => s.SkillId == skillId);
|
||||
if (section != null)
|
||||
{
|
||||
_phaseSections.Remove(section);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearPhaseSections()
|
||||
{
|
||||
_phaseSections.Clear();
|
||||
@@ -196,4 +208,30 @@ public class ProjectPhase : ProjectHierarchyNode
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void SetArchived()
|
||||
{
|
||||
IsArchived = true;
|
||||
}
|
||||
public void SetUnarchived()
|
||||
{
|
||||
IsArchived = false;
|
||||
}
|
||||
public void UpdateDeployStatus(ProjectDeployStatus status)
|
||||
{
|
||||
DeployStatus = status;
|
||||
if (status == ProjectDeployStatus.Deployed)
|
||||
{
|
||||
IsArchived = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum ProjectDeployStatus
|
||||
{
|
||||
NotCompleted,
|
||||
PendingDevDeploy,
|
||||
DevDeployed,
|
||||
PendingDeploy,
|
||||
Deployed
|
||||
}
|
||||
|
||||
@@ -246,4 +246,9 @@ public class ProjectTask : ProjectHierarchyNode
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void ClearTaskSections()
|
||||
{
|
||||
_sections.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,5 +6,6 @@ public enum TaskSectionStatus
|
||||
ReadyToStart = 1, // آماده شروع
|
||||
InProgress = 2, // درحال انجام
|
||||
Incomplete = 3, // ناتمام شده
|
||||
Completed = 4 // تکمیل شده
|
||||
PendingForCompletion = 4, // در انتظار تکمیل
|
||||
Completed = 5 // تکمیل شده
|
||||
}
|
||||
@@ -8,171 +8,171 @@ namespace GozareshgirProgramManager.Domain.ProjectAgg.Events;
|
||||
// Project Events
|
||||
public record ProjectCreatedEvent(Guid ProjectId, string Name) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.UtcNow;
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
public record ProjectStatusUpdatedEvent(Guid ProjectId, ProjectStatus Status) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.UtcNow;
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
public record ProjectAssignedEvent(Guid ProjectId, long UserId) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.UtcNow;
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
public record ProjectUnassignedEvent(Guid ProjectId) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.UtcNow;
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
// Phase Events
|
||||
public record PhaseCreatedEvent(Guid PhaseId, Guid ProjectId, string Name) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.UtcNow;
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
public record PhaseAddedEvent(Guid PhaseId, Guid ProjectId, string Name) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.UtcNow;
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
public record PhaseRemovedEvent(Guid PhaseId, Guid ProjectId) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.UtcNow;
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
public record PhaseStatusUpdatedEvent(Guid PhaseId, PhaseStatus Status) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.UtcNow;
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
public record PhaseAssignedEvent(Guid PhaseId, long UserId) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.UtcNow;
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
public record PhaseUnassignedEvent(Guid PhaseId) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.UtcNow;
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
// Task Events
|
||||
public record TaskCreatedEvent(Guid TaskId, Guid PhaseId, string Name) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.UtcNow;
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
public record TaskAddedEvent(Guid TaskId, Guid PhaseId, string Name) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.UtcNow;
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
public record TaskRemovedEvent(Guid TaskId, Guid PhaseId) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.UtcNow;
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
public record TaskStatusUpdatedEvent(Guid TaskId, TaskStatus Status) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.UtcNow;
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
public record TaskPriorityUpdatedEvent(Guid TaskId, TaskPriority Priority) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.UtcNow;
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
public record TaskAssignedEvent(Guid TaskId, long UserId) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.UtcNow;
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
public record TaskUnassignedEvent(Guid TaskId) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.UtcNow;
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
public record TaskSectionAddedEvent(Guid TaskId, Guid SectionId, Guid SkillId) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.UtcNow;
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
public record TaskSectionRemovedEvent(Guid TaskId, Guid SectionId) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.UtcNow;
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
// TaskSection Events
|
||||
public record TaskSectionStatusChangedEvent(Guid SectionId, TaskSectionStatus OldStatus,
|
||||
TaskSectionStatus NewStatus,long UserId) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.UtcNow;
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
public record TaskSectionAssignedEvent(Guid SectionId, long UserId) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.UtcNow;
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
public record TaskSectionTransferredEvent(Guid SectionId, long FromUserId, long ToUserId) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.UtcNow;
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
// Section Events (Legacy - keeping for backward compatibility)
|
||||
public record ProjectPhaseAddedEvent(Guid ProjectId, Guid PhaseId, string PhaseName) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.UtcNow;
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
public record ProjectTaskStatusChangedEvent(Guid SectionId, TaskStatus OldStatus, TaskStatus NewStatus) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.UtcNow;
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
public record ProjectSectionAddedEvent(Guid SectionId, Guid TaskId) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.UtcNow;
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
|
||||
public record ProjectSectionAssignedEvent(Guid SectionId, long UserId) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.UtcNow;
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
public record ProjectSectionTransferredEvent(Guid SectionId, long FromUserId, long ToUserId) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.UtcNow;
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
public record WorkStartedEvent(Guid SectionId, long UserId, DateTime StartTime, string? Notes) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.UtcNow;
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
public record WorkStoppedEvent(Guid SectionId, long UserId, DateTime StartTime, DateTime EndTime, TimeSpan Duration, string? Notes) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.UtcNow;
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
public record ProjectSectionCompletedEvent(Guid ProjectId, long UserId, TimeSpan TotalTimeSpent, string? Notes) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.UtcNow;
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
public record AdditionalTimeAddedEvent(Guid ProjectId, Guid AdditionalTimeId, TimeSpan Hours, string? Reason, long? AddedByUserId) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.UtcNow;
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
public record AdditionalTimeRemovedEvent(Guid ProjectId, Guid AdditionalTimeId, TimeSpan RemovedHours) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.UtcNow;
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
public record InitialEstimatedTimeUpdatedEvent(Guid ProjectId, TimeSpan OldEstimate, TimeSpan NewEstimate) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.UtcNow;
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ namespace GozareshgirProgramManager.Domain.ProjectAgg.Repositories;
|
||||
|
||||
public interface ITaskSectionActivityRepository:IRepository<Guid,TaskSectionActivity>
|
||||
{
|
||||
Task<TaskSectionActivity?> GetByIdAsync(Guid id);
|
||||
Task<List<TaskSectionActivity>> GetBySectionIdAsync(Guid sectionId);
|
||||
Task<List<TaskSectionActivity>> GetByUserIdAsync(long userId);
|
||||
Task<List<TaskSectionActivity>> GetActiveByUserAsync(long userId);
|
||||
|
||||
@@ -13,4 +13,5 @@ public interface ITaskSectionRepository: IRepository<Guid,TaskSection>
|
||||
|
||||
Task<List<TaskSection>> GetAssignedToUserAsync(long userId);
|
||||
Task<List<TaskSection>> GetActiveSectionsIncludeAllAsync(CancellationToken cancellationToken);
|
||||
Task<bool> HasUserAnyInProgressSectionAsync(long userId, CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
using GozareshgirProgramManager.Domain._Common;
|
||||
using GozareshgirProgramManager.Domain._Common.Exceptions;
|
||||
using GozareshgirProgramManager.Domain.TaskChatAgg.Events;
|
||||
using MessageType = GozareshgirProgramManager.Domain.TaskChatAgg.Enums.MessageType;
|
||||
|
||||
namespace GozareshgirProgramManager.Domain.TaskChatAgg.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// پیام چت تسک - Aggregate Root
|
||||
/// هر کسی که به تسک دسترسی داشته باشد میتواند پیامها را ببیند و ارسال کند
|
||||
/// نیازی به مدیریت گروه و ممبر نیست چون دسترسی از طریق خود تسک کنترل میشود
|
||||
/// </summary>
|
||||
public class TaskChatMessage : EntityBase<Guid>
|
||||
{
|
||||
private TaskChatMessage()
|
||||
{
|
||||
}
|
||||
|
||||
public TaskChatMessage(Guid taskId, long senderUserId, MessageType messageType,
|
||||
string? textContent = null,Guid? fileId = null)
|
||||
{
|
||||
TaskId = taskId;
|
||||
SenderUserId = senderUserId;
|
||||
MessageType = messageType;
|
||||
TextContent = textContent;
|
||||
IsEdited = false;
|
||||
IsDeleted = false;
|
||||
IsPinned = false;
|
||||
if (fileId.HasValue)
|
||||
{
|
||||
SetFile(fileId.Value);
|
||||
}
|
||||
ValidateMessage();
|
||||
AddDomainEvent(new TaskChatMessageSentEvent(Id, taskId, senderUserId, messageType));
|
||||
}
|
||||
|
||||
// Reference به Task (Foreign Key فقط - بدون Navigation Property برای جلوگیری از coupling)
|
||||
public Guid TaskId { get; private set; }
|
||||
|
||||
public long SenderUserId { get; private set; }
|
||||
|
||||
public MessageType MessageType { get; private set; }
|
||||
|
||||
// محتوای متنی (برای پیامهای Text و Caption برای فایل/تصویر)
|
||||
public string? TextContent { get; private set; }
|
||||
|
||||
// ارجاع به فایل (برای پیامهای File, Voice, Image, Video)
|
||||
public Guid? FileId { get; private set; }
|
||||
|
||||
// پیام Reply
|
||||
public Guid? ReplyToMessageId { get; private set; }
|
||||
public TaskChatMessage? ReplyToMessage { get; private set; }
|
||||
|
||||
// وضعیت پیام
|
||||
public bool IsEdited { get; private set; }
|
||||
public DateTime? EditedDate { get; private set; }
|
||||
public bool IsDeleted { get; private set; }
|
||||
public DateTime? DeletedDate { get; private set; }
|
||||
public bool IsPinned { get; private set; }
|
||||
public DateTime? PinnedDate { get; private set; }
|
||||
public long? PinnedByUserId { get; private set; }
|
||||
|
||||
private void ValidateMessage()
|
||||
{
|
||||
// ✅ بررسی پیامهای متنی
|
||||
if (MessageType == MessageType.Text && string.IsNullOrWhiteSpace(TextContent))
|
||||
{
|
||||
throw new BadRequestException("پیام متنی نمیتواند خالی باشد");
|
||||
}
|
||||
|
||||
// ✅ بررسی پیامهای فایلی - باید FileId داشته باشند
|
||||
if ((MessageType == MessageType.File || MessageType == MessageType.Voice ||
|
||||
MessageType == MessageType.Image || MessageType == MessageType.Video)
|
||||
&& FileId == null)
|
||||
{
|
||||
throw new BadRequestException("پیامهای فایلی باید شناسه فایل داشته باشند");
|
||||
}
|
||||
|
||||
// ✅ بررسی یادداشتهای سیستم - باید محتوای متنی داشته باشند
|
||||
if (MessageType == MessageType.Note && string.IsNullOrWhiteSpace(TextContent))
|
||||
{
|
||||
throw new BadRequestException("یادداشت نمیتواند خالی باشد");
|
||||
}
|
||||
}
|
||||
|
||||
public void SetFile(Guid fileId)
|
||||
{
|
||||
if (MessageType != MessageType.File && MessageType != MessageType.Image &&
|
||||
MessageType != MessageType.Video && MessageType != MessageType.Voice)
|
||||
{
|
||||
throw new BadRequestException("فقط میتوان برای پیامهای فایل، تصویر، ویدیو و صدا شناسه فایل تنظیم کرد");
|
||||
}
|
||||
|
||||
FileId = fileId;
|
||||
}
|
||||
|
||||
public void EditMessage(string newTextContent, long editorUserId)
|
||||
{
|
||||
if (IsDeleted)
|
||||
{
|
||||
throw new BadRequestException("نمیتوان پیام حذف شده را ویرایش کرد");
|
||||
}
|
||||
|
||||
if (editorUserId != SenderUserId)
|
||||
{
|
||||
throw new BadRequestException("فقط فرستنده میتواند پیام را ویرایش کند");
|
||||
}
|
||||
|
||||
if ((MessageType != MessageType.Text && !string.IsNullOrWhiteSpace(TextContent)))
|
||||
{
|
||||
throw new BadRequestException("فقط پیامهای متنی قابل ویرایش هستند");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(newTextContent))
|
||||
{
|
||||
throw new BadRequestException("محتوای پیام نمیتواند خالی باشد");
|
||||
}
|
||||
|
||||
TextContent = newTextContent;
|
||||
IsEdited = true;
|
||||
EditedDate = DateTime.Now;
|
||||
AddDomainEvent(new TaskChatMessageEditedEvent(Id, TaskId, editorUserId));
|
||||
}
|
||||
|
||||
public void DeleteMessage(long deleterUserId)
|
||||
{
|
||||
if (IsDeleted)
|
||||
{
|
||||
throw new BadRequestException("پیام قبلاً حذف شده است");
|
||||
}
|
||||
|
||||
if (deleterUserId != SenderUserId)
|
||||
{
|
||||
throw new BadRequestException("فقط فرستنده میتواند پیام را حذف کند");
|
||||
}
|
||||
|
||||
IsDeleted = true;
|
||||
DeletedDate = DateTime.Now;
|
||||
AddDomainEvent(new TaskChatMessageDeletedEvent(Id, TaskId, deleterUserId));
|
||||
}
|
||||
|
||||
public void PinMessage(long pinnerUserId)
|
||||
{
|
||||
if (IsDeleted)
|
||||
{
|
||||
throw new BadRequestException("نمیتوان پیام حذف شده را پین کرد");
|
||||
}
|
||||
|
||||
if (IsPinned)
|
||||
{
|
||||
throw new BadRequestException("این پیام قبلاً پین شده است");
|
||||
}
|
||||
|
||||
IsPinned = true;
|
||||
PinnedDate = DateTime.Now;
|
||||
PinnedByUserId = pinnerUserId;
|
||||
AddDomainEvent(new TaskChatMessagePinnedEvent(Id, TaskId, pinnerUserId));
|
||||
}
|
||||
|
||||
public void UnpinMessage(long unpinnerUserId)
|
||||
{
|
||||
if (!IsPinned)
|
||||
{
|
||||
throw new BadRequestException("این پیام پین نشده است");
|
||||
}
|
||||
|
||||
IsPinned = false;
|
||||
PinnedDate = null;
|
||||
PinnedByUserId = null;
|
||||
AddDomainEvent(new TaskChatMessageUnpinnedEvent(Id, TaskId, unpinnerUserId));
|
||||
}
|
||||
|
||||
public void SetReplyTo(Guid replyToMessageId)
|
||||
{
|
||||
ReplyToMessageId = replyToMessageId;
|
||||
}
|
||||
|
||||
public bool IsSentBy(long userId)
|
||||
{
|
||||
return SenderUserId == userId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace GozareshgirProgramManager.Domain.TaskChatAgg.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// نوع پیام در چت تسک
|
||||
/// </summary>
|
||||
public enum MessageType
|
||||
{
|
||||
Text = 1, // پیام متنی
|
||||
File = 2, // فایل (اسناد، PDF، و غیره)
|
||||
Image = 3, // تصویر
|
||||
Voice = 4, // پیام صوتی
|
||||
Video = 5, // ویدیو
|
||||
Note = 6, // ✅ یادداشت سیستم (برای زمان اضافی و اطلاعات خودکار)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
using GozareshgirProgramManager.Domain._Common;
|
||||
using GozareshgirProgramManager.Domain.TaskChatAgg.Enums;
|
||||
|
||||
namespace GozareshgirProgramManager.Domain.TaskChatAgg.Events;
|
||||
|
||||
// Message Events
|
||||
public record TaskChatMessageSentEvent(Guid MessageId, Guid TaskId, long SenderUserId, MessageType MessageType) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
public record TaskChatMessageEditedEvent(Guid MessageId, Guid TaskId, long EditorUserId) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
public record TaskChatMessageDeletedEvent(Guid MessageId, Guid TaskId, long DeleterUserId) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
public record TaskChatMessagePinnedEvent(Guid MessageId, Guid TaskId, long PinnerUserId) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
public record TaskChatMessageUnpinnedEvent(Guid MessageId, Guid TaskId, long UnpinnerUserId) : IDomainEvent
|
||||
{
|
||||
public DateTime OccurredOn { get; init; } = DateTime.Now;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
using GozareshgirProgramManager.Domain.TaskChatAgg.Entities;
|
||||
|
||||
namespace GozareshgirProgramManager.Domain.TaskChatAgg.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Repository برای مدیریت پیامهای چت تسک
|
||||
/// </summary>
|
||||
public interface ITaskChatMessageRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// دریافت پیام بر اساس شناسه
|
||||
/// </summary>
|
||||
Task<TaskChatMessage?> GetByIdAsync(Guid messageId);
|
||||
|
||||
/// <summary>
|
||||
/// دریافت لیست پیامهای یک تسک (با صفحهبندی)
|
||||
/// </summary>
|
||||
Task<List<TaskChatMessage>> GetTaskMessagesAsync(Guid taskId, int pageNumber, int pageSize);
|
||||
|
||||
/// <summary>
|
||||
/// دریافت تعداد کل پیامهای یک تسک
|
||||
/// </summary>
|
||||
Task<int> GetTaskMessageCountAsync(Guid taskId);
|
||||
|
||||
/// <summary>
|
||||
/// دریافت پیامهای پین شده یک تسک
|
||||
/// </summary>
|
||||
Task<List<TaskChatMessage>> GetPinnedMessagesAsync(Guid taskId);
|
||||
|
||||
/// <summary>
|
||||
/// دریافت آخرین پیام یک تسک
|
||||
/// </summary>
|
||||
Task<TaskChatMessage?> GetLastMessageAsync(Guid taskId);
|
||||
|
||||
/// <summary>
|
||||
/// جستجو در پیامهای یک تسک
|
||||
/// </summary>
|
||||
Task<List<TaskChatMessage>> SearchMessagesAsync(Guid taskId, string searchText, int pageNumber, int pageSize);
|
||||
|
||||
/// <summary>
|
||||
/// دریافت پیامهای یک کاربر خاص در یک تسک
|
||||
/// </summary>
|
||||
Task<List<TaskChatMessage>> GetUserMessagesAsync(Guid taskId, long userId, int pageNumber, int pageSize);
|
||||
|
||||
/// <summary>
|
||||
/// دریافت پیامهای با فایل (تصویر، ویدیو، فایل و...) - پیامهایی که FileId دارند
|
||||
/// </summary>
|
||||
Task<List<TaskChatMessage>> GetMediaMessagesAsync(Guid taskId, int pageNumber, int pageSize);
|
||||
|
||||
/// <summary>
|
||||
/// اضافه کردن پیام جدید
|
||||
/// </summary>
|
||||
Task<TaskChatMessage> AddAsync(TaskChatMessage message);
|
||||
|
||||
/// <summary>
|
||||
/// بهروزرسانی پیام
|
||||
/// </summary>
|
||||
Task UpdateAsync(TaskChatMessage message);
|
||||
|
||||
/// <summary>
|
||||
/// حذف فیزیکی پیام (در صورت نیاز - معمولاً استفاده نمیشود)
|
||||
/// </summary>
|
||||
Task DeleteAsync(TaskChatMessage message);
|
||||
|
||||
/// <summary>
|
||||
/// ذخیره تغییرات
|
||||
/// </summary>
|
||||
Task<int> SaveChangesAsync();
|
||||
|
||||
/// <summary>
|
||||
/// بررسی وجود پیام
|
||||
/// </summary>
|
||||
Task<bool> ExistsAsync(Guid messageId);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace GozareshgirProgramManager.Domain._Common;
|
||||
public interface IRepository<TKey, T> where T:class
|
||||
{
|
||||
T Get(TKey id);
|
||||
Task<T?> GetByIdAsync(TKey id);
|
||||
Task<T?> GetByIdAsync(TKey id, CancellationToken cancellationToken = default);
|
||||
List<T> Get();
|
||||
IQueryable<T> GetQueryable();
|
||||
void Create(T entity);
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
using FluentValidation;
|
||||
using GozareshgirProgramManager.Application._Common.Behaviors;
|
||||
using GozareshgirProgramManager.Application._Common.Interfaces;
|
||||
using GozareshgirProgramManager.Application.Services.FileManagement;
|
||||
using GozareshgirProgramManager.Domain._Common;
|
||||
using GozareshgirProgramManager.Domain.CheckoutAgg.Repositories;
|
||||
using GozareshgirProgramManager.Domain.CustomerAgg.Repositories;
|
||||
using GozareshgirProgramManager.Domain.FileManagementAgg.Repositories;
|
||||
using GozareshgirProgramManager.Domain.ProjectAgg.Repositories;
|
||||
using GozareshgirProgramManager.Domain.RoleAgg.Repositories;
|
||||
using GozareshgirProgramManager.Domain.RoleAgg.Repositories;
|
||||
@@ -14,6 +16,7 @@ using GozareshgirProgramManager.Domain.SalaryPaymentSettingAgg.Repositories;
|
||||
using GozareshgirProgramManager.Domain.SalaryPaymentSettingAgg.Repositories;
|
||||
using GozareshgirProgramManager.Domain.SkillAgg.Repositories;
|
||||
using GozareshgirProgramManager.Domain.SkillAgg.Repositories;
|
||||
using GozareshgirProgramManager.Domain.TaskChatAgg.Repositories;
|
||||
using GozareshgirProgramManager.Domain.UserAgg.Repositories;
|
||||
using GozareshgirProgramManager.Infrastructure.Persistence;
|
||||
using GozareshgirProgramManager.Infrastructure.Persistence.Context;
|
||||
@@ -82,6 +85,14 @@ public static class DependencyInjection
|
||||
|
||||
services.AddScoped<IUserRefreshTokenRepository, UserRefreshTokenRepository>();
|
||||
|
||||
// File Management & Task Chat
|
||||
services.AddScoped<IUploadedFileRepository, Persistence.Repositories.FileManagement.UploadedFileRepository>();
|
||||
services.AddScoped<ITaskChatMessageRepository, Persistence.Repositories.TaskChat.TaskChatMessageRepository>();
|
||||
|
||||
// File Storage Services
|
||||
services.AddScoped<IFileStorageService, Services.FileManagement.LocalFileStorageService>();
|
||||
services.AddScoped<IThumbnailGeneratorService, Services.FileManagement.ThumbnailGeneratorService>();
|
||||
|
||||
// JWT Settings
|
||||
services.Configure<JwtSettings>(configuration.GetSection("JwtSettings"));
|
||||
|
||||
|
||||
@@ -16,10 +16,19 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.1" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.12" />
|
||||
<!--<PackageReference Include="System.Text.Encodings.Web" Version="10.0.0" />-->
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Application\GozareshgirProgramManager.Application\GozareshgirProgramManager.Application.csproj" />
|
||||
<ProjectReference Include="..\..\Domain\GozareshgirProgramManager.Domain\GozareshgirProgramManager.Domain.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.AspNetCore.Hosting.Abstractions">
|
||||
<HintPath>C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App\10.0.1\Microsoft.AspNetCore.Hosting.Abstractions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Extensions.Hosting.Abstractions">
|
||||
<HintPath>C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App\10.0.1\Microsoft.Extensions.Hosting.Abstractions.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,865 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using GozareshgirProgramManager.Infrastructure.Persistence.Context;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace GozareshgirProgramManager.Infrastructure.Migrations
|
||||
{
|
||||
[DbContext(typeof(ProgramManagerDbContext))]
|
||||
[Migration("20251227094008_add phase deploy status and is archived")]
|
||||
partial class addphasedeploystatusandisarchived
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "10.0.1")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.CheckoutAgg.Entities.Checkout", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("CheckoutEndDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<DateTime>("CheckoutStartDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<DateTime>("CreationDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<double>("DeductionFromSalary")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<string>("FullName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<int>("MandatoryHours")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("Month")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<double>("MonthlySalaryDefined")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<double>("MonthlySalaryPay")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<int>("RemainingHours")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("TotalDaysWorked")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("TotalHoursWorked")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<long>("UserId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<int>("Year")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Checkouts", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.CustomerAgg.Customer", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<DateTime>("CreationDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Customers", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.PhaseSection", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("CreationDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid>("PhaseId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("SkillId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<long>("UserId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("PhaseId");
|
||||
|
||||
b.HasIndex("SkillId");
|
||||
|
||||
b.ToTable("PhaseSections");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.Project", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("CreationDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("nvarchar(1000)");
|
||||
|
||||
b.Property<DateTime?>("EndDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<bool>("HasAssignmentOverride")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.Property<DateTime?>("PlannedEndDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<DateTime?>("PlannedStartDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<DateTime?>("StartDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Projects", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.ProjectPhase", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("CreationDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("DeployStatus")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("nvarchar(30)");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("nvarchar(1000)");
|
||||
|
||||
b.Property<DateTime?>("EndDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<bool>("HasAssignmentOverride")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("IsArchived")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.Property<int>("OrderIndex")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<Guid>("ProjectId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime?>("StartDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ProjectId");
|
||||
|
||||
b.ToTable("ProjectPhases", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.ProjectSection", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("CreationDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid>("ProjectId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("SkillId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<long>("UserId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ProjectId");
|
||||
|
||||
b.HasIndex("SkillId");
|
||||
|
||||
b.ToTable("ProjectSections");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.ProjectTask", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("AllocatedTime")
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("nvarchar(30)");
|
||||
|
||||
b.Property<DateTime>("CreationDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("nvarchar(1000)");
|
||||
|
||||
b.Property<DateTime?>("DueDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<DateTime?>("EndDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<bool>("HasAssignmentOverride")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("HasTimeOverride")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.Property<int>("OrderIndex")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<Guid>("PhaseId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Priority")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<DateTime?>("StartDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("PhaseId");
|
||||
|
||||
b.ToTable("ProjectTasks", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.TaskSection", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("CreationDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<long>("CurrentAssignedUserId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("InitialDescription")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("InitialEstimatedHours")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("nvarchar(30)");
|
||||
|
||||
b.Property<long>("OriginalAssignedUserId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<Guid>("SkillId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<Guid>("TaskId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SkillId");
|
||||
|
||||
b.HasIndex("TaskId");
|
||||
|
||||
b.ToTable("TaskSections", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.TaskSectionActivity", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("CreationDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<DateTime?>("EndDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("EndNotes")
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("nvarchar(1000)");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("Notes")
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("nvarchar(1000)");
|
||||
|
||||
b.Property<Guid>("SectionId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("StartDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<long>("UserId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SectionId");
|
||||
|
||||
b.ToTable("TaskSectionActivities", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.TaskSectionAdditionalTime", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("AddedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<long?>("AddedByUserId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<DateTime>("CreationDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Hours")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("nvarchar(30)");
|
||||
|
||||
b.Property<string>("Reason")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<Guid?>("TaskSectionId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TaskSectionId");
|
||||
|
||||
b.ToTable("TaskSectionAdditionalTimes", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.RoleAgg.Entities.Role", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreationDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<long?>("GozareshgirRoleId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("RoleName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("PmRoles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.SalaryPaymentSettingAgg.Entities.SalaryPaymentSetting", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreationDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<DateTime?>("EndSettingDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<bool>("HolidayWorking")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<double>("MonthlySalary")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<DateTime?>("StartSettingDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<long>("UserId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("SalaryPaymentSetting", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.SkillAgg.Entities.Skill", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("CreationDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Skills", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.UserAgg.Entities.User", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<long?>("AccountId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<DateTime>("CreationDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(150)
|
||||
.HasColumnType("nvarchar(150)");
|
||||
|
||||
b.Property<string>("FullName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("Mobile")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("nvarchar(20)");
|
||||
|
||||
b.Property<string>("Password")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("nvarchar(1000)");
|
||||
|
||||
b.Property<string>("ProfilePhotoPath")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("VerifyCode")
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("nvarchar(10)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Users", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.UserAgg.Entities.UserRefreshToken", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("CreationDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<DateTime>("ExpiresAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("IpAddress")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<DateTime?>("RevokedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Token")
|
||||
.IsRequired()
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("UserAgent")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<long>("UserId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ExpiresAt");
|
||||
|
||||
b.HasIndex("Token")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("UserRefreshTokens", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.PhaseSection", b =>
|
||||
{
|
||||
b.HasOne("GozareshgirProgramManager.Domain.ProjectAgg.Entities.ProjectPhase", "Phase")
|
||||
.WithMany("PhaseSections")
|
||||
.HasForeignKey("PhaseId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("GozareshgirProgramManager.Domain.SkillAgg.Entities.Skill", "Skill")
|
||||
.WithMany()
|
||||
.HasForeignKey("SkillId")
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
b.Navigation("Phase");
|
||||
|
||||
b.Navigation("Skill");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.ProjectPhase", b =>
|
||||
{
|
||||
b.HasOne("GozareshgirProgramManager.Domain.ProjectAgg.Entities.Project", "Project")
|
||||
.WithMany("Phases")
|
||||
.HasForeignKey("ProjectId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Project");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.ProjectSection", b =>
|
||||
{
|
||||
b.HasOne("GozareshgirProgramManager.Domain.ProjectAgg.Entities.Project", "Project")
|
||||
.WithMany("ProjectSections")
|
||||
.HasForeignKey("ProjectId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("GozareshgirProgramManager.Domain.SkillAgg.Entities.Skill", "Skill")
|
||||
.WithMany()
|
||||
.HasForeignKey("SkillId")
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
b.Navigation("Project");
|
||||
|
||||
b.Navigation("Skill");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.ProjectTask", b =>
|
||||
{
|
||||
b.HasOne("GozareshgirProgramManager.Domain.ProjectAgg.Entities.ProjectPhase", "Phase")
|
||||
.WithMany("Tasks")
|
||||
.HasForeignKey("PhaseId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Phase");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.TaskSection", b =>
|
||||
{
|
||||
b.HasOne("GozareshgirProgramManager.Domain.SkillAgg.Entities.Skill", "Skill")
|
||||
.WithMany("Sections")
|
||||
.HasForeignKey("SkillId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("GozareshgirProgramManager.Domain.ProjectAgg.Entities.ProjectTask", "Task")
|
||||
.WithMany("Sections")
|
||||
.HasForeignKey("TaskId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Skill");
|
||||
|
||||
b.Navigation("Task");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.TaskSectionActivity", b =>
|
||||
{
|
||||
b.HasOne("GozareshgirProgramManager.Domain.ProjectAgg.Entities.TaskSection", "Section")
|
||||
.WithMany("Activities")
|
||||
.HasForeignKey("SectionId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Section");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.TaskSectionAdditionalTime", b =>
|
||||
{
|
||||
b.HasOne("GozareshgirProgramManager.Domain.ProjectAgg.Entities.TaskSection", null)
|
||||
.WithMany("AdditionalTimes")
|
||||
.HasForeignKey("TaskSectionId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.RoleAgg.Entities.Role", b =>
|
||||
{
|
||||
b.OwnsMany("GozareshgirProgramManager.Domain.PermissionAgg.Entities.Permission", "Permissions", b1 =>
|
||||
{
|
||||
b1.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property<long>("Id"));
|
||||
|
||||
b1.Property<int>("Code")
|
||||
.HasColumnType("int");
|
||||
|
||||
b1.Property<long>("RoleId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b1.HasKey("Id");
|
||||
|
||||
b1.HasIndex("RoleId");
|
||||
|
||||
b1.ToTable("PmRolePermissions", (string)null);
|
||||
|
||||
b1.WithOwner("Role")
|
||||
.HasForeignKey("RoleId");
|
||||
|
||||
b1.Navigation("Role");
|
||||
});
|
||||
|
||||
b.Navigation("Permissions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.SalaryPaymentSettingAgg.Entities.SalaryPaymentSetting", b =>
|
||||
{
|
||||
b.OwnsMany("GozareshgirProgramManager.Domain.SalaryPaymentSettingAgg.Entities.WorkingHours", "WorkingHoursList", b1 =>
|
||||
{
|
||||
b1.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property<long>("Id"));
|
||||
|
||||
b1.Property<TimeSpan>("EndShiftOne")
|
||||
.HasColumnType("time(0)");
|
||||
|
||||
b1.Property<TimeSpan>("EndShiftTwo")
|
||||
.HasColumnType("time(0)");
|
||||
|
||||
b1.Property<bool>("HasRestTime")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b1.Property<bool>("HasShiftOne")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b1.Property<bool>("HasShiftTow")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b1.Property<bool>("IsActiveDay")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b1.Property<int>("PersianDayOfWeek")
|
||||
.HasColumnType("int");
|
||||
|
||||
b1.Property<TimeSpan>("RestTime")
|
||||
.HasColumnType("time(0)");
|
||||
|
||||
b1.Property<long>("SalaryPaymentSettingId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b1.Property<int>("ShiftDurationInMinutes")
|
||||
.HasColumnType("int");
|
||||
|
||||
b1.Property<TimeSpan>("StartShiftOne")
|
||||
.HasColumnType("time(0)");
|
||||
|
||||
b1.Property<TimeSpan>("StartShiftTwo")
|
||||
.HasColumnType("time(0)");
|
||||
|
||||
b1.HasKey("Id");
|
||||
|
||||
b1.HasIndex("SalaryPaymentSettingId");
|
||||
|
||||
b1.ToTable("WorkingHours", (string)null);
|
||||
|
||||
b1.WithOwner("SalaryPaymentSetting")
|
||||
.HasForeignKey("SalaryPaymentSettingId");
|
||||
|
||||
b1.Navigation("SalaryPaymentSetting");
|
||||
});
|
||||
|
||||
b.Navigation("WorkingHoursList");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.UserAgg.Entities.User", b =>
|
||||
{
|
||||
b.OwnsMany("GozareshgirProgramManager.Domain.RoleUserAgg.RoleUser", "RoleUser", b1 =>
|
||||
{
|
||||
b1.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property<long>("Id"));
|
||||
|
||||
b1.Property<long>("RoleId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b1.Property<long>("UserId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b1.HasKey("Id");
|
||||
|
||||
b1.HasIndex("UserId");
|
||||
|
||||
b1.ToTable("RoleUsers", (string)null);
|
||||
|
||||
b1.WithOwner("User")
|
||||
.HasForeignKey("UserId");
|
||||
|
||||
b1.Navigation("User");
|
||||
});
|
||||
|
||||
b.Navigation("RoleUser");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.UserAgg.Entities.UserRefreshToken", b =>
|
||||
{
|
||||
b.HasOne("GozareshgirProgramManager.Domain.UserAgg.Entities.User", "User")
|
||||
.WithMany("RefreshTokens")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.Project", b =>
|
||||
{
|
||||
b.Navigation("Phases");
|
||||
|
||||
b.Navigation("ProjectSections");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.ProjectPhase", b =>
|
||||
{
|
||||
b.Navigation("PhaseSections");
|
||||
|
||||
b.Navigation("Tasks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.ProjectTask", b =>
|
||||
{
|
||||
b.Navigation("Sections");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.TaskSection", b =>
|
||||
{
|
||||
b.Navigation("Activities");
|
||||
|
||||
b.Navigation("AdditionalTimes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.SkillAgg.Entities.Skill", b =>
|
||||
{
|
||||
b.Navigation("Sections");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.UserAgg.Entities.User", b =>
|
||||
{
|
||||
b.Navigation("RefreshTokens");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace GozareshgirProgramManager.Infrastructure.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class addphasedeploystatusandisarchived : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "DeployStatus",
|
||||
table: "ProjectPhases",
|
||||
type: "nvarchar(30)",
|
||||
maxLength: 30,
|
||||
nullable: false,
|
||||
defaultValue: "");
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "IsArchived",
|
||||
table: "ProjectPhases",
|
||||
type: "bit",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "DeployStatus",
|
||||
table: "ProjectPhases");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "IsArchived",
|
||||
table: "ProjectPhases");
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,158 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace GozareshgirProgramManager.Infrastructure.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class addtaskchatuploadedfile : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "TaskChatMessages",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
TaskId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
SenderUserId = table.Column<long>(type: "bigint", nullable: false),
|
||||
MessageType = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
||||
TextContent = table.Column<string>(type: "nvarchar(4000)", maxLength: 4000, nullable: true),
|
||||
FileId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
ReplyToMessageId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
IsEdited = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
|
||||
EditedDate = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
|
||||
DeletedDate = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
IsPinned = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
|
||||
PinnedDate = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
PinnedByUserId = table.Column<long>(type: "bigint", nullable: true),
|
||||
CreationDate = table.Column<DateTime>(type: "datetime2", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_TaskChatMessages", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_TaskChatMessages_TaskChatMessages_ReplyToMessageId",
|
||||
column: x => x.ReplyToMessageId,
|
||||
principalTable: "TaskChatMessages",
|
||||
principalColumn: "Id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "UploadedFiles",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
OriginalFileName = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false),
|
||||
UniqueFileName = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false),
|
||||
FileExtension = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
||||
FileSizeBytes = table.Column<long>(type: "bigint", nullable: false),
|
||||
MimeType = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
|
||||
FileType = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
||||
Category = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
|
||||
StorageProvider = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
||||
StoragePath = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: true),
|
||||
StorageUrl = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: true),
|
||||
ThumbnailUrl = table.Column<string>(type: "nvarchar(1000)", maxLength: 1000, nullable: true),
|
||||
UploadedByUserId = table.Column<long>(type: "bigint", nullable: false),
|
||||
UploadDate = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
Status = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
|
||||
ImageWidth = table.Column<int>(type: "int", nullable: true),
|
||||
ImageHeight = table.Column<int>(type: "int", nullable: true),
|
||||
DurationSeconds = table.Column<int>(type: "int", nullable: true),
|
||||
VirusScanDate = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
IsVirusScanPassed = table.Column<bool>(type: "bit", nullable: true),
|
||||
VirusScanResult = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
|
||||
IsDeleted = table.Column<bool>(type: "bit", nullable: false, defaultValue: false),
|
||||
DeletedDate = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
DeletedByUserId = table.Column<long>(type: "bigint", nullable: true),
|
||||
ReferenceEntityType = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
|
||||
ReferenceEntityId = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
|
||||
CreationDate = table.Column<DateTime>(type: "datetime2", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_UploadedFiles", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_TaskChatMessages_CreationDate",
|
||||
table: "TaskChatMessages",
|
||||
column: "CreationDate");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_TaskChatMessages_FileId",
|
||||
table: "TaskChatMessages",
|
||||
column: "FileId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_TaskChatMessages_IsDeleted",
|
||||
table: "TaskChatMessages",
|
||||
column: "IsDeleted");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_TaskChatMessages_ReplyToMessageId",
|
||||
table: "TaskChatMessages",
|
||||
column: "ReplyToMessageId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_TaskChatMessages_SenderUserId",
|
||||
table: "TaskChatMessages",
|
||||
column: "SenderUserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_TaskChatMessages_TaskId",
|
||||
table: "TaskChatMessages",
|
||||
column: "TaskId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_TaskChatMessages_TaskId_IsPinned",
|
||||
table: "TaskChatMessages",
|
||||
columns: new[] { "TaskId", "IsPinned" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_UploadedFiles_Category",
|
||||
table: "UploadedFiles",
|
||||
column: "Category");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_UploadedFiles_IsDeleted",
|
||||
table: "UploadedFiles",
|
||||
column: "IsDeleted");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_UploadedFiles_ReferenceEntityType_ReferenceEntityId",
|
||||
table: "UploadedFiles",
|
||||
columns: new[] { "ReferenceEntityType", "ReferenceEntityId" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_UploadedFiles_Status",
|
||||
table: "UploadedFiles",
|
||||
column: "Status");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_UploadedFiles_UniqueFileName",
|
||||
table: "UploadedFiles",
|
||||
column: "UniqueFileName",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_UploadedFiles_UploadedByUserId",
|
||||
table: "UploadedFiles",
|
||||
column: "UploadedByUserId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "TaskChatMessages");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "UploadedFiles");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -102,6 +102,131 @@ namespace GozareshgirProgramManager.Infrastructure.Migrations
|
||||
b.ToTable("Customers", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.FileManagementAgg.Entities.UploadedFile", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Category")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<DateTime>("CreationDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<long?>("DeletedByUserId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<DateTime?>("DeletedDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<int?>("DurationSeconds")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("FileExtension")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<long>("FileSizeBytes")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("FileType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<int?>("ImageHeight")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int?>("ImageWidth")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bit")
|
||||
.HasDefaultValue(false);
|
||||
|
||||
b.Property<bool?>("IsVirusScanPassed")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("MimeType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.Property<string>("OriginalFileName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("ReferenceEntityId")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("ReferenceEntityType")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<string>("StoragePath")
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("nvarchar(1000)");
|
||||
|
||||
b.Property<string>("StorageProvider")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<string>("StorageUrl")
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("nvarchar(1000)");
|
||||
|
||||
b.Property<string>("ThumbnailUrl")
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("nvarchar(1000)");
|
||||
|
||||
b.Property<string>("UniqueFileName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<DateTime>("UploadDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<long>("UploadedByUserId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<DateTime?>("VirusScanDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("VirusScanResult")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Category");
|
||||
|
||||
b.HasIndex("IsDeleted");
|
||||
|
||||
b.HasIndex("Status");
|
||||
|
||||
b.HasIndex("UniqueFileName")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("UploadedByUserId");
|
||||
|
||||
b.HasIndex("ReferenceEntityType", "ReferenceEntityId");
|
||||
|
||||
b.ToTable("UploadedFiles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.PhaseSection", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@@ -126,7 +251,7 @@ namespace GozareshgirProgramManager.Infrastructure.Migrations
|
||||
|
||||
b.HasIndex("SkillId");
|
||||
|
||||
b.ToTable("PhaseSections", (string)null);
|
||||
b.ToTable("PhaseSections");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.Project", b =>
|
||||
@@ -179,6 +304,11 @@ namespace GozareshgirProgramManager.Infrastructure.Migrations
|
||||
b.Property<DateTime>("CreationDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("DeployStatus")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("nvarchar(30)");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("nvarchar(1000)");
|
||||
@@ -189,6 +319,9 @@ namespace GozareshgirProgramManager.Infrastructure.Migrations
|
||||
b.Property<bool>("HasAssignmentOverride")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("IsArchived")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
@@ -238,7 +371,7 @@ namespace GozareshgirProgramManager.Infrastructure.Migrations
|
||||
|
||||
b.HasIndex("SkillId");
|
||||
|
||||
b.ToTable("ProjectSections", (string)null);
|
||||
b.ToTable("ProjectSections");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.ProjectTask", b =>
|
||||
@@ -487,6 +620,81 @@ namespace GozareshgirProgramManager.Infrastructure.Migrations
|
||||
b.ToTable("Skills", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.TaskChatAgg.Entities.TaskChatMessage", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<DateTime>("CreationDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<DateTime?>("DeletedDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<DateTime?>("EditedDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("FileId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bit")
|
||||
.HasDefaultValue(false);
|
||||
|
||||
b.Property<bool>("IsEdited")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bit")
|
||||
.HasDefaultValue(false);
|
||||
|
||||
b.Property<bool>("IsPinned")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bit")
|
||||
.HasDefaultValue(false);
|
||||
|
||||
b.Property<string>("MessageType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<long?>("PinnedByUserId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<DateTime?>("PinnedDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<Guid?>("ReplyToMessageId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<long>("SenderUserId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<Guid>("TaskId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("TextContent")
|
||||
.HasMaxLength(4000)
|
||||
.HasColumnType("nvarchar(4000)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreationDate");
|
||||
|
||||
b.HasIndex("FileId");
|
||||
|
||||
b.HasIndex("IsDeleted");
|
||||
|
||||
b.HasIndex("ReplyToMessageId");
|
||||
|
||||
b.HasIndex("SenderUserId");
|
||||
|
||||
b.HasIndex("TaskId");
|
||||
|
||||
b.HasIndex("TaskId", "IsPinned");
|
||||
|
||||
b.ToTable("TaskChatMessages", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.UserAgg.Entities.User", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
@@ -771,6 +979,16 @@ namespace GozareshgirProgramManager.Infrastructure.Migrations
|
||||
b.Navigation("WorkingHoursList");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.TaskChatAgg.Entities.TaskChatMessage", b =>
|
||||
{
|
||||
b.HasOne("GozareshgirProgramManager.Domain.TaskChatAgg.Entities.TaskChatMessage", "ReplyToMessage")
|
||||
.WithMany()
|
||||
.HasForeignKey("ReplyToMessageId")
|
||||
.OnDelete(DeleteBehavior.NoAction);
|
||||
|
||||
b.Navigation("ReplyToMessage");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GozareshgirProgramManager.Domain.UserAgg.Entities.User", b =>
|
||||
{
|
||||
b.OwnsMany("GozareshgirProgramManager.Domain.RoleUserAgg.RoleUser", "RoleUser", b1 =>
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
using GozareshgirProgramManager.Application._Common.Interfaces;
|
||||
using GozareshgirProgramManager.Application._Common.Interfaces;
|
||||
using GozareshgirProgramManager.Domain.CheckoutAgg.Entities;
|
||||
using GozareshgirProgramManager.Domain.CustomerAgg;
|
||||
using GozareshgirProgramManager.Application._Common.Interfaces;
|
||||
using GozareshgirProgramManager.Domain.CustomerAgg;
|
||||
using GozareshgirProgramManager.Domain.FileManagementAgg.Entities;
|
||||
using GozareshgirProgramManager.Domain.ProjectAgg.Entities;
|
||||
using GozareshgirProgramManager.Domain.RoleAgg.Entities;
|
||||
using GozareshgirProgramManager.Domain.RoleUserAgg;
|
||||
using GozareshgirProgramManager.Domain.SalaryPaymentSettingAgg.Entities;
|
||||
using GozareshgirProgramManager.Domain.UserAgg.Entities;
|
||||
using GozareshgirProgramManager.Domain.SkillAgg.Entities;
|
||||
using GozareshgirProgramManager.Domain.TaskChatAgg.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace GozareshgirProgramManager.Infrastructure.Persistence.Context;
|
||||
@@ -40,6 +42,13 @@ public class ProgramManagerDbContext : DbContext, IProgramManagerDbContext
|
||||
public DbSet<Role> Roles { get; set; } = null!;
|
||||
|
||||
public DbSet<Skill> Skills { get; set; } = null!;
|
||||
|
||||
// File Management
|
||||
public DbSet<UploadedFile> UploadedFiles { get; set; } = null!;
|
||||
|
||||
// Task Chat
|
||||
public DbSet<TaskChatMessage> TaskChatMessages { get; set; } = null!;
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.ApplyConfigurationsFromAssembly(typeof(ProgramManagerDbContext).Assembly);
|
||||
|
||||
@@ -48,6 +48,9 @@ public class ProjectPhaseMapping : IEntityTypeConfiguration<ProjectPhase>
|
||||
builder.Property(ph => ph.HasAssignmentOverride)
|
||||
.IsRequired();
|
||||
|
||||
builder.Property(x => x.DeployStatus)
|
||||
.HasConversion<string>().HasMaxLength(30);
|
||||
|
||||
// Relationship with Project
|
||||
builder.HasOne(ph => ph.Project)
|
||||
.WithMany(p => p.Phases)
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
using GozareshgirProgramManager.Domain.TaskChatAgg.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace GozareshgirProgramManager.Infrastructure.Persistence.Mappings;
|
||||
|
||||
public class TaskChatMessageMapping : IEntityTypeConfiguration<TaskChatMessage>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<TaskChatMessage> builder)
|
||||
{
|
||||
builder.ToTable("TaskChatMessages");
|
||||
|
||||
builder.HasKey(x => x.Id);
|
||||
|
||||
builder.Property(x => x.Id)
|
||||
.ValueGeneratedNever();
|
||||
|
||||
// Task Reference
|
||||
builder.Property(x => x.TaskId)
|
||||
.IsRequired();
|
||||
|
||||
builder.HasIndex(x => x.TaskId);
|
||||
|
||||
// Sender
|
||||
builder.Property(x => x.SenderUserId)
|
||||
.IsRequired();
|
||||
|
||||
builder.HasIndex(x => x.SenderUserId);
|
||||
|
||||
// Message Type
|
||||
builder.Property(x => x.MessageType)
|
||||
.IsRequired()
|
||||
.HasConversion<string>()
|
||||
.HasMaxLength(50);
|
||||
|
||||
// Content
|
||||
builder.Property(x => x.TextContent)
|
||||
.HasMaxLength(4000);
|
||||
|
||||
// File Reference
|
||||
builder.Property(x => x.FileId);
|
||||
|
||||
builder.HasIndex(x => x.FileId);
|
||||
|
||||
// Reply
|
||||
builder.Property(x => x.ReplyToMessageId);
|
||||
|
||||
builder.HasOne(x => x.ReplyToMessage)
|
||||
.WithMany()
|
||||
.HasForeignKey(x => x.ReplyToMessageId)
|
||||
.OnDelete(DeleteBehavior.NoAction);
|
||||
|
||||
// Status
|
||||
builder.Property(x => x.IsEdited)
|
||||
.IsRequired()
|
||||
.HasDefaultValue(false);
|
||||
|
||||
builder.Property(x => x.EditedDate);
|
||||
|
||||
builder.Property(x => x.IsDeleted)
|
||||
.IsRequired()
|
||||
.HasDefaultValue(false);
|
||||
|
||||
builder.Property(x => x.DeletedDate);
|
||||
|
||||
builder.HasIndex(x => x.IsDeleted);
|
||||
|
||||
// Pin
|
||||
builder.Property(x => x.IsPinned)
|
||||
.IsRequired()
|
||||
.HasDefaultValue(false);
|
||||
|
||||
builder.Property(x => x.PinnedDate);
|
||||
builder.Property(x => x.PinnedByUserId);
|
||||
|
||||
builder.HasIndex(x => new { x.TaskId, x.IsPinned });
|
||||
|
||||
// Audit
|
||||
builder.Property(x => x.CreationDate)
|
||||
.IsRequired();
|
||||
|
||||
builder.HasIndex(x => x.CreationDate);
|
||||
|
||||
// Query Filter - پیامهای حذف نشده
|
||||
builder.HasQueryFilter(x => !x.IsDeleted);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
using GozareshgirProgramManager.Domain.FileManagementAgg.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace GozareshgirProgramManager.Infrastructure.Persistence.Mappings;
|
||||
|
||||
public class UploadedFileMapping : IEntityTypeConfiguration<UploadedFile>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<UploadedFile> builder)
|
||||
{
|
||||
builder.ToTable("UploadedFiles");
|
||||
|
||||
builder.HasKey(x => x.Id);
|
||||
|
||||
builder.Property(x => x.Id)
|
||||
.ValueGeneratedNever();
|
||||
|
||||
// اطلاعات فایل
|
||||
builder.Property(x => x.OriginalFileName)
|
||||
.IsRequired()
|
||||
.HasMaxLength(500);
|
||||
|
||||
builder.Property(x => x.UniqueFileName)
|
||||
.IsRequired()
|
||||
.HasMaxLength(500);
|
||||
|
||||
builder.HasIndex(x => x.UniqueFileName)
|
||||
.IsUnique();
|
||||
|
||||
builder.Property(x => x.FileExtension)
|
||||
.IsRequired()
|
||||
.HasMaxLength(50);
|
||||
|
||||
builder.Property(x => x.FileSizeBytes)
|
||||
.IsRequired();
|
||||
|
||||
builder.Property(x => x.MimeType)
|
||||
.IsRequired()
|
||||
.HasMaxLength(200);
|
||||
|
||||
builder.Property(x => x.FileType)
|
||||
.IsRequired()
|
||||
.HasConversion<string>()
|
||||
.HasMaxLength(50);
|
||||
|
||||
builder.Property(x => x.Category)
|
||||
.IsRequired()
|
||||
.HasConversion<string>()
|
||||
.HasMaxLength(100);
|
||||
|
||||
// ذخیرهسازی
|
||||
builder.Property(x => x.StorageProvider)
|
||||
.IsRequired()
|
||||
.HasConversion<string>()
|
||||
.HasMaxLength(50);
|
||||
|
||||
builder.Property(x => x.StoragePath)
|
||||
.HasMaxLength(1000);
|
||||
|
||||
builder.Property(x => x.StorageUrl)
|
||||
.HasMaxLength(1000);
|
||||
|
||||
builder.Property(x => x.ThumbnailUrl)
|
||||
.HasMaxLength(1000);
|
||||
|
||||
// متادیتا
|
||||
builder.Property(x => x.UploadedByUserId)
|
||||
.IsRequired();
|
||||
|
||||
builder.Property(x => x.UploadDate)
|
||||
.IsRequired();
|
||||
|
||||
builder.Property(x => x.Status)
|
||||
.IsRequired()
|
||||
.HasConversion<string>()
|
||||
.HasMaxLength(50);
|
||||
|
||||
builder.HasIndex(x => x.Status);
|
||||
builder.HasIndex(x => x.UploadedByUserId);
|
||||
builder.HasIndex(x => x.Category);
|
||||
|
||||
// اطلاعات تصویر
|
||||
builder.Property(x => x.ImageWidth);
|
||||
builder.Property(x => x.ImageHeight);
|
||||
|
||||
// اطلاعات صوت/ویدیو
|
||||
builder.Property(x => x.DurationSeconds);
|
||||
|
||||
// امنیت
|
||||
builder.Property(x => x.VirusScanDate);
|
||||
builder.Property(x => x.IsVirusScanPassed);
|
||||
builder.Property(x => x.VirusScanResult)
|
||||
.HasMaxLength(500);
|
||||
|
||||
// Soft Delete
|
||||
builder.Property(x => x.IsDeleted)
|
||||
.IsRequired()
|
||||
.HasDefaultValue(false);
|
||||
|
||||
builder.Property(x => x.DeletedDate);
|
||||
builder.Property(x => x.DeletedByUserId);
|
||||
|
||||
builder.HasIndex(x => x.IsDeleted);
|
||||
|
||||
// Reference Tracking
|
||||
builder.Property(x => x.ReferenceEntityType)
|
||||
.HasMaxLength(100);
|
||||
|
||||
builder.Property(x => x.ReferenceEntityId)
|
||||
.HasMaxLength(100);
|
||||
|
||||
builder.HasIndex(x => new { x.ReferenceEntityType, x.ReferenceEntityId });
|
||||
|
||||
// Audit
|
||||
builder.Property(x => x.CreationDate)
|
||||
.IsRequired();
|
||||
|
||||
// Query Filter - فایلهای حذف نشده
|
||||
builder.HasQueryFilter(x => !x.IsDeleted);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user