Compare commits

..

85 Commits

Author SHA1 Message Date
380ed8f6b1 feat: update SetTimeProjectCommandHandler to set status to Incomplete when additional time is added 2026-01-07 18:34:37 +03:30
7423391003 Merge branch 'Feature/program-manager/priority'
# Conflicts:
#	ServiceHost/Areas/Admin/Controllers/ProgramManager/ProjectController.cs
2026-01-07 18:05:46 +03:30
572f66f905 feat: implement auto-pending for task sections reaching estimated time 2026-01-07 16:52:50 +03:30
2bea265989 feat: implement task priority change command and update project/task DTOs 2026-01-07 12:11:24 +03:30
ef9b78b924 Merge branch 'refs/heads/master' into Feature/program-manager/priority 2026-01-07 12:01:38 +03:30
8ad296fe61 Merge branch 'Feature/program-manager/fix-bugs' 2026-01-07 11:36:38 +03:30
SamSys
823110ea74 change 2026-01-07 11:24:27 +03:30
061058cbeb fix change status error 2026-01-07 11:21:58 +03:30
c6ed46d8b7 Merge remote-tracking branch 'origin/master' 2026-01-06 21:48:41 +03:30
3da7453ece feat: add assignment status tracking for projects, phases, and tasks 2026-01-06 21:47:22 +03:30
SamSys
9a591fabff change WorningSms methoth 2026-01-06 19:07:54 +03:30
9d09ef60f8 feat: add progress percentage calculations to project and task details 2026-01-06 18:19:16 +03:30
0757ac7e74 Merge branch 'refs/heads/master' into Feature/program-manager/set-Complete-on-Done 2026-01-06 14:22:28 +03:30
a9789023ac feat: add HTTP POST endpoint for ChangePriority method in ProjectController 2026-01-06 13:46:45 +03:30
16b11a8bb8 feat: add ChangePriority method to ProjectController for task priority updates 2026-01-06 10:51:56 +03:30
SamSys
dd5455d80a ignore apsettings 2026-01-05 19:58:54 +03:30
9360dcad71 add UserSecretsId to ServiceHost.csproj 2026-01-05 19:14:11 +03:30
1971252713 feat: improve project board sorting by current user and task status 2026-01-05 17:52:12 +03:30
02cc099104 feat: update time calculation to include minutes in section time setting 2026-01-05 16:45:59 +03:30
209aa5912d feat: configure Serilog for file logging with environment-specific settings 2026-01-05 10:58:30 +03:30
340685a06c feat: enhance project retrieval by including additional times and update search parameter naming 2026-01-05 09:49:31 +03:30
b20a56df26 Merge branch 'Feature/program-manager/set-user-time' 2026-01-04 20:39:49 +03:30
8d93fa4fc6 Add approval workflow for task section completion
- Updated status transitions to include PendingForCompletion before Completed
- Added API endpoint and command/handler for approving/rejecting section completion
- Fixed additional time calculation to include minutes
- Refactored project board query logic and improved user ordering
- Updated launch settings with new HTTPS endpoint
- Documented progress percentage feature for TaskSection
2026-01-04 20:39:20 +03:30
8f10f7057c fix: handle null workshop details in ticket display logic 2026-01-04 17:54:59 +03:30
00b5066f6f feat: implement removal of invalid project, phase, and task sections based on skill validation 2026-01-04 17:40:42 +03:30
abd221cb55 feat: fix SkillName and SkillId assignment in ProjectSetTimeDetailsQueryHandler 2026-01-04 17:18:36 +03:30
33833a408c feat: include PhaseSections in ProjectPhase retrieval for enhanced task data 2026-01-04 16:58:36 +03:30
c2fca9f9eb feat: rename project hierarchy search components and add validation for search query 2026-01-04 15:50:56 +03:30
a16c20440b fix: correct payment amount calculation in installment function 2026-01-04 15:32:56 +03:30
1f365f3642 feat: implement project hierarchy search functionality with validation 2026-01-04 15:14:28 +03:30
0bfcde6a3f feat: add validation to prevent users from starting multiple sections in progress 2026-01-04 14:13:29 +03:30
4ada29a98a feat: enhance ProjectSetTimeDetailsQuery to support multiple hierarchy levels and improve data retrieval 2026-01-04 13:51:36 +03:30
6f64ee1ce4 refactor: streamline ProjectSetTimeDetailsQueryHandler to enhance skill and user data retrieval 2026-01-04 13:01:43 +03:30
582da511c6 feat: add progress percentage calculation to task sections 2026-01-01 19:25:28 +03:30
3340edcc17 feat: add total hours and minutes tracking to project, phase, and task calculations 2026-01-01 15:41:58 +03:30
385a885c93 feat: add TotalHours and Minutes to project and task details for improved time tracking 2026-01-01 14:13:27 +03:30
f99f199a77 refactor: rename SectionItems to SkillItems and add CreationDate to project time details 2026-01-01 13:16:52 +03:30
287b31e356 refactor: improve code readability by formatting and organizing constructor parameters 2026-01-01 12:50:34 +03:30
5f8232809a refactor: update project time management to use skills and improve data structure 2026-01-01 12:29:06 +03:30
3c72311096 refactor: update namespace for AuthorizedBankDetails application components 2026-01-01 10:11:17 +03:30
250d17eba2 Merge remote-tracking branch 'origin/master' 2025-12-31 20:22:53 +03:30
5db8e7d319 show initial workshops on print instead of current 2025-12-31 20:22:42 +03:30
SamSys
3300f60845 change 2025-12-31 20:08:11 +03:30
SamSys
7537cfe5b8 Merge branch 'master' of https://github.com/samsyntax24/OriginalGozareshgir 2025-12-31 20:07:35 +03:30
SamSys
3c1bf7dff0 Log download 2025-12-31 20:07:18 +03:30
6909fcf715 remove financial transaction on institutioncontract veerification 2025-12-31 20:06:56 +03:30
fe66ff5aa3 add: log response content in SepehrPaymentGateway 2025-12-31 19:26:12 +03:30
ce305edac4 add: integrate ILogger for SepehrPaymentGateway logging 2025-12-31 19:12:12 +03:30
a49b825ce9 Merge remote-tracking branch 'origin/master' 2025-12-31 19:07:09 +03:30
fb62523a23 add: integrate Serilog for enhanced logging and monitoring 2025-12-31 19:06:56 +03:30
SamSys
e171a4749c change 2025-12-31 18:34:42 +03:30
9b6c0d4cc4 Merge remote-tracking branch 'origin/master' 2025-12-31 18:31:12 +03:30
f8126b4000 set transaction id on get 2025-12-31 18:31:00 +03:30
SamSys
cf62d75f0e change 2025-12-31 18:23:07 +03:30
SamSys
f93e59b77c Merge branch 'master' of https://github.com/samsyntax24/OriginalGozareshgir 2025-12-31 18:21:15 +03:30
SamSys
8f37d9f388 init SendWarningSms and LegalActionSms 2025-12-31 18:21:01 +03:30
8e72b56758 refactor: format BlockMessageForElectronicContract method parameters for improved readability 2025-12-31 18:16:51 +03:30
a6e1251445 Merge branch 'master' of https://github.com/syntax24/OriginalGozareshgir into Feature/gateway/add-debt-payment
# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.
2025-12-31 18:13:06 +03:30
490a1a69d5 add: implement Sepehr payment gateway integration and enhance financial invoice handling 2025-12-31 18:12:27 +03:30
7e3ea39d5b add: update payment gateway integration and enhance transaction handling in callback 2025-12-31 16:08:52 +03:30
66a6c411d6 add: implement payment callback handling and enhance financial invoice structure 2025-12-31 15:49:44 +03:30
SamSys
b03a806dfb BackgroundTask change for new institutionContract sms 2025-12-31 14:11:10 +03:30
14ff0a2e59 Merge branch 'Feature/program-manager/test-upload' 2025-12-30 20:58:25 +03:30
58f695fe95 Merge branch 'Feature/institution-contract/change-account-with-data' 2025-12-30 20:58:12 +03:30
b4ccacd37e Merge branch 'Feature/institution-contract/refactor-creation'
# Conflicts:
#	CompanyManagment.EFCore/Repository/InstitutionContractRepository.cs
2025-12-30 20:56:34 +03:30
1fef8e355a Merge remote-tracking branch 'origin/master' 2025-12-30 20:54:58 +03:30
SamSys
4d326b1983 reminderSms Change to electronicInstitutionContract 2025-12-30 18:38:08 +03:30
aa37ca4b28 add: enhance deployment status validation in ChangeDeployStatusProject command and include Activities in ProjectDeployBoardList query 2025-12-30 17:19:59 +03:30
45615684ed add: enhance skill calculation in ProjectDeployBoardDetailsQueryHandler and refine query conditions in ProjectDeployBoardListQueryHandler 2025-12-30 15:55:57 +03:30
eb9a3e52fe feat: implement async methods for activating and deactivating personal contracting parties 2025-12-30 13:50:09 +03:30
94955ea1b4 add: update GetProjectDeployBoardDetails method to use ProjectDeployBoardDetailsQuery 2025-12-30 12:54:53 +03:30
16c1ae04a9 remove some injections 2025-12-30 12:44:55 +03:30
656bb49fab add: remove GetByIdAsync method and add Id property to ProjectDeployBoardListItem 2025-12-30 12:30:06 +03:30
cf3f0564f9 feat: remove Excel download endpoint from institution contract controller 2025-12-30 12:13:57 +03:30
fb5b98bf25 add: update UpdateDeployStatus method to archive project phase upon deployment 2025-12-30 11:54:56 +03:30
1e733f3f20 add: implement ChangeDeployStatusProject command and handler for updating project deployment status 2025-12-30 11:10:40 +03:30
d855684cd7 add: implement AutoUpdateDeployStatusCommand for automatic deployment status updates 2025-12-29 12:16:51 +03:30
9e5e8d8e5d refactor: improve code formatting and structure in ProjectController 2025-12-29 11:45:32 +03:30
9eefdd8fd1 add: implement GetProjectDeployBoardDetails endpoint for retrieving project board details 2025-12-29 10:27:23 +03:30
2159901614 add: implement ProjectDeployBoardDetailsQuery and its handler with validation for project phase details 2025-12-27 19:42:13 +03:30
7ce7854091 add: include DeployStatus in ProjectDeployBoardListItem for deployment status tracking 2025-12-27 16:26:07 +03:30
4c638cbdae add: implement DeployStatus and IsArchived properties in ProjectPhase, and create ProjectDeployBoardListQueryHandler for project deployment management 2025-12-27 13:59:37 +03:30
39a5918a11 add: implement ProjectDeployBoardListQueryHandler and initialize GetProjectsListQueryHandler structure 2025-12-24 18:55:01 +03:30
b9e271de1a fix: update OccurredOn property to use DateTime.Now instead of DateTime.UtcNow in event records 2025-12-23 15:00:18 +03:30
e661bc2dcb add: enhance ProjectBoardListQueryHandler and ProjectPhase to manage task section statuses and deployment states 2025-12-23 14:52:41 +03:30
92 changed files with 4710 additions and 1558 deletions

