Compare commits

...

60 Commits

Author SHA1 Message Date
18867b4929 set workshopId for FineController.cs 2026-01-13 11:10:48 +03:30
a9df0669c6 add client api FineController.cs 2026-01-13 11:08:03 +03:30
5c75316f40 add fine api client controller 2026-01-13 10:50:53 +03:30
d9c431e20e add project name search for board list 2026-01-13 09:23:53 +03:30
gozareshgir
2746bf69ea Merge branch 'master' into Feature/SmsRepoetApi 2026-01-12 14:38:15 +03:30
gozareshgir
77dbb50512 BlueDeActiveAfterZeroDebt hangfire completed 2026-01-12 14:32:50 +03:30
gozareshgir
1c7e8824c7 DeActiveInstitutionEndOfContract hangfire completed 2026-01-12 13:10:58 +03:30
0eff1b9a66 change default task priority from medium to low 2026-01-12 12:07:43 +03:30
gozareshgir
0d33d79620 unblock hangfire completed 2026-01-11 22:07:58 +03:30
gozareshgir
e4355faffc block and unblock 2026-01-11 21:10:29 +03:30
gozareshgir
577fe5db76 Merge branch 'master' of https://pm.gozareshgir.ir/gozareshgir/OriginalGozareshgir into Feature/SmsRepoetApi 2026-01-11 12:58:36 +03:30
587fa40d81 fix percnetage condition 2026-01-10 10:45:38 +03:30
b741ab9ed2 fix contains no element error for empty skills 2026-01-10 10:34:20 +03:30
b6fde4903a Merge branch 'Feature/institution-contract/sent-to-customer-flag' 2026-01-08 15:03:07 +03:30
0772604432 feat: enhance GetMessagesQuery to include additional time notes in message retrieval 2026-01-08 15:02:43 +03:30
SamSys
ec8333c715 merg from master 2026-01-08 15:00:40 +03:30
SamSys
8aa93e089a legal Action Sms completed 2026-01-08 14:56:33 +03:30
59891d1199 Merge remote-tracking branch 'origin/master' 2026-01-08 14:16:22 +03:30
7cb39b1b92 feat: add UserId filter to ProjectBoardListQuery for enhanced task assignment tracking 2026-01-08 14:16:08 +03:30
SamSys
5580d56874 change logger on program.cs 2026-01-08 14:14:27 +03:30
SamSys
423b49e6e7 Merge branch 'master' of https://github.com/samsyntax24/OriginalGozareshgir 2026-01-08 14:05:30 +03:30
SamSys
0ab3052251 Send Warning and leagal action Message 2026-01-08 14:04:34 +03:30
38027352d6 Merge branch 'Feature/program-manager/priority'
# Conflicts:
#	ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/ProjectBoardList/ProjectBoardListResponse.cs
2026-01-08 14:00:49 +03:30
43562fb49c Merge branch 'Feature/program-manager/chat'
# Conflicts:
#	.gitignore
#	ServiceHost/appsettings.Development.json
#	ServiceHost/appsettings.json
2026-01-08 13:51:06 +03:30
SamSys
5202779d9f changes 2026-01-08 12:18:01 +03:30
7c611825a4 feat: refactor task priority handling to use ProjectTaskPriority across commands, DTOs, and repositories 2026-01-08 12:09:18 +03:30
SamSys
67a85735f0 merge from master 2026-01-08 11:36:18 +03:30
SamSys
bf46dfd1dc Merge branch 'master' of https://github.com/samsyntax24/OriginalGozareshgir 2026-01-08 11:35:10 +03:30
SamSys
a1ed3ad648 logeer change 2026-01-08 11:35:03 +03:30
SamSys
35e6355069 get Warning sms List on new repo 2026-01-08 11:19:25 +03:30
8679abb1e7 feat: enhance ChangeTaskPriorityCommand to support multi-level priority updates for tasks, phases, and projects 2026-01-08 11:18:15 +03:30
6f076bdc77 feat: update application URL in launchSettings and enhance task sorting by priority in ProjectBoardListQueryHandler 2026-01-08 10:46:04 +03:30
SamSys
4de2e12ac5 AmaApiReport 2026-01-07 18:38:12 +03:30
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
SamSys
23b65cfbfe GetSms Report Expand List 2026-01-07 16:59:21 +03:30
572f66f905 feat: implement auto-pending for task sections reaching estimated time 2026-01-07 16:52:50 +03:30
SamSys
48b75d2baa Sms Report get list init 2026-01-07 16:29:03 +03:30
140414b866 feat: add SetIsSent endpoint to update contract send status 2026-01-07 16:23:11 +03:30
4ade9e12a6 feat: add InstitutionContractIsSentFlag to track contract send status 2026-01-07 15:03:21 +03:30
SamSys
63edb33bf5 Sms Report Init 2026-01-07 14:49:44 +03:30
dd7e816767 feat: add SetContractSendFlag method and related request for contract send tracking 2026-01-07 14:46:34 +03:30
1deeff996f feat: implement InstitutionContractSendFlag repository and model for contract send tracking 2026-01-07 14:35:17 +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
582da511c6 feat: add progress percentage calculation to task sections 2026-01-01 19:25:28 +03:30
63 changed files with 4102 additions and 1964 deletions

2
.gitignore vendored
View File

@@ -362,6 +362,8 @@ MigrationBackup/
# # Fody - auto-generated XML schema
# FodyWeavers.xsd
.idea
/ServiceHost/appsettings.Development.json
/ServiceHost/appsettings.json
# Storage folder - ignore all uploaded files, thumbnails, and temporary files
ServiceHost/Storage

View File

@@ -2,6 +2,8 @@
public enum TypeOfSmsSetting
{
//همه انواع پیامک
All = 0,
/// <summary>
/// پیامک
@@ -23,7 +25,7 @@ public enum TypeOfSmsSetting
/// <summary>
/// پیامک
/// هشدار اول
/// هشدار بدهی
/// </summary>
Warning,
@@ -38,4 +40,14 @@ public enum TypeOfSmsSetting
/// </summary>
InstitutionContractConfirm,
/// <summary>
/// ارسال کد تاییدیه قرارداد مالی
/// </summary>
SendInstitutionContractConfirmationCode,
/// <summary>
/// یادآور وظایف
/// </summary>
TaskReminder,
}

View File

@@ -17,4 +17,18 @@ public class ApiResultViewModel
public string DeliveryUnixTime { get; set; }
public string DeliveryColor { get; set; }
public string FullName { get; set; }
}
public class ApiReportDto
{
public int MessageId { get; set; }
public long Mobile { get; set; }
public string SendUnixTime { get; set; }
public string DeliveryState { get; set; }
public string DeliveryUnixTime { get; set; }
public string DeliveryColor { get; set; }
}

View File

@@ -19,6 +19,13 @@ public interface ISmsService
bool SendAccountsInfo(string number,string fullName, string userName);
Task<ApiResultViewModel> GetByMessageId(int messId);
Task<List<ApiResultViewModel>> GetApiResult(string startDate, string endDate);
#region ForApi
Task<List<ApiReportDto>> GetApiReport(string startDate, string endDate);
#endregion
string DeliveryStatus(byte? dv);
string DeliveryColorStatus(byte? dv);
string UnixTimeStampToDateTime(int? unixTimeStamp);

View File

@@ -1,5 +1,6 @@
using _0_Framework.Application;
using _0_Framework.Application.Enums;
using _0_Framework.Application.Sms;
using Company.Domain.ContarctingPartyAgg;
using Company.Domain.InstitutionContractAgg;
@@ -12,19 +13,21 @@ public class JobSchedulerRegistrator
private readonly IBackgroundJobClient _backgroundJobClient;
private readonly SmsReminder _smsReminder;
private readonly IInstitutionContractRepository _institutionContractRepository;
private readonly IInstitutionContractSmsServiceRepository _institutionContractSmsServiceRepository;
private static DateTime? _lastRunCreateTransaction;
private static DateTime? _lastRunSendMonthlySms;
private readonly ISmsService _smsService;
private readonly ILogger<JobSchedulerRegistrator> _logger;
public JobSchedulerRegistrator(SmsReminder smsReminder, IBackgroundJobClient backgroundJobClient, IInstitutionContractRepository institutionContractRepository, ISmsService smsService, ILogger<JobSchedulerRegistrator> logger)
public JobSchedulerRegistrator(SmsReminder smsReminder, IBackgroundJobClient backgroundJobClient, IInstitutionContractRepository institutionContractRepository, ISmsService smsService, ILogger<JobSchedulerRegistrator> logger, IInstitutionContractSmsServiceRepository institutionContractSmsServiceRepository)
{
_smsReminder = smsReminder;
_backgroundJobClient = backgroundJobClient;
_institutionContractRepository = institutionContractRepository;
_smsService = smsService;
_logger = logger;
_institutionContractSmsServiceRepository = institutionContractSmsServiceRepository;
}
public void Register()
@@ -58,17 +61,43 @@ public class JobSchedulerRegistrator
"*/1 * * * *" // هر 1 دقیقه یکبار چک کن
);
//RecurringJob.AddOrUpdate(
// "InstitutionContract.SendWarningSms",
// () => SendWarningSms(),
// "*/1 * * * *" // هر 1 دقیقه یکبار چک کن
//);
RecurringJob.AddOrUpdate(
"InstitutionContract.SendWarningSms",
() => SendWarningSms(),
"*/1 * * * *" // هر 1 دقیقه یکبار چک کن
);
//RecurringJob.AddOrUpdate(
// "InstitutionContract.SendLegalActionSms",
// () => SendLegalActionSms(),
// "*/1 * * * *" // هر 1 دقیقه یکبار چک کن
//);
RecurringJob.AddOrUpdate(
"InstitutionContract.SendLegalActionSms",
() => SendLegalActionSms(),
"*/1 * * * *" // هر 1 دقیقه یکبار چک کن
);
RecurringJob.AddOrUpdate(
"InstitutionContract.Block",
() => Block(),
"*/30 * * * *" // هر 30 دقیقه یکبار چک کن
);
RecurringJob.AddOrUpdate(
"InstitutionContract.UnBlock",
() => UnBlock(),
"*/10 * * * *"
);
RecurringJob.AddOrUpdate(
"InstitutionContract.DeActiveInstitutionEndOfContract",
() => DeActiveInstitutionEndOfContract(),
"*/30 * * * *"
);
RecurringJob.AddOrUpdate(
"InstitutionContract.BlueDeActiveAfterZeroDebt",
() => BlueDeActiveAfterZeroDebt(),
"*/10 * * * *"
);
}
@@ -79,14 +108,14 @@ public class JobSchedulerRegistrator
[DisableConcurrentExecution(timeoutInSeconds: 1200)]
public async System.Threading.Tasks.Task CreateFinancialTransaction()
{
var now =DateTime.Now;
var now = DateTime.Now;
var endOfMonth = now.ToFarsi().FindeEndOfMonth();
var endOfMonthGr = endOfMonth.ToGeorgianDateTime();
_logger.LogInformation("CreateFinancialTransaction job run");
if (now.Date == endOfMonthGr.Date && now.Hour >= 2 && now.Hour < 4 &&
now.Date != _lastRunCreateTransaction?.Date)
{
var month = endOfMonth.Substring(5, 2);
var year = endOfMonth.Substring(0, 4);
var monthName = month.ToFarsiMonthByNumber();
@@ -101,17 +130,17 @@ public class JobSchedulerRegistrator
try
{
await _institutionContractRepository.CreateTransactionForInstitutionContracts(endNewGr, endNewFa, description);
await _institutionContractRepository.CreateTransactionForInstitutionContracts(endNewGr, endNewFa, description);
_lastRunCreateTransaction = now;
Console.WriteLine("CreateTransAction executed");
}
catch (Exception e)
{
await _smsService.Alarm("09114221321", "خطا-ایجاد سند مالی");
}
}
}
@@ -134,7 +163,7 @@ public class JobSchedulerRegistrator
try
{
await _institutionContractRepository.SendMonthlySms(now);
await _institutionContractSmsServiceRepository.SendMonthlySms(now);
_lastRunSendMonthlySms = now;
Console.WriteLine("Send Monthly sms executed");
@@ -156,7 +185,7 @@ public class JobSchedulerRegistrator
public async System.Threading.Tasks.Task SendReminderSms()
{
_logger.LogInformation("SendReminderSms job run");
await _institutionContractRepository.SendReminderSmsForBackgroundTask();
await _institutionContractSmsServiceRepository.SendReminderSmsForBackgroundTask();
}
/// <summary>
@@ -167,7 +196,7 @@ public class JobSchedulerRegistrator
public async System.Threading.Tasks.Task SendBlockSms()
{
_logger.LogInformation("SendBlockSms job run");
await _institutionContractRepository.SendBlockSmsForBackgroundTask();
await _institutionContractSmsServiceRepository.SendBlockSmsForBackgroundTask();
}
@@ -179,7 +208,7 @@ public class JobSchedulerRegistrator
public async System.Threading.Tasks.Task SendInstitutionContractConfirmSms()
{
_logger.LogInformation("SendInstitutionContractConfirmSms job run");
await _institutionContractRepository.SendInstitutionContractConfirmSmsTask();
await _institutionContractSmsServiceRepository.SendInstitutionContractConfirmSmsTask();
}
/// <summary>
@@ -190,14 +219,86 @@ public class JobSchedulerRegistrator
public async System.Threading.Tasks.Task SendWarningSms()
{
_logger.LogInformation("SendWarningSms job run");
await _institutionContractRepository.SendWarningSmsTask();
await _institutionContractSmsServiceRepository.SendWarningOrLegalActionSmsTask(TypeOfSmsSetting.Warning);
}
/// <summary>
/// پیامک اقدام قضایی
/// </summary>
/// <returns></returns>
[DisableConcurrentExecution(timeoutInSeconds: 100)]
public async System.Threading.Tasks.Task SendLegalActionSms()
{
_logger.LogInformation("SendWarningSms job run");
await _institutionContractRepository.SendLegalActionSmsTask();
await _institutionContractSmsServiceRepository.SendWarningOrLegalActionSmsTask(TypeOfSmsSetting.LegalAction);
}
/// <summary>
/// بلاگ سازی
/// </summary>
/// <returns></returns>
[DisableConcurrentExecution(timeoutInSeconds: 100)]
public async System.Threading.Tasks.Task Block()
{
_logger.LogInformation("block job run");
var now = DateTime.Now;
var executeDate = now.ToFarsi().Substring(8, 2);
if (executeDate == "20")
{
if (now.Hour >= 9 && now.Hour < 10)
{
await _institutionContractSmsServiceRepository.Block(now);
}
}
}
/// <summary>
/// آنبلاک
/// </summary>
/// <returns></returns>
[DisableConcurrentExecution(timeoutInSeconds: 100)]
public async System.Threading.Tasks.Task UnBlock()
{
_logger.LogInformation("UnBlock job run");
await _institutionContractSmsServiceRepository.UnBlock();
}
/// <summary>
/// غیر فعال سازی قراداد های پایان یافته
/// </summary>
/// <returns></returns>
[DisableConcurrentExecution(timeoutInSeconds: 100)]
public async System.Threading.Tasks.Task DeActiveInstitutionEndOfContract()
{
_logger.LogInformation("DeActiveInstitutionEndOfContract job run");
var now = DateTime.Now;
var executeDate = now.ToFarsi().Substring(8, 2);
if (executeDate == "01")
{
if (now.Hour >= 9 && now.Hour < 10)
{
await _institutionContractSmsServiceRepository.DeActiveInstitutionEndOfContract(now);
}
}
}
/// <summary>
/// غیرفعال سازس قرارداد های آبی که بدهی ندارند
/// </summary>
/// <returns></returns>
[DisableConcurrentExecution(timeoutInSeconds: 800)]
public async System.Threading.Tasks.Task BlueDeActiveAfterZeroDebt()
{
_logger.LogInformation("BlueDeActiveAfterZeroDebt job run");
await _institutionContractSmsServiceRepository.BlueDeActiveAfterZeroDebt();
}
}

