Merge branch 'Feature/fine/client-api' into Main

# Conflicts:
#	.gitignore
#	ServiceHost/Program.cs
This commit is contained in:
2026-01-13 11:30:45 +03:30
16 changed files with 2706 additions and 1627 deletions

View File

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

@@ -93,65 +93,7 @@ public interface IInstitutionContractRepository : IRepository<long, InstitutionC
Task<GetInstitutionAmendmentVerificationDetailsViewModel> GetAmendmentVerificationDetails(Guid id, long amendmentId); Task<GetInstitutionAmendmentVerificationDetailsViewModel> GetAmendmentVerificationDetails(Guid id, long amendmentId);
#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 #region CreateMontlyTransaction
@@ -164,24 +106,12 @@ public interface IInstitutionContractRepository : IRepository<long, InstitutionC
#endregion #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<long> GetIdByInstallmentId(long installmentId);
Task<InstitutionContract> GetPreviousContract(long currentInstitutionContractId); 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

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

File diff suppressed because it is too large Load Diff

View File

@@ -685,6 +685,7 @@ public class PersonalBootstrapper
services.AddTransient<ISmsSettingsRepository, SmsSettingsRepository>(); services.AddTransient<ISmsSettingsRepository, SmsSettingsRepository>();
services.AddTransient<ISmsSettingApplication, SmsSettingApplication>(); services.AddTransient<ISmsSettingApplication, SmsSettingApplication>();
services.AddTransient<IInstitutionContractSmsServiceRepository, InstitutionContractSmsServiceRepository>();
#endregion #endregion

View File

@@ -6,5 +6,7 @@ namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.Project
public record ProjectBoardListQuery: IBaseQuery<List<ProjectBoardListResponse>> public record ProjectBoardListQuery: IBaseQuery<List<ProjectBoardListResponse>>
{ {
public long? UserId { get; set; }
public string? SearchText { get; set; }
public TaskSectionStatus? Status { 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.Application._Common.Models;
using GozareshgirProgramManager.Domain.ProjectAgg.Enums; using GozareshgirProgramManager.Domain.ProjectAgg.Enums;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Query.Internal;
namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.ProjectBoardList; namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.ProjectBoardList;
@@ -24,7 +23,8 @@ public class ProjectBoardListQueryHandler : IBaseQueryHandler<ProjectBoardListQu
var currentUserId = _authHelper.GetCurrentUserId(); var currentUserId = _authHelper.GetCurrentUserId();
var queryable = _programManagerDbContext.TaskSections.AsNoTracking() 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) .Include(x => x.Task)
.ThenInclude(x => x.Phase) .ThenInclude(x => x.Phase)
.ThenInclude(x => x.Project) .ThenInclude(x => x.Project)
@@ -40,10 +40,23 @@ public class ProjectBoardListQueryHandler : IBaseQueryHandler<ProjectBoardListQu
{ {
queryable = queryable.Where(x => x.Status == request.Status); 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 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) var assignedUser = data.Select(x => x.CurrentAssignedUserId)
.Concat(data.Select(x => x.OriginalAssignedUserId)).ToList(); .Concat(data.Select(x => x.OriginalAssignedUserId)).ToList();
var allUserIds = activityUserIds.Concat(assignedUser).Distinct().ToList(); var allUserIds = activityUserIds.Concat(assignedUser).Distinct().ToList();
@@ -67,7 +80,7 @@ public class ProjectBoardListQueryHandler : IBaseQueryHandler<ProjectBoardListQu
{ {
Activity = a, Activity = a,
TimeSpent = timeSpent, TimeSpent = timeSpent,
TotalSeconds = timeSpent.TotalSeconds, timeSpent.TotalSeconds,
FormattedTime = timeSpent.ToString(@"hh\:mm") FormattedTime = timeSpent.ToString(@"hh\:mm")
}; };
}).ToList(); }).ToList();

View File