2
.gitignore vendored
View File

@@ -362,3 +362,5 @@ MigrationBackup/
# # Fody - auto-generated XML schema
# FodyWeavers.xsd
.idea
/ServiceHost/appsettings.Development.json
/ServiceHost/appsettings.json

View File

@@ -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,

View File

@@ -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

View File

@@ -75,7 +75,6 @@ public interface IAccountApplication
void CameraLogin(CameraLoginRequest request);
Task<GetPmUserDto> GetPmUserAsync(long accountId);
}
public class CameraLoginRequest

View File

@@ -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

View File

@@ -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();
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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
{

View File

@@ -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

View File

@@ -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

View File

@@ -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
{

View File

@@ -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
{
}

View File

@@ -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);
}

View File

@@ -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; }
}

View File

@@ -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; }
}

View File

@@ -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);
}

View File

@@ -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
{

View File

@@ -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)
//{

View File

@@ -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;
}
}

View File

@@ -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)

View 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();
}
}

View 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}");
}
}
}

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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}",

View File

@@ -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
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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>();

View File

@@ -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();
}
}

View File

@@ -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>ÊæÇäÏ ÎÇá? ÈÇÔÏ");
}
}

View File

@@ -0,0 +1,62 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Domain._Common;
using GozareshgirProgramManager.Domain.ProjectAgg.Repositories;
using GozareshgirProgramManager.Domain.ProjectAgg.Enums;
namespace GozareshgirProgramManager.Application.Modules.Projects.Commands.AutoPendingFullTimeTaskSections;
public record AutoPendingFullTimeTaskSectionsCommand : IBaseCommand;
public class AutoPendingFullTimeTaskSectionsCommandHandler : IBaseCommandHandler<AutoPendingFullTimeTaskSectionsCommand>
{
private readonly ITaskSectionRepository _taskSectionRepository;
private readonly IUnitOfWork _unitOfWork;
public AutoPendingFullTimeTaskSectionsCommandHandler(
ITaskSectionRepository taskSectionRepository,
IUnitOfWork unitOfWork)
{
_taskSectionRepository = taskSectionRepository;
_unitOfWork = unitOfWork;
}
public async Task<OperationResult> Handle(AutoPendingFullTimeTaskSectionsCommand request, CancellationToken cancellationToken)
{
try
{
// تمام سکشن‌هایی که هنوز Pending یا Completed نشده‌اند را دریافت کن
var taskSections = await _taskSectionRepository.GetAllNotCompletedOrPendingIncludeAllAsync(cancellationToken);
foreach (var section in taskSections)
{
var totalSpent = section.GetTotalTimeSpent();
var estimate = section.FinalEstimatedHours;
if (estimate.TotalMinutes <= 0)
continue; // تسک بدون تخمین را نادیده بگیر
if (totalSpent >= estimate)
{
// مهم: وضعیت را مستقل از فعال/غیرفعال بودن فعالیت‌ها PendingForCompletion کنیم
if (section.IsInProgress())
{
// اگر فعالیت فعال دارد، با وضعیت جدید متوقف شود
section.StopWork(TaskSectionStatus.PendingForCompletion, "اتمام خودکار - رسیدن به ۱۰۰٪ زمان تخمینی");
}
else
{
section.UpdateStatus(TaskSectionStatus.PendingForCompletion);
}
}
}
await _unitOfWork.SaveChangesAsync(cancellationToken);
return OperationResult.Success();
}
catch (Exception ex)
{
return OperationResult.Failure($"خطا در در انتظار تکمیل قرار دادن خودکار تسک‌ها: {ex.Message}");
}
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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] }
};

View File

@@ -0,0 +1,41 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Domain._Common;
using GozareshgirProgramManager.Domain.ProjectAgg.Enums;
using GozareshgirProgramManager.Domain.ProjectAgg.Repositories;
namespace GozareshgirProgramManager.Application.Modules.Projects.Commands.ChangeTaskPriority;
/// <summary>
/// Command to change a task priority.
/// </summary>
public record ChangeTaskPriorityCommand(Guid TaskId, TaskPriority Priority) : IBaseCommand;
public class ChangeTaskPriorityCommandHandler : IBaseCommandHandler<ChangeTaskPriorityCommand>
{
private readonly IProjectTaskRepository _taskRepository;
private readonly IUnitOfWork _unitOfWork;
public ChangeTaskPriorityCommandHandler(IProjectTaskRepository taskRepository, IUnitOfWork unitOfWork)
{
_taskRepository = taskRepository;
_unitOfWork = unitOfWork;
}
public async Task<OperationResult> Handle(ChangeTaskPriorityCommand request, CancellationToken cancellationToken)
{
var task = await _taskRepository.GetByIdAsync(request.TaskId, cancellationToken);
if (task is null)
return OperationResult.NotFound("تسک یافت نشد");
// Idempotent: if already same priority, skip extra work
if (task.Priority != request.Priority)
{
task.SetPriority(request.Priority);
}
await _unitOfWork.SaveChangesAsync(cancellationToken);
return OperationResult.Success();
}
}

View File

@@ -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; }
}

View File

@@ -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,24 +296,64 @@ 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);
var initialTime = TimeSpan.FromHours(initData.Hours)
.Add(TimeSpan.FromMinutes(initData.Minutes));
if (initialTime <= TimeSpan.Zero)
{
@@ -145,10 +365,26 @@ public class SetTimeProjectCommandHandler : IBaseCommandHandler<SetTimeProjectCo
section.ClearAdditionalTimes();
// افزودن زمان‌های اضافی
bool hasAdditionalTime = false;
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);
hasAdditionalTime = true;
}
// تغییر status به Incomplete فقط اگر زمان اضافی اضافه شده باشد و در وضعیتی غیر از ReadyToStart باشد
if (hasAdditionalTime && section.Status != TaskSectionStatus.ReadyToStart)
{
// اگر سکشن درحال انجام است، باید متوقف شود قبل از تغییر status
if (section.Status == TaskSectionStatus.InProgress)
{
section.StopWork(TaskSectionStatus.Incomplete);
}
else
{
section.UpdateStatus(TaskSectionStatus.Incomplete);
}
}
}

View File

@@ -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 کاراکتر باشد.");

View File

@@ -89,6 +89,7 @@ public class ProjectSectionDto
public TimeSpan FinalEstimatedHours { get; set; }
public TimeSpan TotalTimeSpent { get; set; }
public double ProgressPercentage { get; set; }
public bool IsCompleted { get; set; }
public bool IsInProgress { get; set; }

View File

@@ -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; } = [];
}

View File

@@ -166,6 +166,7 @@ public static class ProjectMappingExtensions
CreationDate = section.CreationDate,
FinalEstimatedHours = section.FinalEstimatedHours,
TotalTimeSpent = section.GetTotalTimeSpent(),
ProgressPercentage = section.GetProgressPercentage(),
IsCompleted = section.IsCompleted(),
IsInProgress = section.IsInProgress(),
Activities = section.Activities.Select(a => a.ToDto()).ToList(),
@@ -188,6 +189,7 @@ public static class ProjectMappingExtensions
CreationDate = section.CreationDate,
FinalEstimatedHours = section.FinalEstimatedHours,
TotalTimeSpent = section.GetTotalTimeSpent(),
ProgressPercentage = section.GetProgressPercentage(),
IsCompleted = section.IsCompleted(),
IsInProgress = section.IsInProgress()
// No activities or additional times for summary

View File

@@ -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>;

View File

@@ -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;
}
}

View File

@@ -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 حرف باشد.");
}
}

View File

@@ -0,0 +1,8 @@
namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectHierarchySearch;
/// <summary>
/// پوسته‌ی پاسخ برای نتایج جستجوی سراسری
/// </summary>
public record GetProjectSearchResponse(
List<ProjectHierarchySearchResultDto> Results);

View File

@@ -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; }
}

View File

@@ -1,18 +1,28 @@
using GozareshgirProgramManager.Domain.ProjectAgg.Enums;
namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectsList;
public record GetProjectListDto
// Base DTO shared across project, phase, and task
public class GetProjectItemDto
{
public Guid Id { get; init; }
public string Name { get; init; } = string.Empty;
public int Percentage { get; init; }
public ProjectHierarchyLevel Level { get; init; }
public Guid? ParentId { get; init; }
public bool HasFront { get; set; }
public bool HasBackend { get; set; }
public bool HasDesign { get; set; }
public int TotalHours { get; set; }
public int Minutes { get; set; }
public AssignmentStatus Front { get; set; }
public AssignmentStatus Backend { get; set; }
public AssignmentStatus Design { get; set; }
}
// Project DTO (no extra fields; inherits from base)
public class GetProjectDto : GetProjectItemDto
{
}
// Phase DTO (no extra fields; inherits from base)
public class GetPhaseDto : GetProjectItemDto
{
}

View File