View File

@@ -91,65 +91,7 @@ public interface IInstitutionContractRepository : IRepository<long, InstitutionC
Task<List<InstitutionContractPrintViewModel>> PrintAllAsync(List<long> ids);
#region ReminderSMS
/// <summary>
/// دریافت لیست - ارسال پیامک
/// فراخوانی از سمت بک گراند سرویس
/// </summary>
/// <returns></returns>
Task<bool> SendReminderSmsForBackgroundTask();
/// <summary>
/// ارسال پیامک صورت حساب ماهانه
/// </summary>
/// <param name="now"></param>
/// <returns></returns>
Task SendMonthlySms(DateTime now);
/// <summary>
/// ارسال پیامک مسدودی از طرف بک گراند سرویس
/// </summary>
/// <returns></returns>
Task SendBlockSmsForBackgroundTask();
/// <summary>
/// دریافت لیست واجد شرایط بلاک
/// جهت ارسال پیامک مسدودی
/// </summary>
/// <param name="checkDate"></param>
/// <returns></returns>
Task<List<BlockSmsListData>> GetBlockListData(DateTime checkDate);
/// <summary>
/// ارسال پیامک مسدودی
/// </summary>
/// <param name="smsListData"></param>
/// <param name="typeOfSms"></param>
/// <param name="sendMessStart"></param>
/// <param name="sendMessEnd"></param>
/// <returns></returns>
Task SendBlockSmsToContractingParties(List<BlockSmsListData> smsListData, string typeOfSms,
string sendMessStart, string sendMessEnd);
/// <summary>
///دریافت لیست بدهکارن
/// جهت ارسال پیامک
/// </summary>
/// <returns></returns>
Task<List<SmsListData>> GetSmsListData(DateTime checkDate, TypeOfSmsSetting typeOfSmsSetting);
/// <summary>
/// ارسال پیامک های یاد آور بدهی
/// </summary>
/// <returns></returns>
Task SendReminderSmsToContractingParties(List<SmsListData> smsListData, string typeOfSms, string sendMessStart, string sendMessEnd);
/// <summary>
/// ارسال پیامک یادآور تایید قراداد مالی
/// </summary>
/// <returns></returns>
Task SendInstitutionContractConfirmSmsTask();
#endregion
#region CreateMontlyTransaction
@@ -162,24 +104,12 @@ public interface IInstitutionContractRepository : IRepository<long, InstitutionC
#endregion
#region WarningSms
/// <summary>
/// پیامک های هشدار
/// </summary>
/// <returns></returns>
Task SendWarningSmsTask();
#endregion
#region legalAction
/// <summary>
/// پیامک اقدام قضائی
/// </summary>
/// <returns></returns>
Task SendLegalActionSmsTask();
#endregion
Task<long> GetIdByInstallmentId(long installmentId);
Task<InstitutionContract> GetPreviousContract(long currentInstitutionContractId);

View File

@@ -0,0 +1,145 @@
using _0_Framework.Application.Enums;
using _0_Framework.Domain;
using CompanyManagment.App.Contracts.InstitutionContract;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Company.Domain.InstitutionContractAgg;
public interface IInstitutionContractSmsServiceRepository : IRepository<long, InstitutionContract>
{
#region reminderSMs
/// <summary>
/// ارسال پیامک یادآور تایید قراداد مالی
/// </summary>
/// <returns></returns>
Task SendInstitutionContractConfirmSmsTask();
#endregion
//هشدار و اقدام قضایی
#region WarningOrLegalActionSmsListData
/// <summary>
/// اجرای تسک پیامک هشدار یا اقدام قضایی
/// </summary>
/// <param name="typeOfSmsSetting"></param>
/// <returns></returns>
Task SendWarningOrLegalActionSmsTask(TypeOfSmsSetting typeOfSmsSetting);
/// <summary>
/// دریافت لیست بدهکاران آبی جهت هشدار یا اقدام قضایی
/// </summary>
/// <param name="typeOfSmsSetting"></param>
/// <returns></returns>
Task<List<SmsListData>> GetWarningOrLegalActionSmsListData(TypeOfSmsSetting typeOfSmsSetting);
/// <summary>
/// ارسال پیامک هشدار یا اقدام قضایی
/// </summary>
/// <param name="smsListData"></param>
/// <param name="typeOfSmsSetting"></param>
/// <returns></returns>
Task SendWarningOrLegalActionSms(List<SmsListData> smsListData, TypeOfSmsSetting typeOfSmsSetting);
#endregion
//بلاک - آنبلاک - پیامک بلاک -
// غیر فعال سازی قراداد های پایان یافته
#region Block
/// <summary>
/// ارسال پیامک مسدودی از طرف بک گراند سرویس
/// </summary>
/// <returns></returns>
Task SendBlockSmsForBackgroundTask();
/// <summary>
/// دریافت لیست واجد شرایط بلاک
/// جهت ارسال پیامک مسدودی
/// </summary>
/// <param name="checkDate"></param>
/// <returns></returns>
Task<List<BlockSmsListData>> GetBlockListData(DateTime checkDate);
/// <summary>
/// ارسال پیامک مسدودی
/// </summary>
/// <param name="smsListData"></param>
/// <param name="typeOfSms"></param>
/// <param name="sendMessStart"></param>
/// <param name="sendMessEnd"></param>
/// <returns></returns>
Task SendBlockSmsToContractingParties(List<BlockSmsListData> smsListData, string typeOfSms,
string sendMessStart, string sendMessEnd);
/// <summary>
/// بلاک سازی
/// </summary>
/// <param name="checkDate"></param>
/// <returns></returns>
Task Block(DateTime checkDate);
/// <summary>
/// دریافت لیست بدهکارانی که باید بلاک شوند
/// </summary>
/// <param name="checkDate"></param>
/// <returns></returns>
Task<List<long>> GetToBeBlockList(DateTime checkDate);
/// <summary>
/// آنبلاک
/// </summary>
/// <returns></returns>
Task UnBlock();
/// <summary>
/// غیر فعالسازی قرارداد های پایان یافته
/// </summary>
/// <param name="checkDate"></param>
/// <returns></returns>
Task DeActiveInstitutionEndOfContract(DateTime checkDate);
/// <summary>
/// غیرفعال سازس قرارداد های آبی که بدهی ندارند
/// </summary>
/// <returns></returns>
Task BlueDeActiveAfterZeroDebt();
#endregion
#region ReminderSMS
/// <summary>
/// دریافت لیست - ارسال پیامک
/// فراخوانی از سمت بک گراند سرویس
/// </summary>
/// <returns></returns>
Task<bool> SendReminderSmsForBackgroundTask();
/// <summary>
/// ارسال پیامک صورت حساب ماهانه
/// </summary>
/// <param name="now"></param>
/// <returns></returns>
Task SendMonthlySms(DateTime now);
/// <summary>
///دریافت لیست بدهکارن
/// جهت ارسال پیامک
/// </summary>
/// <returns></returns>
Task<List<SmsListData>> GetSmsListData(DateTime checkDate, TypeOfSmsSetting typeOfSmsSetting);
/// <summary>
/// ارسال پیامک های یاد آور بدهی
/// </summary>
/// <returns></returns>
Task SendReminderSmsToContractingParties(List<SmsListData> smsListData, string typeOfSms, string sendMessStart, string sendMessEnd);
#endregion
}

View File

@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Company.Domain.InstitutionContractSendFlagAgg;
/// <summary>
/// Interface برای Repository مربوط به فلگ ارسال قرارداد
/// </summary>
public interface IInstitutionContractSendFlagRepository
{
/// <summary>
/// ایجاد یک رکورد جدید برای فلگ ارسال قرارداد
/// </summary>
Task Create(InstitutionContractSendFlag flag);
/// <summary>
/// بازیابی فلگ بر اساس شناسه قرارداد
/// </summary>
Task<InstitutionContractSendFlag> GetByContractId(long contractId);
/// <summary>
/// به‌روزرسانی فلگ ارسال
/// </summary>
Task Update(InstitutionContractSendFlag flag);
/// <summary>
/// بررسی اینکه آیا قرارداد ارسال شده است
/// </summary>
Task<bool> IsContractSent(long contractId);
}