@@ -21,7 +21,7 @@ public record ProjectDeployBoardDetailTaskItem(
TimeSpan DoneTimeSpan, TimeSpan DoneTimeSpan,
int Percentage, int Percentage,
List<ProjectDeployBoardDetailItemSkill> Skills) List<ProjectDeployBoardDetailItemSkill> Skills)
: ProjectDeployBoardDetailPhaseItem(Name, TotalTimeSpan, DoneTimeSpan,Percentage); : ProjectDeployBoardDetailPhaseItem(Name, TotalTimeSpan, DoneTimeSpan, Percentage);
public record ProjectDeployBoardDetailItemSkill(string OriginalUserFullName, string SkillName, int TimePercentage); public record ProjectDeployBoardDetailItemSkill(string OriginalUserFullName, string SkillName, int TimePercentage);
@@ -73,6 +73,7 @@ public class
var doneTime = t.Sections.Aggregate(TimeSpan.Zero, var doneTime = t.Sections.Aggregate(TimeSpan.Zero,
(sum, next) => sum.Add(next.GetTotalTimeSpent())); (sum, next) => sum.Add(next.GetTotalTimeSpent()));
var skills = t.Sections var skills = t.Sections
.Select(s => .Select(s =>
{ {
@@ -82,13 +83,23 @@ public class
var skillName = s.Skill?.Name ?? "بدون مهارت"; var skillName = s.Skill?.Name ?? "بدون مهارت";
var timePercentage = (int)s.GetProgressPercentage(); var timePercentage = (int)s.GetProgressPercentage();
return new ProjectDeployBoardDetailItemSkill( return new ProjectDeployBoardDetailItemSkill(
originalUserFullName, originalUserFullName,
skillName, skillName,
timePercentage); timePercentage);
}).ToList(); }).ToList();
var taskPercentage = (int)Math.Round(skills.Average(x => x.TimePercentage));
int taskPercentage;
if (skills.Count == 0)
{
taskPercentage = 0;
}
else
{
taskPercentage = (int)Math.Round(skills.Average(x => x.TimePercentage));
}
return new ProjectDeployBoardDetailTaskItem( return new ProjectDeployBoardDetailTaskItem(
t.Name, t.Name,
@@ -105,7 +116,7 @@ public class
(sum, next) => sum.Add(next.DoneTimeSpan)); (sum, next) => sum.Add(next.DoneTimeSpan));
var phasePercentage = tasksRes.Average(x => x.Percentage); var phasePercentage = tasksRes.Average(x => x.Percentage);
var phaseRes = new ProjectDeployBoardDetailPhaseItem(phase.Name, totalTimeSpan, doneTimeSpan, var phaseRes = new ProjectDeployBoardDetailPhaseItem(phase.Name, totalTimeSpan, doneTimeSpan,
(int)phasePercentage); (int)phasePercentage);

View File

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

View File

@@ -20,7 +20,7 @@ public class ProjectTask : ProjectHierarchyNode
{ {
PhaseId = phaseId; PhaseId = phaseId;
_sections = new List<TaskSection>(); _sections = new List<TaskSection>();
Priority = ProjectTaskPriority.Medium; Priority = ProjectTaskPriority.Low;
AddDomainEvent(new TaskCreatedEvent(Id, phaseId, name)); AddDomainEvent(new TaskCreatedEvent(Id, phaseId, name));
} }

View File

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

View File

@@ -1289,7 +1289,7 @@
تمدید قرارداد تمدید قرارداد
</p> </p>
</a> </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"> 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> <i class="fa fa-file-text-o faSize"></i>
<p> <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

@@ -64,15 +64,16 @@ if (!Directory.Exists(logDirectory))
} }
Log.Logger = new LoggerConfiguration() Log.Logger = new LoggerConfiguration()
////NO EF Core log //NO EF Core log
//.MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Warning) .MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Warning)
////NO DbCommand log //NO DbCommand log
//.MinimumLevel.Override("Microsoft.EntityFrameworkCore.Database.Command", LogEventLevel.Warning) .MinimumLevel.Override("Microsoft.EntityFrameworkCore.Database.Command", LogEventLevel.Warning)
////NO Microsoft Public log //NO Microsoft Public log
//.MinimumLevel.Override("Microsoft", LogEventLevel.Warning) .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.MinimumLevel.Information()
//.MinimumLevel.Information()
.WriteTo.File( .WriteTo.File(
path: Path.Combine(logDirectory, "gozareshgir_log.txt"), path: Path.Combine(logDirectory, "gozareshgir_log.txt"),
rollingInterval: RollingInterval.Day, rollingInterval: RollingInterval.Day,
@@ -379,8 +380,30 @@ builder.Services.AddParbad().ConfigureGateways(gateways =>
}); });
// فقط Serilog برای File استفاده می‌شه، کنسول از لاگر پیش‌فرض ASP.NET استفاده می‌کنه if (builder.Environment.IsDevelopment())
builder.Host.UseSerilog(); // این باعث میشه کنسول پیش‌فرض هم کار کنه {
builder.Host.UseSerilog((context, services, configuration) =>
{
var logConfig = configuration
.ReadFrom.Configuration(context.Configuration)
.ReadFrom.Services(services)
.Enrich.FromLogContext();
logConfig.WriteTo.File(
path: Path.Combine(logDirectory, "gozareshgir_log.txt"),
rollingInterval: RollingInterval.Day,
retainedFileCountLimit: 30,
shared: true,
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level}] {Message}{NewLine}{Exception}"
);
}, writeToProviders: true); // این باعث میشه کنسول پیش‌فرض هم کار کنه
}
else
{
builder.Host.UseSerilog();
}
Log.Information("SERILOG STARTED SUCCESSFULLY"); Log.Information("SERILOG STARTED SUCCESSFULLY");