@@ -3,6 +3,7 @@ using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Domain.ProjectAgg.Entities;
using GozareshgirProgramManager.Domain.ProjectAgg.Enums;
using Microsoft.EntityFrameworkCore;
using System.Linq;
namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectsList;
@@ -17,278 +18,343 @@ public class GetProjectsListQueryHandler : IBaseQueryHandler<GetProjectsListQuer
public async Task<OperationResult<GetProjectsListResponse>> Handle(GetProjectsListQuery request, CancellationToken cancellationToken)
{
List<GetProjectListDto> projects;
var projects = new List<GetProjectDto>();
var phases = new List<GetPhaseDto>();
var tasks = new List<GetTaskDto>();
switch (request.HierarchyLevel)
{
case ProjectHierarchyLevel.Project:
projects = await GetProjects(request.ParentId, cancellationToken);
await SetSkillFlags(projects, cancellationToken);
break;
case ProjectHierarchyLevel.Phase:
projects = await GetPhases(request.ParentId, cancellationToken);
phases = await GetPhases(request.ParentId, cancellationToken);
await SetSkillFlags(phases, cancellationToken);
break;
case ProjectHierarchyLevel.Task:
projects = await GetTasks(request.ParentId, cancellationToken);
tasks = await GetTasks(request.ParentId, cancellationToken);
// Tasks don't need SetSkillFlags because they have Sections list
break;
default:
return OperationResult<GetProjectsListResponse>.Failure("سطح سلسله مراتب نامعتبر است");
}
await SetSkillFlags(projects, cancellationToken);
var response = new GetProjectsListResponse(projects);
var response = new GetProjectsListResponse(projects, phases, tasks);
return OperationResult<GetProjectsListResponse>.Success(response);
}
private async Task<List<GetProjectListDto>> GetProjects(Guid? parentId, CancellationToken cancellationToken)
private async Task<List<GetProjectDto>> GetProjects(Guid? parentId, CancellationToken cancellationToken)
{
var query = _context.Projects.AsQueryable();
// پروژه‌ها سطح بالا هستند و parentId ندارند، فقط در صورت null بودن parentId نمایش داده می‌شوند
if (parentId.HasValue)
{
return new List<GetProjectListDto>(); // پروژه‌ها parent ندارند
return new List<GetProjectDto>();
}
var projects = await query
var entities = await query
.OrderByDescending(p => p.CreationDate)
.ToListAsync(cancellationToken);
var result = new List<GetProjectListDto>();
foreach (var project in projects)
var result = new List<GetProjectDto>();
foreach (var project in entities)
{
var percentage = await CalculateProjectPercentage(project, cancellationToken);
result.Add(new GetProjectListDto
var (percentage, totalTime) = await CalculateProjectPercentage(project, cancellationToken);
result.Add(new GetProjectDto
{
Id = project.Id,
Name = project.Name,
Level = ProjectHierarchyLevel.Project,
ParentId = null,
Percentage = percentage
Percentage = percentage,
TotalHours = (int)totalTime.TotalHours,
Minutes = totalTime.Minutes,
});
}
return result;
}
private async Task<List<GetProjectListDto>> GetPhases(Guid? parentId, CancellationToken cancellationToken)
private async Task<List<GetPhaseDto>> GetPhases(Guid? parentId, CancellationToken cancellationToken)
{
var query = _context.ProjectPhases.AsQueryable();
if (parentId.HasValue)
{
query = query.Where(x => x.ProjectId == parentId);
}
var phases = await query
var entities = await query
.OrderByDescending(p => p.CreationDate)
.ToListAsync(cancellationToken);
var result = new List<GetProjectListDto>();
foreach (var phase in phases)
var result = new List<GetPhaseDto>();
foreach (var phase in entities)
{
var percentage = await CalculatePhasePercentage(phase, cancellationToken);
result.Add(new GetProjectListDto
var (percentage, totalTime) = await CalculatePhasePercentage(phase, cancellationToken);
result.Add(new GetPhaseDto
{
Id = phase.Id,
Name = phase.Name,
Level = ProjectHierarchyLevel.Phase,
ParentId = phase.ProjectId,
Percentage = percentage
Percentage = percentage,
TotalHours = (int)totalTime.TotalHours,
Minutes = totalTime.Minutes,
});
}
return result;
}
private async Task<List<GetProjectListDto>> GetTasks(Guid? parentId, CancellationToken cancellationToken)
private async Task<List<GetTaskDto>> GetTasks(Guid? parentId, CancellationToken cancellationToken)
{
var query = _context.ProjectTasks.AsQueryable();
if (parentId.HasValue)
{
query = query.Where(x => x.PhaseId == parentId);
}
var tasks = await query
var entities = await query
.OrderByDescending(t => t.CreationDate)
.ToListAsync(cancellationToken);
var result = new List<GetProjectListDto>();
var result = new List<GetTaskDto>();
// دریافت تمام Skills
var allSkills = await _context.Skills
.Select(s => new { s.Id, s.Name })
.ToListAsync(cancellationToken);
foreach (var task in tasks)
foreach (var task in entities)
{
var percentage = await CalculateTaskPercentage(task, cancellationToken);
result.Add(new GetProjectListDto
var (percentage, totalTime) = await CalculateTaskPercentage(task, cancellationToken);
var sections = await _context.TaskSections
.Include(s => s.Activities)
.Include(s => s.Skill)
.Where(s => s.TaskId == task.Id)
.ToListAsync(cancellationToken);
// جمع‌آوری تمام userId های مورد نیاز
var userIds = sections
.Where(s => s.CurrentAssignedUserId > 0)
.Select(s => s.CurrentAssignedUserId)
.Distinct()
.ToList();
// دریافت اطلاعات کاربران
var users = await _context.Users
.Where(u => userIds.Contains(u.Id))
.Select(u => new { u.Id, u.FullName })
.ToDictionaryAsync(u => u.Id, u => u.FullName, cancellationToken);
// محاسبه SpentTime و RemainingTime
var spentTime = TimeSpan.FromTicks(sections.Sum(s => s.Activities.Sum(a => a.GetTimeSpent().Ticks)));
var remainingTime = totalTime - spentTime;
// ساخت section DTOs برای تمام Skills
var sectionDtos = allSkills.Select(skill =>
{
var section = sections.FirstOrDefault(s => s.SkillId == skill.Id);
if (section == null)
{
// اگر section وجود نداشت، یک DTO با وضعیت Unassigned برمی‌گردانیم
return new GetTaskSectionDto
{
Id = Guid.Empty,
SkillName = skill.Name ?? string.Empty,
SpentTime = TimeSpan.Zero,
TotalTime = TimeSpan.Zero,
Percentage = 0,
UserFullName = string.Empty,
AssignmentStatus = AssignmentStatus.Unassigned
};
}
// اگر section وجود داشت
return new GetTaskSectionDto
{
Id = section.Id,
SkillName = skill.Name ?? string.Empty,
SpentTime = TimeSpan.FromTicks(section.Activities.Sum(a => a.GetTimeSpent().Ticks)),
TotalTime = section.FinalEstimatedHours,
Percentage = (int)section.GetProgressPercentage(),
UserFullName = section.CurrentAssignedUserId > 0 && users.ContainsKey(section.CurrentAssignedUserId)
? users[section.CurrentAssignedUserId]
: string.Empty,
AssignmentStatus = GetAssignmentStatus(section)
};
}).ToList();
result.Add(new GetTaskDto
{
Id = task.Id,
Name = task.Name,
Level = ProjectHierarchyLevel.Task,
ParentId = task.PhaseId,
Percentage = percentage
Percentage = percentage,
TotalHours = (int)totalTime.TotalHours,
Minutes = totalTime.Minutes,
SpentTime = spentTime,
RemainingTime = remainingTime,
Sections = sectionDtos,
Priority = task.Priority
});
}
return result;
}
private async Task SetSkillFlags(List<GetProjectListDto> projects, CancellationToken cancellationToken)
private async Task SetSkillFlags<TItem>(List<TItem> items, CancellationToken cancellationToken) where TItem : GetProjectItemDto
{
if (!projects.Any())
if (!items.Any())
return;
var projectIds = projects.Select(x => x.Id).ToList();
var hierarchyLevel = projects.First().Level;
var ids = items.Select(x => x.Id).ToList();
var hierarchyLevel = items.First().Level;
switch (hierarchyLevel)
{
case ProjectHierarchyLevel.Project:
await SetSkillFlagsForProjects(projects, projectIds, cancellationToken);
await SetSkillFlagsForProjects(items, ids, cancellationToken);
break;
case ProjectHierarchyLevel.Phase:
await SetSkillFlagsForPhases(projects, projectIds, cancellationToken);
break;
case ProjectHierarchyLevel.Task:
await SetSkillFlagsForTasks(projects, projectIds, cancellationToken);
await SetSkillFlagsForPhases(items, ids, cancellationToken);
break;
}
}
private async Task SetSkillFlagsForProjects(List<GetProjectListDto> projects, List<Guid> projectIds, CancellationToken cancellationToken)
private async Task SetSkillFlagsForProjects<TItem>(List<TItem> items, List<Guid> projectIds, CancellationToken cancellationToken) where TItem : GetProjectItemDto
{
var projectSections = await _context.ProjectSections
.Include(x => x.Skill)
.Where(s => projectIds.Contains(s.ProjectId))
// For projects: gather all phases, then tasks, then sections
var phases = await _context.ProjectPhases
.Where(ph => projectIds.Contains(ph.ProjectId))
.Select(ph => ph.Id)
.ToListAsync(cancellationToken);
var tasks = await _context.ProjectTasks
.Where(t => phases.Contains(t.PhaseId))
.Select(t => t.Id)
.ToListAsync(cancellationToken);
var sections = await _context.TaskSections
.Include(s => s.Skill)
.Where(s => tasks.Contains(s.TaskId))
.ToListAsync(cancellationToken);
if (!projectSections.Any())
return;
foreach (var project in projects)
foreach (var item in items)
{
var sections = projectSections.Where(s => s.ProjectId == project.Id).ToList();
project.HasBackend = sections.Any(x => x.Skill?.Name == "Backend");
project.HasFront = sections.Any(x => x.Skill?.Name == "Frontend");
project.HasDesign = sections.Any(x => x.Skill?.Name == "UI/UX Design");
var relatedPhases = phases; // used for filtering tasks by project
var relatedTasks = await _context.ProjectTasks
.Where(t => t.PhaseId != Guid.Empty && relatedPhases.Contains(t.PhaseId))
.Select(t => t.Id)
.ToListAsync(cancellationToken);
var itemSections = sections.Where(s => relatedTasks.Contains(s.TaskId));
item.Backend = GetAssignmentStatus(itemSections.FirstOrDefault(x => x.Skill?.Name == "Backend"));
item.Front = GetAssignmentStatus(itemSections.FirstOrDefault(x => x.Skill?.Name == "Frontend"));
item.Design = GetAssignmentStatus(itemSections.FirstOrDefault(x => x.Skill?.Name == "UI/UX Design"));
}
}
private async Task SetSkillFlagsForPhases(List<GetProjectListDto> projects, List<Guid> phaseIds, CancellationToken cancellationToken)
private async Task SetSkillFlagsForPhases<TItem>(List<TItem> items, List<Guid> phaseIds, CancellationToken cancellationToken) where TItem : GetProjectItemDto
{
var phaseSections = await _context.PhaseSections
.Include(x => x.Skill)
.Where(s => phaseIds.Contains(s.PhaseId))
// For phases: gather tasks, then sections
var tasks = await _context.ProjectTasks
.Where(t => phaseIds.Contains(t.PhaseId))
.Select(t => t.Id)
.ToListAsync(cancellationToken);
var sections = await _context.TaskSections
.Include(s => s.Skill)
.Where(s => tasks.Contains(s.TaskId))
.ToListAsync(cancellationToken);
if (!phaseSections.Any())
return;
foreach (var phase in projects)
foreach (var item in items)
{
var sections = phaseSections.Where(s => s.PhaseId == phase.Id).ToList();
phase.HasBackend = sections.Any(x => x.Skill?.Name == "Backend");
phase.HasFront = sections.Any(x => x.Skill?.Name == "Frontend");
phase.HasDesign = sections.Any(x => x.Skill?.Name == "UI/UX Design");
// Filter tasks for this phase
var phaseTaskIds = await _context.ProjectTasks
.Where(t => t.PhaseId == item.Id)
.Select(t => t.Id)
.ToListAsync(cancellationToken);
var itemSections = sections.Where(s => phaseTaskIds.Contains(s.TaskId));
item.Backend = GetAssignmentStatus(itemSections.FirstOrDefault(x => x.Skill?.Name == "Backend"));
item.Front = GetAssignmentStatus(itemSections.FirstOrDefault(x => x.Skill?.Name == "Frontend"));
item.Design = GetAssignmentStatus(itemSections.FirstOrDefault(x => x.Skill?.Name == "UI/UX Design"));
}
}
private async Task SetSkillFlagsForTasks(List<GetProjectListDto> projects, List<Guid> taskIds, CancellationToken cancellationToken)
private async Task<(int Percentage, TimeSpan TotalTime)> CalculateProjectPercentage(Project project, CancellationToken cancellationToken)
{
var taskSections = await _context.TaskSections
.Include(x => x.Skill)
.Where(s => taskIds.Contains(s.TaskId))
.ToListAsync(cancellationToken);
if (!taskSections.Any())
return;
foreach (var task in projects)
{
var sections = taskSections.Where(s => s.TaskId == task.Id).ToList();
task.HasBackend = sections.Any(x => x.Skill?.Name == "Backend");
task.HasFront = sections.Any(x => x.Skill?.Name == "Frontend");
task.HasDesign = sections.Any(x => x.Skill?.Name == "UI/UX Design");
}
}
private async Task<int> CalculateProjectPercentage(Project project, CancellationToken cancellationToken)
{
// گرفتن تمام فازهای پروژه
var phases = await _context.ProjectPhases
.Where(ph => ph.ProjectId == project.Id)
.ToListAsync(cancellationToken);
if (!phases.Any())
return 0;
// محاسبه درصد هر فاز و میانگین‌گیری
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
.Where(t => t.PhaseId == phase.Id)
.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;
return ((int)section.GetProgressPercentage(),section.FinalEstimatedHours);
}
if (totalEstimatedHours <= 0)
return 0;
private static AssignmentStatus GetAssignmentStatus(TaskSection? section)
{
// تعیین تکلیف نشده: section وجود ندارد
if (section == null)
return AssignmentStatus.Unassigned;
// محاسبه کل زمان صرف شده از activities
var totalSpentHours = section.Activities.Sum(a => a.GetTimeSpent().TotalHours);
// بررسی وجود user
bool hasUser = section.CurrentAssignedUserId > 0;
// بررسی وجود time (InitialEstimatedHours بزرگتر از صفر باشد)
bool hasTime = section.InitialEstimatedHours > TimeSpan.Zero;
// محاسبه درصد (حداکثر 100%)
var percentage = (totalSpentHours / totalEstimatedHours) * 100;
return Math.Min((int)Math.Round(percentage), 100);
// تعیین تکلیف شده: هم user و هم time تعیین شده
if (hasUser && hasTime)
return AssignmentStatus.Assigned;
// فقط کاربر تعیین شده: user دارد ولی time ندارد
if (hasUser && !hasTime)
return AssignmentStatus.UserOnly;
// تعیین تکلیف نشده: نه user دارد نه time
return AssignmentStatus.Unassigned;
}
}

View File

@@ -1,5 +1,6 @@
namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectsList;
public record GetProjectsListResponse(
List<GetProjectListDto> Projects);
List<GetProjectDto> Projects,
List<GetPhaseDto> Phases,
List<GetTaskDto> Tasks);

View File

@@ -0,0 +1,32 @@
using GozareshgirProgramManager.Domain.ProjectAgg.Enums;
namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectsList;
public class GetTaskDto
{
public Guid Id { get; init; }
public string Name { get; init; } = string.Empty;
public int Percentage { get; init; }
public ProjectHierarchyLevel Level { get; init; }
public Guid? ParentId { get; init; }
public int TotalHours { get; set; }
public int Minutes { get; set; }
// Task-specific fields
public TimeSpan SpentTime { get; init; }
public TimeSpan RemainingTime { get; init; }
public TaskPriority Priority { get; set; }
public List<GetTaskSectionDto> Sections { get; init; }
}
public class GetTaskSectionDto
{
public Guid Id { get; init; }
public string SkillName { get; init; } = string.Empty;
public TimeSpan SpentTime { get; init; }
public TimeSpan TotalTime { get; init; }
public int Percentage { get; init; }
public string UserFullName{ get; init; } = string.Empty;
public AssignmentStatus AssignmentStatus { get; set; }
}

View File

@@ -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,68 +53,83 @@ 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 .OrderByDescending(x => x.CurrentAssignedUserId == currentUserId)
.ThenBy(x => GetStatusOrder(x.Status))
.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")
var timeSpent = a.GetTimeSpent();
return new
{
Activity = a,
TimeSpent = timeSpent,
TotalSeconds = timeSpent.TotalSeconds,
FormattedTime = timeSpent.ToString(@"hh\:mm")
};
}).ToList();
// ادغام پشت سر هم فعالیت‌های یک کاربر
var mergedHistories = new List<ProjectProgressHistoryDto>();
foreach (var activityData in activityTimeData)
{
var lastHistory = mergedHistories.LastOrDefault();
// اگر آخرین history برای همین کاربر باشد، زمان‌ها را جمع می‌کنیم
if (lastHistory != null && lastHistory.UserId == activityData.Activity.UserId)
{
var totalTimeSpan = lastHistory.WorkedTimeSpan + activityData.TimeSpent;
lastHistory.WorkedTimeSpan = totalTimeSpan;
lastHistory.WorkedTime = totalTimeSpan.ToString(@"hh\:mm");
}
else
{
// در غیر این صورت، یک history جدید اضافه می‌کنیم
mergedHistories.Add(new ProjectProgressHistoryDto()
{
UserId = activityData.Activity.UserId,
IsCurrentUser = activityData.Activity.UserId == currentUserId,
Name = users.GetValueOrDefault(activityData.Activity.UserId, "ناشناس"),
WorkedTime = activityData.FormattedTime,
WorkedTimeSpan = activityData.TimeSpent,
});
}
}
return new ProjectBoardListResponse()
{
Id = x.Id,
PhaseName = x.Task.Phase.Name,
ProjectName = x.Task.Phase.Project.Name,
TaskName = x.Task.Name,
SectionStatus = x.Status,
Progress = new ProjectProgressDto()
{
CompleteSecond = x.FinalEstimatedHours.TotalSeconds,
Percentage = (int)x.GetProgressPercentage(),
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 mergedHistories = new List<ProjectProgressHistoryDto>();
foreach (var activityData in activityTimeData)
{
var lastHistory = mergedHistories.LastOrDefault();
// اگر آخرین history برای همین کاربر باشد، زمان‌ها را جمع می‌کنیم
if (lastHistory != null && lastHistory.UserId == activityData.Activity.UserId)
{
var totalTimeSpan = lastHistory.WorkedTimeSpan + activityData.TimeSpent;
lastHistory.WorkedTimeSpan = totalTimeSpan;
lastHistory.WorkedTime = totalTimeSpan.ToString(@"hh\:mm");
}
else
{
// در غیر این صورت، یک history جدید اضافه می‌کنیم
mergedHistories.Add(new ProjectProgressHistoryDto()
{
UserId = activityData.Activity.UserId,
IsCurrentUser = activityData.Activity.UserId == currentUserId,
Name = users.GetValueOrDefault(activityData.Activity.UserId, "ناشناس"),
WorkedTime = activityData.FormattedTime,
WorkedTimeSpan = activityData.TimeSpent,
});
}
}
return new ProjectBoardListResponse()
{
Id = x.Id,
PhaseName = x.Task.Phase.Name,
ProjectName = x.Task.Phase.Project.Name,
TaskName = x.Task.Name,
SectionStatus = x.Status,
Progress = new ProjectProgressDto()
{
CompleteSecond = x.FinalEstimatedHours.TotalSeconds,
CurrentSecond = activityTimeData.Sum(a => a.TotalSeconds),
Histories = mergedHistories
},
OriginalUser = users.GetValueOrDefault(x.OriginalAssignedUserId, "ناشناس"),
AssignedUser = x.CurrentAssignedUserId == x.OriginalAssignedUserId ? null
: users.GetValueOrDefault(x.CurrentAssignedUserId, "ناشناس"),
SkillName = x.Skill?.Name??"-",
};
}).ToList();
return OperationResult<List<ProjectBoardListResponse>>.Success(result);
}
private static int GetStatusOrder(TaskSectionStatus status)
{
return status switch
{
TaskSectionStatus.InProgress => 0,
TaskSectionStatus.Incomplete => 1,
TaskSectionStatus.NotAssigned => 2,
TaskSectionStatus.ReadyToStart => 2,
TaskSectionStatus.PendingForCompletion => 3,
_ => 99
};
}
}