View File

@@ -0,0 +1,82 @@
using System;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace Company.Domain.InstitutionContractSendFlagAgg;
/// <summary>
/// نمایندگی فلگ ارسال قرارداد در MongoDB
/// این موجودیت برای ردیابی اینکه آیا قرارداد ارسال شده است استفاده می‌شود
/// </summary>
public class InstitutionContractSendFlag
{
public InstitutionContractSendFlag(long institutionContractId,bool isSent)
{
Id = Guid.NewGuid();
InstitutionContractId = institutionContractId;
IsSent = isSent;
CreatedDate = DateTime.Now;
}
/// <summary>
/// شناسه یکتای MongoDB
/// </summary>
[BsonId]
[BsonRepresentation(BsonType.String)]
public Guid Id { get; set; }
/// <summary>
/// شناسه قرارداد در SQL
/// </summary>
public long InstitutionContractId { get; set; }
/// <summary>
/// آیا قرارداد ارسال شده است
/// </summary>
public bool IsSent { get; set; }
/// <summary>
/// تاریخ و زمان ارسال
/// </summary>
public DateTime? SentDate { get; set; }
/// <summary>
/// تاریخ و زمان ایجاد رکورد
/// </summary>
public DateTime CreatedDate { get; set; }
/// <summary>
/// تاریخ و زمان آخرین به‌روزرسانی
/// </summary>
public DateTime? LastModifiedDate { get; set; }
/// <summary>
/// علامت‌گذاری قرارداد به عنوان ارسال‌شده
/// </summary>
public void MarkAsSent()
{
IsSent = true;
SentDate = DateTime.Now;
LastModifiedDate = DateTime.Now;
}
/// <summary>
/// بازگردانی علامت ارسال
/// </summary>
public void MarkAsNotSent()
{
IsSent = false;
SentDate = null;
LastModifiedDate = DateTime.Now;
}
/// <summary>
/// به‌روزرسانی علامت آخری اصلاح
/// </summary>
public void UpdateLastModified()
{
LastModifiedDate = DateTime.Now;
}
}

View File

@@ -1,10 +1,30 @@
using CompanyManagment.App.Contracts.SmsResult;
using _0_Framework.Domain;
using CompanyManagment.App.Contracts.SmsResult;
using CompanyManagment.App.Contracts.SmsResult.Dto;
using System.Collections.Generic;
using _0_Framework.Domain;
using System.Threading.Tasks;
namespace Company.Domain.SmsResultAgg;
public interface ISmsResultRepository : IRepository<long, SmsResult>
{
#region ForApi
/// <summary>
/// دریافت لیست پیامکها
/// </summary>
/// <param name="command"></param>
/// <returns></returns>
Task<List<SmsReportDto>> GetSmsReportList(SmsReportSearchModel searchModel);
/// <summary>
/// دریافت اکسپند لیست هر تاریخ
/// </summary>
/// <param name="searchModel"></param>
/// <param name="date"></param>
/// <returns></returns>
Task<List<SmsReportListDto>> GetSmsReportExpandList(SmsReportSearchModel searchModel, string date);
#endregion
List<SmsResultViewModel> Search(SmsResultSearchModel searchModel);
}

View File

@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Company.Domain.InstitutionContractSendFlagAgg;
using MongoDB.Driver;
namespace CompanyManagement.Infrastructure.Mongo.InstitutionContractSendFlagRepo;
/// <summary>
/// Repository برای مدیریت فلگ ارسال قرارداد در MongoDB
/// </summary>
public class InstitutionContractSendFlagRepository : IInstitutionContractSendFlagRepository
{
private readonly IMongoCollection<InstitutionContractSendFlag> _collection;
public InstitutionContractSendFlagRepository(IMongoDatabase database)
{
_collection = database.GetCollection<InstitutionContractSendFlag>("InstitutionContractSendFlag");
}
public async Task Create(InstitutionContractSendFlag flag)
{
await _collection.InsertOneAsync(flag);
}
public async Task<InstitutionContractSendFlag> GetByContractId(long contractId)
{
var filter = Builders<InstitutionContractSendFlag>.Filter
.Eq(x => x.InstitutionContractId, contractId);
return await _collection.Find(filter).FirstOrDefaultAsync();
}
public async Task Update(InstitutionContractSendFlag flag)
{
var filter = Builders<InstitutionContractSendFlag>.Filter
.Eq(x => x.InstitutionContractId, flag.InstitutionContractId);
await _collection.ReplaceOneAsync(filter, flag);
}
public async Task<bool> IsContractSent(long contractId)
{
var flag = await GetByContractId(contractId);
return flag != null && flag.IsSent;
}
public async Task Remove(long contractId)
{
var filter = Builders<InstitutionContractSendFlag>.Filter
.Eq(x => x.InstitutionContractId, contractId);
await _collection.DeleteOneAsync(filter);
}
}

View File

@@ -96,6 +96,8 @@ public class GetInstitutionContractListItemsViewModel
/// مبلغ قسط
/// </summary>
public double InstallmentAmount { get; set; }
public bool InstitutionContractIsSentFlag { get; set; }
}
public class InstitutionContractListWorkshop

View File

@@ -148,7 +148,7 @@ public interface IInstitutionContractApplication
/// <param name="id">شناسه قرارداد</param>
/// <returns>نتیجه عملیات</returns>
OperationResult UnSign(long id);
/// <summary>
/// ایجاد حساب کاربری برای طرف قرارداد
/// </summary>
@@ -305,6 +305,14 @@ public interface IInstitutionContractApplication
Task<InstitutionContractDiscountResponse> SetDiscountForCreation(InstitutionContractSetDiscountForCreationRequest request);
Task<InstitutionContractDiscountResponse> ResetDiscountForCreation(InstitutionContractResetDiscountForExtensionRequest request);
Task<OperationResult> CreationComplete(InstitutionContractExtensionCompleteRequest request);
/// <summary>
/// تعیین فلگ ارسال قرارداد در MongoDB
/// اگر فلگ وجود نداشتن‌د ایجاد می‌کند
/// </summary>
/// <param name="request">درخواست تعیین فلگ</param>
/// <returns>نتیجه عملیات</returns>
Task<OperationResult> SetContractSendFlag(SetInstitutionContractSendFlagRequest request);
}
public class CreationSetContractingPartyResponse

View File

@@ -0,0 +1,19 @@
namespace CompanyManagment.App.Contracts.InstitutionContract;
/// <summary>
/// درخواست برای تعیین فلگ ارسال قرارداد
/// </summary>
public class SetInstitutionContractSendFlagRequest
{
/// <summary>
/// شناسه قرارداد
/// </summary>
public long InstitutionContractId { get; set; }
/// <summary>
/// آیا قرارداد ارسال شده است
/// </summary>
public bool IsSent { get; set; }
}

View File

@@ -0,0 +1,15 @@
namespace CompanyManagment.App.Contracts.SmsResult.Dto;
/// <summary>
/// وضعیت ارسال پیامک
/// </summary>
public enum SendStatus
{
All=0,
/// <summary>
/// موفق
/// </summary>
Success,
//ناموفق
Failed,
}

View File

@@ -0,0 +1,54 @@
using System;
namespace CompanyManagment.App.Contracts.SmsResult.Dto;
public class SmsReportDto
{
/// <summary>
/// تاریخ ارسال
/// </summary>
public string SentDate { get; set; }
}
public class SmsReportListDto
{
/// <summary>
/// آی دی
/// </summary>
public long Id { get; set; }
/// <summary>
/// آی دی پیامک در sms.ir
/// </summary>
public int MessageId { get; set; }
/// <summary>
/// وضعیت ارسال
/// </summary>
public string Status { get; set; }
/// <summary>
/// نوع پیامک
/// </summary>
public string TypeOfSms { get; set; }
/// <summary>
/// نام طرف حساب
/// </summary>
public string ContractingPartyName { get; set; }
/// <summary>
/// شماره موبایل
/// </summary>
public string Mobile { get; set; }
/// <summary>
/// ساعت و دقیقه
/// </summary>
public string HourAndMinute { get; set; }
}

View File

@@ -0,0 +1,43 @@
using _0_Framework.Application.Enums;
namespace CompanyManagment.App.Contracts.SmsResult.Dto;
public class SmsReportSearchModel
{
//نوع پیامک
public TypeOfSmsSetting TypeOfSms { get; set; }
/// <summary>
/// وضعیت ارسال پیامک
/// </summary>
public SendStatus SendStatus { get; set; }
/// <summary>
/// شماره موبایل
/// </summary>
public string Mobile { get; set; }
/// <summary>
/// آی دی طرف حساب
/// </summary>
public long ContractingPatyId { get; set; }
/// <summary>
/// سال
/// </summary>
public string Year { get; set; }
/// <summary>
/// ماه
/// </summary>
public string Month { get; set; }
/// <summary>
/// تاریخ شروع
/// </summary>
public string StartDateFa { get; set; }
/// <summary>
/// تاریخ پایان
/// </summary>
public string EndDateFa { get; set; }
}

View File

@@ -1,14 +1,34 @@
using System;
using _0_Framework.Application;
using CompanyManagment.App.Contracts.SmsResult.Dto;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using _0_Framework.Application;
namespace CompanyManagment.App.Contracts.SmsResult;
public interface ISmsResultApplication
{
#region ForApi
/// <summary>
/// دریافت لیست پیامکها
/// </summary>
/// <param name="searchModel"></param>
/// <returns></returns>
Task<List<SmsReportDto>> GetSmsReportList(SmsReportSearchModel searchModel);
/// <summary>
/// دریافت اکسپند لیست هر تاریخ
/// </summary>
/// <param name="searchModel"></param>
/// <param name="date"></param>
/// <returns></returns>
Task<List<SmsReportListDto>> GetSmsReportExpandList(SmsReportSearchModel searchModel, string date);
#endregion
OperationResult Create(CreateSmsResult command);
List<SmsResultViewModel> Search(SmsResultSearchModel searchModel);
}

View File