View File

@@ -18,6 +18,7 @@ public class ProjectBoardListResponse
public class ProjectProgressDto
{
public double CurrentSecond { get; set; }
public int Percentage { get; set; }
public double CompleteSecond { get; set; }
public List<ProjectProgressHistoryDto> Histories { get; set; }
}

View File

@@ -0,0 +1,116 @@
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,
int Percentage);
public record ProjectDeployBoardDetailTaskItem(
string Name,
TimeSpan TotalTimeSpan,
TimeSpan DoneTimeSpan,
int Percentage,
List<ProjectDeployBoardDetailItemSkill> Skills)
: ProjectDeployBoardDetailPhaseItem(Name, TotalTimeSpan, DoneTimeSpan,Percentage);
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 timePercentage = (int)s.GetProgressPercentage();
return new ProjectDeployBoardDetailItemSkill(
originalUserFullName,
skillName,
timePercentage);
}).ToList();
var taskPercentage = (int)Math.Round(skills.Average(x => x.TimePercentage));
return new ProjectDeployBoardDetailTaskItem(
t.Name,
totalTime,
doneTime,
taskPercentage,
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 phasePercentage = tasksRes.Average(x => x.Percentage);
var phaseRes = new ProjectDeployBoardDetailPhaseItem(phase.Name, totalTimeSpan, doneTimeSpan,
(int)phasePercentage);
var res = new ProjectDeployBoardDetailsResponse(phaseRes, tasksRes);
return OperationResult<ProjectDeployBoardDetailsResponse>.Success(res);
}
}

View File

@@ -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("شناسه بخش اصلی نمی‌تواند خالی باشد");
}
}

View File

@@ -0,0 +1,76 @@
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 int Percentage { 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,
Percentage = (int)Math.Round(g.Average(x => x.GetProgressPercentage()))
}).ToList();
var response = new GetProjectsDeployBoardListResponse(list);
return OperationResult<GetProjectsDeployBoardListResponse>.Success(response);
}
}

View File

@@ -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; }
}

View File

@@ -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);
}

View File

@@ -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("سطح معادل نامعتبر است.");
}
}

View File

@@ -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();

View File

@@ -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
}

View File

@@ -246,4 +246,9 @@ public class ProjectTask : ProjectHierarchyNode
}
#endregion
public void ClearTaskSections()
{
_sections.Clear();
}
}

View File