@@ -19,6 +19,7 @@ using Company.Domain.PaymentTransactionAgg;
using Company.Domain.RepresentativeAgg;
using Company.Domain.RollCallServiceAgg;
using Company.Domain.WorkshopAgg;
using Company.Domain.InstitutionContractSendFlagAgg;
using CompanyManagment.App.Contracts.FinancialInvoice;
using CompanyManagment.App.Contracts.FinancialStatment;
using CompanyManagment.App.Contracts.InstitutionContract;
@@ -51,6 +52,7 @@ public class InstitutionContractApplication : IInstitutionContractApplication
private readonly IPaymentTransactionRepository _paymentTransactionRepository;
private readonly IRollCallServiceRepository _rollCallServiceRepository;
private readonly ISepehrPaymentGatewayService _sepehrPaymentGatewayService;
private readonly IInstitutionContractSendFlagRepository _institutionContractSendFlagRepository;
public InstitutionContractApplication(IInstitutionContractRepository institutionContractRepository,
@@ -62,7 +64,8 @@ public class InstitutionContractApplication : IInstitutionContractApplication
IAccountApplication accountApplication, ISmsService smsService,
IFinancialInvoiceRepository financialInvoiceRepository, IHttpClientFactory httpClientFactory,
IPaymentTransactionRepository paymentTransactionRepository, IRollCallServiceRepository rollCallServiceRepository,
ISepehrPaymentGatewayService sepehrPaymentGatewayService,ILogger<SepehrPaymentGateway> sepehrGatewayLogger)
ISepehrPaymentGatewayService sepehrPaymentGatewayService,ILogger<SepehrPaymentGateway> sepehrGatewayLogger,
IInstitutionContractSendFlagRepository institutionContractSendFlagRepository)
{
_institutionContractRepository = institutionContractRepository;
_contractingPartyRepository = contractingPartyRepository;
@@ -80,6 +83,7 @@ public class InstitutionContractApplication : IInstitutionContractApplication
_rollCallServiceRepository = rollCallServiceRepository;
_sepehrPaymentGatewayService = sepehrPaymentGatewayService;
_paymentGateway = new SepehrPaymentGateway(httpClientFactory,sepehrGatewayLogger);
_institutionContractSendFlagRepository = institutionContractSendFlagRepository;
}
public OperationResult Create(CreateInstitutionContract command)
@@ -894,6 +898,7 @@ public class InstitutionContractApplication : IInstitutionContractApplication
return opration.Succcedded();
}
public void CreateContractingPartyAccount(long contractingPartyid, long accountId)
{
_institutionContractRepository.CreateContractingPartyAccount(contractingPartyid, accountId);
@@ -1820,7 +1825,60 @@ public class InstitutionContractApplication : IInstitutionContractApplication
installments.Add(lastInstallment);
return installments;
}
}
/// <summary>
/// تعیین فلگ ارسال قرارداد
/// اگر فلگ وجود نداشتن‌د ایجاد می‌کند
/// </summary>
public async Task<OperationResult> SetContractSendFlag(SetInstitutionContractSendFlagRequest request)
{
var operationResult = new OperationResult();
try
{
// بازیابی قرارداد از SQL
var contract = _institutionContractRepository.Get(request.InstitutionContractId);
if (contract == null)
return operationResult.Failed("قرارداد مورد نظر یافت نشد");
// بررسی اینکه آیا فلگ در MongoDB وجود دارد
var existingFlag = await _institutionContractSendFlagRepository
.GetByContractId(request.InstitutionContractId);
if (existingFlag != null)
{
// اگر فلگ وجود داشتن‌د، آن را اپدیت کنیم
if (request.IsSent)
{
existingFlag.MarkAsSent();
}
else
{
existingFlag.MarkAsNotSent();
}
existingFlag.UpdateLastModified();
await _institutionContractSendFlagRepository.Update(existingFlag);
}
else
{
// اگر فلگ وجود ندارد، آن را ایجاد کنیم
var newFlag = new InstitutionContractSendFlag(
request.InstitutionContractId,
request.IsSent
);
await _institutionContractSendFlagRepository.Create(newFlag);
}
return operationResult.Succcedded();
}
catch (Exception ex)
{
return operationResult.Failed($"خطا در تعیین فلگ ارسال: {ex.Message}");
}
}
}
#region CustomViewModels

View File

@@ -1,8 +1,10 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using _0_Framework.Application;
using Company.Domain.SmsResultAgg;
using CompanyManagment.App.Contracts.SmsResult;
using CompanyManagment.App.Contracts.SmsResult.Dto;
namespace CompanyManagment.Application;
@@ -15,6 +17,23 @@ public class SmsResultApplication : ISmsResultApplication
_smsResultRepository = smsResultRepository;
}
#region ForApi
public async Task<List<SmsReportDto>> GetSmsReportList(SmsReportSearchModel searchModel)
{
return await _smsResultRepository.GetSmsReportList(searchModel);
}
public async Task<List<SmsReportListDto>> GetSmsReportExpandList(SmsReportSearchModel searchModel, string date)
{
return await _smsResultRepository.GetSmsReportExpandList(searchModel, date);
}
#endregion
public OperationResult Create(CreateSmsResult command)
{
var op = new OperationResult();

View File

@@ -1,13 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using _0_Framework.Application;
using _0_Framework.Application;
using _0_Framework.Application.Enums;
using Company.Domain.InstitutionContractAgg;
using Company.Domain.SmsResultAgg;
using CompanyManagment.App.Contracts.InstitutionContract;
using CompanyManagment.App.Contracts.SmsResult;
using CompanyManagment.EFCore.Repository;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace CompanyManagment.Application;
@@ -15,11 +16,13 @@ public class SmsSettingApplication : ISmsSettingApplication
{
private readonly ISmsSettingsRepository _smsSettingsRepository;
private readonly IInstitutionContractRepository _institutionContractRepository;
private readonly IInstitutionContractSmsServiceRepository _institutionContractSmsServiceRepository;
public SmsSettingApplication(ISmsSettingsRepository smsSettingsRepository, IInstitutionContractRepository institutionContractRepository)
public SmsSettingApplication(ISmsSettingsRepository smsSettingsRepository, IInstitutionContractRepository institutionContractRepository, IInstitutionContractSmsServiceRepository institutionContractSmsServiceRepository)
{
_smsSettingsRepository = smsSettingsRepository;
_institutionContractRepository = institutionContractRepository;
_institutionContractSmsServiceRepository = institutionContractSmsServiceRepository;
}
@@ -116,12 +119,12 @@ public class SmsSettingApplication : ISmsSettingApplication
public async Task<List<SmsListData>> GetSmsListData(TypeOfSmsSetting typeOfSmsSetting)
{
return await _institutionContractRepository.GetSmsListData(DateTime.Now, typeOfSmsSetting);
return await _institutionContractSmsServiceRepository.GetSmsListData(DateTime.Now, typeOfSmsSetting);
}
public async Task<List<BlockSmsListData>> GetBlockSmsListData(TypeOfSmsSetting typeOfSmsSetting)
{
return await _institutionContractRepository.GetBlockListData(DateTime.Now);
return await _institutionContractSmsServiceRepository.GetBlockListData(DateTime.Now);
}
@@ -134,7 +137,7 @@ public class SmsSettingApplication : ISmsSettingApplication
if (command.Any())
{
await _institutionContractRepository.SendReminderSmsToContractingParties(command, typeOfSms, sendMessStart, sendMessEnd);
await _institutionContractSmsServiceRepository.SendReminderSmsToContractingParties(command, typeOfSms, sendMessStart, sendMessEnd);
return op.Succcedded();
}
else
@@ -153,7 +156,7 @@ public class SmsSettingApplication : ISmsSettingApplication
string sendMessEnd = "پایان مسدودی آنی ";
if (command.Any())
{
await _institutionContractRepository.SendBlockSmsToContractingParties(command, typeOfSms, sendMessStart,
await _institutionContractSmsServiceRepository.SendBlockSmsToContractingParties(command, typeOfSms, sendMessStart,
sendMessEnd);
return op.Succcedded();
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,20 @@
using System.Collections.Generic;
using System.Linq;
using _0_Framework.Application;
using _0_Framework.Application;
using _0_Framework.InfraStructure;
using Company.Domain.SmsResultAgg;
using CompanyManagment.App.Contracts.SmsResult;
using CompanyManagment.App.Contracts.SmsResult.Dto;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using _0_Framework.Application.Enums;
using static Microsoft.EntityFrameworkCore.DbLoggerCategory;
namespace CompanyManagment.EFCore.Repository;
public class SmsResultRepository : RepositoryBase<long, SmsResult> , ISmsResultRepository
public class SmsResultRepository : RepositoryBase<long, SmsResult>, ISmsResultRepository
{
private readonly CompanyContext _context;
public SmsResultRepository(CompanyContext context) : base(context)
@@ -15,9 +22,263 @@ public class SmsResultRepository : RepositoryBase<long, SmsResult> , ISmsResultR
_context = context;
}
#region ForApi
public async Task<List<SmsReportDto>> GetSmsReportList(SmsReportSearchModel searchModel)
{
// مرحله 1: همه رکوردها را با projection ساده بگیرید
var rawQuery = await _context.SmsResults
.Select(x => new
{
x.id,
x.ContractingPatyId,
x.Mobile,
x.Status,
x.TypeOfSms,
x.CreationDate,
DateOnly = x.CreationDate.Date // فقط تاریخ بدون ساعت
})
.AsNoTracking()
.ToListAsync(); // اینجا SQL اجرا می‌شود و همه داده‌ها به client می‌آیند
if (searchModel.ContractingPatyId > 0)
{
rawQuery = rawQuery.Where(x => x.ContractingPatyId == searchModel.ContractingPatyId).ToList();
}
if (!string.IsNullOrWhiteSpace(searchModel.Mobile))
{
rawQuery = rawQuery.Where(x => x.Mobile.Contains(searchModel.Mobile)).ToList();
}
if (searchModel.TypeOfSms != TypeOfSmsSetting.All && searchModel.TypeOfSms != TypeOfSmsSetting.Warning)
{
var typeOfSms = "All";
switch (searchModel.TypeOfSms)
{
case TypeOfSmsSetting.InstitutionContractDebtReminder:
typeOfSms = "یادآور بدهی ماهانه";
break;
case TypeOfSmsSetting.MonthlyInstitutionContract:
typeOfSms = "صورت حساب ماهانه";
break;
case TypeOfSmsSetting.BlockContractingParty:
typeOfSms = "اعلام مسدودی طرف حساب";
break;
case TypeOfSmsSetting.LegalAction:
typeOfSms = "اقدام قضایی";
break;
case TypeOfSmsSetting.InstitutionContractConfirm:
typeOfSms = "یادآور تایید قرارداد مالی";
break;
case TypeOfSmsSetting.SendInstitutionContractConfirmationCode:
typeOfSms = "کد تاییدیه قرارداد مالی";
break;
case TypeOfSmsSetting.TaskReminder:
typeOfSms = "یادآور وظایف";
break;
}
rawQuery = rawQuery.Where(x => x.TypeOfSms == typeOfSms).ToList();
}
if (searchModel.TypeOfSms == TypeOfSmsSetting.Warning)
{
rawQuery = rawQuery.Where(x => x.TypeOfSms.Contains("هشدار")).ToList();
}
if (searchModel.SendStatus != SendStatus.All)
{
var status = "All";
switch (searchModel.SendStatus)
{
case SendStatus.Success: status = "موفق";
break;
case SendStatus.Failed: status = "ناموفق";
break;
}
rawQuery = rawQuery.Where(x => x.Status == status).ToList();
}
#region searchByDate
if (!string.IsNullOrWhiteSpace(searchModel.StartDateFa) &&
!string.IsNullOrWhiteSpace(searchModel.EndDateFa))
{
if (searchModel.StartDateFa.TryToGeorgianDateTime(out var startGr) == false ||
searchModel.EndDateFa.TryToGeorgianDateTime(out var endGr) == false)
return new List<SmsReportDto>();
rawQuery = rawQuery.Where(x => x.CreationDate.Date >= startGr.Date && x.CreationDate.Date <= endGr.Date).ToList();
}
else
{
if (!string.IsNullOrWhiteSpace(searchModel.Year) && !string.IsNullOrWhiteSpace(searchModel.Month))
{
var start = searchModel.Year + "/" + searchModel.Month + "/01";
var end = start.FindeEndOfMonth();
var startGr = start.ToGeorgianDateTime();
var endGr = end.ToGeorgianDateTime();
rawQuery = rawQuery.Where(x => x.CreationDate.Date >= startGr.Date && x.CreationDate.Date <= endGr.Date).ToList();
}
else if (!string.IsNullOrWhiteSpace(searchModel.Year) && string.IsNullOrWhiteSpace(searchModel.Month))
{
var start = searchModel.Year + "/01/01";
var findEndOfYear = searchModel.Year + "/12/01";
var end = findEndOfYear.FindeEndOfMonth();
var startGr = start.ToGeorgianDateTime();
var endGr = end.ToGeorgianDateTime();
rawQuery = rawQuery.Where(x => x.CreationDate.Date >= startGr.Date && x.CreationDate.Date <= endGr.Date).ToList();
}
}
#endregion
// مرحله 2: گروه‌بندی و انتخاب آخرین رکورد هر روز روی Client
var grouped = rawQuery
.GroupBy(x => x.DateOnly)
.Select(g => g.OrderByDescending(x => x.CreationDate).First())
.OrderByDescending(x => x.CreationDate)
.ToList();
// مرحله 3: تبدیل به DTO و ToFarsi
var result = grouped.Select(x => new SmsReportDto
{
SentDate = x.CreationDate.ToFarsi()
}).ToList();
return result;
}
public async Task<List<SmsReportListDto>> GetSmsReportExpandList(SmsReportSearchModel searchModel, string date)
{
if(string.IsNullOrWhiteSpace(date))
return new List<SmsReportListDto>();
if (date.TryToGeorgianDateTime(out var searchDate) == false)
return new List<SmsReportListDto>();
var query = await _context.SmsResults.Where(x => x.CreationDate.Date == searchDate.Date)
.Select(x =>
new
{
x.id,
x.MessageId,
x.Status,
x.TypeOfSms,
x.ContractingPartyName,
x.Mobile,
x.ContractingPatyId,
x.InstitutionContractId,
x.CreationDate,
x.CreationDate.Hour,
x.CreationDate.Minute
}).AsNoTracking()
.ToListAsync(); ;
if (searchModel.ContractingPatyId > 0)
{
query = query.Where(x => x.ContractingPatyId == searchModel.ContractingPatyId).ToList();
}
if (!string.IsNullOrWhiteSpace(searchModel.Mobile))
{
query = query.Where(x => x.Mobile.Contains(searchModel.Mobile)).ToList();
}
if (searchModel.TypeOfSms != TypeOfSmsSetting.All && searchModel.TypeOfSms != TypeOfSmsSetting.Warning)
{
var typeOfSms = "All";
switch (searchModel.TypeOfSms)
{
case TypeOfSmsSetting.InstitutionContractDebtReminder:
typeOfSms = "یادآور بدهی ماهانه";
break;
case TypeOfSmsSetting.MonthlyInstitutionContract:
typeOfSms = "صورت حساب ماهانه";
break;
case TypeOfSmsSetting.BlockContractingParty:
typeOfSms = "اعلام مسدودی طرف حساب";
break;
case TypeOfSmsSetting.LegalAction:
typeOfSms = "اقدام قضایی";
break;
case TypeOfSmsSetting.InstitutionContractConfirm:
typeOfSms = "یادآور تایید قرارداد مالی";
break;
case TypeOfSmsSetting.SendInstitutionContractConfirmationCode:
typeOfSms = "کد تاییدیه قرارداد مالی";
break;
case TypeOfSmsSetting.TaskReminder:
typeOfSms = "یادآور وظایف";
break;
}
query = query.Where(x => x.TypeOfSms == typeOfSms).ToList();
}
if (searchModel.TypeOfSms == TypeOfSmsSetting.Warning)
{
query = query.Where(x => x.TypeOfSms.Contains("هشدار")).ToList();
}
if (searchModel.SendStatus != SendStatus.All)
{
var status = "All";
switch (searchModel.SendStatus)
{
case SendStatus.Success:
status = "موفق";
break;
case SendStatus.Failed:
status = "ناموفق";
break;
}
query = query.Where(x => x.Status == status).ToList();
}
if (query.Count == 0)
return new List<SmsReportListDto>();
var result = query.OrderByDescending(x => x.CreationDate.Hour)
.ThenByDescending(x => x.CreationDate.Minute).Select(x =>
new SmsReportListDto()
{
Id = x.id,
MessageId = x.MessageId,
Status = x.Status,
TypeOfSms = x.TypeOfSms,
ContractingPartyName = x.ContractingPartyName,
Mobile = x.Mobile,
HourAndMinute = x.CreationDate.TimeOfDay.ToString(@"hh\:mm"),
}).ToList();
return result;
}
#endregion
public List<App.Contracts.SmsResult.SmsResultViewModel> Search(SmsResultSearchModel searchModel)
{
var query = _context.SmsResults.Select(x => new App.Contracts.SmsResult.SmsResultViewModel()
{
Id = x.id,
@@ -64,7 +325,7 @@ public class SmsResultRepository : RepositoryBase<long, SmsResult> , ISmsResultR
var endGr = end.ToGeorgianDateTime();
query = query.Where(x => x.CreationDate.Date >= startGr.Date && x.CreationDate.Date <= endGr.Date);
}
else if (!string.IsNullOrWhiteSpace(searchModel.Year) && string.IsNullOrWhiteSpace(searchModel.Month))
{
@@ -74,7 +335,7 @@ public class SmsResultRepository : RepositoryBase<long, SmsResult> , ISmsResultR
var startGr = start.ToGeorgianDateTime();
var endGr = end.ToGeorgianDateTime();
query = query.Where(x => x.CreationDate.Date >= startGr.Date && x.CreationDate.Date <= endGr.Date);
}
}
@@ -82,12 +343,12 @@ public class SmsResultRepository : RepositoryBase<long, SmsResult> , ISmsResultR
query = query.OrderByDescending(x => x.CreationDate)
.ThenByDescending(x=>x.CreationDate.Hour).ThenByDescending(x=>x.CreationDate.Minute);
.ThenByDescending(x => x.CreationDate.Hour).ThenByDescending(x => x.CreationDate.Minute);
return query.Skip(searchModel.PageIndex).Take(30).ToList();
}
}

View File

@@ -207,16 +207,11 @@ public class SmsService : ISmsService
}
public async Task<List<ApiResultViewModel>> GetApiResult(string startDate, string endDate)
{
var st = new DateTime(2024, 6, 2);
var ed = new DateTime(2024, 7, 1);
if (!string.IsNullOrWhiteSpace(startDate) && startDate.Length == 10)
{
st = startDate.ToGeorgianDateTime();
}
if (!string.IsNullOrWhiteSpace(endDate) && endDate.Length == 10)
{
ed = endDate.ToGeorgianDateTime();
}
if(startDate.TryToGeorgianDateTime(out var st) == false || endDate.TryToGeorgianDateTime(out var ed) == false)
return new List<ApiResultViewModel>();
var res = new List<ApiResultViewModel>();
Int32 unixTimestamp = (int)st.Subtract(new DateTime(1970, 1, 1)).TotalSeconds;
Int32 unixTimestamp2 = (int)ed.Subtract(new DateTime(1970, 1, 1)).TotalSeconds;
@@ -248,6 +243,44 @@ public class SmsService : ISmsService
return res;
}
public async Task<List<ApiReportDto>> GetApiReport(string startDate, string endDate)
{
if (startDate.TryToGeorgianDateTime(out var st) == false || endDate.TryToGeorgianDateTime(out var ed) == false)
return new List<ApiReportDto>();
var res = new List<ApiReportDto>();
Int32 unixTimestamp = (int)st.Subtract(new DateTime(1970, 1, 1)).TotalSeconds;
Int32 unixTimestamp2 = (int)ed.Subtract(new DateTime(1970, 1, 1)).TotalSeconds;
// int? fromDateUnixTime = null; // unix time - for instance: 1700598600
//int? toDateUnixTime = null; // unix time - for instance: 1703190600
int pageNumber = 2;
int pageSize = 100; // max: 100
SmsIr smsIr = new SmsIr("Og5M562igmzJRhQPnq0GdtieYdLgtfikjzxOmeQBPxJjZtyge5Klc046Lfw1mxSa");
var response = await smsIr.GetArchivedReportAsync(pageNumber, pageSize, unixTimestamp, unixTimestamp2);
MessageReportResult[] messages = response.Data;
foreach (var message in messages)
{
var appendData = new ApiReportDto()
{
MessageId = message.MessageId,
Mobile = message.Mobile,
SendUnixTime = UnixTimeStampToDateTime(message.SendDateTime),
DeliveryState = DeliveryStatus(message.DeliveryState),
DeliveryUnixTime = UnixTimeStampToDateTime(message.DeliveryDateTime),
DeliveryColor = DeliveryColorStatus(message.DeliveryState),
};
res.Add(appendData);
}
return res;
}
public string DeliveryStatus(byte? dv)
{
string mess = "";

View File

@@ -61,6 +61,7 @@ using Company.Domain.HolidayItemAgg;
using Company.Domain.InstitutionContractAgg;
using Company.Domain.InstitutionContractContactInfoAgg;
using Company.Domain.InstitutionContractExtensionTempAgg;
using Company.Domain.InstitutionContractSendFlagAgg;
using Company.Domain.InstitutionPlanAgg;
using Company.Domain.InsuranceAgg;
using Company.Domain.InsuranceEmployeeInfoAgg;
@@ -123,6 +124,7 @@ using Company.Domain.ZoneAgg;
using CompanyManagement.Infrastructure.Excel.SalaryAid;
using CompanyManagement.Infrastructure.Mongo.EmployeeFaceEmbeddingRepo;
using CompanyManagement.Infrastructure.Mongo.InstitutionContractInsertTempRepo;
using CompanyManagement.Infrastructure.Mongo.InstitutionContractSendFlagRepo;
using CompanyManagment.App.Contracts.AdminMonthlyOverview;
using CompanyManagment.App.Contracts.AndroidApkVersion;
using CompanyManagment.App.Contracts.AuthorizedPerson;
@@ -561,6 +563,7 @@ public class PersonalBootstrapper
services.AddTransient<ISmsSettingsRepository, SmsSettingsRepository>();
services.AddTransient<ISmsSettingApplication, SmsSettingApplication>();
services.AddTransient<IInstitutionContractSmsServiceRepository, InstitutionContractSmsServiceRepository>();
#endregion
@@ -658,6 +661,9 @@ public class PersonalBootstrapper
services.AddTransient<ICameraBugReportApplication, CameraBugReportApplication>();
services.AddTransient<ICameraBugReportRepository, CameraBugReportRepository>(); // MongoDB Implementation
// InstitutionContractSendFlag - MongoDB
services.AddTransient<IInstitutionContractSendFlagRepository, InstitutionContractSendFlagRepository>();
services.AddDbContext<CompanyContext>(x => x.UseSqlServer(connectionString));
}
}

View File

@@ -10,7 +10,7 @@ public record AddTaskToPhaseCommand(
Guid PhaseId,
string Name,
string? Description = null,
TaskPriority Priority = TaskPriority.Medium,
ProjectTaskPriority Priority = ProjectTaskPriority.Medium,
int OrderIndex = 0,
DateTime? DueDate = null
) : IBaseCommand;

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,109 @@
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;
public record ChangeTaskPriorityCommand(
Guid Id,
ProjectHierarchyLevel Level,
ProjectTaskPriority Priority
) : IBaseCommand;
public class ChangeTaskPriorityCommandHandler : IBaseCommandHandler<ChangeTaskPriorityCommand>
{
private readonly IProjectTaskRepository _taskRepository;
private readonly IProjectPhaseRepository _phaseRepository;
private readonly IProjectRepository _projectRepository;
private readonly IUnitOfWork _unitOfWork;
public ChangeTaskPriorityCommandHandler(
IProjectTaskRepository taskRepository,
IProjectPhaseRepository phaseRepository,
IProjectRepository projectRepository,
IUnitOfWork unitOfWork)
{
_taskRepository = taskRepository;
_phaseRepository = phaseRepository;
_projectRepository = projectRepository;
_unitOfWork = unitOfWork;
}
public async Task<OperationResult> Handle(ChangeTaskPriorityCommand request, CancellationToken cancellationToken)
{
switch (request.Level)
{
case ProjectHierarchyLevel.Task:
return await HandleTaskLevelAsync(request.Id, request.Priority, cancellationToken);
case ProjectHierarchyLevel.Phase:
return await HandlePhaseLevelAsync(request.Id, request.Priority, cancellationToken);
case ProjectHierarchyLevel.Project:
return await HandleProjectLevelAsync(request.Id, request.Priority, cancellationToken);
default:
return OperationResult.Failure("سطح نامعتبر است");
}
}
// Task-level priority update
private async Task<OperationResult> HandleTaskLevelAsync(Guid taskId, ProjectTaskPriority priority, CancellationToken ct)
{
var task = await _taskRepository.GetByIdAsync(taskId, ct);
if (task is null)
return OperationResult.NotFound("تسک یافت نشد");
if (task.Priority != priority)
{
task.SetPriority(priority);
}
await _unitOfWork.SaveChangesAsync(ct);
return OperationResult.Success();
}
// Phase-level bulk priority update
private async Task<OperationResult> HandlePhaseLevelAsync(Guid phaseId, ProjectTaskPriority priority, CancellationToken ct)
{
var phase = await _phaseRepository.GetWithTasksAsync(phaseId);
if (phase is null)
return OperationResult.NotFound("فاز یافت نشد");
var tasks = phase.Tasks?.ToList() ?? new List<Domain.ProjectAgg.Entities.ProjectTask>();
foreach (var t in tasks)
{
if (t.Priority != priority)
{
t.SetPriority(priority);
}
}
await _unitOfWork.SaveChangesAsync(ct);
return OperationResult.Success();
}
// Project-level bulk priority update across all phases
private async Task<OperationResult> HandleProjectLevelAsync(Guid projectId, ProjectTaskPriority priority, CancellationToken ct)
{
var project = await _projectRepository.GetWithFullHierarchyAsync(projectId);
if (project is null)
return OperationResult.NotFound("پروژه یافت نشد");
var phases = project.Phases?.ToList() ?? new List<Domain.ProjectAgg.Entities.ProjectPhase>();
foreach (var phase in phases)
{
var tasks = phase.Tasks?.ToList() ?? new List<Domain.ProjectAgg.Entities.ProjectTask>();
foreach (var t in tasks)
{
if (t.Priority != priority)
{
t.SetPriority(priority);
}
}
}
await _unitOfWork.SaveChangesAsync(ct);
return OperationResult.Success();
}
}

View File

@@ -352,7 +352,8 @@ public class SetTimeProjectCommandHandler : IBaseCommandHandler<SetTimeProjectCo
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)
{
@@ -364,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).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

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

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