@@ -157,6 +157,27 @@ public class TaskSection : EntityBase<Guid>
return TimeSpan.FromTicks(_activities.Sum(a => a.GetTimeSpent().Ticks));
}
/// <summary>
/// محاسبه درصد پیشرفت بر اساس زمان مصرف شده به تایم برآورد شده
/// اگر وضعیت Completed باشد، همیشه 100 درصد برمی‌گرداند
/// </summary>
public double GetProgressPercentage()
{
// اگر تسک کامل شده، همیشه 100 درصد
if (Status == TaskSectionStatus.Completed)
return 100.0;
// اگر تایم برآورد شده صفر است، درصد صفر است
if (FinalEstimatedHours.TotalHours <= 0)
return 0.0;
var timeSpent = GetTotalTimeSpent();
var percentage = (timeSpent.TotalMinutes / FinalEstimatedHours.TotalMinutes) * 100.0;
// محدود کردن درصد به 100 (در صورتی که زمان مصرف شده بیشتر از تخمین باشد)
return Math.Min(percentage, 100.0);
}
public bool IsCompleted()
{
return Status == TaskSectionStatus.Completed;
@@ -249,7 +270,7 @@ public class TaskSection : EntityBase<Guid>
// متوقف کردن فعالیت با EndDate دقیق شده
activeActivity.StopWorkWithSpecificTime(adjustedEndDate, "متوقف خودکار - بیش از تایم تعیین شده");
UpdateStatus(TaskSectionStatus.Incomplete);
UpdateStatus(TaskSectionStatus.PendingForCompletion);
}
}
}

View File

@@ -0,0 +1,23 @@
namespace GozareshgirProgramManager.Domain.ProjectAgg.Enums;
/// <summary>
/// وضعیت تکلیف دهی برای بخش‌های مختلف پروژه
/// </summary>
public enum AssignmentStatus
{
/// <summary>
/// تعیین تکلیف نشده
/// </summary>
Unassigned = 0,
/// <summary>
/// تعیین تکلیف شده
/// </summary>
Assigned = 1,
/// <summary>
/// فقط کاربر تعیین شده
/// </summary>
UserOnly = 2,
}

View File

@@ -6,5 +6,6 @@ public enum TaskSectionStatus
ReadyToStart = 1, // آماده شروع
InProgress = 2, // درحال انجام
Incomplete = 3, // ناتمام شده
Completed = 4 // تکمیل شده
PendingForCompletion = 4, // در انتظار تکمیل
Completed = 5 // تکمیل شده
}

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -13,4 +13,8 @@ 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);
// جدید: دریافت سکشن‌هایی که هنوز Completed یا PendingForCompletion نشده‌اند با اطلاعات کامل
Task<List<TaskSection>> GetAllNotCompletedOrPendingIncludeAllAsync(CancellationToken cancellationToken);
}

View File

@@ -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);

View File

@@ -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
}
}
}

View File

@@ -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");
}
}
}

View File

@@ -126,7 +126,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 +179,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 +194,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 +246,7 @@ namespace GozareshgirProgramManager.Infrastructure.Migrations
b.HasIndex("SkillId");
b.ToTable("ProjectSections", (string)null);
b.ToTable("ProjectSections");
});
modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.ProjectTask", b =>

View File

@@ -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)

View File

@@ -19,6 +19,7 @@ public class ProjectPhaseRepository : RepositoryBase<Guid, ProjectPhase>, IProje
public Task<ProjectPhase?> GetWithTasksAsync(Guid phaseId)
{
return _context.ProjectPhases
.Include(x=>x.PhaseSections)
.Include(p => p.Tasks)
.ThenInclude(t => t.Sections)
.ThenInclude(s => s.Skill)

View File

@@ -19,6 +19,7 @@ public class TaskSectionRepository:RepositoryBase<Guid,TaskSection>,ITaskSection
{
return await _context.TaskSections
.Include(x => x.Activities)
.Include(x=>x.AdditionalTimes)
.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
}
@@ -45,4 +46,20 @@ public class TaskSectionRepository:RepositoryBase<Guid,TaskSection>,ITaskSection
.Include(x => x.AdditionalTimes)
.ToListAsync(cancellationToken);
}
public async Task<bool> HasUserAnyInProgressSectionAsync(long userId, CancellationToken cancellationToken = default)
{
return await _context.TaskSections
.AnyAsync(x => x.CurrentAssignedUserId == userId && x.Status == TaskSectionStatus.InProgress,
cancellationToken);
}
public Task<List<TaskSection>> GetAllNotCompletedOrPendingIncludeAllAsync(CancellationToken cancellationToken)
{
return _context.TaskSections
.Where(x => x.Status != TaskSectionStatus.Completed && x.Status != TaskSectionStatus.PendingForCompletion)
.Include(x => x.Activities)
.Include(x => x.AdditionalTimes)
.ToListAsync(cancellationToken);
}
}

View File

@@ -43,9 +43,9 @@ public class RepositoryBase<TKey, T> : IRepository<TKey, T> where T : class
return _context.Find<T>(id);
}
public async Task<T?> GetByIdAsync(TKey id)
public async Task<T?> GetByIdAsync(TKey id, CancellationToken cancellationToken = default)
{
return await _context.Set<T>().FindAsync(id);
return await _context.Set<T>().FindAsync([id], cancellationToken);
}
public List<T> Get()

View File

@@ -1,7 +1,10 @@
using System.Runtime.InteropServices;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Application.Modules.Projects.Commands.ApproveTaskSectionCompletion;
using GozareshgirProgramManager.Application.Modules.Projects.Commands.AssignProject;
using GozareshgirProgramManager.Application.Modules.Projects.Commands.AutoStopOverTimeTaskSections;
using GozareshgirProgramManager.Application.Modules.Projects.Commands.AutoUpdateDeployStatus;
using GozareshgirProgramManager.Application.Modules.Projects.Commands.ChangeDeployStatusProject;
using GozareshgirProgramManager.Application.Modules.Projects.Commands.ChangeStatusSection;
using GozareshgirProgramManager.Application.Modules.Projects.Commands.CreateProject;
using GozareshgirProgramManager.Application.Modules.Projects.Commands.DeleteProject;
@@ -12,80 +15,99 @@ using GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectA
using GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectsList;
using GozareshgirProgramManager.Application.Modules.Projects.Queries.ProjectBoardDetail;
using GozareshgirProgramManager.Application.Modules.Projects.Queries.ProjectBoardList;
using GozareshgirProgramManager.Application.Modules.Projects.Queries.ProjectDeployBoardDetail;
using GozareshgirProgramManager.Application.Modules.Projects.Queries.ProjectDeployBoardList;
using GozareshgirProgramManager.Application.Modules.Projects.Queries.ProjectSetTimeDetails;
using GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectHierarchySearch;
using MediatR;
using Microsoft.AspNetCore.Mvc;
using ServiceHost.BaseControllers;
using GozareshgirProgramManager.Application.Modules.Projects.Commands.ChangeTaskPriority;
using GozareshgirProgramManager.Application.Modules.Projects.Commands.AutoPendingFullTimeTaskSections;
namespace ServiceHost.Areas.Admin.Controllers.ProgramManager;
public class ProjectController : ProgramManagerBaseController
{
private readonly IMediator _mediator;
public ProjectController(IMediator mediator)
{
_mediator = mediator;
}
[HttpGet]
public async Task<ActionResult<OperationResult<GetProjectsListResponse>>> Get([FromQuery]GetProjectsListQuery query)
public async Task<ActionResult<OperationResult<GetProjectsListResponse>>> Get(
[FromQuery] GetProjectsListQuery query)
{
var res=await _mediator.Send(query);
var res = await _mediator.Send(query);
return res;
}
[HttpGet("search")]
public async Task<ActionResult<OperationResult<GetProjectSearchResponse>>> Search(
[FromQuery] string search)
{
var searchQuery = new GetProjectSearchQuery(search);
var res = await _mediator.Send(searchQuery);
return res;
}
[HttpPost]
public async Task<ActionResult<OperationResult>> Create([FromBody] CreateProjectCommand command)
{
var res=await _mediator.Send(command);
var res = await _mediator.Send(command);
return res;
}
[HttpPut]
public async Task<ActionResult<OperationResult>> Edit([FromBody] EditProjectCommand command)
{
var res=await _mediator.Send(command);
var res = await _mediator.Send(command);
return res;
}
[HttpDelete]
public async Task<ActionResult<OperationResult>> Delete([FromQuery] DeleteProjectCommand command)
{
var res=await _mediator.Send(command);
var res = await _mediator.Send(command);
return res;
}
[HttpGet("assign")]
public async Task<ActionResult<OperationResult<GetProjectAssignDetailsResponse>>> GetAssignableProjects(GetProjectAssignDetailsQuery query)
public async Task<ActionResult<OperationResult<GetProjectAssignDetailsResponse>>> GetAssignableProjects(
GetProjectAssignDetailsQuery query)
{
var res=await _mediator.Send(query);
var res = await _mediator.Send(query);
return res;
}
[HttpPost("assign")]
public async Task<ActionResult<OperationResult>> Assign(AssignProjectCommand command)
{
var res=await _mediator.Send(command);
var res = await _mediator.Send(command);
return res;
}
[HttpGet("set-time")]
public async Task<ActionResult<OperationResult<ProjectSetTimeResponse>>> GetSetTimeProjectDetails(ProjectSetTimeDetailsQuery query)
public async Task<ActionResult<OperationResult<ProjectSetTimeResponse>>> GetSetTimeProjectDetails(
ProjectSetTimeDetailsQuery query)
{
var res=await _mediator.Send(query);
var res = await _mediator.Send(query);
return res;
}
[HttpPost("set-time")]
public async Task<ActionResult<OperationResult>> SetTimeProject(SetTimeProjectCommand command)
{
var res=await _mediator.Send(command);
var res = await _mediator.Send(command);
return res;
}
[HttpPost("change-status")]
public async Task<ActionResult<OperationResult>> ChangeStatus(ChangeStatusSectionCommand command)
{
var res = await _mediator.Send(command);
var res = await _mediator.Send(command);
return res;
}
@@ -97,14 +119,18 @@ public class ProjectController : ProgramManagerBaseController
}
[HttpGet("board")]
public async Task<ActionResult<OperationResult<List<ProjectBoardListResponse>>>> GetProjectBoard([FromQuery] ProjectBoardListQuery query)
public async Task<ActionResult<OperationResult<List<ProjectBoardListResponse>>>> GetProjectBoard(
[FromQuery] ProjectBoardListQuery query)
{
// اجرای Command برای متوقف کردن تسک‌های overtime قبل از نمایش
await _mediator.Send(new AutoStopOverTimeTaskSectionsCommand());
// سپس تسک‌هایی که به 100% زمان تخمینی رسیده‌اند را به حالت PendingForCompletion ببریم
await _mediator.Send(new AutoPendingFullTimeTaskSectionsCommand());
var res = await _mediator.Send(query);
return res;
}
[HttpGet("board/details")]
public async Task<ActionResult<OperationResult<ProjectBoardDetailResponse>>> GetProjectBoardDetails(Guid id)
{
@@ -112,4 +138,43 @@ public class ProjectController : ProgramManagerBaseController
var res = await _mediator.Send(query);
return res;
}
[HttpGet("deploy-board")]
public async Task<ActionResult<OperationResult<GetProjectsDeployBoardListResponse>>> GetProjectDeployBoard()
{
// قبل از دریافت دیتا، وضعیت دیپلوی را بر اساس تکمیل شدن تمام سکشن‌ها به‌روزرسانی می‌کنیم
await _mediator.Send(new AutoUpdateDeployStatusCommand());
var request = new GetProjectDeployBoardListQuery();
return await _mediator.Send(request);
}
[HttpGet("deploy-board/details")]
public async Task<ActionResult<OperationResult<ProjectDeployBoardDetailsResponse>>> GetProjectDeployBoardDetails(Guid id)
{
var query = new ProjectDeployBoardDetailsQuery(id);
var res = await _mediator.Send(query);
return res;
}
[HttpPost("deploy-board/change-status")]
public async Task<ActionResult<OperationResult>> ChangeDeployStatus(ChangeDeployStatusProjectCommand command)
{
var res = await _mediator.Send(command);
return res;
}
[HttpPost("approve-completion")]
public async Task<ActionResult<OperationResult>> ApproveTaskSectionCompletion([FromBody] ApproveTaskSectionCompletionCommand command)
{
var res = await _mediator.Send(command);
return res;
}
[HttpPost("change-priority")]
public async Task<ActionResult<OperationResult>> ChangePriority([FromBody] ChangeTaskPriorityCommand command)
{
var res = await _mediator.Send(command);
return res;
}
}