@@ -1,20 +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,47 +18,47 @@ 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, totalTime) = await CalculateProjectPercentage(project, cancellationToken);
result.Add(new GetProjectListDto
result.Add(new GetProjectDto
{
Id = project.Id,
Name = project.Name,
@@ -68,28 +69,24 @@ public class GetProjectsListQueryHandler : IBaseQueryHandler<GetProjectsListQuer
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, totalTime) = await CalculatePhasePercentage(phase, cancellationToken);
result.Add(new GetProjectListDto
result.Add(new GetPhaseDto
{
Id = phase.Id,
Name = phase.Name,
@@ -100,28 +97,87 @@ public class GetProjectsListQueryHandler : IBaseQueryHandler<GetProjectsListQuer
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, totalTime) = await CalculateTaskPercentage(task, cancellationToken);
result.Add(new GetProjectListDto
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,
@@ -129,187 +185,176 @@ public class GetProjectsListQueryHandler : IBaseQueryHandler<GetProjectsListQuer
ParentId = task.PhaseId,
Percentage = percentage,
TotalHours = (int)totalTime.TotalHours,
Minutes = totalTime.Minutes
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");
}
}
private async Task SetSkillFlagsForTasks(List<GetProjectListDto> projects, List<Guid> taskIds, 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");
// 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<(int Percentage, TimeSpan TotalTime)> CalculateProjectPercentage(Project project, CancellationToken cancellationToken)
{
// گرفتن تمام فازهای پروژه
var phases = await _context.ProjectPhases
.Where(ph => ph.ProjectId == project.Id)
.ToListAsync(cancellationToken);
if (!phases.Any())
return (0, TimeSpan.Zero);
// محاسبه درصد هر فاز و میانگین‌گیری
var phasePercentages = new List<int>();
var totalTime = TimeSpan.Zero;
foreach (var phase in phases)
{
var (phasePercentage, phaseTime) = await CalculatePhasePercentage(phase, cancellationToken);
phasePercentages.Add(phasePercentage);
totalTime += phaseTime;
}
var averagePercentage = phasePercentages.Any() ? (int)phasePercentages.Average() : 0;
return (averagePercentage, totalTime);
}
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, TimeSpan.Zero);
// محاسبه درصد هر تسک و میانگین‌گیری
var taskPercentages = new List<int>();
var totalTime = TimeSpan.Zero;
foreach (var task in tasks)
{
var (taskPercentage, taskTime) = await CalculateTaskPercentage(task, cancellationToken);
taskPercentages.Add(taskPercentage);
totalTime += taskTime;
}
var averagePercentage = taskPercentages.Any() ? (int)taskPercentages.Average() : 0;
return (averagePercentage, totalTime);
}
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, TimeSpan.Zero);
// محاسبه درصد هر سکشن و میانگین‌گیری
var sectionPercentages = new List<int>();
var totalTime = TimeSpan.Zero;
foreach (var section in sections)
{
var (sectionPercentage, sectionTime) = CalculateSectionPercentage(section);
sectionPercentages.Add(sectionPercentage);
totalTime += sectionTime;
}
var averagePercentage = sectionPercentages.Any() ? (int)sectionPercentages.Average() : 0;
return (averagePercentage, totalTime);
}
private static (int Percentage, TimeSpan TotalTime) CalculateSectionPercentage(TaskSection section)
{
// محاسبه کل زمان تخمین زده شده (اولیه + اضافی)
var totalEstimatedHours = section.FinalEstimatedHours.TotalHours;
return ((int)section.GetProgressPercentage(),section.FinalEstimatedHours);
}
// محاسبه کل زمان صرف شده از activities
var totalSpentTime = TimeSpan.FromHours(section.Activities.Sum(a => a.GetTimeSpent().TotalHours));
private static AssignmentStatus GetAssignmentStatus(TaskSection? section)
{
// تعیین تکلیف نشده: section وجود ندارد
if (section == null)
return AssignmentStatus.Unassigned;
if (totalEstimatedHours <= 0)
return (0, section.FinalEstimatedHours);
// بررسی وجود user
bool hasUser = section.CurrentAssignedUserId > 0;
// بررسی وجود time (InitialEstimatedHours بزرگتر از صفر باشد)
bool hasTime = section.InitialEstimatedHours > TimeSpan.Zero;
var totalSpentHours = totalSpentTime.TotalHours;
// تعیین تکلیف شده: هم user و هم time تعیین شده
if (hasUser && hasTime)
return AssignmentStatus.Assigned;
// محاسبه درصد (حداکثر 100%)
var percentage = (totalSpentHours / totalEstimatedHours) * 100;
return (Math.Min((int)Math.Round(percentage), 100), section.FinalEstimatedHours);
// فقط کاربر تعیین شده: 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 ProjectTaskPriority 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

@@ -6,5 +6,7 @@ namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.Project
public record ProjectBoardListQuery: IBaseQuery<List<ProjectBoardListResponse>>
{
public long? UserId { get; set; }
public string? SearchText { get; set; }
public TaskSectionStatus? Status { get; set; }
}

View File

@@ -3,7 +3,6 @@ using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Domain.ProjectAgg.Enums;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Query.Internal;
namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.ProjectBoardList;
@@ -24,7 +23,8 @@ public class ProjectBoardListQueryHandler : IBaseQueryHandler<ProjectBoardListQu
var currentUserId = _authHelper.GetCurrentUserId();
var queryable = _programManagerDbContext.TaskSections.AsNoTracking()
.Where(x => x.InitialEstimatedHours > TimeSpan.Zero && x.Status != TaskSectionStatus.Completed)
.Where(x => x.InitialEstimatedHours > TimeSpan.Zero
&& x.Status != TaskSectionStatus.Completed)
.Include(x => x.Task)
.ThenInclude(x => x.Phase)
.ThenInclude(x => x.Project)
@@ -40,10 +40,23 @@ public class ProjectBoardListQueryHandler : IBaseQueryHandler<ProjectBoardListQu
{
queryable = queryable.Where(x => x.Status == request.Status);
}
if (request.UserId is > 0)
{
queryable = queryable.Where(x => x.CurrentAssignedUserId == request.UserId);
}
if (!string.IsNullOrWhiteSpace(request.SearchText))
{
queryable = queryable.Where(x=>x.Task.Name.Contains(request.SearchText)
|| x.Task.Phase.Name.Contains(request.SearchText)
|| x.Task.Phase.Project.Name.Contains(request.SearchText));
}
var data = await queryable.ToListAsync(cancellationToken);
var activityUserIds = data.SelectMany(x => x.Activities).Select(a => a.UserId).Distinct().ToList();
var activityUserIds = data.SelectMany(x => x.Activities)
.Select(a => a.UserId).Distinct().ToList();
var assignedUser = data.Select(x => x.CurrentAssignedUserId)
.Concat(data.Select(x => x.OriginalAssignedUserId)).ToList();
var allUserIds = activityUserIds.Concat(assignedUser).Distinct().ToList();
@@ -54,6 +67,9 @@ public class ProjectBoardListQueryHandler : IBaseQueryHandler<ProjectBoardListQu
var result = data
.OrderByDescending(x => x.CurrentAssignedUserId == currentUserId)
.ThenByDescending(x=>x.Task.Priority)
.ThenBy(x => GetStatusOrder(x.Status))
.Select(x =>
{
// محاسبه یکبار برای هر Activity و Cache کردن نتیجه
@@ -64,7 +80,7 @@ public class ProjectBoardListQueryHandler : IBaseQueryHandler<ProjectBoardListQu
{
Activity = a,
TimeSpent = timeSpent,
TotalSeconds = timeSpent.TotalSeconds,
timeSpent.TotalSeconds,
FormattedTime = timeSpent.ToString(@"hh\:mm")
};
}).ToList();
@@ -95,9 +111,6 @@ public class ProjectBoardListQueryHandler : IBaseQueryHandler<ProjectBoardListQu
});
}
}
mergedHistories = mergedHistories.OrderByDescending(h => h.IsCurrentUser).ToList();
return new ProjectBoardListResponse()
{
Id = x.Id,
@@ -105,9 +118,11 @@ public class ProjectBoardListQueryHandler : IBaseQueryHandler<ProjectBoardListQu
ProjectName = x.Task.Phase.Project.Name,
TaskName = x.Task.Name,
SectionStatus = x.Status,
TaskPriority = x.Task.Priority,
Progress = new ProjectProgressDto()
{
CompleteSecond = x.FinalEstimatedHours.TotalSeconds,
Percentage = (int)x.GetProgressPercentage(),
CurrentSecond = activityTimeData.Sum(a => a.TotalSeconds),
Histories = mergedHistories
},
@@ -117,19 +132,21 @@ public class ProjectBoardListQueryHandler : IBaseQueryHandler<ProjectBoardListQu
SkillName = x.Skill?.Name??"-",
TaskId = x.TaskId
};
})
.OrderByDescending(r =>
{
// اگر AssignedUser null نباشد، بررسی کن که برابر current user هست یا نه
if (r.AssignedUser != null)
{
return users.FirstOrDefault(u => u.Value == r.AssignedUser).Key == currentUserId;
}
// اگر AssignedUser null بود، از OriginalUser بررسی کن
return users.FirstOrDefault(u => u.Value == r.OriginalUser).Key == currentUserId;
})
.ToList();
}).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

@@ -13,13 +13,14 @@ public class ProjectBoardListResponse
public string? AssignedUser { get; set; }
public string OriginalUser { get; set; }
public string SkillName { get; set; }
public ProjectTaskPriority TaskPriority { get; set; }
public Guid TaskId { get; set; }
}
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

@@ -12,14 +12,16 @@ public record ProjectDeployBoardDetailsResponse(
public record ProjectDeployBoardDetailPhaseItem(
string Name,
TimeSpan TotalTimeSpan,
TimeSpan DoneTimeSpan);
TimeSpan DoneTimeSpan,
int Percentage);
public record ProjectDeployBoardDetailTaskItem(
string Name,
TimeSpan TotalTimeSpan,
TimeSpan DoneTimeSpan,
int Percentage,
List<ProjectDeployBoardDetailItemSkill> Skills)
: ProjectDeployBoardDetailPhaseItem(Name, TotalTimeSpan, DoneTimeSpan);
: ProjectDeployBoardDetailPhaseItem(Name, TotalTimeSpan, DoneTimeSpan, Percentage);
public record ProjectDeployBoardDetailItemSkill(string OriginalUserFullName, string SkillName, int TimePercentage);
@@ -71,6 +73,7 @@ public class
var doneTime = t.Sections.Aggregate(TimeSpan.Zero,
(sum, next) => sum.Add(next.GetTotalTimeSpent()));
var skills = t.Sections
.Select(s =>
{
@@ -79,22 +82,30 @@ public class
var skillName = s.Skill?.Name ?? "بدون مهارت";
var totalTimeSpent = s.GetTotalTimeSpent();
var timePercentage = s.FinalEstimatedHours.Ticks > 0
? (int)((totalTimeSpent.Ticks / (double)s.FinalEstimatedHours.Ticks) * 100)
: 0;
var timePercentage = (int)s.GetProgressPercentage();
return new ProjectDeployBoardDetailItemSkill(
originalUserFullName,
skillName,
timePercentage);
}).ToList();
int taskPercentage;
if (skills.Count == 0)
{
taskPercentage = 0;
}
else
{
taskPercentage = (int)Math.Round(skills.Average(x => x.TimePercentage));
}
return new ProjectDeployBoardDetailTaskItem(
t.Name,
totalTime,
doneTime,
taskPercentage,
skills);
}).ToList();
@@ -104,7 +115,10 @@ public class
var doneTimeSpan = tasksRes.Aggregate(TimeSpan.Zero,
(sum, next) => sum.Add(next.DoneTimeSpan));
var phaseRes = new ProjectDeployBoardDetailPhaseItem(phase.Name, totalTimeSpan, doneTimeSpan);
var phasePercentage = tasksRes.Average(x => x.Percentage);
var phaseRes = new ProjectDeployBoardDetailPhaseItem(phase.Name, totalTimeSpan, doneTimeSpan,
(int)phasePercentage);
var res = new ProjectDeployBoardDetailsResponse(phaseRes, tasksRes);

View File

@@ -17,6 +17,7 @@ public record ProjectDeployBoardListItem()
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);
@@ -66,7 +67,8 @@ public class ProjectDeployBoardListQueryHandler:IBaseQueryHandler<GetProjectDepl
.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
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

@@ -1,7 +1,6 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Application.Modules.TaskChat.DTOs;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace GozareshgirProgramManager.Application.Modules.TaskChat.Queries.GetMessages;
@@ -25,6 +24,39 @@ public class GetMessagesQueryHandler : IBaseQueryHandler<GetMessagesQuery, Pagin
_authHelper = authHelper;
}
private List<MessageDto> CreateAdditionalTimeNotes(
IEnumerable<Domain.ProjectAgg.Entities.TaskSectionAdditionalTime> additionalTimes,
Dictionary<long, string> users,
Guid taskId)
{
var notes = new List<MessageDto>();
foreach (var additionalTime in additionalTimes)
{
var addedByUserName = additionalTime.AddedByUserId.HasValue && users.TryGetValue(additionalTime.AddedByUserId.Value, out var user)
? user
: "سیستم";
var noteContent = $"⏱️ زمان اضافی: {additionalTime.Hours.TotalHours.ToString("F2")} ساعت - {(string.IsNullOrWhiteSpace(additionalTime.Reason) ? "بدون علت" : additionalTime.Reason)} - توسط {addedByUserName}";
var noteDto = new MessageDto
{
Id = Guid.NewGuid(),
TaskId = taskId,
SenderUserId = 0,
SenderName = "سیستم",
MessageType = "Note",
TextContent = noteContent,
CreationDate = additionalTime.CreationDate,
IsMine = false
};
notes.Add(noteDto);
}
return notes;
}
public async Task<OperationResult<PaginationResult<MessageDto>>> Handle(GetMessagesQuery request, CancellationToken cancellationToken)
{
var currentUserId = _authHelper.GetCurrentUserId();
@@ -44,36 +76,52 @@ public class GetMessagesQueryHandler : IBaseQueryHandler<GetMessagesQuery, Pagin
.ToListAsync(cancellationToken);
// ✅ گرفتن تمامی کاربران برای نمایش نام کامل فرستنده به جای "کاربر"
// این بخش تمام UserId هایی که در پیام‌ها استفاده شده را جمع‌آوری می‌کند
// و یک Dictionary ایجاد می‌کند که UserId را به FullName نگاشت می‌کند
var senderUserIds = messages.Select(m => m.SenderUserId).Distinct().ToList();
var users = await _context.Users
.Where(u => senderUserIds.Contains(u.Id))
.ToDictionaryAsync(u => u.Id, u => u.FullName, cancellationToken);
// ✅ گرفتن تمامی زمان‌های اضافی (Additional Times) برای نمایش به صورت نوت
// در اینجا تمامی TaskSections مربوط به این تسک را می‌گیریم
// و برای هر کدام تمام AdditionalTimes آن را بارگذاری می‌کنیم
var taskSections = await _context.TaskSections
.Where(ts => ts.TaskId == request.TaskId)
.Include(ts => ts.AdditionalTimes)
.ToListAsync(cancellationToken);
// ✅ تمام زمان‌های اضافی را یکجا بگیر و مرتب کن
var allAdditionalTimes = taskSections
.SelectMany(ts => ts.AdditionalTimes)
.OrderBy(at => at.CreationDate)
.ToList();
var messageDtos = new List<MessageDto>();
// ✅ ابتدا زمان‌های اضافی قبل از اولین پیام را اضافه کن (اگر پیامی وجود داشته باشد)
if (messages.Any())
{
var firstMessageDate = messages.First().CreationDate;
var additionalTimesBeforeFirstMessage = allAdditionalTimes
.Where(at => at.CreationDate < firstMessageDate)
.ToList();
messageDtos.AddRange(CreateAdditionalTimeNotes(additionalTimesBeforeFirstMessage, users, request.TaskId));
}
else
{
// ✅ اگر هیچ پیامی وجود ندارد، همه زمان‌های اضافی را نمایش بده
messageDtos.AddRange(CreateAdditionalTimeNotes(allAdditionalTimes, users, request.TaskId));
}
foreach (var message in messages)
{
// ✅ نام فرستنده را از Dictionary Users بگیر، در صورت عدم وجود "کاربر ناشناس" نمایش بده
var senderName = users.ContainsKey(message.SenderUserId)
? users[message.SenderUserId]
: "کاربر ناشناس";
var senderName = users.GetValueOrDefault(message.SenderUserId, "کاربر ناشناس");
var dto = new MessageDto
{
Id = message.Id,
TaskId = message.TaskId,
SenderUserId = message.SenderUserId,
SenderName = senderName, // ✅ از User واقعی استفاده می‌کنیم
SenderName = senderName,
MessageType = message.MessageType.ToString(),
TextContent = message.TextContent,
ReplyToMessageId = message.ReplyToMessageId,
@@ -88,10 +136,7 @@ public class GetMessagesQueryHandler : IBaseQueryHandler<GetMessagesQuery, Pagin
if (message.ReplyToMessage != null)
{
// ✅ برای پیام‌های Reply نیز نام فرستنده را درست نمایش بده
var replySenderName = users.ContainsKey(message.ReplyToMessage.SenderUserId)
? users[message.ReplyToMessage.SenderUserId]
: "کاربر ناشناس";
var replySenderName = users.GetValueOrDefault(message.ReplyToMessage.SenderUserId, "کاربر ناشناس");
dto.ReplyToMessage = new MessageDto
{
@@ -125,55 +170,31 @@ public class GetMessagesQueryHandler : IBaseQueryHandler<GetMessagesQuery, Pagin
messageDtos.Add(dto);
// ✅ اینجا بخش جدید است: نوت‌های زمان اضافی را بین پیام‌ها اضافه کن
// این بخش تمام AdditionalTimes را که بعد از این پیام اضافه شده‌اند را پیدا می‌کند
var additionalTimesAfterMessage = taskSections
.SelectMany(ts => ts.AdditionalTimes)
.Where(at => at.AddedAt > message.CreationDate) // ✅ تغییر به AddedAt (زمان واقعی اضافه شدن)
.OrderBy(at => at.AddedAt)
.FirstOrDefault();
// ✅ پیدا کردن پیام بعدی (اگر وجود داشته باشد)
var currentIndex = messages.IndexOf(message);
var nextMessage = currentIndex < messages.Count - 1 ? messages[currentIndex + 1] : null;
if (additionalTimesAfterMessage != null)
if (nextMessage != null)
{
// ✅ تمام AdditionalTimes بین این پیام و پیام قبلی را بگیر
var additionalTimesByDate = taskSections
.SelectMany(ts => ts.AdditionalTimes)
.Where(at => at.AddedAt <= message.CreationDate &&
(messageDtos.Count == 1 || at.AddedAt > messageDtos[messageDtos.Count - 2].CreationDate))
.OrderBy(at => at.AddedAt)
// ✅ زمان‌های اضافی بین این پیام و پیام بعدی
var additionalTimesBetween = allAdditionalTimes
.Where(at => at.CreationDate > message.CreationDate && at.CreationDate < nextMessage.CreationDate)
.ToList();
foreach (var additionalTime in additionalTimesByDate)
{
// ✅ نام کاربری که این زمان اضافی را اضافه کرد
var addedByUserName = additionalTime.AddedByUserId.HasValue && users.TryGetValue(additionalTime.AddedByUserId.Value, out var user)
? user
: "سیستم";
messageDtos.AddRange(CreateAdditionalTimeNotes(additionalTimesBetween, users, request.TaskId));
}
else
{
// ✅ این آخرین پیام است، زمان‌های اضافی بعد از آن را اضافه کن
var additionalTimesAfterLastMessage = allAdditionalTimes
.Where(at => at.CreationDate > message.CreationDate)
.ToList();
// ✅ محتوای نوت را با اطلاعات کامل ایجاد کن
// نمایش می‌دهد: مقدار زمان + علت + نام کسی که اضافه کرد
var noteContent = $"⏱️ زمان اضافی: {additionalTime.Hours.TotalHours:F2} ساعت - {(string.IsNullOrWhiteSpace(additionalTime.Reason) ? "بدون علت" : additionalTime.Reason)} - توسط {addedByUserName}";
// ✅ نوت را به عنوان MessageDto خاصی ایجاد کن
var noteDto = new MessageDto
{
Id = Guid.NewGuid(),
TaskId = request.TaskId,
SenderUserId = 0, // ✅ سیستم برای نشان دادن اینکه یک پیام خودکار است
SenderName = "سیستم",
MessageType = "Note", // ✅ نوع پیام: Note (یادداشت سیستم)
TextContent = noteContent,
CreationDate = additionalTime.AddedAt, // ✅ تاریخ اضافه شدن زمان اضافی
IsMine = false
};
messageDtos.Add(noteDto);
}
messageDtos.AddRange(CreateAdditionalTimeNotes(additionalTimesAfterLastMessage, users, request.TaskId));
}
}
// ✅ مرتب کردن نهایی تمام پیام‌ها (معمولی + نوت‌ها) بر اساس زمان ایجاد
// اینطور که نوت‌های زمان اضافی در جای درست خود قرار می‌گیرند
messageDtos = messageDtos.OrderBy(m => m.CreationDate).ToList();
var response = new PaginationResult<MessageDto>()

View File

@@ -20,7 +20,7 @@ public class ProjectTask : ProjectHierarchyNode
{
PhaseId = phaseId;
_sections = new List<TaskSection>();
Priority = TaskPriority.Medium;
Priority = ProjectTaskPriority.Low;
AddDomainEvent(new TaskCreatedEvent(Id, phaseId, name));
}
@@ -30,7 +30,7 @@ public class ProjectTask : ProjectHierarchyNode
// Task-specific properties
public Enums.TaskStatus Status { get; private set; } = Enums.TaskStatus.NotStarted;
public TaskPriority Priority { get; private set; }
public ProjectTaskPriority Priority { get; private set; }
public DateTime? StartDate { get; private set; }
public DateTime? EndDate { get; private set; }
public DateTime? DueDate { get; private set; }
@@ -119,7 +119,7 @@ public class ProjectTask : ProjectHierarchyNode
AddDomainEvent(new TaskStatusUpdatedEvent(Id, status));
}
public void SetPriority(TaskPriority priority)
public void SetPriority(ProjectTaskPriority priority)
{
Priority = priority;
AddDomainEvent(new TaskPriorityUpdatedEvent(Id, priority));

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

@@ -3,7 +3,7 @@ namespace GozareshgirProgramManager.Domain.ProjectAgg.Enums;
/// <summary>
/// اولویت تسک
/// </summary>
public enum TaskPriority
public enum ProjectTaskPriority
{
/// <summary>
/// پایین

View File

@@ -78,7 +78,7 @@ public record TaskStatusUpdatedEvent(Guid TaskId, TaskStatus Status) : IDomainEv
public DateTime OccurredOn { get; init; } = DateTime.Now;
}
public record TaskPriorityUpdatedEvent(Guid TaskId, TaskPriority Priority) : IDomainEvent
public record TaskPriorityUpdatedEvent(Guid TaskId, ProjectTaskPriority Priority) : IDomainEvent
{
public DateTime OccurredOn { get; init; } = DateTime.Now;
}

View File

@@ -36,7 +36,7 @@ public interface IProjectTaskRepository : IRepository<Guid, ProjectTask>
/// <summary>
/// Get tasks by priority
/// </summary>
Task<List<ProjectTask>> GetByPriorityAsync(ProjectAgg.Enums.TaskPriority priority);
Task<List<ProjectTask>> GetByPriorityAsync(ProjectAgg.Enums.ProjectTaskPriority priority);
/// <summary>
/// Get tasks assigned to user

View File

@@ -14,4 +14,7 @@ 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

@@ -15,6 +15,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.1" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.12" />
<!--<PackageReference Include="System.Text.Encodings.Web" Version="10.0.0" />-->

View File

@@ -58,7 +58,7 @@ public class ProjectTaskRepository : RepositoryBase<Guid, ProjectTask>, IProject
.ToListAsync();
}
public Task<List<ProjectTask>> GetByPriorityAsync(TaskPriority priority)
public Task<List<ProjectTask>> GetByPriorityAsync(ProjectTaskPriority priority)
{
return _context.ProjectTasks
.Where(t => t.Priority == priority)

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);
}
@@ -52,4 +53,13 @@ public class TaskSectionRepository:RepositoryBase<Guid,TaskSection>,ITaskSection
.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

@@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
namespace GozareshgirProgramManager.Infrastructure.Services.FileManagement;
/// <summary>

View File

@@ -22,6 +22,8 @@ using GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectH
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;
@@ -122,6 +124,8 @@ public class ProjectController : ProgramManagerBaseController
{
// اجرای Command برای متوقف کردن تسک‌های overtime قبل از نمایش
await _mediator.Send(new AutoStopOverTimeTaskSectionsCommand());
// سپس تسک‌هایی که به 100% زمان تخمینی رسیده‌اند را به حالت PendingForCompletion ببریم
await _mediator.Send(new AutoPendingFullTimeTaskSectionsCommand());
var res = await _mediator.Send(query);
return res;
@@ -165,4 +169,12 @@ public class ProjectController : ProgramManagerBaseController
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

@@ -0,0 +1,59 @@
using _0_Framework.Application.Sms;
using CompanyManagment.App.Contracts.SmsResult;
using CompanyManagment.App.Contracts.SmsResult.Dto;
using Microsoft.AspNetCore.Mvc;
using ServiceHost.BaseControllers;
namespace ServiceHost.Areas.Admin.Controllers;
public class SmsReportController : AdminBaseController
{
private readonly ISmsResultApplication _smsResultApplication;
private readonly ISmsService _smsService;
public SmsReportController(ISmsResultApplication smsResultApplication, ISmsService smsService)
{
_smsResultApplication = smsResultApplication;
_smsService = smsService;
}
/// <summary>
/// دریافت لیست پیامک ها
/// </summary>
/// <param name="searchModel"></param>
/// <returns></returns>
[HttpGet]
public async Task<List<SmsReportDto>> GetSmsReportList(SmsReportSearchModel searchModel)
{
var result =await _smsResultApplication.GetSmsReportList(searchModel);
return result;
}
/// <summary>
/// دریافت اطلاعات هر تاریخ برای اکسپند
/// </summary>
/// <param name="searchModel"></param>
/// <param name="date"></param>
/// <returns></returns>
[HttpGet("GetExpandedList")]
public async Task<List<SmsReportListDto>> GetSmsReportExpandList(SmsReportSearchModel searchModel, string date)
{
var result =await _smsResultApplication.GetSmsReportExpandList(searchModel, date);
return result;
}
/// <summary>
/// گزارش ای پی آی
/// </summary>
/// <param name="startDate"></param>
/// <param name="endDate"></param>
/// <returns></returns>
[HttpGet("GetApiReport")]
public async Task<List<ApiReportDto>> GetApiReport(string startDate, string endDate)
{
var result =await _smsService.GetApiReport(startDate, endDate);
return result;
}
}

View File

@@ -916,6 +916,17 @@ public class institutionContractController : AdminBaseController
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
$"قرارداد های مالی.xlsx");
}
/// <summary>
/// تنظیم وضعیت ارسال قرارداد
/// </summary>
[HttpPost("set-is-sent")]
public async Task<ActionResult<OperationResult>> SetIsSent([FromBody] SetInstitutionContractSendFlagRequest request)
{
var result = await _institutionContractApplication.SetContractSendFlag(request);
return result;
}
}
public class InstitutionContractCreationGetRepresentativeIdResponse
@@ -969,4 +980,4 @@ public class VerifyCodeRequest
{
public long ContractingPartyId { get; set; }
public string verifyCode { get; set; }
}
}