View File

@@ -48,7 +48,8 @@ public class institutionContractController : AdminBaseController
IPersonalContractingPartyApp contractingPartyApplication, IContactInfoApplication contactInfoApplication,
IAccountApplication accountApplication, IEmployerApplication employerApplication,
IWorkshopApplication workshopApplication, ITemporaryClientRegistrationApplication temporaryClientRegistration,
ITemporaryClientRegistrationApplication clientRegistrationApplication, IHttpClientFactory httpClientFactory)
ITemporaryClientRegistrationApplication clientRegistrationApplication, IHttpClientFactory httpClientFactory
,ILogger<SepehrPaymentGateway> sepehrGatewayLogger)
{
_institutionContractApplication = institutionContractApplication;
_contractingPartyApplication = contractingPartyApplication;
@@ -58,7 +59,7 @@ public class institutionContractController : AdminBaseController
_workshopApplication = workshopApplication;
_temporaryClientRegistration = temporaryClientRegistration;
_clientRegistrationApplication = clientRegistrationApplication;
_paymentGateway = new SepehrPaymentGateway(httpClientFactory);
_paymentGateway = new SepehrPaymentGateway(httpClientFactory, sepehrGatewayLogger);
}
/// <summary>
@@ -234,7 +235,8 @@ public class institutionContractController : AdminBaseController
if (balance == "0")
{
result = _institutionContractApplication.DeActive(id);
if (result.IsSuccedded) result = _institutionContractApplication.DeActiveAllConnections(id);
if (result.IsSuccedded)
result = _institutionContractApplication.DeActiveAllConnections(id);
}
else
{
@@ -331,21 +333,6 @@ public class institutionContractController : AdminBaseController
});
}
/// <summary>
/// دانلود اکسل
/// </summary>
/// <returns></returns>
[HttpGet("excel")]
public IActionResult OnGetDownloadExcel()
{
var institutionContractViewModels =
_institutionContractApplication.NewSearch(new() { IsActiveString = "both", TypeOfContract = "both" });
var bytes = InstitutionContractExcelGenerator.GenerateExcel(new List<InstitutionContractExcelViewModel>());
return File(bytes,
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
$"قرارداد های مالی.xlsx");
}
[HttpPost("create/inquiry")]
public async Task<ActionResult<OperationResult<ContractingPartyTempViewModel>>> CreateInquiry(
[FromBody] InstitutionContractCreationInquiryRequest request)

View File

@@ -79,7 +79,8 @@ namespace ServiceHost.Areas.AdminNew.Pages.Company.AndroidApk
CompanyContext context, AccountContext accountContext, IHttpClientFactory httpClientFactory,
IOptions<AppSettingConfiguration> appSetting,
ITemporaryClientRegistrationApplication clientRegistrationApplication, IOnlinePayment onlinePayment,
IFaceEmbeddingService faceEmbeddingService, IAuthHelper authHelper, IInstitutionContractApplication institutionContractApplication)
IFaceEmbeddingService faceEmbeddingService, IAuthHelper authHelper, IInstitutionContractApplication institutionContractApplication
,ILogger<SepehrPaymentGateway> sepehrGatewayLogger)
{
_application = application;
_rollCallDomainService = rollCallDomainService;
@@ -91,7 +92,7 @@ namespace ServiceHost.Areas.AdminNew.Pages.Company.AndroidApk
_faceEmbeddingService = faceEmbeddingService;
_authHelper = authHelper;
_institutionContractApplication = institutionContractApplication;
_paymentGateway = new SepehrPaymentGateway(httpClientFactory);
_paymentGateway = new SepehrPaymentGateway(httpClientFactory, sepehrGatewayLogger);
}
public void OnGet()

View File

@@ -219,7 +219,7 @@
</div>
<div class="Rtable-cell--content">
<div class="d-flex align-items-center justify-content-end">
<a href="@Url.Page("./Index", "DownloadFile", new { path = @item.FullPath, fileName = @item.FileName })" class="btn-delete1">
<a href="@Url.Page("./Index", "DownloadFileLog", new { path = @item.FullPath, fileName = @item.FileName })" class="btn-delete1">
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5M16.5 12 12 16.5m0 0L7.5 12m4.5 4.5V3" />
</svg>

View File

@@ -1,4 +1,4 @@
using _0_Framework.Application;
using _0_Framework.Application;
using _0_Framework.Infrastructure;
using _0_Framework.InfraStructure;
using backService;
@@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Configuration.UserSecrets;
using System.IO;
namespace ServiceHost.Areas.AdminNew.Pages.Company.FileBackup
{
@@ -68,8 +69,8 @@ namespace ServiceHost.Areas.AdminNew.Pages.Company.FileBackup
{
FileName = Path.GetFileName(x),
FullPath = x,
CreationDate = Path.GetFileName(x).ExtractTimeFromInsurancebackup(),
}).OrderByDescending(x => x.CreationDate).ToList();
CreationDate = new DateTime(),
}).ToList();
#endregion
@@ -89,5 +90,22 @@ namespace ServiceHost.Areas.AdminNew.Pages.Company.FileBackup
byte[] fileContent = System.IO.File.ReadAllBytes(path);
return File(fileContent, "application/zip", fileName);
}
}
public IActionResult OnGetDownloadFileLog(string path, string fileName)
{
if (!System.IO.File.Exists(path))
return NotFound();
var stream = new FileStream(
path,
FileMode.Open,
FileAccess.Read,
FileShare.ReadWrite // 🔑 کلید حل مشکل
);
return File(stream, "text/plain", fileName);
}
}
}

View File

@@ -80,7 +80,7 @@ namespace ServiceHost.Areas.AdminNew.Pages.Company.Ticket
public IActionResult OnGetShowDetailTicketByAdmin(long ticketID)
{
var res = _ticketApplication.GetDetails(ticketID);
res.WorkshopName = _workshopApplication.GetDetails(res.WorkshopId).WorkshopFullName;
res.WorkshopName = _workshopApplication.GetDetails(res.WorkshopId)?.WorkshopFullName??"";
return Partial("DetailTicketModal", res);
}

View File

@@ -1,8 +1,10 @@
using _0_Framework.Application;
using _0_Framework.Application.PaymentGateway;
using CompanyManagment.App.Contracts.FinancialInvoice;
using CompanyManagment.App.Contracts.FinancialStatment;
using CompanyManagment.App.Contracts.FinancilTransaction;
using CompanyManagment.App.Contracts.PaymentTransaction;
using CompanyManagment.App.Contracts.SepehrPaymentGateway;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
@@ -10,20 +12,19 @@ using ServiceHost.BaseControllers;
namespace ServiceHost.Areas.Client.Controllers;
public record CreateFinancialPayRequest(long Id, string BaseUrl);
public class FinancialController : ClientBaseController
{
private readonly IFinancialStatmentApplication _financialStatementApplication;
private readonly IAuthHelper _authHelper;
private readonly IPaymentGateway _paymentGateway;
private readonly IPaymentTransactionApplication _paymentTransactionApplication;
private readonly ISepehrPaymentGatewayService _sepehrPaymentGatewayService;
private readonly IFinancialInvoiceApplication _financialInvoiceApplication;
public FinancialController(IFinancialStatmentApplication financialStatementApplication, IAuthHelper authHelper,IHttpClientFactory httpClientFactory, IPaymentTransactionApplication paymentTransactionApplication,IOptions<AppSettingConfiguration> appSetting)
public FinancialController(IFinancialStatmentApplication financialStatementApplication, IAuthHelper authHelper, ISepehrPaymentGatewayService sepehrPaymentGatewayService, IFinancialInvoiceApplication financialInvoiceApplication)
{
_financialStatementApplication = financialStatementApplication;
_authHelper = authHelper;
_paymentTransactionApplication = paymentTransactionApplication;
_paymentGateway = new AqayePardakhtPaymentGateway(httpClientFactory, appSetting);
_sepehrPaymentGatewayService = sepehrPaymentGatewayService;
_financialInvoiceApplication = financialInvoiceApplication;
}
[HttpGet]
@@ -45,64 +46,28 @@ public class FinancialController : ClientBaseController
return result;
}
/// <summary>
/// ساخت
/// ساخت درگاه پرداخت برای موجودی حساب کلاینت
/// </summary>
/// <param name="id"></param>
/// <param name="baseUrl"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
[HttpPost("CreatePay")]
[AllowAnonymous]
public async Task<ActionResult<OperationResult<string>>> CreatePay([FromForm] CreateFinancialPayRequest request, CancellationToken cancellationToken)
public async Task<ActionResult<OperationResult<string>>> CreatePay([FromForm] CreateFinancialPayRequest request)
{
var op = new OperationResult<string>();
var balanceAmount = await _financialStatementApplication.GetBalanceAmount(request.Id);
if (balanceAmount.Amount<=0)
{
return op.Failed("موجودی حساب شما صفر است");
}
var callbackUrl = Url.Action(
action: "OnGetCallBack",
var gateWayCallBackUrl = Url.Action(
action: "Verify",
controller: "General", // نام کنترلر بدون کلمه‌ی "Controller"
values: null,
protocol: Request.Scheme); // http یا https
// گام 2: ایجاد درگاه پرداخت سپهر
var transactionCommand = new CreatePaymentTransaction()
var gatewayResult = await _financialStatementApplication.CreatePaymentGateWayAndCreateInvoice(request, gateWayCallBackUrl);
if (!gatewayResult.IsSuccedded)
{
Amount = balanceAmount.Amount,
ContractingPartyId = balanceAmount.ContractingPartyId,
CallBackUrl = request.BaseUrl
};
var transaction = await _paymentTransactionApplication.Create(transactionCommand);
if (!transaction.IsSuccedded)
{
return op.Failed(transaction.Message);
return op.Failed(gatewayResult.Message);
}
var command = new CreatePaymentGatewayRequest()
{
CallBackUrl = callbackUrl,
Amount = balanceAmount.Amount/10,
TransactionId = transaction.SendId.ToString(),
};
var gatewayResponse = await _paymentGateway.Create(command, cancellationToken);
if (gatewayResponse.IsSuccess)
{
_ = await _paymentTransactionApplication.SetTransactionId(transaction.SendId, gatewayResponse.Token);
return Redirect(_paymentGateway.GetStartPayUrl(gatewayResponse.Token));
}
if (gatewayResponse.ErrorCode.HasValue)
{
return op.Failed($"خطا در ایجاد درگاه پرداخت: {gatewayResponse.ErrorCode.Value}");
}
return op.Failed("خطا در ایجاد درگاه پرداخت");
// گام 3: بازگشتی به درگاه پرداخت
return Redirect(gatewayResult.Data.PaymentUrl);
}
}

View File

@@ -1,49 +1,25 @@
using _0_Framework.Application;
using _0_Framework.Application.PaymentGateway;
using Company.Domain.BankAgg;
using Company.Domain.PaymentTransactionAgg;
using CompanyManagment.App.Contracts.FinancialStatment;
using CompanyManagment.App.Contracts.FinancilTransaction;
using CompanyManagment.App.Contracts.PaymentCallback;
using CompanyManagment.App.Contracts.PaymentTransaction;
using CompanyManagment.App.Contracts.Workshop;
using CompanyManagment.EFCore.Migrations;
using Microsoft.AspNetCore.Authorization;
using GozareshgirProgramManager.Application._Common.Constants;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using NuGet.Protocol;
using Parbad;
using ServiceHost.BaseControllers;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Net.Http;
using System.Security.Cryptography;
using System.Threading;
using CompanyManagment.App.Contracts.FinancialInvoice;
using CompanyManagment.App.Contracts.InstitutionContract;
using GozareshgirProgramManager.Application._Common.Constants;
using GozareshgirProgramManager.Infrastructure.Persistence.Context;
namespace ServiceHost.Controllers;
public class GeneralController : GeneralBaseController
{
private readonly IPaymentTransactionApplication _paymentTransactionApplication;
private readonly IPaymentGateway _paymentGateway;
private readonly IFinancialStatmentApplication _financialStatmentApplication;
private readonly IFinancialInvoiceApplication _financialInvoiceApplication;
private readonly IInstitutionContractApplication _institutionContractApplication;
private readonly IPaymentCallbackHandler _paymentCallbackHandler;
public GeneralController(IPaymentTransactionApplication paymentTransactionApplication,
IHttpClientFactory clientFactory, IFinancialStatmentApplication financialStatmentApplication,
IFinancialInvoiceApplication financialInvoiceApplication,
IInstitutionContractApplication institutionContractApplication)
IPaymentCallbackHandler paymentCallbackHandler)
{
_paymentTransactionApplication = paymentTransactionApplication;
_paymentGateway = new SepehrPaymentGateway(clientFactory);
_financialStatmentApplication = financialStatmentApplication;
_financialInvoiceApplication = financialInvoiceApplication;
_institutionContractApplication = institutionContractApplication;
_paymentCallbackHandler = paymentCallbackHandler;
}
/// <summary>
@@ -67,15 +43,15 @@ public class GeneralController : GeneralBaseController
});
}
[HttpGet("pm-permissions")]
public IActionResult GetPMPermissions()
{
var permissions = ProgramManagerPermissionCode.GetAllCodes();
return new JsonResult(permissions);
}
// [HttpGet("pm-permissions")]
// public IActionResult GetPMPermissions()
// {
// var permissions = ProgramManagerPermissionCode.GetAllCodes();
// return new JsonResult(permissions);
// }
[HttpGet("/api/callback"), HttpPost("/api/callback")]
public async Task<IActionResult> Verify([FromForm]SepehrGatewayPayResponse payResponse)
public async Task<IActionResult> Verify([FromForm] SepehrGatewayPayResponse payResponse)
{
if (!long.TryParse(payResponse.invoiceid, out var paymentTransactionId))
{
@@ -89,215 +65,29 @@ public class GeneralController : GeneralBaseController
return NotFound("Transaction not found");
}
if (transaction.Status != PaymentTransactionStatus.Pending)
// ایجاد command برای ارسال به PaymentCallbackHandler
var command = new VerifyPaymentCallbackCommand
{
return BadRequest("این تراکنش قبلا پرداخت شده است");
}
if (payResponse.respcode != 0)
{
return await HandleFailedTransaction(transaction);
}
var extraData = JsonConvert.DeserializeObject<IDictionary<string, object>>(payResponse.payload);
extraData.TryGetValue("financialInvoiceId", out var financialInvoiceIdObj);
if (financialInvoiceIdObj == null ||
!long.TryParse(financialInvoiceIdObj.ToString(), out var financialInvoiceId))
{
return BadRequest("فاکتور مالی نامعتبر است");
}
var financialInvoice = _financialInvoiceApplication.GetDetails(financialInvoiceId);
if (financialInvoice == null)
{
return BadRequest("فاکتور مالی نامعتبر است");
}
if (financialInvoice.Status != FinancialInvoiceStatus.Unpaid)
{
return BadRequest("فاکتور مالی نامعتبر است");
}
if (financialInvoice.Amount != transaction.Amount)
{
return await HandleFailedTransaction(transaction);
}
var verifyCommand = new VerifyPaymentGateWayRequest()
{
Amount = transaction.Amount,
TransactionId = payResponse.invoiceid,
DigitalReceipt = payResponse.digitalreceipt
ResponseCode = payResponse.respcode,
InvoiceId = paymentTransactionId,
Payload = payResponse.payload,
Amount = payResponse.amount,
TraceNumber = payResponse.tracenumber,
Rrn = payResponse.rrn,
DigitalReceipt = payResponse.digitalreceipt,
IssuerBank = payResponse.issuerbank,
CardNumber = payResponse.cardnumber
};
var verifyRes = await _paymentGateway.Verify(verifyCommand, CancellationToken.None);
#if DEBUG
verifyRes.IsSuccess = true;
#endif
// پردازش callback در Application Layer
var result = await _paymentCallbackHandler.VerifySepehrPaymentCallback(command, CancellationToken.None);
_financialInvoiceApplication.SetPaid(financialInvoiceId, DateTime.Now);
if (verifyRes.IsSuccess)
if (result.IsSuccedded)
{
var command = new CreateFinancialStatment()
{
ContractingPartyId = transaction.ContractingPartyId,
Deptor = 0,
Creditor = transaction.Amount,
DeptorString = "0",
TypeOfTransaction = "credit",
DescriptionOption = financialInvoice.Description + "شماره فاکتور" + financialInvoice.InvoiceNumber,
Description = "درگاه بانکی",
};
var statementResult = _financialStatmentApplication.CreateFromBankGateway(command);
if (!statementResult.IsSuccedded)
{
return new JsonResult(statementResult);
}
var setSuccessResult = _paymentTransactionApplication.SetSuccess(paymentTransactionId,
payResponse.cardnumber, payResponse.issuerbank, payResponse.rrn.ToString(),
payResponse.digitalreceipt);
if (financialInvoice.Items?.Any(x =>
x.Type is FinancialInvoiceItemType.BuyInstitutionContract
or FinancialInvoiceItemType.BuyInstitutionContractInstallment) ?? false)
{
var financialItems = financialInvoice.Items
.Where(x => x.Type == FinancialInvoiceItemType.BuyInstitutionContract);
foreach (var editFinancialInvoiceItem in financialItems)
{
await _institutionContractApplication.SetPendingWorkflow(editFinancialInvoiceItem.EntityId,
InstitutionContractSigningType.OtpBased);
}
var financialInstallmentItems = financialInvoice.Items
.Where(x => x.Type == FinancialInvoiceItemType.BuyInstitutionContractInstallment);
foreach (var editFinancialInvoiceItem in financialInstallmentItems)
{
var institutionContractId =
await _institutionContractApplication.GetIdByInstallmentId(
editFinancialInvoiceItem.EntityId);
await _institutionContractApplication.SetPendingWorkflow(institutionContractId,
InstitutionContractSigningType.OtpBased);
}
}
if (!setSuccessResult.IsSuccedded)
{
return await HandleFailedTransaction(transaction);
}
return Redirect(BuildCallbackUrl(transaction.CallBackUrl, true, transaction.Id));
}
// در غیر این صورت تراکنش ناموفق است
return await HandleFailedTransaction(transaction);
//var data = JsonConvert.SerializeObject(invoice.AdditionalData);
//var statics =
//JsonConvert.SerializeObject(res);
//await _onlinePayment.CancelAsync(invoice);
//return new JsonResult(new
//{
// data,
// statics
//});
//// Check if the invoice is new, or it's already processed before.
//if (invoice.Status != PaymentFetchResultStatus.ReadyForVerifying)
//{
// // You can also see if the invoice is already verified before.
// var isAlreadyVerified = invoice.IsAlreadyVerified;
// return Content("The payment was not successful.");
//}
//// Note: Save the verifyResult.TransactionCode in your database.
}
[HttpPost("/api/callback3232")]
public async Task<IActionResult> OnGetCallBack(string? transid, string? cardnumber, string? tracking_number,
string bank, string invoice_id, string? status, CancellationToken cancellationToken)
{
if (!long.TryParse(invoice_id, out var paymentTransactionId))
{
return BadRequest("Invalid invoice_id");
}
var transaction = await _paymentTransactionApplication.GetDetails(paymentTransactionId);
if (transaction == null)
{
return NotFound("Transaction not found");
}
if (transaction.Status != PaymentTransactionStatus.Pending)
{
return BadRequest("این تراکنش قبلا پرداخت شده است");
}
// اگر شماره کارت یا شماره پیگیری خالی باشد، تراکنش ناموفق است
if (string.IsNullOrWhiteSpace(cardnumber) || string.IsNullOrWhiteSpace(tracking_number))
{
return await HandleFailedTransaction(transaction);
}
var verifyCommand = new VerifyPaymentGateWayRequest()
{
Amount = transaction.Amount / 10,
TransactionId = transid
};
var verifyRes = await _paymentGateway.Verify(verifyCommand, cancellationToken);
// اگر استاتوس 1 باشد، تراکنش موفق است
if (verifyRes.IsSuccess)
{
var command = new CreateFinancialStatment()
{
ContractingPartyId = transaction.ContractingPartyId,
Deptor = 0,
Creditor = transaction.Amount,
DeptorString = "0",
TypeOfTransaction = "credit",
DescriptionOption = "بابت قرارداد مابین (روابط کار)",
Description = "درگاه بانکی",
};
var statementResult = _financialStatmentApplication.CreateFromBankGateway(command);
if (!statementResult.IsSuccedded)
{
return await HandleFailedTransaction(transaction);
}
var setSuccessResult =
_paymentTransactionApplication.SetSuccess(paymentTransactionId, cardnumber, bank, null, null);
if (!setSuccessResult.IsSuccedded)
{
return new JsonResult(setSuccessResult);
}
return Redirect(BuildCallbackUrl(transaction.CallBackUrl, true, transaction.Id));
}
// در غیر این صورت تراکنش ناموفق است
return await HandleFailedTransaction(transaction);
}
private async Task<IActionResult> HandleFailedTransaction(PaymentTransactionDetailsViewModel transaction)
{
var result = _paymentTransactionApplication.SetFailed(transaction.Id);
if (!result.IsSuccedded)
{
return new JsonResult(result);
}
// در صورت ناموفق بودن
return Redirect(BuildCallbackUrl(transaction.CallBackUrl, false, transaction.Id));
}