View File

@@ -1289,7 +1289,7 @@
تمدید قرارداد
</p>
</a>
<a permission="30715" class="btn btn-inverse pull-left rad" style="background-color: #f57373;border: 1px solid #f57373;margin-left:5px;"
<a class="btn btn-inverse pull-left rad" style="background-color: #f57373;border: 1px solid #f57373;margin-left:5px;"
asp-page="./FinancialStatments" asp-route-name="@item.ContractingPartyName" asp-route-id="@item.ContractingPartyId" asp-route-pageNumber="0">
<i class="fa fa-file-text-o faSize"></i>
<p>

View File

@@ -0,0 +1,95 @@
using _0_Framework.Application;
using CompanyManagment.App.Contracts.Fine;
using CompanyManagment.App.Contracts.FineSubject;
using Microsoft.AspNetCore.Mvc;
using ServiceHost.BaseControllers;
namespace ServiceHost.Areas.Client.Controllers;
public class FineController:ClientBaseController
{
private readonly IFineApplication _fineApplication;
private readonly IFineSubjectApplication _fineSubjectApplication;
private readonly long _workshopId;
public FineController(IFineApplication fineApplication, IFineSubjectApplication fineSubjectApplication,
IAuthHelper authHelper)
{
_fineApplication = fineApplication;
_fineSubjectApplication = fineSubjectApplication;
_workshopId = authHelper.GetWorkshopId();
}
[HttpGet]
public ActionResult<FinesGroupedViewModel> GetList([FromQuery]FineSearchViewModel searchModel)
{
searchModel.WorkshopId = _workshopId;
var res = _fineApplication.GetSearchListAsGrouped(searchModel);
return res;
}
[HttpPost]
public ActionResult<OperationResult> Create([FromBody]CreateFineViewModel command)
{
command.WorkshopId = _workshopId;
var res =_fineApplication.Create(command);
return res;
}
[HttpPut]
public ActionResult<OperationResult> Edit([FromBody]EditFineViewModel command)
{
command.WorkshopId = _workshopId;
var res = _fineApplication.Edit(command);
return res;
}
[HttpGet("{id:long}")]
public ActionResult<EditFineViewModel> Details(long id)
{
var res = _fineApplication.GetDetails(id);
return res;
}
[HttpDelete(("{id:long}"))]
public ActionResult<OperationResult> Remove(long id)
{
var res = _fineApplication.Remove(id);
return res;
}
#region FineSubject
[HttpGet("subject")]
public ActionResult<List<FineSubjectViewModel>> GetList()
{
var res = _fineSubjectApplication.GetAll(_workshopId);
return res;
}
[HttpPost("subject/{id:long}")]
public ActionResult<OperationResult> CreateSubject(CreateFineSubjectViewModel command)
{
command.WorkshopId = _workshopId;
var res = _fineSubjectApplication.Create(command);
return res;
}
[HttpPut("subject/{id:long}")]
public ActionResult<OperationResult> EditSubject(EditFineSubjectViewModel command)
{
command.WorkshopId = _workshopId;
var res = _fineSubjectApplication.Edit(command);
return res;
}
[HttpDelete("subject/{id:long}")]
public ActionResult<OperationResult> RemoveSubject(long id)
{
var res = _fineSubjectApplication.Delete(id);
return res;
}
#endregion
}

View File

@@ -63,10 +63,17 @@ if (!Directory.Exists(logDirectory))
Directory.CreateDirectory(logDirectory);
}
// فقط برای فایل از Serilog استفاده می‌شود
// تنظیمات MinimumLevel از appsettings.json خوانده می‌شود
Log.Logger = new LoggerConfiguration()
.Enrich.FromLogContext()
//NO EF Core log
.MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Warning)
//NO DbCommand log
.MinimumLevel.Override("Microsoft.EntityFrameworkCore.Database.Command", LogEventLevel.Warning)
//NO Microsoft Public log
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
//.MinimumLevel.Information()
.WriteTo.File(
path: Path.Combine(logDirectory, "gozareshgir_log.txt"),
rollingInterval: RollingInterval.Day,
@@ -373,29 +380,30 @@ builder.Services.AddParbad().ConfigureGateways(gateways =>
});
// فقط Serilog برای File استفاده می‌شه، کنسول از لاگر پیش‌فرض ASP.NET استفاده می‌کنه
builder.Host.UseSerilog((context, services, configuration) =>
if (builder.Environment.IsDevelopment())
{
var logConfig = configuration
.ReadFrom.Configuration(context.Configuration)
.ReadFrom.Services(services)
.Enrich.FromLogContext();
// در محیط Development، EF Core Commands را هم لاگ می‌کنیم
if (context.HostingEnvironment.IsDevelopment())
builder.Host.UseSerilog((context, services, configuration) =>
{
logConfig
.MinimumLevel.Override("Microsoft.EntityFrameworkCore.Database.Command", LogEventLevel.Information);
}
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); // این باعث میشه کنسول پیش‌فرض هم کار کنه
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); // این باعث میشه کنسول پیش‌فرض هم کار کنه
}
else
{
builder.Host.UseSerilog();
}
Log.Information("SERILOG STARTED SUCCESSFULLY");

View File

@@ -19,7 +19,7 @@
"sqlDebugging": true,
"dotnetRunMessages": "true",
"nativeDebugging": true,
"applicationUrl": "https://localhost:5004;http://localhost:5003;https://192.168.0.117:5006",
"applicationUrl": "https://localhost:5004;http://localhost:5003;",
"jsWebView2Debugging": false,
"hotReloadEnabled": true
},
@@ -44,7 +44,7 @@
"sqlDebugging": true,
"dotnetRunMessages": "true",
"nativeDebugging": true,
"applicationUrl": "https://localhost:5004;http://localhost:5003;https://192.168.0.117:5006;",
"applicationUrl": "https://localhost:5004;http://localhost:5003;",
"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'">

View File

@@ -1,77 +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
},
"FileStorage": {
"BaseUrl": "https://localhost:7032/uploads",
"MaxFileSizeBytes": 104857600,
"AllowedImageExtensions": [ ".jpg", ".jpeg", ".png", ".gif", ".webp" ],
"AllowedVideoExtensions": [ ".mp4", ".avi", ".mov", ".wmv" ],
"AllowedAudioExtensions": [ ".mp3", ".wav", ".ogg", ".m4a" ],
"AllowedDocumentExtensions": [ ".pdf", ".doc", ".docx", ".xls", ".xlsx", ".txt" ]
}
}

View File

@@ -1,62 +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://Logs//Gozareshgir//"
},
"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
},
"FileStorage": {
"BaseUrl": "https://api.pm.gozareshgir.ir/uploads",
"MaxFileSizeBytes": 104857600,
"AllowedImageExtensions": [ ".jpg", ".jpeg", ".png", ".gif", ".webp" ],
"AllowedVideoExtensions": [ ".mp4", ".avi", ".mov", ".wmv" ],
"AllowedAudioExtensions": [ ".mp3", ".wav", ".ogg", ".m4a" ],
"AllowedDocumentExtensions": [ ".pdf", ".doc", ".docx", ".xls", ".xlsx", ".txt" ]
}
}