View File

@@ -32,6 +32,8 @@ using GozareshgirProgramManager.Application.Modules.Users.Commands.CreateUser;
using GozareshgirProgramManager.Infrastructure;
using GozareshgirProgramManager.Infrastructure.Persistence.Seed;
using Microsoft.OpenApi;
using Serilog;
using Serilog.Events;
using ServiceHost.Hubs.ProgramManager;
using ServiceHost.Notifications.ProgramManager;
using ServiceHost.Conventions;
@@ -53,7 +55,30 @@ builder.Services.AddHttpClient("holidayApi", c => c.BaseAddress = new System.Uri
var connectionString = builder.Configuration.GetConnectionString("MesbahDb");
var connectionStringTestDb = builder.Configuration.GetConnectionString("TestDb");
#region Serilog
var logDirectory = @"C:\Logs\Gozareshgir\";
if (!Directory.Exists(logDirectory))
{
Directory.CreateDirectory(logDirectory);
}
// فقط برای فایل از Serilog استفاده می‌شود
// تنظیمات MinimumLevel از appsettings.json خوانده می‌شود
Log.Logger = new LoggerConfiguration()
.Enrich.FromLogContext()
.WriteTo.File(
path: Path.Combine(logDirectory, "gozareshgir_log.txt"),
rollingInterval: RollingInterval.Day,
retainedFileCountLimit: 30,
shared: true,
outputTemplate:
"{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level}] {Message}{NewLine}{Exception}"
).CreateLogger();
#endregion
builder.Services.AddProgramManagerApplication();
builder.Services.AddProgramManagerInfrastructure(builder.Configuration);
@@ -348,7 +373,25 @@ builder.Services.AddParbad().ConfigureGateways(gateways =>
});
// فقط Serilog برای File استفاده می‌شه، کنسول از لاگر پیش‌فرض ASP.NET استفاده می‌کنه
builder.Host.UseSerilog((context, services, configuration) =>
{
var logConfig = configuration
.ReadFrom.Configuration(context.Configuration)
.ReadFrom.Services(services)
.Enrich.FromLogContext();
logConfig.WriteTo.File(
path: Path.Combine(logDirectory, "gozareshgir_log.txt"),
rollingInterval: RollingInterval.Day,
retainedFileCountLimit: 30,
shared: true,
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level}] {Message}{NewLine}{Exception}"
);
}, writeToProviders: true); // این باعث میشه کنسول پیش‌فرض هم کار کنه
Log.Information("SERILOG STARTED SUCCESSFULLY");
var app = builder.Build();
app.UseCors("AllowSpecificOrigins");

View File

@@ -19,7 +19,7 @@
"sqlDebugging": true,
"dotnetRunMessages": "true",
"nativeDebugging": true,
"applicationUrl": "https://localhost:5004;http://localhost:5003;",
"applicationUrl": "https://localhost:5004;http://localhost:5003;https://192.168.0.117:5006",
"jsWebView2Debugging": false,
"hotReloadEnabled": true
},
@@ -44,7 +44,7 @@
"sqlDebugging": true,
"dotnetRunMessages": "true",
"nativeDebugging": true,
"applicationUrl": "https://localhost:5004;http://localhost:5003;",
"applicationUrl": "https://localhost:5004;http://localhost:5003;https://192.168.0.117:5006;",
"jsWebView2Debugging": false,
"hotReloadEnabled": true
}

View File

@@ -10,6 +10,7 @@
</PropertyGroup>
<PropertyGroup>
<RazorCompileOnBuild>true</RazorCompileOnBuild>
<UserSecretsId>a6049acf-0286-4947-983a-761d06d65f36</UserSecretsId>
</PropertyGroup>
<!--<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
@@ -94,11 +95,16 @@
<PackageReference Include="MongoDB.Driver" Version="3.5.2" />
<PackageReference Include="Parbad.AspNetCore" Version="1.5.0" />
<PackageReference Include="Parbad.Storage.Cache" Version="1.5.0" />
<PackageReference Include="Serilog" Version="4.3.0" />
<PackageReference Include="SocialExplorer.FastDBF" Version="1.0.0" />
<PackageReference Include="System.Data.OleDb" Version="10.0.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.0.1" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
</ItemGroup>
<ItemGroup>
<Folder Include="Storage\Task\" />

View File

@@ -1,71 +0,0 @@
{
"DetailedErrors": true,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"ConnectionStrings": {
//تست
//"MesbahDb": "Data Source=DESKTOP-NUE119G\\MSNEW;Initial Catalog=Mesbah_db;Integrated Security=True"
//server
"MesbahDbServer": "Data Source=171.22.24.15;Initial Catalog=mesbah_db;Persist Security Info=False;User ID=ir_db;Password=R2rNp[170]18[3019]#@ATt;TrustServerCertificate=true;",
//local
"MesbahDb": "Data Source=.;Initial Catalog=mesbah_db;Integrated Security=True;TrustServerCertificate=true;",
//server
//"MesbahDb": "Data Source=185.208.175.186;Initial Catalog=mesbah_db;Persist Security Info=False;User ID=ir_db;Password=R2rNp[170]18[3019]#@ATt;TrustServerCertificate=true;",
//dad-mehr
//"MesbahDb": "Data Source=.;Initial Catalog=teamWork;Integrated Security=True;TrustServerCertificate=true;",
"TestDb": "Data Source=.;Initial Catalog=TestDb;Integrated Security=True;TrustServerCertificate=true;",
//program_manager_db
"ProgramManagerDb": "Data Source=.;Initial Catalog=program_manager_db;Integrated Security=True;TrustServerCertificate=true;",
//"ProgramManagerDb": "Data Source=185.208.175.186;Initial Catalog=program_manager_db;Persist Security Info=False;User ID=ir_db;Password=R2rNp[170]18[3019]#@ATt;TrustServerCertificate=true;"
"ProgramManagerDbServer": "Data Source=171.22.24.15;Initial Catalog=program_manager_db;Persist Security Info=False;User ID=ir_db;Password=R2rNp[170]18[3019]#@ATt;TrustServerCertificate=true;"
//mahan Docker
//"MesbahDb": "Data Source=localhost,5069;Initial Catalog=mesbah_db;User ID=sa;Password=YourPassword123;TrustServerCertificate=True;"
},
"GoogleRecaptchaV3": {
"SiteKey": "6Lfhp_AnAAAAAB79WkrMoHd1k8ir4m8VvfjE7FTH",
"SecretKey": "6Lfhp_AnAAAAANjDDY6DPrbbUQS7k6ZCRmrVP5Lb"
},
"SmsSecrets": {
"ApiKey": "Og5M562igmzJRhQPnq0GdtieYdLgtfikjzxOmeQBPxJjZtyge5Klc046Lfw1mxSa",
"SecretKey": "dadmehr"
},
"Domain": ".dadmehrg.ir",
"MongoDb": {
"ConnectionString": "mongodb://localhost:27017",
"DatabaseName": "Gozareshgir"
},
"SmsSettings": {
"IsTestMode": true,
"TestNumbers": [
"09116967898"
//, "09116067106", "09114221321"
]
},
"InternalApi": {
"Local": "https://localhost:7032",
"Dadmehrg": "https://api.pm.dadmehrg.ir",
"Gozareshgir": "https://api.pm.gozareshgir.ir"
},
"SepehrGateWayTerminalId": 99213700,
"JwtSettings": {
"SecretKey": ">3£>^1UBG@yw)QdhRC3$£:;r8~?qpp^oKK4D3a~8L2>enF;lkgh",
"Issuer": "GozareshgirApp",
"Audience": "GozareshgirUsers",
"ExpirationMinutes": 30
}
}

View File

@@ -1,54 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"ConnectionStrings": {
//"MesbahDb": "Data Source=.\\MSSQLSERVER2019;Initial Catalog=mesbah_db;Persist Security Info=False;User ID=mesbah_db;Password=sa142857$@;"
"MesbahDb": "Data Source=.;Initial Catalog=mesbah_db;Integrated Security=True;TrustServerCertificate=true;",
//dad-mehr
//"MesbahDb": "Data Source=.;Initial Catalog=teamWork;Integrated Security=True;TrustServerCertificate=true;",
//testDb
"TestDb": "Data Source=.;Initial Catalog=TestDb;Integrated Security=True;TrustServerCertificate=true;",
//program_manager_db
"ProgramManagerDb": "Data Source=.;Initial Catalog=program_manager_db;Integrated Security=True;TrustServerCertificate=true;"
},
"BackupOptions": {
"DbName": "mesbah_db",
"DbBackupZipPath": "c://EveryHourBackupList//",
"FastDbBackupZipPath": "c://FastBackupZipList//",
"InsuranceListZipPath": "c://InsuranceListZipPath//"
},
"faceModels": {
"Faces": "c://labels//20//"
},
"AllowedHosts": "*",
//"Domain": ".dad-mehr.ir"
"Domain": ".gozareshgir.ir",
"MongoDb": {
"ConnectionString": "mongodb://localhost:27017",
"DatabaseName": "Gozareshgir"
},
"SmsSettings": {
"IsTestMode": false,
"TestNumbers": []
},
"InternalApi": {
"Local": "https://localhost:7032",
"Dadmehrg": "https://api.pm.dadmehrg.ir",
"Gozareshgir": "https://api.pm.gozareshgir.ir"
},
"SepehrGateWayTerminalId": 99213700,
"JwtSettings": {
"SecretKey": ">3£>^1UBG@yw)QdhRC3$£:;r8~?qpp^oKK4D3a~8L2>enF;lkgh",
"Issuer": "GozareshgirApp",
"Audience": "GozareshgirUsers",
"ExpirationMinutes": 30
}
}