Compare commits

...

138 Commits

Author SHA1 Message Date
3f55446f82 Merge branch 'Feature/loan/client-api' 2026-02-06 18:29:45 +03:30
3b4f880d54 Merge branch 'Feature/roll-call/client-api' 2026-02-05 15:11:09 +03:30
fb239e3159 Merge branch 'Feature/salary-aid/client-api' 2026-02-05 15:02:32 +03:30
c844867cab add OnPostCreateFromExcelData method to SalaryAidController for bulk salary aid creation from Excel data 2026-02-05 15:00:56 +03:30
779514f5c0 refactor ValidateExcel method in SalaryAidController to use ValidateExcelRequest model 2026-02-05 14:18:18 +03:30
bc491eec18 add EditDetails endpoint to SalaryAidController for salary aid detail retrieval 2026-02-05 13:55:02 +03:30
67910d2fa5 add EditDetails method to SalaryAidController for retrieving salary aid details 2026-02-05 12:50:53 +03:30
9475c786d3 add endpoint to remove employee roll calls by date 2026-02-05 11:58:52 +03:30
db32b1e6ea set WorkshopId in search model for salary aid list retrieval 2026-02-05 11:50:23 +03:30
79a9d72b86 rename SalaryAidViewModels property to Items in grouped view models 2026-02-05 11:32:16 +03:30
dddc4b143a add edit functionality for employee roll call details 2026-02-05 11:29:41 +03:30
a8cb226d20 change list key for salary aid 2026-02-05 11:05:54 +03:30
ffe8fa67e2 refactor salary aid controller to update Excel validation endpoint 2026-02-05 10:33:09 +03:30
a0d2023a6c add Excel validation for salary aid import and update pagination in salary aid list 2026-02-05 10:32:08 +03:30
c2fdc217b9 refactor SetTimeProjectCommandHandler to validate total time after applying changes 2026-02-05 10:14:13 +03:30
db0047d3d3 set time get details 2026-02-03 17:54:32 +03:30
fa4c39904a chnage loan list to new type 2026-02-03 13:44:47 +03:30
gozareshgir
a14a78309e Merge branch 'Feature/SmsRepoetApi' 2026-02-02 19:46:54 +03:30
gozareshgir
4ccade4c7a change sms url 2026-02-02 19:45:51 +03:30
085d138bc5 Merge branch 'Feature/SmsRepoetApi' 2026-02-02 19:19:32 +03:30
3dace574ff refactor date parsing method in RollCallCaseHistoryController 2026-02-02 19:08:25 +03:30
bf2a102a55 add Excel export functionality for roll call case history 2026-02-02 19:07:11 +03:30
88744bd4cf add endpoint to download case history as Excel file 2026-02-02 17:54:33 +03:30
5942075dd6 refactor roll call case history controller and add total working hours endpoint 2026-02-02 17:31:37 +03:30
61015ae5c1 Merge remote-tracking branch 'origin/master' 2026-02-02 16:54:32 +03:30
57a5000124 refactor GetProjectsListQueryHandler to improve task and phase status aggregation logic 2026-02-02 16:52:53 +03:30
7cbb9eef69 add validation and management for additional time entries in SetTimeProjectCommand 2026-02-02 16:07:43 +03:30
gozareshgir
7a065e9d16 Checkout Except EmployeeId = 7175 2026-02-02 13:57:15 +03:30
0e7787dd56 add page size for search model 2026-02-01 18:14:28 +03:30
gozareshgir
e2bab8c1ce HasRollCall Method Changeed 2026-02-01 13:20:22 +03:30
gozareshgir
b088d3089d merge from smsReportApi 2026-01-29 15:29:19 +03:30
gozareshgir
45b4690066 Test singnalR instandSms 2026-01-29 15:26:04 +03:30
179de86840 update SMS report links in menu to use absolute URLs 2026-01-27 21:10:18 +03:30
e0d10510e0 update RemoveSmsSetting method to return OperationResult 2026-01-27 20:20:54 +03:30
a55492b16a add CancelSendVerificationSms flag to InstitutionContractExtensionCompleteRequest and update SMS sending logic 2026-01-27 17:43:05 +03:30
9596c8f8b6 refactor FileUploadService to simplify category folder naming 2026-01-27 16:24:34 +03:30
8622f12f12 add file upload service and integrate with message sending 2026-01-27 15:54:38 +03:30
a20a847065 add mahan user for static accounts 2026-01-27 15:37:02 +03:30
258a809451 add .env files to .gitignore 2026-01-27 15:10:18 +03:30
gozareshgir
6285c7320e New PermissionCode to ProgramManager 2026-01-27 14:05:20 +03:30
9bca1b81d6 Merge remote-tracking branch 'origin/master' 2026-01-26 18:08:51 +03:30
9ff6b5cf56 fix rollcall mannaul edit bug 2026-01-26 18:08:33 +03:30
gozareshgir
04642b7257 Merge branch 'master' into Fix/program-manager/fix-some-bugs 2026-01-25 20:05:30 +03:30
c1c9fe51cb fix creation for institutioncontract on Not Authorized contracting party 2026-01-25 19:08:13 +03:30
gozareshgir
0d2ac58bbb change 2026-01-25 12:52:25 +03:30
43ccb3a1dd Merge remote-tracking branch 'origin/master' 2026-01-24 19:10:59 +03:30
0134111aba fix bug for extensions 2026-01-24 19:10:24 +03:30
gozareshgir
3cc7adae35 Merge branch 'Feature/CheckoutReward' 2026-01-24 18:58:24 +03:30
gozareshgir
c97ea5356f Add Reward To checkout Completed 2026-01-24 18:57:53 +03:30
69f4819bf6 Add Migration For Reward checkout 2026-01-24 16:58:45 +03:30
gozareshgir
1257e15b62 changeMapping 2026-01-24 16:45:10 +03:30
gozareshgir
331fb24a99 CheckoutReward 2026-01-24 16:29:01 +03:30
3be1547137 fix mannually verify error 2026-01-24 16:25:22 +03:30
900b4b3f4d add convention for print InstitutionContract for pending data 2026-01-22 12:58:43 +03:30
bdc6f95af8 fix activate all after create new 2026-01-22 12:26:13 +03:30
7a73e69afa Merge branch 'master' into Fix/program-manager/fix-some-bugs 2026-01-22 11:06:58 +03:30
5c3c9739d1 fix regex and validation for rollcall history case details 2026-01-19 15:30:28 +03:30
gozareshgir
21302803b6 insurance WorkingDays bug Fixed 2026-01-19 12:32:51 +03:30
355ec72140 comment excel data 2026-01-18 14:17:23 +03:30
b22aa86aea add create - edit - remove salary aid api controller 2026-01-15 10:35:43 +03:30
f0feac9601 add salary aid controller 2026-01-14 19:27:23 +03:30
gozareshgir
8ec13ffae1 Merge branch 'master' of https://pm.gozareshgir.ir/gozareshgir/OriginalGozareshgir 2026-01-14 14:40:50 +03:30
gozareshgir
5508d4e88f Checkout Compute Minuts Base 2026-01-14 14:39:51 +03:30
43abb74c61 Merge branch 'Feature/program-manager/chat'
# Conflicts:
#	ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/TaskChat/Queries/GetMessages/GetMessagesQuery.cs
2026-01-14 10:57:07 +03:30
73e6681baa add message type to search query 2026-01-14 10:46:44 +03:30
90fa0ac8f8 change excel download api controller 2026-01-14 10:20:58 +03:30
90b2fd2eab add order for skills in set time 2026-01-14 10:13:57 +03:30
eb8352e8fc add rollcall download excel controller. 2026-01-14 09:37:55 +03:30
4c7599b568 add print to rollcall case history 2026-01-13 18:50:18 +03:30
d179c90c48 change paginated to list 2026-01-13 18:31:10 +03:30
gozareshgir
2fc124bf6d Get Sms Details 2026-01-13 17:35:29 +03:30
gozareshgir
1d88ca0fbb changes 2026-01-13 17:05:07 +03:30
gozareshgir
e2911dfc2a addnew dto 2026-01-13 17:00:59 +03:30
gozareshgir
cfb96d1277 Chenge Post Methods to fromBody 2026-01-13 16:42:50 +03:30
gozareshgir
b5c5be2cb6 change smsResult grouping 2026-01-13 16:26:04 +03:30
8850328fd4 add GetEditDetails controller method 2026-01-13 16:08:13 +03:30
b7172630e2 set orders for projects 2026-01-13 15:49:51 +03:30
gozareshgir
f5c8888137 IsDev mode chak for instant sms 2026-01-13 15:33:08 +03:30
gozareshgir
4d7923936e InstantSms Send Completed 2026-01-13 15:16:33 +03:30
0604514190 Merge branch 'refs/heads/master' into Fix/program-manager/fix-some-bugs 2026-01-13 14:51:25 +03:30
915f16c7c0 add rollcall case history upsert 2026-01-13 14:03:10 +03:30
gozareshgir
532065e3a8 Merge branch 'master' into Feature/SmsRepoetApi 2026-01-13 11:20:57 +03:30
gozareshgir
f7bfa37a77 SmsSettings List , create, edit, delete 2026-01-13 11:17:25 +03:30
87c3cebb60 set workshopId to readonly 2026-01-13 09:38:44 +03:30
d9c431e20e add project name search for board list 2026-01-13 09:23:53 +03:30
ff5180eb75 remove add task to phase 2026-01-12 17:39:54 +03:30
a1c9335487 add remaining time and spent time to get project list 2026-01-12 16:48:54 +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
20ece4886c add task priority to CreateProjectCommand 2026-01-12 12:20:08 +03:30
0eff1b9a66 change default task priority from medium to low 2026-01-12 12:07:43 +03:30
0d72392701 complete rollcall history details 2026-01-12 10:48:19 +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
8f6007835c complete rollcallCase history details by employee 2026-01-11 17:25:56 +03:30
05d860795e add rollcall title case history query 2026-01-11 15:12:08 +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
607c0780b6 Merge branch 'master' into Feature/loan/client-api 2026-01-10 11:50:52 +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
80a58f8cdc feat: add TaskId to ProjectBoardListQueryHandler and ProjectBoardListResponse for enhanced task tracking 2026-01-08 12:14:26 +03:30
ef49302f8a feat: add workshop ID handling in LoanController for loan search filtering 2026-01-08 12:03:54 +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
c8dddabdff fix: correct logic for editing text messages in TaskChatMessage.cs 2026-01-08 11:05:36 +03:30
SamSys
4de2e12ac5 AmaApiReport 2026-01-07 18:38:12 +03:30
SamSys
23b65cfbfe GetSms Report Expand List 2026-01-07 16:59:21 +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
95d66c2d89 feat: enhance message queries to display real sender names and add system notes for additional times 2026-01-07 11:17:16 +03:30
609daf4353 feat: update file storage paths and enhance thumbnail generation with category support 2026-01-07 10:50:23 +03:30
a81e01ce2b Remove Storage folder from git tracking 2026-01-07 10:44:09 +03:30
2cd838a5e3 feat: enhance thumbnail generation with category support and update storage paths 2026-01-07 10:25:33 +03:30
34bd7ba444 add set file message to TaskChatMessage.cs 2026-01-06 12:21:12 +03:30
43b124664e feat: integrate authentication checks in message command handlers 2026-01-05 16:06:35 +03:30
d2dd67343b feat: add file management entities and services for chat message handling 2026-01-05 15:40:06 +03:30
3d2b5ff6bd feat: implement TaskChatMessage entity and repository for chat message management 2026-01-05 11:45:37 +03:30
4ab9f60932 feat: add methods for creating, calculating installments, and removing loans in LoanController 2026-01-01 14:41:13 +03:30
9cfae54db3 feat: add LoanController for managing loan applications and details 2026-01-01 13:13:04 +03:30
203 changed files with 22213 additions and 2591 deletions

7
.gitignore vendored
View File

@@ -364,3 +364,10 @@ MigrationBackup/
.idea
/ServiceHost/appsettings.Development.json
/ServiceHost/appsettings.json
# Storage folder - ignore all uploaded files, thumbnails, and temporary files
ServiceHost/Storage
.env
.env.*

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,19 @@ public enum TypeOfSmsSetting
/// </summary>
InstitutionContractConfirm,
/// <summary>
/// ارسال کد تاییدیه قرارداد مالی
/// </summary>
SendInstitutionContractConfirmationCode,
/// <summary>
/// لینک تاییدیه ایجاد قرارداد مالی
/// </summary>
SendInstitutionContractConfirmationLink,
/// <summary>
/// یادآور وظایف
/// </summary>
TaskReminder,
}

View File

@@ -7,7 +7,7 @@ namespace _0_Framework.Application;
public class PagedResult<T> where T : class
{
public int TotalCount { get; set; }
public List<T> List { get; set; }
public List<T> List { get; set; } = [];
}
public class PagedResult<T,TMeta>:PagedResult<T> where T : class
{

View File

@@ -18,3 +18,34 @@ public class ApiResultViewModel
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; }
public string FullName { get; set; }
}
public class SmsDetailsDto
{
public string MessageText { 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; }
public string FullName { get; set; }
}

View File

@@ -16,9 +16,22 @@ public interface ISmsService
/// <param name="code"></param>
/// <returns></returns>
Task<SentSmsViewModel> SendVerifyCodeToClient(string number, string code);
bool SendAccountsInfo(string number,string fullName, string userName);
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);
/// <summary>
/// دریافت جزئیات پیامک
/// </summary>
/// <param name="messId"></param>
/// <param name="fullName"></param>
/// <returns></returns>
Task<SmsDetailsDto> GetSmsDetailsByMessageId(int messId, string fullName);
#endregion
string DeliveryStatus(byte? dv);
string DeliveryColorStatus(byte? dv);
string UnixTimeStampToDateTime(int? unixTimeStamp);

View File

@@ -31,8 +31,9 @@ public static class StaticWorkshopAccounts
/// 381 - مهدی قربانی
/// 392 - عمار حسن دوست
/// 20 - سمیرا الهی نیا
/// 322 - ماهان چمنی
/// </summary>
public static List<long> StaticAccountIds = [2, 3, 380, 381, 392, 20, 476];
public static List<long> StaticAccountIds = [2, 3, 380, 381, 392, 20, 476,322];
/// <summary>
/// این تاریخ در جدول اکانت لفت ورک به این معنیست

View File

@@ -2,6 +2,7 @@
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<NuGetAudit>false</NuGetAudit>
</PropertyGroup>
<ItemGroup>

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,7 +108,7 @@ 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");
@@ -101,7 +130,7 @@ public class JobSchedulerRegistrator
try
{
await _institutionContractRepository.CreateTransactionForInstitutionContracts(endNewGr, endNewFa, description);
await _institutionContractRepository.CreateTransactionForInstitutionContracts(endNewGr, endNewFa, description);
_lastRunCreateTransaction = now;
Console.WriteLine("CreateTransAction executed");
@@ -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

@@ -31,7 +31,7 @@ public class Checkout : EntityBase
string overNightWorkValue, string fridayWorkValue, string rotatingShifValue, string absenceValue,
string totalDayOfLeaveCompute, string totalDayOfYearsCompute, string totalDayOfBunosesCompute,
ICollection<CheckoutLoanInstallment> loanInstallments,
ICollection<CheckoutSalaryAid> salaryAids, CheckoutRollCall checkoutRollCall, TimeSpan employeeMandatoryHours, bool hasInsuranceShareTheSameAsList)
ICollection<CheckoutSalaryAid> salaryAids, CheckoutRollCall checkoutRollCall, TimeSpan employeeMandatoryHours, bool hasInsuranceShareTheSameAsList, ICollection<CheckoutReward> rewards,double rewardPay)
{
EmployeeFullName = employeeFullName;
FathersName = fathersName;
@@ -71,7 +71,7 @@ public class Checkout : EntityBase
TotalClaims = totalClaims;
TotalDeductions = totalDeductions;
TotalPayment = totalPayment;
RewardPay = 0;
RewardPay = rewardPay;
IsActiveString = "true";
Signature = signature;
MarriedAllowance = marriedAllowance;
@@ -93,6 +93,7 @@ public class Checkout : EntityBase
CheckoutRollCall = checkoutRollCall;
EmployeeMandatoryHours = employeeMandatoryHours;
HasInsuranceShareTheSameAsList = hasInsuranceShareTheSameAsList;
Rewards = rewards;
}
@@ -130,7 +131,7 @@ public class Checkout : EntityBase
public double BonusesPay { get; private set; }
public double YearsPay { get; private set; }
public double LeavePay { get; private set; }
public double? RewardPay { get; private set; }
public double RewardPay { get; private set; }
public double InsuranceDeduction { get; private set; }
public double TaxDeducation { get; private set; }
public double InstallmentDeduction { get; private set; }
@@ -223,6 +224,8 @@ public class Checkout : EntityBase
public ICollection<CheckoutLoanInstallment> LoanInstallments { get; set; } = [];
public ICollection<CheckoutSalaryAid> SalaryAids { get; set; } = [];
public ICollection<CheckoutReward> Rewards { get; set; } = [];
public CheckoutRollCall CheckoutRollCall { get; private set; }
#endregion
@@ -239,7 +242,7 @@ public class Checkout : EntityBase
double insuranceDeduction, double taxDeducation, double installmentDeduction,
double salaryAidDeduction, double absenceDeduction, string sumOfWorkingDays
, string archiveCode, string personnelCode,
string totalClaims, string totalDeductions, double totalPayment, double? rewardPay)
string totalClaims, string totalDeductions, double totalPayment, double rewardPay)
{
EmployeeFullName = employeeFullName;
FathersName = fathersName;
@@ -337,6 +340,11 @@ public class Checkout : EntityBase
InstallmentDeduction = installmentsAmount;
}
public void SetReward(ICollection<CheckoutReward> rewards, double rewardAmount)
{
RewardPay = rewardAmount;
Rewards = rewards;
}
public void SetCheckoutRollCall(CheckoutRollCall checkoutRollCall)
{
CheckoutRollCall = checkoutRollCall;

View File

@@ -0,0 +1,57 @@
using System;
namespace Company.Domain.CheckoutAgg.ValueObjects;
public class CheckoutReward
{
public CheckoutReward(string amount, double amountDouble, string grantDateFa, DateTime grantDateGr, string description, string title, long entityId)
{
Amount = amount;
AmountDouble = amountDouble;
GrantDateFa = grantDateFa;
GrantDateGr = grantDateGr;
Description = description;
Title = title;
EntityId = entityId;
}
/// <summary>
/// مبلغ پاداش
/// string
/// </summary>
public string Amount { get; set; }
/// <summary>
/// مبلغ پاداش
/// double
/// </summary>
public double AmountDouble { get; set; }
/// <summary>
/// تاریخ اعطاء
/// شمسی
/// </summary>
public string GrantDateFa { get; set; }
/// <summary>
/// تاریخ اعطاء
/// میلادی
/// </summary>
public DateTime GrantDateGr { get; set; }
/// <summary>
/// توضیحات
/// </summary>
public string Description { get; set; }
/// <summary>
/// عنوان
/// </summary>
public string Title { get; set; }
/// <summary>
/// آی دی پاداش
/// </summary>
public long EntityId { get; set; }
}

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,15 +1,16 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using _0_Framework.Domain;
using _0_Framework.Domain;
using Company.Domain.CustomizeWorkshopEmployeeSettingsAgg.Entities;
using CompanyManagment.App.Contracts.Contract;
using CompanyManagment.App.Contracts.CustomizeCheckout;
using CompanyManagment.App.Contracts.Leave;
using CompanyManagment.App.Contracts.Loan;
using CompanyManagment.App.Contracts.Reward;
using CompanyManagment.App.Contracts.RollCall;
using CompanyManagment.App.Contracts.SalaryAid;
using CompanyManagment.App.Contracts.WorkingHoursTemp;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Company.Domain.RollCallAgg;
@@ -53,6 +54,9 @@ public interface IRollCallMandatoryRepository : IRepository<long, RollCall>
List<SalaryAidViewModel> SalaryAidsForCheckout(long employeeId, long workshopId, DateTime checkoutStart,
DateTime checkoutEnd);
List<RewardViewModel> RewardForCheckout(long employeeId, long workshopId, DateTime checkoutEnd,
DateTime checkoutStart);
Task<ComputingViewModel> RotatingShiftReport(long workshopId, long employeeId, DateTime contractStart,
DateTime contractEnd, string shiftwork, bool hasRollCall, CreateWorkingHoursTemp command,bool holidayWorking);
}

View File

@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using _0_Framework.Application;
using _0_Framework.Domain;
using CompanyManagment.App.Contracts.RollCall;
using CompanyManagment.App.Contracts.WorkingHoursTemp;
@@ -91,5 +92,9 @@ namespace Company.Domain.RollCallAgg
Task<List<RollCall>> GetRollCallsUntilNowWithWorkshopIdEmployeeIds(long workshopId, List<long> employeeIds,
DateTime fromDate);
#endregion
Task<PagedResult<RollCallCaseHistoryTitleDto>> GetCaseHistoryTitles(long workshopId,RollCallCaseHistorySearchModel searchModel);
Task<List<RollCallCaseHistoryDetail>> GetCaseHistoryDetails(long workshopId,
string titleId, RollCallCaseHistorySearchModel searchModel);
}
}

View File

@@ -1,10 +1,32 @@
using CompanyManagment.App.Contracts.SmsResult;
using System.Collections.Generic;
using _0_Framework.Application.Enums;
using _0_Framework.Domain;
using CompanyManagment.App.Contracts.SmsResult;
using CompanyManagment.App.Contracts.SmsResult.Dto;
using System.Collections.Generic;
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>
/// <param name="typeOfSmsSetting"></param>
/// <returns></returns>
Task<List<SmsReportListDto>> GetSmsReportExpandList(SmsReportSearchModel searchModel, string date, string typeOfSmsSetting);
#endregion
List<SmsResultViewModel> Search(SmsResultSearchModel searchModel);
}

View File

@@ -1,6 +1,7 @@
using _0_Framework.Application.Enums;
using _0_Framework.Domain;
using CompanyManagment.App.Contracts.SmsResult;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Company.Domain.SmsResultAgg;
@@ -27,4 +28,25 @@ public interface ISmsSettingsRepository : IRepository<long, SmsSetting>
/// <param name="id"></param>
/// <returns></returns>
Task RemoveItem(long id);
#region ForApi
/// <summary>
/// دریافت لیست پیامک های خودکار بر اساس نوع آن
/// Api
/// </summary>
/// <param name="typeOfSmsSetting"></param>
/// <returns></returns>
Task<List<SmsSettingDto>> GetSmsSettingList(TypeOfSmsSetting typeOfSmsSetting);
/// <summary>
/// دریافت اطلاعات تنظیمات پیامک جهت ویرایش
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
Task<SmsSettingDto> GetSmsSettingDataToEdit(long id);
#endregion
}

View File

@@ -1,6 +1,12 @@
using _0_Framework.Excel;
using _0_Framework.Application;
using CompanyManagment.App.Contracts.RollCall;
using OfficeOpenXml;
using OfficeOpenXml.Drawing;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
namespace CompanyManagement.Infrastructure.Excel.RollCall;
@@ -308,6 +314,111 @@ public class RollCallExcelGenerator : ExcelGenerator
return package.GetAsByteArray();
}
public static byte[] CaseHistoryExcelForEmployee(List<RollCallCaseHistoryDetail> data, string titleId)
{
if (!Regex.IsMatch(titleId, @"^\d{4}_\d{2}$"))
throw new ArgumentException("Invalid titleId format.", nameof(titleId));
var splitDate = titleId.Split("_");
var year = Convert.ToInt32(splitDate.First());
var month = Convert.ToInt32(splitDate.Last());
var startDateFa = $"{year:D4}/{month:D2}/01";
var startDate = startDateFa.ToGeorgianDateTime();
var endDateFa = startDateFa.FindeEndOfMonth();
var endDate = endDateFa.ToGeorgianDateTime();
var dateRange = (int)(endDate.Date - startDate.Date).TotalDays + 1;
var dates = Enumerable.Range(0, dateRange).Select(x => startDate.AddDays(x)).ToList();
var safeData = data ?? new List<RollCallCaseHistoryDetail>();
var first = safeData.FirstOrDefault();
var totalWorkingTime = new TimeSpan(safeData.Sum(x => x.TotalWorkingTime.Ticks));
var viewModel = new CaseHistoryRollCallExcelForEmployeeViewModel
{
EmployeeId = first?.EmployeeId ?? 0,
DateGr = startDate,
PersonnelCode = first?.PersonnelCode,
EmployeeFullName = first?.EmployeeFullName,
PersianMonthName = month.ToFarsiMonthByIntNumber(),
PersianYear = year.ToString(),
TotalWorkingHoursFa = totalWorkingTime.ToFarsiHoursAndMinutes("-"),
TotalWorkingTimeSpan = $"{(int)totalWorkingTime.TotalHours}:{totalWorkingTime.Minutes:00}",
RollCalls = dates.Select((date, index) =>
{
var item = index < safeData.Count ? safeData[index] : null;
var records = item?.Records ?? new List<RollCallCaseHistoryDetailRecord>();
return new RollCallItemForEmployeeExcelViewModel
{
DateGr = date,
DateFa = date.ToFarsi(),
DayOfWeekFa = date.DayOfWeek.DayOfWeeKToPersian(),
PersonnelCode = item?.PersonnelCode,
EmployeeFullName = item?.EmployeeFullName,
IsAbsent = item?.Status == RollCallRecordStatus.Absent,
HasLeave = item?.Status == RollCallRecordStatus.Leaved,
IsHoliday = false,
TotalWorkingHours = (item?.TotalWorkingTime ?? TimeSpan.Zero).ToFarsiHoursAndMinutes("-"),
StartsItems = JoinRecords(records, r => r.StartTime),
EndsItems = JoinRecords(records, r => r.EndTime),
EnterTimeDifferences = JoinRecords(records, r => FormatSignedTimeSpan(r.EntryTimeDifference)),
ExitTimeDifferences = JoinRecords(records, r => FormatSignedTimeSpan(r.ExitTimeDifference))
};
}).ToList()
};
return CaseHistoryExcelForEmployee(viewModel);
}
public static byte[] CaseHistoryExcelForOneDay(List<RollCallCaseHistoryDetail> data, string titleId)
{
if (!Regex.IsMatch(titleId, @"^\d{4}/\d{2}/\d{2}$"))
throw new ArgumentException("Invalid titleId format.", nameof(titleId));
var dateGr = titleId.ToGeorgianDateTime();
var safeData = data ?? new List<RollCallCaseHistoryDetail>();
var viewModel = new CaseHistoryRollCallForOneDayViewModel
{
DateFa = titleId,
DateGr = dateGr,
DayOfWeekFa = dateGr.DayOfWeek.DayOfWeeKToPersian(),
RollCalls = safeData.Select(item =>
{
var records = item.Records ?? new List<RollCallCaseHistoryDetailRecord>();
return new RollCallItemForOneDayExcelViewModel
{
EmployeeFullName = item.EmployeeFullName,
PersonnelCode = item.PersonnelCode,
StartsItems = JoinRecords(records, r => r.StartTime),
EndsItems = JoinRecords(records, r => r.EndTime),
TotalWorkingHours = item.TotalWorkingTime.ToFarsiHoursAndMinutes("-")
};
}).ToList()
};
return CaseHistoryExcelForOneDay(viewModel);
}
private static string JoinRecords(IEnumerable<RollCallCaseHistoryDetailRecord> records, Func<RollCallCaseHistoryDetailRecord, string> selector)
{
var safeRecords = records ?? Enumerable.Empty<RollCallCaseHistoryDetailRecord>();
var values = safeRecords.Select(selector).Where(x => !string.IsNullOrWhiteSpace(x)).ToList();
return values.Count == 0 ? string.Empty : string.Join(Environment.NewLine, values);
}
private static string FormatSignedTimeSpan(TimeSpan value)
{
if (value == TimeSpan.Zero)
return "-";
var abs = value.Duration();
var sign = value.Ticks < 0 ? "-" : "+";
return $"{(int)abs.TotalHours}:{abs.Minutes:00}{sign}";
}
private string CalculateExitMinuteDifference(TimeSpan early, TimeSpan late)
{
if (early == TimeSpan.Zero && late == TimeSpan.Zero)

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

@@ -193,4 +193,9 @@ public class CreateCheckout
/// پایه سنوات قبل از تاثیر ساعت کار
/// </summary>
public double BaseYearUnAffected { get; set; }
/// <summary>
/// آیا برای محاسبه پاداش مجاز است
/// </summary>
public bool RewardPayCompute { get; set; }
}

View File

@@ -4,6 +4,7 @@
<TargetFramework>net10.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
<NuGetAudit>false</NuGetAudit>
</PropertyGroup>
<ItemGroup>

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

@@ -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
@@ -316,6 +324,7 @@ public class InstitutionContractCreationWorkshopsResponse
{
public List<WorkshopTempViewModel> WorkshopTemps { get; set; }
public string TotalAmount { get; set; }
public Guid TempId { get; set; }
}
public class InstitutionContractCreationWorkshopsRequest

View File

@@ -7,6 +7,7 @@ public class InstitutionContractExtensionCompleteRequest
public Guid TemporaryId { get; set; }
public bool IsInstallment { get; set; }
public long LawId { get; set; }
public bool CancelSendVerificationSms { get; set; }
}
public class InstitutionContractCreationCompleteRequest

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

@@ -1,4 +1,6 @@
namespace CompanyManagment.App.Contracts.InstitutionContract;
using System.Collections.Generic;
namespace CompanyManagment.App.Contracts.InstitutionContract;
/// <summary>
/// لیست پیامکهای بدهکاران قرارداد ملی
@@ -113,10 +115,30 @@ public class BlockSmsListData
/// <summary>
/// لیست قراداد های آبی
/// جهت ارسال هشدار یا اقدام قضائی
///پیامک آنی یادآور
/// </summary>
public class BlueWarningSmsData
public class InstantReminderSendSms
{
/// <summary>
/// نام طرف حساب
/// </summary>
public string FullName { get; set; }
/// <summary>
/// مبلغ بدهی
/// </summary>
public string Amount { get; set; }
public List<InstantReminderSmsList> InstantReminderSmsList { get; set; }
}
public class InstantReminderSmsList
{
/// <summary>
/// شماره تماس طرف حساب
/// </summary>
public string PhoneNumber { get; set; }
}

View File

@@ -21,6 +21,6 @@ public class LoanGroupedViewModel
{
public List<LoanGroupedByDateViewModel> GroupedByDate { get; set; }
public List<LoanGroupedByEmployeeViewModel>GroupedByEmployee { get; set; }
public List<LoanViewModel> LoanListViewModel { get; set; }
public PagedResult<LoanViewModel> LoanListViewModel { get; set; }
}

View File

@@ -16,5 +16,6 @@ public class LoanSearchViewModel
public string EndDate { get; set; }
public int PageIndex { get; set; }
public int PageSize { get; set; } = 30;
public bool ShowAsGrouped { get; set; }
}

View File

@@ -4,6 +4,8 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using _0_Framework.Application;
using CompanyManagment.App.Contracts.Workshop;
using Microsoft.AspNetCore.Mvc;
namespace CompanyManagment.App.Contracts.RollCall
{
@@ -125,7 +127,62 @@ namespace CompanyManagment.App.Contracts.RollCall
/// <param name="command"></param>
/// <returns></returns>
Task<OperationResult> RecalculateValues(long workshopId, List<ReCalculateRollCallValues> command);
Task<PagedResult<RollCallCaseHistoryTitleDto>> GetCaseHistoryTitles(long workshopId,RollCallCaseHistorySearchModel searchModel);
Task<List<RollCallCaseHistoryDetail>> GetCaseHistoryDetails(long workshopId, string titleId,
RollCallCaseHistorySearchModel searchModel);
Task<RollCallCaseHistoryExcelDto> DownloadCaseHistoryExcel(long workshopId, string titleId,
RollCallCaseHistorySearchModel searchModel);
}
public class RollCallCaseHistoryExcelDto
{
public byte[] Bytes { get; set; }
public string FileName { get; set; }
public string MimeType { get; set; }
}
public class RollCallCaseHistoryDetail
{
public string EmployeeFullName { get; set; }
public string PersonnelCode { get; set; }
public TimeSpan TotalWorkingTime { get; set; }
public List<RollCallCaseHistoryDetailRecord> Records { get; set; }
public RollCallRecordStatus Status { get; set; }
public long EmployeeId { get; set; }
}
public enum RollCallRecordStatus
{
Worked = 0,
Absent = 1,
Leaved = 2
}
public class RollCallCaseHistoryDetailRecord
{
public TimeSpan EntryTimeDifference { get; set; }
public string StartTime { get; set; }
public string EndTime { get; set; }
public TimeSpan ExitTimeDifference { get; set; }
}
public class RollCallCaseHistorySearchModel:PaginationRequest
{
public string StartDate { get; set; }
public string EndDate { get; set; }
public string OneDayDate { get; set; }
public long? EmployeeId { get; set; }
}
public class RollCallCaseHistoryTitleDto
{
public string Id { get; set; }
public string Title { get; set; }
}
public class ReCalculateRollCallValues
{
public long EmployeeId { get; set; }

View File

@@ -8,7 +8,6 @@ public class CreateSalaryAidViewModel
public long WorkshopId { get; set; }
public string Amount { get; set; }
public string SalaryDateTime { get; set; }
public string CalculationDateTime { get; set; }
public string NationalCode { get; set; }
public int CalculationMonth { get; set; }
public int CalculationYear { get; set; }

View File

@@ -8,7 +8,7 @@ public class SalaryAidGroupedByDateViewModel
public string YearFa { get; set; }
public int Month { get; set; }
public int Year { get; set; }
public List<SalaryAidGroupedByDateViewModelItems> SalaryAidViewModels { get; set; }
public List<SalaryAidGroupedByDateViewModelItems> Items { get; set; }
public string TotalAmount { get; set; }
}

View File

@@ -6,7 +6,7 @@ public class SalaryAidGroupedByEmployeeViewModel
{
public string EmployeeName { get; set; }
public long EmployeeId { get; set; }
public List<SalaryAidGroupedByEmployeeViewModelItems> SalaryAidViewModels { get; set; }
public List<SalaryAidGroupedByEmployeeViewModelItems> Items { get; set; }
public string TotalAmount { get; set; }
}

View File

@@ -3,15 +3,15 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using _0_Framework.Application;
namespace CompanyManagment.App.Contracts.SalaryAid;
public class SalaryAidSearchViewModel
public class SalaryAidSearchViewModel:PaginationRequest
{
public string StartDate { get; set; }
public string EndDate { get; set; }
public long EmployeeId { get; set; }
public long WorkshopId { get; set; }
public int PageIndex { get; set; }
public bool ShowAsGrouped { get; set; }
}

View File

@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Security.Cryptography;
using _0_Framework.Application;
namespace CompanyManagment.App.Contracts.SalaryAid;
@@ -7,7 +8,7 @@ public class SalaryAidsGroupedViewModel
{
public List<SalaryAidGroupedByEmployeeViewModel> GroupedByEmployee { get; set; }
public List<SalaryAidGroupedByDateViewModel> GroupedByDate { get; set; }
public List<SalaryAidViewModel> SalaryAidListViewModels { get; set; }
public PagedResult<SalaryAidViewModel> List { get; set; }
}

View File

@@ -60,3 +60,43 @@ public class SmsSettingViewModel
/// </summary>
public List<EditSmsSetting> EditSmsSettings { get; set; }
}
/// <summary>
/// لیست تنظیمات پیامک خودکار
/// </summary>
public class SmsSettingDto
{
/// <summary>
/// آی دی
/// </summary>
public long Id { get; set; }
/// <summary>
/// عدد روز از ماه
/// </summary>
public int DayOfMonth { get; set; }
/// <summary>
/// نمایش ساعت و دقیقه
/// </summary>
public string TimeOfDayDisplay { get; set; }
}
public class CreateSmsSettingDto
{
/// <summary>
/// عدد روز از ماه
/// </summary>
public int DayOfMonth { get; set; }
/// <summary>
/// نمایش ساعت و دقیقه
/// </summary>
public string TimeOfDayDisplay { 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,59 @@
using System;
namespace CompanyManagment.App.Contracts.SmsResult.Dto;
public class SmsReportDto
{
/// <summary>
/// تاریخ ارسال
/// </summary>
public string SentDate { get; set; }
/// <summary>
/// نوع پیامک
/// </summary>
public string TypeOfSms { 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,36 @@
using System;
using _0_Framework.Application;
using _0_Framework.Application.Enums;
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>
/// <param name="typeOfSmsSetting"></param>
/// <returns></returns>
Task<List<SmsReportListDto>> GetSmsReportExpandList(SmsReportSearchModel searchModel, string date, string typeOfSmsSetting);
#endregion
OperationResult Create(CreateSmsResult command);
List<SmsResultViewModel> Search(SmsResultSearchModel searchModel);
}

View File

@@ -75,4 +75,45 @@ public interface ISmsSettingApplication
/// <param name="command"></param>
/// <returns></returns>
Task<OperationResult> InstantSendBlockSms(List<BlockSmsListData> command);
#region ForApi
/// <summary>
/// دریافت لیست پیامک های خودکار بر اساس نوع آن
/// Api
/// </summary>
/// <param name="typeOfSmsSetting"></param>
/// <returns></returns>
Task<List<SmsSettingDto>> GetSmsSettingList(TypeOfSmsSetting typeOfSmsSetting);
/// <summary>
/// دریافت اطلاعات تنظیمات پیامک جهت ویرایش
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
Task<SmsSettingDto> GetSmsSettingDataToEdit(long id);
/// <summary>
/// ویرایش تنظیمات پیامک
/// </summary>
/// <param name="command"></param>
/// <returns></returns>
Task<OperationResult> EditSmsSetting(SmsSettingDto command);
/// <summary>
/// دریافت لیست ارسال آنی
/// </summary>
/// <returns></returns>
Task<List<InstantReminderSendSms>> GetInstantReminderSmsListData(TypeOfSmsSetting typeOfSmsSetting);
/// <summary>
/// ارسال پیامک آنی
/// </summary>
/// <param name="typeOfSmsSetting"></param>
/// <param name="phoneNumbers"></param>
/// <returns></returns>
Task<OperationResult> InstantSmsSendApi(TypeOfSmsSetting typeOfSmsSetting, List<string> phoneNumbers);
#endregion
}

View File

@@ -152,5 +152,8 @@ public class CreateWorkshop
/// </summary>
public bool IsStaticCheckout { get; set; }
/// <summary>
/// آیا پاداش در فیش حقوقی محاسبه شود
/// </summary>
public bool RewardComputeOnCheckout { get; set; }
}

View File

@@ -240,6 +240,16 @@ public class CheckoutApplication : ICheckoutApplication
command.InstallmentDeduction = loanInstallments.Sum(x => x.AmountForMonth.MoneyToDouble());
var rewards = new List<CheckoutReward>();
double rewardPay = 0;
if (command.RewardPayCompute)
{
rewards = _rollCallMandatoryRepository.RewardForCheckout(command.EmployeeId, command.WorkshopId, checkoutEnd.ToGeorgianDateTime(), checkoutStart.ToGeorgianDateTime())
.Select(x => new CheckoutReward(x.Amount, x.AmountDouble, x.GrantDateFa, x.GrantDateGr, x.Description, x.Title, x.Id)).ToList();
rewardPay = rewards.Sum(x => x.AmountDouble);
}
@@ -361,7 +371,7 @@ public class CheckoutApplication : ICheckoutApplication
var totalClaimsDouble = monthlyWage + bacicYears + consumableItem + housingAllowance + marriedAllowance + command.OvertimePay +
command.NightworkPay + familyAllowance + bunos + years + command.LeavePay + command.FridayPay + command.ShiftPay;
command.NightworkPay + familyAllowance + bunos + years + command.LeavePay + command.FridayPay + command.ShiftPay + rewardPay;
var totalClaims = totalClaimsDouble.ToMoney();
var totalDeductionDouble = insuranceDeduction + command.AbsenceDeduction + command.InstallmentDeduction + command.SalaryAidDeduction;
var totalDeductions = totalDeductionDouble.ToMoney();
@@ -386,7 +396,7 @@ public class CheckoutApplication : ICheckoutApplication
, command.OvertimePay, command.NightworkPay, command.FridayPay, 0, command.ShiftPay, familyAllowance, bunos, years, command.LeavePay, insuranceDeduction, 0, command.InstallmentDeduction, command.SalaryAidDeduction, command.AbsenceDeduction, sumOfWorkingDays,
command.ArchiveCode, command.PersonnelCode, totalClaims, totalDeductions, totalPayment, command.Signature, marriedAllowance, command.LeaveCheckout, command.CreditLeaves, command.AbsencePeriod, command.AverageHoursPerDay, command.HasRollCall, command.OverTimeWorkValue, command.OverNightWorkValue
, command.FridayWorkValue, command.RotatingShiftValue, command.AbsenceValue, command.TotalDayOfLeaveCompute, command.TotalDayOfYearsCompute, command.TotalDayOfBunosesCompute,
loanInstallments, salaryAids,checkoutRollCall,command.EmployeeMandatoryHours, hasInsuranceShareTheSameAsList);
loanInstallments, salaryAids,checkoutRollCall,command.EmployeeMandatoryHours, hasInsuranceShareTheSameAsList, rewards, rewardPay);
_checkoutRepository.CreateCkeckout(checkout).GetAwaiter().GetResult();
//_checkoutRepository.SaveChanges();

View File

@@ -13,6 +13,7 @@
<ItemGroup>
<ProjectReference Include="..\0_Framework\0_Framework.csproj" />
<ProjectReference Include="..\Company.Domain\Company.Domain.csproj" />
<ProjectReference Include="..\CompanyManagement.Infrastructure.Excel\CompanyManagement.Infrastructure.Excel.csproj" />
<ProjectReference Include="..\CompanyManagment.App.Contracts\CompanyManagment.App.Contracts.csproj" />
<ProjectReference Include="..\CompanyManagment.EFCore\CompanyManagment.EFCore.csproj" />
</ItemGroup>

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);
@@ -1511,8 +1516,9 @@ public class InstitutionContractApplication : IInstitutionContractApplication
.Where(x => x.WorkshopCreated && x.WorkshopId is > 0).ToList();
var currentWorkshops = institutionContract.WorkshopGroup.CurrentWorkshops.ToList();
var accountId = _contractingPartyRepository
.GetAccountByPersonalContractingParty(institutionContract.ContractingPartyId).Id;
var account = _contractingPartyRepository
.GetAccountByPersonalContractingParty(institutionContract.ContractingPartyId);
var accountId = account.Id;
foreach (var createdWorkshop in initialCreatedWorkshops)
{
if (currentWorkshops.Any(x => x.WorkshopId == createdWorkshop.WorkshopId))
@@ -1564,7 +1570,7 @@ public class InstitutionContractApplication : IInstitutionContractApplication
var previousInstitutionContract = await _institutionContractRepository
.GetPreviousContract(institutionContract.id);
previousInstitutionContract?.DeActive();
ReActiveAllAfterCreateNew(institutionContract.ContractingPartyId);
await _contractingPartyRepository.ActiveAllAsync(institutionContract.ContractingPartyId);
await _institutionContractRepository.SaveChangesAsync();
return op.Succcedded();
}
@@ -1820,7 +1826,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

@@ -1524,7 +1524,8 @@ public class InsuranceListApplication : IInsuranceListApplication
var dateOfBirth = employeeData.DateOfBirthGr.ToFarsi();
var dateOfIssue = employeeData.DateOfIssueGr.ToFarsi();
var leftDate = employeeData.LeftWorkDateGr != null ? employeeData.LeftWorkDateGr.Value.AddDays(-1) : new DateTime();
var workingDays = Tools.GetEmployeeInsuranceWorkingDays(employeeData.StartWorkDateGr, leftDate, startDateGr, endDateGr, employeeData.EmployeeId);
var workingDays = Tools.GetEmployeeInsuranceWorkingDays(employeeData.StartWorkDateGr, leftDate, startDateGr, endDateGr, employeeData.EmployeeId);
var leftWorkFa = workingDays.hasLeftWorkInMonth ? employeeData.LeftWorkDateGr.ToFarsi() : "";
var startWorkFa = employeeData.StartWorkDateGr.ToFarsi();
var workshop = _workShopRepository.GetDetails(workshopId);
@@ -1606,7 +1607,7 @@ public class InsuranceListApplication : IInsuranceListApplication
MaritalStatus = employeeData.MaritalStatus,
StartMonthCurrent = startMonthFa,
WorkingDays = workingDays.countWorkingDays,
WorkingDays = employeeData.WorkingDays,
StartWorkDate = startWorkFa,
StartWorkDateGr = employeeData.StartWorkDateGr,
LeftWorkDate = leftWorkFa,

View File

@@ -3,9 +3,11 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using _0_Framework.Application;
using _0_Framework.Domain.CustomizeCheckoutShared.Enums;
using _0_Framework.Exceptions;
using Company.Domain.CheckoutAgg;
using Company.Domain.CustomizeCheckoutAgg;
using Company.Domain.CustomizeCheckoutTempAgg;
@@ -16,6 +18,8 @@ using Company.Domain.LeaveAgg;
using Company.Domain.RollCallAgg;
using Company.Domain.RollCallAgg.DomainService;
using Company.Domain.RollCallEmployeeAgg;
using Company.Domain.WorkshopAgg;
using CompanyManagement.Infrastructure.Excel.RollCall;
using CompanyManagment.App.Contracts.Checkout;
using CompanyManagment.App.Contracts.Employee;
using CompanyManagment.App.Contracts.RollCall;
@@ -34,8 +38,9 @@ public class RollCallApplication : IRollCallApplication
private readonly ICustomizeWorkshopSettingsRepository _customizeWorkshopSettingsRepository;
private readonly ICustomizeWorkshopEmployeeSettingsRepository _customizeWorkshopEmployeeSettingsRepository;
private readonly ICustomizeCheckoutTempRepository _customizeCheckoutTempRepository;
private readonly IWorkshopRepository _workshopRepository;
public RollCallApplication(IRollCallRepository rollCallRepository, IRollCallEmployeeRepository rollCallEmployeeRepository, IEmployeeRepository employeeRepository, ILeaveRepository leaveRepository, ICustomizeCheckoutRepository customizeCheckoutRepository, ICheckoutRepository checkoutRepository, IRollCallDomainService rollCallDomainService, ICustomizeWorkshopSettingsRepository customizeWorkshopSettingsRepository, ICustomizeWorkshopEmployeeSettingsRepository customizeWorkshopEmployeeSettingsRepository, ICustomizeCheckoutTempRepository customizeCheckoutTempRepository)
public RollCallApplication(IRollCallRepository rollCallRepository, IRollCallEmployeeRepository rollCallEmployeeRepository, IEmployeeRepository employeeRepository, ILeaveRepository leaveRepository, ICustomizeCheckoutRepository customizeCheckoutRepository, ICheckoutRepository checkoutRepository, IRollCallDomainService rollCallDomainService, ICustomizeWorkshopSettingsRepository customizeWorkshopSettingsRepository, ICustomizeWorkshopEmployeeSettingsRepository customizeWorkshopEmployeeSettingsRepository, ICustomizeCheckoutTempRepository customizeCheckoutTempRepository, IWorkshopRepository workshopRepository)
{
_rollCallRepository = rollCallRepository;
_rollCallEmployeeRepository = rollCallEmployeeRepository;
@@ -47,7 +52,8 @@ public class RollCallApplication : IRollCallApplication
_customizeWorkshopSettingsRepository = customizeWorkshopSettingsRepository;
_customizeWorkshopEmployeeSettingsRepository = customizeWorkshopEmployeeSettingsRepository;
_customizeCheckoutTempRepository = customizeCheckoutTempRepository;
}
_workshopRepository = workshopRepository;
}
public OperationResult Create(CreateRollCall command)
{
@@ -447,8 +453,7 @@ public class RollCallApplication : IRollCallApplication
return operation.Failed("کارمند در بازه انتخاب شده مرخصی ساعتی دارد");
}
if (newRollCallDates == null || !newRollCallDates.All(x => employeeStatuses.Any(y => x.StartDate.Value.Date >= y.StartDateGr.Date && x.EndDate.Value.Date <= y.EndDateGr.Date)))
return operation.Failed("کارمند در بازه وارد شده غیر فعال است");
@@ -458,6 +463,9 @@ public class RollCallApplication : IRollCallApplication
_rollCallDomainService.GetEmployeeShiftDateByRollCallStartDate(command.WorkshopId, command.EmployeeId,
x.StartDate!.Value,x.EndDate.Value);
});
if (newRollCallDates == null || !newRollCallDates.All(x => employeeStatuses.Any(y => x.ShiftDate.Date >= y.StartDateGr.Date && x.ShiftDate.Date <= y.EndDateGr.Date)))
return operation.Failed("کارمند در بازه وارد شده غیر فعال است");
if (newRollCallDates.Any(x => x.ShiftDate.Date != date.Date))
{
@@ -487,8 +495,8 @@ public class RollCallApplication : IRollCallApplication
if (newRollCallDates == null || !newRollCallDates.All(x => employeeStatuses.Any(y => x.StartDate.Value.Date >= y.StartDateGr.Date
&& x.EndDate.Value.Date <= y.EndDateGr.Date)))
if (newRollCallDates == null || !newRollCallDates.All(x => employeeStatuses.Any(y => x.ShiftDate.Date >= y.StartDateGr.Date
&& x.ShiftDate.Date <= y.EndDateGr.Date)))
return operation.Failed("کارمند در بازه وارد شده غیر فعال است");
@@ -632,9 +640,6 @@ public class RollCallApplication : IRollCallApplication
return operation.Failed("کارمند در بازه انتخاب شده مرخصی ساعتی دارد");
}
if (newRollCallDates == null || !newRollCallDates.All(x => employeeStatuses.Any(y => x.StartDate.Value.Date >= y.StartDateGr.Date && x.EndDate.Value.Date <= y.EndDateGr.Date)))
return operation.Failed("کارمند در بازه وارد شده غیر فعال است");
newRollCallDates.ForEach(x =>
{
@@ -642,6 +647,11 @@ public class RollCallApplication : IRollCallApplication
_rollCallDomainService.GetEmployeeShiftDateByRollCallStartDate(command.WorkshopId, command.EmployeeId,
x.StartDate!.Value,x.EndDate.Value);
});
if (newRollCallDates == null || !newRollCallDates.All(x => employeeStatuses.Any(y => x.ShiftDate.Date >= y.StartDateGr.Date && x.ShiftDate.Date <= y.EndDateGr.Date)))
return operation.Failed("کارمند در بازه وارد شده غیر فعال است");
if (newRollCallDates.Any(x => x.ShiftDate.Date != date.Date))
{
return operation.Failed("حضور غیاب در حال ویرایش را نمیتوانید از تاریخ شیفت عقب تر یا جلو تر ببرید");
@@ -664,7 +674,7 @@ public class RollCallApplication : IRollCallApplication
&& (y.StartDate.Value.Date <= x.ContractEndGr.Date))))
return operation.Failed("برای بازه های وارد شده فیش حقوقی ثبت شده است");
if (newRollCallDates == null || !newRollCallDates.All(x => employeeStatuses.Any(y => x.StartDate.Value.Date >= y.StartDateGr.Date && x.EndDate.Value.Date <= y.EndDateGr.Date)))
if (newRollCallDates == null || !newRollCallDates.All(x => employeeStatuses.Any(y => x.ShiftDate.Date >= y.StartDateGr.Date && x.ShiftDate.Date <= y.EndDateGr.Date)))
return operation.Failed("کارمند در بازه وارد شده غیر فعال است");
var currentDayRollCall = employeeRollCalls.FirstOrDefault(x => x.EndDate == null);
@@ -858,4 +868,58 @@ public class RollCallApplication : IRollCallApplication
}
}
public async Task<PagedResult<RollCallCaseHistoryTitleDto>> GetCaseHistoryTitles(long workshopId,
RollCallCaseHistorySearchModel searchModel)
{
return await _rollCallRepository.GetCaseHistoryTitles(workshopId,searchModel);
}
public async Task<List<RollCallCaseHistoryDetail>> GetCaseHistoryDetails(long workshopId,
string titleId, RollCallCaseHistorySearchModel searchModel)
{
return await _rollCallRepository.GetCaseHistoryDetails(workshopId, titleId, searchModel);
}
public async Task<RollCallCaseHistoryExcelDto> DownloadCaseHistoryExcel(long workshopId, string titleId,
RollCallCaseHistorySearchModel searchModel)
{
var data = await _rollCallRepository
.GetCaseHistoryDetails(workshopId, titleId, searchModel);
string nameSecondPart = "";
byte[] excelBytes;
if (Regex.IsMatch(titleId, @"^\d{4}_\d{2}$"))
{
var splitDate = titleId.Split("_");
var year = Convert.ToInt32(splitDate.First());
var month = Convert.ToInt32(splitDate.Last());
var monthName = Convert.ToInt32(month).ToFarsiMonthByIntNumber();
nameSecondPart = $"{year}/{monthName}";
excelBytes = RollCallExcelGenerator.CaseHistoryExcelForEmployee(data, titleId);
}
else if (Regex.IsMatch(titleId, @"^\d{4}/\d{2}/\d{2}$"))
{
var oneDayDate = titleId.ToGeorgianDateTime();
nameSecondPart = $" {oneDayDate.DayOfWeek.DayOfWeeKToPersian()}،{titleId}";
excelBytes = RollCallExcelGenerator.CaseHistoryExcelForOneDay(data, titleId);
}
else
{
throw new BadRequestException("شناسه سر تیتر وارد شده نامعتبر است");
}
var workshopFullName = _workshopRepository.Get(workshopId)?.WorkshopFullName ?? "بدون کارگاه";
var fileName = $"{workshopFullName} - {nameSecondPart}.xlsx";
var res = new RollCallCaseHistoryExcelDto()
{
Bytes = excelBytes,
MimeType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
FileName = fileName
};
return res;
}
}

View File

@@ -1,20 +1,45 @@
using System.Collections.Generic;
using System.Linq;
using _0_Framework.Application;
using _0_Framework.Application;
using _0_Framework.Application.Enums;
using _0_Framework.Application.Sms;
using Company.Domain.SmsResultAgg;
using CompanyManagment.App.Contracts.SmsResult;
using CompanyManagment.App.Contracts.SmsResult.Dto;
using CompanyManagment.EFCore.Services;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using SmsResult = Company.Domain.SmsResultAgg.SmsResult;
namespace CompanyManagment.Application;
public class SmsResultApplication : ISmsResultApplication
{
private readonly ISmsResultRepository _smsResultRepository;
private readonly ISmsService _smsService;
public SmsResultApplication(ISmsResultRepository smsResultRepository)
public SmsResultApplication(ISmsResultRepository smsResultRepository, ISmsService smsService)
{
_smsResultRepository = smsResultRepository;
_smsService = smsService;
}
#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, string typeOfSmsSetting)
{
return await _smsResultRepository.GetSmsReportExpandList(searchModel, date, typeOfSmsSetting);
}
#endregion
public OperationResult Create(CreateSmsResult command)
{
var op = new OperationResult();
@@ -48,4 +73,6 @@ public class SmsResultApplication : ISmsResultApplication
}).ToList();
return result;
}
}

View File

@@ -1,13 +1,16 @@
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;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Hosting;
namespace CompanyManagment.Application;
@@ -15,11 +18,16 @@ public class SmsSettingApplication : ISmsSettingApplication
{
private readonly ISmsSettingsRepository _smsSettingsRepository;
private readonly IInstitutionContractRepository _institutionContractRepository;
private readonly IInstitutionContractSmsServiceRepository _institutionContractSmsServiceRepository;
private readonly IHostEnvironment _hostEnvironment;
public SmsSettingApplication(ISmsSettingsRepository smsSettingsRepository, IInstitutionContractRepository institutionContractRepository)
public SmsSettingApplication(ISmsSettingsRepository smsSettingsRepository, IInstitutionContractRepository institutionContractRepository, IInstitutionContractSmsServiceRepository institutionContractSmsServiceRepository, IHostEnvironment hostEnvironment)
{
_smsSettingsRepository = smsSettingsRepository;
_institutionContractRepository = institutionContractRepository;
_institutionContractSmsServiceRepository = institutionContractSmsServiceRepository;
_hostEnvironment = hostEnvironment;
}
@@ -116,25 +124,31 @@ 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);
}
public async Task<OperationResult> InstantSendReminderSms(List<SmsListData> command)
{
var op = new OperationResult();
if (_hostEnvironment.IsDevelopment())
{
return op.Failed(" در محیط توسعه امکان ارسال وجود ندارد ");
}
string typeOfSms = "یادآور بدهی ماهانه";
string sendMessStart = "شروع یادآور آنی";
string sendMessEnd = "پایان یادآور آنی";
if (command.Any())
{
await _institutionContractRepository.SendReminderSmsToContractingParties(command, typeOfSms, sendMessStart, sendMessEnd);
await _institutionContractSmsServiceRepository.SendReminderSmsToContractingParties(command, typeOfSms, sendMessStart, sendMessEnd);
return op.Succcedded();
}
else
@@ -148,12 +162,19 @@ public class SmsSettingApplication : ISmsSettingApplication
public async Task<OperationResult> InstantSendBlockSms(List<BlockSmsListData> command)
{
var op = new OperationResult();
if (_hostEnvironment.IsDevelopment())
{
return op.Failed(" در محیط توسعه امکان ارسال وجود ندارد ");
}
string typeOfSms = "اعلام مسدودی طرف حساب";
string sendMessStart = "شروع مسدودی آنی";
string sendMessEnd = "پایان مسدودی آنی ";
if (command.Any())
{
await _institutionContractRepository.SendBlockSmsToContractingParties(command, typeOfSms, sendMessStart,
await _institutionContractSmsServiceRepository.SendBlockSmsToContractingParties(command, typeOfSms, sendMessStart,
sendMessEnd);
return op.Succcedded();
}
@@ -162,4 +183,166 @@ public class SmsSettingApplication : ISmsSettingApplication
return op.Failed("موردی انتخاب نشده است");
}
}
#region ForApi
/// <summary>
/// دریافت لیست پیامک های خودکار بر اساس نوع آن
/// Api
/// </summary>
/// <param name="typeOfSmsSetting"></param>
/// <returns></returns>
public async Task<List<SmsSettingDto>> GetSmsSettingList(TypeOfSmsSetting typeOfSmsSetting)
{
return await _smsSettingsRepository.GetSmsSettingList(typeOfSmsSetting);
}
public async Task<SmsSettingDto> GetSmsSettingDataToEdit(long id)
{
return await _smsSettingsRepository.GetSmsSettingDataToEdit(id);
}
public async Task<OperationResult> EditSmsSetting(SmsSettingDto command)
{
var op = new OperationResult();
var editSmsSetting = _smsSettingsRepository.Get(command.Id);
var timeSpan = new TimeSpan();
if (string.IsNullOrWhiteSpace(command.TimeOfDayDisplay))
return op.Failed("ساعت وارد نشده است");
try
{
timeSpan = TimeSpan.ParseExact(command.TimeOfDayDisplay, @"hh\:mm", null);
}
catch (Exception e)
{
return op.Failed("فرمت ساعت اشتباه است");
}
if (command.DayOfMonth < 1 || command.DayOfMonth > 31)
{
return op.Failed("عدد روز می بایست بین 1 تا 31 باشد");
}
if (_smsSettingsRepository.Exists(x => x.DayOfMonth == command.DayOfMonth && x.TimeOfDay == timeSpan && x.TypeOfSmsSetting == editSmsSetting.TypeOfSmsSetting && x.id != command.Id))
return op.Failed("رکورد ایجاد شده تکراری است");
editSmsSetting.Edit(command.DayOfMonth, timeSpan);
await _smsSettingsRepository.SaveChangesAsync();
return op.Succcedded();
}
public async Task<List<InstantReminderSendSms>> GetInstantReminderSmsListData(TypeOfSmsSetting typeOfSmsSetting)
{
var result = new List<InstantReminderSendSms>();
if (typeOfSmsSetting == TypeOfSmsSetting.InstitutionContractDebtReminder)
{
var data = await _institutionContractSmsServiceRepository.GetSmsListData(DateTime.Now, TypeOfSmsSetting.InstitutionContractDebtReminder);
if (data.Any())
{
result = data.GroupBy(x => x.PartyName).Select(m => new InstantReminderSendSms()
{
FullName = m.Key,
Amount = m.Select(c => c.Amount).First(),
InstantReminderSmsList = m.Select(c => new InstantReminderSmsList()
{
PhoneNumber = c.PhoneNumber,
}).ToList()
}).ToList();
}
}
if (typeOfSmsSetting == TypeOfSmsSetting.BlockContractingParty)
{
var data = await _institutionContractSmsServiceRepository.GetBlockListData(DateTime.Now);
if (data.Any())
{
result = data.GroupBy(x => x.PartyName).Select(m => new InstantReminderSendSms()
{
FullName = m.Key,
Amount = m.Select(c => c.Amount).First(),
InstantReminderSmsList = m.Select(c => new InstantReminderSmsList()
{
PhoneNumber = c.PhoneNumber,
}).ToList()
}).ToList();
}
}
return result;
}
public async Task<OperationResult> InstantSmsSendApi(TypeOfSmsSetting typeOfSmsSetting, List<string> phoneNumbers)
{
var op = new OperationResult();
if (_hostEnvironment.IsDevelopment())
{
var str = "";
foreach (var item in phoneNumbers)
{
str += $" {item}, ";
}
return op.Failed(" در محیط توسعه امکان ارسال وجود ندارد " + " لیست ارسال شما " + str);
}
if (typeOfSmsSetting == TypeOfSmsSetting.InstitutionContractDebtReminder)
{
if (phoneNumbers.Any())
{
var data = await _institutionContractSmsServiceRepository.GetSmsListData(DateTime.Now, TypeOfSmsSetting.InstitutionContractDebtReminder);
if (data.Any())
{
phoneNumbers = phoneNumbers.Where(x => x.Length == 11).ToList();
var sendItems = data.Where(x => phoneNumbers.Contains(x.PhoneNumber)).ToList();
var res = await InstantSendReminderSms(sendItems);
return res;
}
return op.Succcedded();
}
return op.Failed("موردی انتخاب نشده است");
}
if (typeOfSmsSetting == TypeOfSmsSetting.BlockContractingParty)
{
if (phoneNumbers.Any())
{
var data = await _institutionContractSmsServiceRepository.GetBlockListData(DateTime.Now);
if (data.Any())
{
phoneNumbers = phoneNumbers.Where(x => x.Length == 11).ToList();
var sendItems = data.Where(x => phoneNumbers.Contains(x.PhoneNumber)).ToList();
var res = await InstantSendBlockSms(sendItems);
return res;
}
return op.Succcedded();
}
return op.Failed("موردی انتخاب نشده است");
}
return op.Failed("خطای انتخاب نوع ارسال");
}
#endregion
}

View File

@@ -34,7 +34,7 @@ class CheckoutMapping : IEntityTypeConfiguration<Checkout>
builder.Property(x => x.FamilyAllowance);
builder.Property(x => x.HousingAllowance);
builder.Property(x => x.ConsumableItems);
builder.Property(x => x.RewardPay).HasColumnType("float").IsRequired(false);
builder.Property(x => x.RewardPay);
builder.Property(x => x.LeaveCheckout);
builder.Property(x => x.CreditLeaves);
@@ -82,6 +82,15 @@ class CheckoutMapping : IEntityTypeConfiguration<Checkout>
salaryAid.Property(x => x.CalculationDateTimeFa).HasMaxLength(15);
});
builder.OwnsMany(x => x.Rewards, reward =>
{
reward.Property(x => x.Description).HasColumnType("ntext");
reward.Property(x => x.Title).HasMaxLength(255);
reward.Property(x=> x.Amount).HasMaxLength(25);
reward.Property(x => x.GrantDateFa).HasMaxLength(10);
});
builder.OwnsOne(x => x.CheckoutRollCall, rollCall =>
{
rollCall.Property(x => x.TotalPresentTimeSpan).HasTimeSpanConversion();

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,66 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace CompanyManagment.EFCore.Migrations
{
/// <inheritdoc />
public partial class AddRewardtocheckout : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<double>(
name: "RewardPay",
table: "Checkouts",
type: "float",
nullable: false,
defaultValue: 0.0,
oldClrType: typeof(double),
oldType: "float",
oldNullable: true);
migrationBuilder.CreateTable(
name: "CheckoutReward",
columns: table => new
{
Checkoutid = table.Column<long>(type: "bigint", nullable: false),
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Amount = table.Column<string>(type: "nvarchar(25)", maxLength: 25, nullable: true),
AmountDouble = table.Column<double>(type: "float", nullable: false),
GrantDateFa = table.Column<string>(type: "nvarchar(10)", maxLength: 10, nullable: true),
GrantDateGr = table.Column<DateTime>(type: "datetime2", nullable: false),
Description = table.Column<string>(type: "ntext", nullable: true),
Title = table.Column<string>(type: "nvarchar(255)", maxLength: 255, nullable: true),
EntityId = table.Column<long>(type: "bigint", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_CheckoutReward", x => new { x.Checkoutid, x.Id });
table.ForeignKey(
name: "FK_CheckoutReward_Checkouts_Checkoutid",
column: x => x.Checkoutid,
principalTable: "Checkouts",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "CheckoutReward");
migrationBuilder.AlterColumn<double>(
name: "RewardPay",
table: "Checkouts",
type: "float",
nullable: true,
oldClrType: typeof(double),
oldType: "float");
}
}
}

View File

@@ -635,7 +635,7 @@ namespace CompanyManagment.EFCore.Migrations
.HasMaxLength(10)
.HasColumnType("nvarchar(10)");
b.Property<double?>("RewardPay")
b.Property<double>("RewardPay")
.HasColumnType("float");
b.Property<string>("RotatingShiftValue")
@@ -7501,6 +7501,49 @@ namespace CompanyManagment.EFCore.Migrations
.HasForeignKey("Checkoutid");
});
b.OwnsMany("Company.Domain.CheckoutAgg.ValueObjects.CheckoutReward", "Rewards", b1 =>
{
b1.Property<long>("Checkoutid")
.HasColumnType("bigint");
b1.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property<int>("Id"));
b1.Property<string>("Amount")
.HasMaxLength(25)
.HasColumnType("nvarchar(25)");
b1.Property<double>("AmountDouble")
.HasColumnType("float");
b1.Property<string>("Description")
.HasColumnType("ntext");
b1.Property<long>("EntityId")
.HasColumnType("bigint");
b1.Property<string>("GrantDateFa")
.HasMaxLength(10)
.HasColumnType("nvarchar(10)");
b1.Property<DateTime>("GrantDateGr")
.HasColumnType("datetime2");
b1.Property<string>("Title")
.HasMaxLength(255)
.HasColumnType("nvarchar(255)");
b1.HasKey("Checkoutid", "Id");
b1.ToTable("CheckoutReward");
b1.WithOwner()
.HasForeignKey("Checkoutid");
});
b.OwnsMany("Company.Domain.CheckoutAgg.ValueObjects.CheckoutSalaryAid", "SalaryAids", b1 =>
{
b1.Property<long>("Checkoutid")
@@ -7545,6 +7588,8 @@ namespace CompanyManagment.EFCore.Migrations
b.Navigation("LoanInstallments");
b.Navigation("Rewards");
b.Navigation("SalaryAids");
b.Navigation("Workshop");

View File

@@ -531,6 +531,7 @@ public class CheckoutRepository : RepositoryBase<long, Checkout>, ICheckoutRepos
entity.SetSalaryAid(command.SalaryAids, command.SalaryAidDeduction);
entity.SetLoanInstallment(command.LoanInstallments, command.InstallmentDeduction);
entity.SetReward(command.Rewards,command.RewardPay);
entity.SetCheckoutRollCall(command.CheckoutRollCall);
entity.SetEmployeeMandatoryHours(command.EmployeeMandatoryHours);
if(command.HasInsuranceShareTheSameAsList)
@@ -934,7 +935,7 @@ public class CheckoutRepository : RepositoryBase<long, Checkout>, ICheckoutRepos
TotalClaims = item.TotalClaims,
TotalDeductions = item.TotalDeductions,
TotalPayment = item.TotalPayment.ToMoney(),
RewardPay = item.RewardPay.ToMoneyNullable(),
RewardPay = item.RewardPay.ToMoney(),
ContractStartGr = item.ContractStart,
ContractEndGr = item.ContractEnd,
IsLeft = false,
@@ -1335,7 +1336,7 @@ public class CheckoutRepository : RepositoryBase<long, Checkout>, ICheckoutRepos
TotalClaims = x.TotalClaims,
TotalDeductions = x.TotalDeductions,
TotalPayment = x.TotalPayment.ToMoney(),
RewardPay = x.RewardPay.ToMoneyNullable(),
RewardPay = x.RewardPay.ToMoney(),
ContractStartGr = x.ContractStart,
ContractEndGr = x.ContractEnd,
IsLeft = false,

View File

@@ -713,10 +713,15 @@ public class EmployeeDocumentsRepository : RepositoryBase<long, EmployeeDocument
var itemsQuery = _companyContext.EmployeeDocumentItems
.Where(x => x.DocumentStatus != DocumentStatus.Unsubmitted)
.Include(x => x.EmployeeDocuments)
.ThenInclude(x => x.Workshop).ThenInclude(x => x.WorkshopEmployers).ThenInclude(x => x.Employer)
.GroupBy(x => x.WorkshopId).Select(x => new WorkshopWithEmployeeDocumentsViewModel()
.ThenInclude(x => x.Workshop)
.ThenInclude(x => x.WorkshopEmployers)
.ThenInclude(x => x.Employer)
.GroupBy(x => x.WorkshopId)
.Select(x => new WorkshopWithEmployeeDocumentsViewModel()
{
SubmittedItemsCount = x.Count(y => y.DocumentStatus == DocumentStatus.SubmittedByAdmin || y.DocumentStatus == DocumentStatus.SubmittedByClient),
SubmittedItemsCount = x
.Count(y => y.DocumentStatus == DocumentStatus.SubmittedByAdmin
|| y.DocumentStatus == DocumentStatus.SubmittedByClient),
WorkshopId = x.Key,
WorkshopFullName = x.First().EmployeeDocuments.Workshop.WorkshopName,
EmployerName = x.First().EmployeeDocuments.Workshop.WorkshopEmployers.First().Employer.FullName

File diff suppressed because it is too large Load Diff

View File

@@ -171,22 +171,29 @@ public class LoanRepository : RepositoryBase<long, Loan>, ILoanRepository
query = query.Where(x => x.StartInstallmentPayment >= startDate && x.StartInstallmentPayment <= endDate);
}
result.LoanListViewModel = query.OrderByDescending(x => x.StartInstallmentPayment).Skip(searchModel.PageIndex)
.Take(30).ToList()
.Select(x => new LoanViewModel()
{
EmployeeFullName = employees.FirstOrDefault(e => e.id == x.EmployeeId).FullName,
PersonnelCode = personnelCodes.FirstOrDefault(p => p.EmployeeId == x.EmployeeId).PersonnelCode.ToString(),
Amount = x.Amount.ToMoney(),
AmountPerMonth = x.AmountPerMonth.ToMoney(),
StartDateTime = x.StartInstallmentPayment.ToFarsi(),
Count = x.Count,
Id = x.id,
WorkshopId = x.WorkshopId,
EmployeeId = x.EmployeeId,
YearFa = pc.GetYear(x.StartInstallmentPayment).ToString(),
result.LoanListViewModel = new PagedResult<LoanViewModel>()
{
TotalCount = query.Count(),
List = query.OrderByDescending(x => x.StartInstallmentPayment)
.ApplyPagination(searchModel.PageIndex, searchModel.PageSize)
.Take(30).ToList()
.Select(x => new LoanViewModel()
{
EmployeeFullName = employees.FirstOrDefault(e => e.id == x.EmployeeId).FullName,
PersonnelCode = personnelCodes.FirstOrDefault(p => p.EmployeeId == x.EmployeeId).PersonnelCode
.ToString(),
Amount = x.Amount.ToMoney(),
AmountPerMonth = x.AmountPerMonth.ToMoney(),
StartDateTime = x.StartInstallmentPayment.ToFarsi(),
Count = x.Count,
Id = x.id,
WorkshopId = x.WorkshopId,
EmployeeId = x.EmployeeId,
YearFa = pc.GetYear(x.StartInstallmentPayment).ToString(),
}).ToList()
};
}).ToList();
return result;
}

View File

@@ -245,7 +245,7 @@ public class PersonalContractingPartyRepository : RepositoryBase<long, PersonalC
return new();
}
return _accountContext.Accounts.Where(x => x.id == accId && x.IsActiveString == "true").Select(x =>
return _accountContext.Accounts.Where(x => x.id == accId).Select(x =>
new AccountViewModel()
{
Id = x.id,
@@ -845,8 +845,7 @@ public class PersonalContractingPartyRepository : RepositoryBase<long, PersonalC
public async Task<OperationResult> ActiveAllAsync(long id)
{
OperationResult result = new OperationResult();
await using var transaction =await _context.Database.BeginTransactionAsync();
await using var accountTransaction = await _accountContext.Database.BeginTransactionAsync();
try
{
var personel = _context.PersonalContractingParties
@@ -890,15 +889,12 @@ public class PersonalContractingPartyRepository : RepositoryBase<long, PersonalC
}
await _context.SaveChangesAsync();
await transaction.CommitAsync();
await accountTransaction.CommitAsync();
result.Succcedded();
}
catch (Exception)
{
result.Failed("فعال کردن طرف حساب با خطا مواجه شد");
await transaction.RollbackAsync();
await accountTransaction.RollbackAsync();
}
return result;

View File

@@ -74,7 +74,7 @@ public class ReportClientRepository : IReportClientRepository
TotalClaims = x.TotalClaims,
TotalDeductions = x.TotalDeductions,
TotalPayment = x.TotalPayment.ToMoney(),
RewardPay = x.RewardPay.ToMoneyNullable(),
RewardPay = x.RewardPay.ToMoney(),
MarriedAllowance = x.MarriedAllowance.ToMoney(),
}).Where(x => x.WorkshopId == workshopId);
@@ -448,7 +448,7 @@ public class ReportClientRepository : IReportClientRepository
TotalClaims = x.TotalClaims,
TotalDeductions = x.TotalDeductions,
TotalPayment = x.TotalPayment.ToMoney(),
RewardPay = x.RewardPay.ToMoneyNullable(),
RewardPay = x.RewardPay.ToMoney(),
MarriedAllowance = x.MarriedAllowance.ToMoney(),
}).Where(x => x.WorkshopId == workshopId);

View File

@@ -82,25 +82,45 @@ public class RollCallEmployeeRepository : RepositoryBase<long, RollCallEmployee>
var service = _rollCallServiceRepository.GetAllServiceByWorkshopId(workshopId);
//اگر سرویس حضور غیاب نداشت
if (!service.Any(x => x.StartService.Date <= contractStart.Date && x.EndService.Date >= contractEnd.Date))
return false;
//var rollCallEmployee = GetByEmployeeIdAndWorkshopId(employeeId, workshopId);
//if (rollCallEmployee == null)
// return false;
var rollCallEmployee = _context.RollCallEmployees
.Where(x => x.EmployeeId == employeeId && x.WorkshopId == workshopId)
.Include(x => x.EmployeesStatus);
if (!rollCallEmployee.Any())
var rollCallEmployee = _context.RollCallEmployees.Include(xs => xs.EmployeesStatus)
.FirstOrDefault(x => x.EmployeeId == employeeId && x.WorkshopId == workshopId);
//اگر تنظیمات حضور غیاب نداشت
if (rollCallEmployee == null)
return false;
var a = rollCallEmployee.Any(x => x.EmployeesStatus.Any(s =>
(s.StartDate <= contractStart.Date && s.EndDate.Date >= contractEnd.Date) ||
(s.StartDate.Date <= contractStart.Date && s.EndDate.Date > contractStart.Date)));
//var result = _employeeRollCallStatusRepository.w(x => x.RollCallEmployeeId == rollCallEmployee.Id &&
// (x.StartDate.Date <= contractStart.Date && x.EndDate.Date >= contractEnd.Date) ||
// (x.StartDate.Date <= contractStart.Date && x.EndDate.Date > contractStart.Date));
return a;
//اگر استاتوس نداشت
if (!rollCallEmployee.EmployeesStatus.Any())
return false;
var leftWork =
_context.LeftWorkList.FirstOrDefault(x =>
x.StartWorkDate <= contractEnd.Date && x.LeftWorkDate > contractStart);
if (leftWork == null)
return false;
var status = rollCallEmployee.EmployeesStatus.FirstOrDefault(s =>
(s.StartDate <= contractStart.Date && s.EndDate.Date >= contractEnd.Date));
//اگر استاتوس کامل پوشش داد
if (status != null)
return true;
status = rollCallEmployee.EmployeesStatus.FirstOrDefault(s =>
(s.StartDate.Date <= contractStart.Date && s.EndDate.Date > contractStart.Date &&
s.EndDate.Date < contractEnd.Date));
//اگر قبل از پایان فیس استاتوس قطع شده ولی ترک کار داره
if (status != null && leftWork.HasLeft)
return true;
return false;
}
public List<RollCallEmployeeViewModel> GetByWorkshopId(long workshopId)
{

View File

@@ -5199,10 +5199,10 @@ public class RollCallMandatoryRepository : RepositoryBase<long, RollCall>, IRoll
};
}
private List<RewardViewModel> RewardForCheckout(long employeeId, long workshopId, DateTime checkoutEnd,
public List<RewardViewModel> RewardForCheckout(long employeeId, long workshopId, DateTime checkoutEnd,
DateTime checkoutStart)
{
return _context.Rewards.Where(x =>
var result = _context.Rewards.Where(x =>
x.WorkshopId == workshopId && x.EmployeeId == employeeId && x.GrantDate <= checkoutEnd &&
x.GrantDate >= checkoutStart).Select(x => new RewardViewModel
{
@@ -5215,6 +5215,8 @@ public class RollCallMandatoryRepository : RepositoryBase<long, RollCall>, IRoll
IsActive = x.IsActive,
Id = x.id
}).ToList();
return result;
}
private List<FineViewModel> FinesForCheckout(long employeeId, long workshopId, DateTime contractStart,

File diff suppressed because it is too large Load Diff

View File

@@ -146,7 +146,7 @@ public class SalaryAidRepository : RepositoryBase<long, SalaryAid>, ISalaryAidRe
{
YearFa = x.Key.YearFa,
MonthFa = x.Key.MonthFa,
SalaryAidViewModels = x.Select(s => new SalaryAidGroupedByDateViewModelItems()
Items = x.Select(s => new SalaryAidGroupedByDateViewModelItems()
{
Amount = s.Amount,
EmployeeName = s.EmployeeFullName,
@@ -175,7 +175,7 @@ public class SalaryAidRepository : RepositoryBase<long, SalaryAid>, ISalaryAidRe
EmployeeId = x.Key,
TotalAmount = x.Sum(s => s.Amount).ToMoney(),
EmployeeName = employees.FirstOrDefault(e => e.id == x.Key).FullName,
SalaryAidViewModels = x.Select(s => new SalaryAidGroupedByEmployeeViewModelItems()
Items = x.Select(s => new SalaryAidGroupedByEmployeeViewModelItems()
{
Amount = s.Amount.ToMoney(),
Id = s.id,
@@ -197,22 +197,27 @@ public class SalaryAidRepository : RepositoryBase<long, SalaryAid>, ISalaryAidRe
query = query.Where(x => x.SalaryAidDateTime >= startDate && x.SalaryAidDateTime <= endDate);
}
result.SalaryAidListViewModels = query.OrderByDescending(x => x.SalaryAidDateTime).Skip(searchModel.PageIndex).Take(30).ToList().Select(
x => new SalaryAidViewModel()
{
Amount = x.Amount.ToMoney(),
CreationDate = x.CreationDate.ToFarsi(),
Id = x.id,
EmployeeId = x.EmployeeId,
SalaryAidDateTimeFa = x.SalaryAidDateTime.ToFarsi(),
SalaryAidDateTimeGe = x.SalaryAidDateTime,
WorkshopId = x.WorkshopId,
YearFa = x.SalaryAidDateTime.ToFarsi().Substring(0, 4),
MonthFa = x.SalaryAidDateTime.ToFarsi().Substring(5, 2),
PersonnelCode = personnelCodes.FirstOrDefault(p => p.EmployeeId == x.EmployeeId).PersonnelCode.ToString(),
EmployeeFullName = employees.FirstOrDefault(e => e.id == x.EmployeeId).FullName,
AmountDouble = x.Amount,
}).ToList();
result.List = new PagedResult<SalaryAidViewModel>()
{
TotalCount = query.Count(),
List = query.OrderByDescending(x => x.SalaryAidDateTime).ApplyPagination(searchModel.PageIndex,searchModel.PageSize).ToList()
.Select(x => new SalaryAidViewModel()
{
Amount = x.Amount.ToMoney(),
CreationDate = x.CreationDate.ToFarsi(),
Id = x.id,
EmployeeId = x.EmployeeId,
SalaryAidDateTimeFa = x.SalaryAidDateTime.ToFarsi(),
SalaryAidDateTimeGe = x.SalaryAidDateTime,
WorkshopId = x.WorkshopId,
YearFa = x.SalaryAidDateTime.ToFarsi().Substring(0, 4),
MonthFa = x.SalaryAidDateTime.ToFarsi().Substring(5, 2),
PersonnelCode = personnelCodes.FirstOrDefault(p => p.EmployeeId == x.EmployeeId).PersonnelCode
.ToString(),
EmployeeFullName = employees.FirstOrDefault(e => e.id == x.EmployeeId).FullName,
AmountDouble = x.Amount,
}).ToList()
};
return result;
}

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,6 +22,235 @@ 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.SendInstitutionContractConfirmationLink:
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, x.TypeOfSms))
.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(),
TypeOfSms = x.TypeOfSms
}).ToList();
return result;
}
public async Task<List<SmsReportListDto>> GetSmsReportExpandList(SmsReportSearchModel searchModel, string date, string typeOfSmsSetting)
{
if(string.IsNullOrWhiteSpace(date) || string.IsNullOrWhiteSpace(typeOfSmsSetting))
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 (typeOfSmsSetting.Contains("هشدار"))
{
query = query.Where(x => x.TypeOfSms.Contains("هشدار")).ToList();
}
query = query.Where(x => x.TypeOfSms == typeOfSmsSetting).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)
{
@@ -82,7 +318,7 @@ 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

@@ -1,4 +1,5 @@
using System.Linq;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using _0_Framework.Application.Enums;
using _0_Framework.InfraStructure;
@@ -68,4 +69,48 @@ public class SmsSettingsRepository : RepositoryBase<long, SmsSetting>, ISmsSetti
_context.SmsSettings.Remove(removeItem);
await _context.SaveChangesAsync();
}
#region ForApi
/// <summary>
/// دریافت لیست پیامک های خودکار بر اساس نوع آن
/// Api
/// </summary>
/// <param name="typeOfSmsSetting"></param>
/// <returns></returns>
public async Task<List<SmsSettingDto>> GetSmsSettingList(TypeOfSmsSetting typeOfSmsSetting)
{
var data = await _context.SmsSettings
.Where(x => x.TypeOfSmsSetting == typeOfSmsSetting)
.OrderBy(x => x.DayOfMonth).ThenBy(x => x.TimeOfDay)
.Select(x =>
new SmsSettingDto()
{
Id = x.id,
DayOfMonth = x.DayOfMonth,
TimeOfDayDisplay = x.TimeOfDay.ToString(@"hh\:mm")
}).ToListAsync();
return data;
}
public async Task<SmsSettingDto> GetSmsSettingDataToEdit(long id)
{
var edit = new SmsSettingDto();
var getItem = await _context.SmsSettings.FirstOrDefaultAsync(x => x.id == id);
if (getItem != null)
{
edit.Id = getItem.id;
edit.TimeOfDayDisplay = getItem.TimeOfDay.ToString(@"hh\:mm");
edit.DayOfMonth = getItem.DayOfMonth;
}
return edit;
}
#endregion
}

View File

@@ -160,7 +160,9 @@ public class WorkshopRepository : RepositoryBase<long, Company.Domain.WorkshopAg
public EditWorkshop GetDetails(long id)
{
var emp = _context.WorkshopEmployers.Where(x => x.WorkshopId == id)
.Select(x => x.EmployerId).ToList();
.Select(x => x.Employer).ToList();
var contractingPart = emp.Select(x => x.ContractingPartyId).ToList();
bool rewardCompute = contractingPart.Any(x=>x == 30804);
return _context.Workshops.Select(x => new EditWorkshop
{
Id = x.id,
@@ -193,7 +195,7 @@ public class WorkshopRepository : RepositoryBase<long, Company.Domain.WorkshopAg
BonusesOptions = string.IsNullOrWhiteSpace(x.BonusesOptions) ? "EndOfContract1402leftWork1403" : x.BonusesOptions,
YearsOptions = x.YearsOptions,
IsOldContract = x.IsOldContract,
EmployerIdList = emp,
EmployerIdList = emp.Select(e=>e.id).ToList(),
HasRollCallFreeVip = x.HasRollCallFreeVip,
WorkshopHolidayWorking = x.WorkshopHolidayWorking,
InsuranceCheckoutOvertime = x.InsuranceCheckoutOvertime,
@@ -205,6 +207,7 @@ public class WorkshopRepository : RepositoryBase<long, Company.Domain.WorkshopAg
SignCheckout = x.SignCheckout,
RotatingShiftCompute = x.RotatingShiftCompute,
IsStaticCheckout = x.IsStaticCheckout,
RewardComputeOnCheckout = rewardCompute
}).FirstOrDefault(x => x.Id == id);
}

View File

@@ -2859,7 +2859,7 @@ public class YearlySalaryRepository : RepositoryBase<long, YearlySalary>, IYearl
var contactCanToleaveList = new List<ContractsCanToLeave>();
var allContractsBetween = _context.Contracts.AsSplitQuery().Include(x => x.WorkingHoursList)
.Where(x => x.WorkshopIds == workshopId && x.EmployeeId == employeeId &&
x.ContractEnd >= startDate && x.ContarctStart <= endDate).ToList();
x.ContractEnd >= startDate && x.ContarctStart <= endDate).OrderBy(x=>x.ContarctStart).ToList();
var isWorkshopStaticCheckout = _context.Workshops.FirstOrDefault(x => x.id == workshopId)!.IsStaticCheckout;
int mandatoryDays = 0;
double allCanToLeave = 0;

View File

@@ -205,18 +205,33 @@ public class SmsService : ISmsService
};
return appendData;
}
public async Task<SmsDetailsDto> GetSmsDetailsByMessageId(int messId, string fullName)
{
SmsIr smsIr = new SmsIr("Og5M562igmzJRhQPnq0GdtieYdLgtfikjzxOmeQBPxJjZtyge5Klc046Lfw1mxSa");
var response = await smsIr.GetReportAsync(messId);
MessageReportResult messages = response.Data;
var appendData = new SmsDetailsDto()
{
Mobile = messages.Mobile,
MessageText = messages.MessageText,
SendUnixTime = UnixTimeStampToDateTime(messages.SendDateTime),
DeliveryState = DeliveryStatus(messages.DeliveryState),
DeliveryUnixTime = UnixTimeStampToDateTime(messages.DeliveryDateTime),
DeliveryColor = DeliveryColorStatus(messages.DeliveryState),
FullName = fullName
};
return appendData;
}
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 +263,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

@@ -89,6 +89,9 @@ EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BackgroundInstitutionContract.Task", "BackgroundInstitutionContract\BackgroundInstitutionContract.Task\BackgroundInstitutionContract.Task.csproj", "{F78FBB92-294B-88BA-168D-F0C578B0D7D6}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ProgramManager", "ProgramManager", "{67AFF7B6-4C4F-464C-A90D-9BDB644D83A9}"
ProjectSection(SolutionItems) = preProject
ProgramManager\appsettings.FileStorage.json = ProgramManager\appsettings.FileStorage.json
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{48F6F6A5-7340-42F8-9216-BEB7A4B7D5A1}"
EndProject

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

@@ -9,6 +9,7 @@
<ItemGroup>
<PackageReference Include="FluentValidation" Version="12.1.1" />
<PackageReference Include="MediatR" Version="14.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.3.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.1" />
</ItemGroup>
@@ -18,4 +19,10 @@
<ProjectReference Include="..\..\Domain\GozareshgirProgramManager.Domain\GozareshgirProgramManager.Domain.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Http.Features">
<HintPath>C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App\10.0.1\Microsoft.AspNetCore.Http.Features.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

View File

@@ -209,22 +209,38 @@ public class CreateOrEditCheckoutCommandHandler : IBaseCommandHandler<CreateOrEd
}
}
//حقوق نهایی
var monthlySalaryPay = (totalHoursWorked * monthlySalaryDefined) / mandatoryHours;
// اگر اضافه کار داشت حقوق تعین شده به عنوان حقوق نهایی در نظر گرفته میشود
monthlySalaryPay = monthlySalaryPay > monthlySalaryDefined ? monthlySalaryDefined : monthlySalaryPay;
////حقوق نهایی
//var monthlySalaryPay = (totalHoursWorked * monthlySalaryDefined) / mandatoryHours;
//// اگر اضافه کار داشت حقوق تعین شده به عنوان حقوق نهایی در نظر گرفته میشود
//monthlySalaryPay = monthlySalaryPay > monthlySalaryDefined ? monthlySalaryDefined : monthlySalaryPay;
//حقوق کسر شده
var deductionFromSalary = monthlySalaryDefined - monthlySalaryPay;
////حقوق کسر شده
//var deductionFromSalary = monthlySalaryDefined - monthlySalaryPay;
//new chang salary compute
var monthlySalaryPay = totalHoursWorked * monthlySalaryDefined;
//زمان باقی مانده
var remainingTime = totalHoursWorked - mandatoryHours;
//تناسب به دقیقه
#region MyRegion
//var monthlySalaryDefinedTest = monthlySalaryDefined * mandatoryHours;
//var monthlySalaryPayTest = totalHoursWorked * monthlySalaryDefined;
////// اگر اضافه کار داشت حقوق تعین شده به عنوان حقوق نهایی در نظر گرفته میشود
//monthlySalaryPayTest = monthlySalaryPayTest > monthlySalaryDefinedTest ? monthlySalaryDefinedTest : monthlySalaryPayTest;
//////حقوق کسر شده
//var deductionFromSalaryTest = monthlySalaryDefinedTest - monthlySalaryPayTest;
#endregion
var computeResult = new ComputeResultDto
{
MandatoryHours = mandatoryHours,
MonthlySalaryPay = monthlySalaryPay,
DeductionFromSalary = deductionFromSalary,
DeductionFromSalary = 0 /*deductionFromSalary*/,
RemainingHours = remainingTime
};
Console.WriteLine(mandatoryHours);

View File

@@ -1,10 +1,11 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using DNTPersianUtils.Core;
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Application.Modules.SalaryPaymentSettings.Queries.GetUserListWhoHaveSettings;
using GozareshgirProgramManager.Domain._Common;
using GozareshgirProgramManager.Domain.CheckoutAgg.Enums;
using Microsoft.EntityFrameworkCore;
using PersianTools.Core;
using PersianDateTime = PersianTools.Core.PersianDateTime;
namespace GozareshgirProgramManager.Application.Modules.Checkouts.Queries.GetUserToGropCreate;
@@ -45,8 +46,8 @@ public class GetUserToGroupCreatingQueryHandler : IBaseQueryHandler<GetUserToGro
"ایجاد فیش فقط برای ماه های گذشته امکان پذیر است");
var lastMonthStart = lastMonth;
var lastMonthEnd = lastMonth;
//var lastMonthStart = lastMonth;
var lastMonthEnd = ((selectedDate.ToFarsi().FindeEndOfMonth())).ToGeorgianDateTime();
var query =
await (from u in _context.Users
@@ -60,8 +61,8 @@ public class GetUserToGroupCreatingQueryHandler : IBaseQueryHandler<GetUserToGro
// LEFT JOIN
//فیش
join ch in _context.Checkouts
.Where(x => x.CheckoutStartDate < lastMonthStart
&& x.CheckoutEndDate >= lastMonthStart)
.Where(x => x.CheckoutStartDate < lastMonthEnd
&& x.CheckoutEndDate > selectedDate)
on u.Id equals ch.UserId into chJoin
from ch in chJoin.DefaultIfEmpty()

View File

@@ -1,16 +0,0 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Domain.ProjectAgg.Enums;
namespace GozareshgirProgramManager.Application.Modules.Projects.Commands.AddTaskToPhase;
/// <summary>
/// Command to add a task to an existing phase
/// </summary>
public record AddTaskToPhaseCommand(
Guid PhaseId,
string Name,
string? Description = null,
ProjectTaskPriority Priority = ProjectTaskPriority.Medium,
int OrderIndex = 0,
DateTime? DueDate = null
) : IBaseCommand;

View File

@@ -1,53 +0,0 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Domain._Common;
using GozareshgirProgramManager.Domain.ProjectAgg.Repositories;
using MediatR;
namespace GozareshgirProgramManager.Application.Modules.Projects.Commands.AddTaskToPhase;
public class AddTaskToPhaseCommandHandler : IRequestHandler<AddTaskToPhaseCommand, OperationResult>
{
private readonly IProjectPhaseRepository _phaseRepository;
private readonly IUnitOfWork _unitOfWork;
public AddTaskToPhaseCommandHandler(
IProjectPhaseRepository phaseRepository,
IUnitOfWork unitOfWork)
{
_phaseRepository = phaseRepository;
_unitOfWork = unitOfWork;
}
public async Task<OperationResult> Handle(AddTaskToPhaseCommand request, CancellationToken cancellationToken)
{
try
{
// Get phase
var phase = await _phaseRepository.GetByIdAsync(request.PhaseId);
if (phase == null)
{
return OperationResult.NotFound("فاز یافت نشد");
}
// Add task
var task = phase.AddTask(request.Name, request.Description);
task.SetPriority(request.Priority);
task.SetOrderIndex(request.OrderIndex);
if (request.DueDate.HasValue)
{
task.SetDates(dueDate: request.DueDate);
}
// Save changes
await _unitOfWork.SaveChangesAsync(cancellationToken);
return OperationResult.Success();
}
catch (Exception ex)
{
return OperationResult.Failure($"خطا در افزودن تسک: {ex.Message}");
}
}
}

View File

@@ -4,4 +4,5 @@ using GozareshgirProgramManager.Domain.ProjectAgg.Enums;
namespace GozareshgirProgramManager.Application.Modules.Projects.Commands.CreateProject;
public record CreateProjectCommand(string Name,ProjectHierarchyLevel Level,
ProjectTaskPriority? Priority,
Guid? ParentId):IBaseCommand;

View File

@@ -16,7 +16,8 @@ public class CreateProjectCommandHandler : IBaseCommandHandler<CreateProjectComm
private readonly IUnitOfWork _unitOfWork;
public CreateProjectCommandHandler(IProjectRepository projectRepository, IUnitOfWork unitOfWork, IProjectTaskRepository projectTaskRepository, IProjectPhaseRepository projectPhaseRepository)
public CreateProjectCommandHandler(IProjectRepository projectRepository, IUnitOfWork unitOfWork,
IProjectTaskRepository projectTaskRepository, IProjectPhaseRepository projectPhaseRepository)
{
_projectRepository = projectRepository;
_unitOfWork = unitOfWork;
@@ -56,7 +57,7 @@ public class CreateProjectCommandHandler : IBaseCommandHandler<CreateProjectComm
if (!request.ParentId.HasValue)
throw new BadRequestException("برای ایجاد فاز، شناسه پروژه الزامی است");
if(!_projectRepository.Exists(x=>x.Id == request.ParentId.Value))
if (!_projectRepository.Exists(x => x.Id == request.ParentId.Value))
{
throw new BadRequestException("والد پروژه یافت نشد");
}
@@ -70,13 +71,14 @@ public class CreateProjectCommandHandler : IBaseCommandHandler<CreateProjectComm
if (!request.ParentId.HasValue)
throw new BadRequestException("برای ایجاد تسک، شناسه فاز الزامی است");
if(!_projectPhaseRepository.Exists(x=>x.Id == request.ParentId.Value))
if (!_projectPhaseRepository.Exists(x => x.Id == request.ParentId.Value))
{
throw new BadRequestException("والد پروژه یافت نشد");
}
var projectTask = new ProjectTask(request.Name, request.ParentId.Value);
var priority = request.Priority ?? ProjectTaskPriority.Low;
var projectTask = new ProjectTask(request.Name, request.ParentId.Value, priority);
await _projectTaskRepository.CreateAsync(projectTask);
}
}

View File

@@ -12,6 +12,7 @@ public record SetTimeProjectCommand(
public class SetTimeSectionTime
{
public Guid? Id { get; set; }
public string Description { get; set; }
public int Hours { get; set; }
public int Minutes { get; set; }

View File

@@ -349,6 +349,15 @@ public class SetTimeProjectCommandHandler : IBaseCommandHandler<SetTimeProjectCo
return OperationResult.Success();
}
private void ValidateTotalTimeNotLessThanSpent(TimeSpan newTotalTime, TimeSpan currentTotalSpent)
{
if (newTotalTime < currentTotalSpent)
{
throw new BadRequestException(
$"تایم کل سکشن نمی‌تواند کمتر از زمان مصرف شده ({currentTotalSpent.TotalHours:F2} ساعت) باشد");
}
}
private void SetSectionTime(TaskSection section, SetTimeProjectSkillItem sectionItem, long? addedByUserId)
{
var initData = sectionItem.InitData;
@@ -363,18 +372,62 @@ public class SetTimeProjectCommandHandler : IBaseCommandHandler<SetTimeProjectCo
// تنظیم زمان اولیه
section.UpdateInitialEstimatedHours(initialTime, initData.Description);
section.ClearAdditionalTimes();
// افزودن زمان‌های اضافی
bool hasAdditionalTime = false;
foreach (var additionalTime in sectionItem.AdditionalTime)
// مدیریت هوشمند زمان‌های اضافی
var existingAdditionalTimes = section.AdditionalTimes.ToList();
var incomingAdditionalTimes = sectionItem.AdditionalTime ?? [];
bool hasRealChange = false;
// حذف آیتم‌هایی که دیگر در لیست نیستند
foreach (var existingTime in existingAdditionalTimes)
{
var additionalTimeSpan = TimeSpan.FromHours(additionalTime.Hours).Add(TimeSpan.FromMinutes(additionalTime.Minutes));
section.AddAdditionalTime(additionalTimeSpan, additionalTime.Description, addedByUserId);
hasAdditionalTime = true;
var stillExists = incomingAdditionalTimes.Any(x => x.Id == existingTime.Id);
if (!stillExists)
{
section.RemoveAdditionalTime(existingTime.Id);
hasRealChange = true;
}
}
// تغییر status به Incomplete فقط اگر زمان اضافی اضافه شده باشد و در وضعیتی غیر از ReadyToStart باشد
if (hasAdditionalTime && section.Status != TaskSectionStatus.ReadyToStart)
// ویرایش یا اضافه کردن آیتم‌های جدید
foreach (var additionalTime in incomingAdditionalTimes)
{
var additionalTimeSpan = TimeSpan.FromHours(additionalTime.Hours)
.Add(TimeSpan.FromMinutes(additionalTime.Minutes));
if (additionalTimeSpan <= TimeSpan.Zero)
continue;
var existingAdditionalTime = existingAdditionalTimes.FirstOrDefault(x => x.Id == additionalTime.Id);
if (existingAdditionalTime != null)
{
// اگر آیتم با این ID وجود دارد، بررسی کن اگر تغییر کرده باشد
if (existingAdditionalTime.HasChanged(additionalTimeSpan, additionalTime.Description))
{
// ویرایش بدون حذف و ایجاد دوباره
existingAdditionalTime.Update(additionalTimeSpan, additionalTime.Description);
hasRealChange = true;
}
}
else
{
// اگر ID نداشت یا ID جدید بود، اضافه کن
if (additionalTime.Id == null || additionalTime.Id == Guid.Empty)
{
section.AddAdditionalTime(additionalTimeSpan, additionalTime.Description, addedByUserId);
hasRealChange = true;
}
}
}
// اعتبارسنجی بعد از اعمال تمام تغییرات
var currentTotalSpent = section.GetTotalTimeSpent();
var newTotalTime = section.FinalEstimatedHours;
ValidateTotalTimeNotLessThanSpent(newTotalTime, currentTotalSpent);
// تغییر status به Incomplete فقط اگر تغییری واقعی اعمال شده باشد و در وضعیتی غیر از ReadyToStart باشد
if (hasRealChange && section.Status != TaskSectionStatus.ReadyToStart)
{
// اگر سکشن درحال انجام است، باید متوقف شود قبل از تغییر status
if (section.Status == TaskSectionStatus.InProgress)

View File

@@ -10,8 +10,10 @@ public class GetProjectItemDto
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; }
public TimeSpan TotalTime { get; init; }
public TimeSpan RemainingTime { get; init; }
public AssignmentStatus Front { get; set; }
public AssignmentStatus Backend { get; set; }
public AssignmentStatus Design { get; set; }

View File

@@ -16,7 +16,8 @@ public class GetProjectsListQueryHandler : IBaseQueryHandler<GetProjectsListQuer
_context = context;
}
public async Task<OperationResult<GetProjectsListResponse>> Handle(GetProjectsListQuery request, CancellationToken cancellationToken)
public async Task<OperationResult<GetProjectsListResponse>> Handle(GetProjectsListQuery request,
CancellationToken cancellationToken)
{
var projects = new List<GetProjectDto>();
var phases = new List<GetPhaseDto>();
@@ -51,13 +52,14 @@ public class GetProjectsListQueryHandler : IBaseQueryHandler<GetProjectsListQuer
{
return new List<GetProjectDto>();
}
var entities = await query
.OrderByDescending(p => p.CreationDate)
.ToListAsync(cancellationToken);
var result = new List<GetProjectDto>();
foreach (var project in entities)
{
var (percentage, totalTime) = await CalculateProjectPercentage(project, cancellationToken);
var (percentage, totalTime,remainingTime) = await CalculateProjectPercentage(project, cancellationToken);
result.Add(new GetProjectDto
{
Id = project.Id,
@@ -65,10 +67,12 @@ public class GetProjectsListQueryHandler : IBaseQueryHandler<GetProjectsListQuer
Level = ProjectHierarchyLevel.Project,
ParentId = null,
Percentage = percentage,
TotalHours = (int)totalTime.TotalHours,
Minutes = totalTime.Minutes,
TotalTime = totalTime,
RemainingTime = remainingTime
});
}
result = result.OrderByDescending(x => x.Percentage).ToList();
return result;
}
@@ -79,13 +83,14 @@ public class GetProjectsListQueryHandler : IBaseQueryHandler<GetProjectsListQuer
{
query = query.Where(x => x.ProjectId == parentId);
}
var entities = await query
.OrderByDescending(p => p.CreationDate)
.ToListAsync(cancellationToken);
var result = new List<GetPhaseDto>();
foreach (var phase in entities)
{
var (percentage, totalTime) = await CalculatePhasePercentage(phase, cancellationToken);
var (percentage, totalTime,remainingTime) = await CalculatePhasePercentage(phase, cancellationToken);
result.Add(new GetPhaseDto
{
Id = phase.Id,
@@ -93,10 +98,12 @@ public class GetProjectsListQueryHandler : IBaseQueryHandler<GetProjectsListQuer
Level = ProjectHierarchyLevel.Phase,
ParentId = phase.ProjectId,
Percentage = percentage,
TotalHours = (int)totalTime.TotalHours,
Minutes = totalTime.Minutes,
TotalTime = totalTime,
RemainingTime = remainingTime
});
}
result = result.OrderByDescending(x => x.Percentage).ToList();
return result;
}
@@ -107,6 +114,7 @@ public class GetProjectsListQueryHandler : IBaseQueryHandler<GetProjectsListQuer
{
query = query.Where(x => x.PhaseId == parentId);
}
var entities = await query
.OrderByDescending(t => t.CreationDate)
.ToListAsync(cancellationToken);
@@ -118,7 +126,7 @@ public class GetProjectsListQueryHandler : IBaseQueryHandler<GetProjectsListQuer
foreach (var task in entities)
{
var (percentage, totalTime) = await CalculateTaskPercentage(task, cancellationToken);
var (percentage, totalTime,remainingTime) = await CalculateTaskPercentage(task, cancellationToken);
var sections = await _context.TaskSections
.Include(s => s.Activities)
.Include(s => s.Skill)
@@ -140,7 +148,6 @@ public class GetProjectsListQueryHandler : IBaseQueryHandler<GetProjectsListQuer
// محاسبه 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 =>
@@ -184,18 +191,20 @@ public class GetProjectsListQueryHandler : IBaseQueryHandler<GetProjectsListQuer
Level = ProjectHierarchyLevel.Task,
ParentId = task.PhaseId,
Percentage = percentage,
TotalHours = (int)totalTime.TotalHours,
Minutes = totalTime.Minutes,
TotalTime = totalTime,
SpentTime = spentTime,
RemainingTime = remainingTime,
Sections = sectionDtos,
Priority = task.Priority
});
}
result = result.OrderByDescending(x => x.Percentage).ToList();
return result;
}
private async Task SetSkillFlags<TItem>(List<TItem> items, CancellationToken cancellationToken) where TItem : GetProjectItemDto
private async Task SetSkillFlags<TItem>(List<TItem> items, CancellationToken cancellationToken)
where TItem : GetProjectItemDto
{
if (!items.Any())
return;
@@ -213,124 +222,139 @@ public class GetProjectsListQueryHandler : IBaseQueryHandler<GetProjectsListQuer
}
private async Task SetSkillFlagsForProjects<TItem>(List<TItem> items, List<Guid> projectIds, CancellationToken cancellationToken) where TItem : GetProjectItemDto
private async Task SetSkillFlagsForProjects<TItem>(List<TItem> items, List<Guid> projectIds,
CancellationToken cancellationToken) where TItem : GetProjectItemDto
{
// For projects: gather all phases, then tasks, then sections
var phases = await _context.ProjectPhases
.Where(ph => projectIds.Contains(ph.ProjectId))
.Select(ph => ph.Id)
.Select(ph => new { ph.Id, ph.ProjectId })
.ToListAsync(cancellationToken);
var phaseIds = phases.Select(ph => ph.Id).ToList();
var tasks = await _context.ProjectTasks
.Where(t => phases.Contains(t.PhaseId))
.Select(t => t.Id)
.Where(t => phaseIds.Contains(t.PhaseId))
.Select(t => new { t.Id, t.PhaseId })
.ToListAsync(cancellationToken);
var taskIds = tasks.Select(t => t.Id).ToList();
var sections = await _context.TaskSections
.Include(s => s.Skill)
.Where(s => tasks.Contains(s.TaskId))
.Where(s => taskIds.Contains(s.TaskId))
.ToListAsync(cancellationToken);
// Convert to tuple list for AggregatePhaseStatuses
var tasksList = tasks.Select(t => (t.Id, t.PhaseId)).ToList();
foreach (var item in items)
{
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"));
var projectPhaseIds = phases.Where(ph => ph.ProjectId == item.Id).Select(ph => ph.Id).ToList();
// برای هر Skill، وضعیت‌های تمام Phases را تجمیع کنیم
item.Backend = AggregatePhaseStatuses(projectPhaseIds, tasksList, sections, "Backend");
item.Front = AggregatePhaseStatuses(projectPhaseIds, tasksList, sections, "Frontend");
item.Design = AggregatePhaseStatuses(projectPhaseIds, tasksList, sections, "UI/UX Design");
}
}
private async Task SetSkillFlagsForPhases<TItem>(List<TItem> items, List<Guid> phaseIds, CancellationToken cancellationToken) where TItem : GetProjectItemDto
private async Task SetSkillFlagsForPhases<TItem>(List<TItem> items, List<Guid> phaseIds,
CancellationToken cancellationToken) where TItem : GetProjectItemDto
{
// For phases: gather tasks, then sections
var tasks = await _context.ProjectTasks
.Where(t => phaseIds.Contains(t.PhaseId))
.Select(t => t.Id)
.Select(t => new { t.Id, t.PhaseId })
.ToListAsync(cancellationToken);
var taskIds = tasks.Select(t => t.Id).ToList();
var sections = await _context.TaskSections
.Include(s => s.Skill)
.Where(s => tasks.Contains(s.TaskId))
.Where(s => taskIds.Contains(s.TaskId))
.ToListAsync(cancellationToken);
foreach (var item in items)
{
// Filter tasks for this phase
var phaseTaskIds = await _context.ProjectTasks
.Where(t => t.PhaseId == item.Id)
.Select(t => t.Id)
.ToListAsync(cancellationToken);
var phaseTaskIds = tasks.Where(t => t.PhaseId == item.Id).Select(t => t.Id).ToList();
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"));
item.Backend = AggregateAssignmentStatus(itemSections.Where(x => x.Skill?.Name == "Backend"));
item.Front = AggregateAssignmentStatus(itemSections.Where(x => x.Skill?.Name == "Frontend"));
item.Design = AggregateAssignmentStatus(itemSections.Where(x => x.Skill?.Name == "UI/UX Design"));
}
}
private async Task<(int Percentage, TimeSpan TotalTime)> CalculateProjectPercentage(Project project, CancellationToken cancellationToken)
private async Task<(int Percentage, TimeSpan TotalTime,TimeSpan RemainingTime)> 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);
return (0, TimeSpan.Zero,TimeSpan.Zero);
var phasePercentages = new List<int>();
var totalTime = TimeSpan.Zero;
var remainingTime = TimeSpan.Zero;
foreach (var phase in phases)
{
var (phasePercentage, phaseTime) = await CalculatePhasePercentage(phase, cancellationToken);
var (phasePercentage, phaseTime,phaseRemainingTime) = await CalculatePhasePercentage(phase, cancellationToken);
phasePercentages.Add(phasePercentage);
totalTime += phaseTime;
remainingTime += phaseRemainingTime;
}
var averagePercentage = phasePercentages.Any() ? (int)phasePercentages.Average() : 0;
return (averagePercentage, totalTime);
return (averagePercentage, totalTime,remainingTime);
}
private async Task<(int Percentage, TimeSpan TotalTime)> CalculatePhasePercentage(ProjectPhase phase, CancellationToken cancellationToken)
private async Task<(int Percentage, TimeSpan TotalTime,TimeSpan RemainingTime)> 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);
return (0, TimeSpan.Zero,TimeSpan.Zero);
var taskPercentages = new List<int>();
var totalTime = TimeSpan.Zero;
var remainingTime = TimeSpan.Zero;
foreach (var task in tasks)
{
var (taskPercentage, taskTime) = await CalculateTaskPercentage(task, cancellationToken);
var (taskPercentage, taskTime,taskRemainingTime) = await CalculateTaskPercentage(task, cancellationToken);
taskPercentages.Add(taskPercentage);
totalTime += taskTime;
remainingTime += taskRemainingTime;
}
var averagePercentage = taskPercentages.Any() ? (int)taskPercentages.Average() : 0;
return (averagePercentage, totalTime);
return (averagePercentage, totalTime,remainingTime);
}
private async Task<(int Percentage, TimeSpan TotalTime)> CalculateTaskPercentage(ProjectTask task, CancellationToken cancellationToken)
private async Task<(int Percentage, TimeSpan TotalTime, TimeSpan RemainingTime)> CalculateTaskPercentage(
ProjectTask task, CancellationToken cancellationToken)
{
var sections = await _context.TaskSections
.Include(s => s.Activities)
.Include(x=>x.AdditionalTimes)
.Include(x => x.AdditionalTimes)
.Where(s => s.TaskId == task.Id)
.ToListAsync(cancellationToken);
if (!sections.Any())
return (0, TimeSpan.Zero);
return (0, TimeSpan.Zero, TimeSpan.Zero);
var sectionPercentages = new List<int>();
var totalTime = TimeSpan.Zero;
var spentTime = TimeSpan.Zero;
foreach (var section in sections)
{
var (sectionPercentage, sectionTime) = CalculateSectionPercentage(section);
var sectionSpent = TimeSpan.FromTicks(section.Activities.Sum(x => x.GetTimeSpent().Ticks));
sectionPercentages.Add(sectionPercentage);
totalTime += sectionTime;
spentTime += sectionSpent;
}
var remainingTime = totalTime - spentTime;
var averagePercentage = sectionPercentages.Any() ? (int)sectionPercentages.Average() : 0;
return (averagePercentage, totalTime);
return (averagePercentage, totalTime, remainingTime);
}
private static (int Percentage, TimeSpan TotalTime) CalculateSectionPercentage(TaskSection section)
{
return ((int)section.GetProgressPercentage(),section.FinalEstimatedHours);
return ((int)section.GetProgressPercentage(), section.FinalEstimatedHours);
}
private static AssignmentStatus GetAssignmentStatus(TaskSection? section)
@@ -356,5 +380,57 @@ public class GetProjectsListQueryHandler : IBaseQueryHandler<GetProjectsListQuer
// تعیین تکلیف نشده: نه user دارد نه time
return AssignmentStatus.Unassigned;
}
private static AssignmentStatus AggregatePhaseStatuses(
List<Guid> phaseIds,
List<(Guid Id, Guid PhaseId)> tasks,
List<TaskSection> sections,
string skillName)
{
var phaseStatuses = new List<AssignmentStatus>();
foreach (var phaseId in phaseIds)
{
var phaseTaskIds = tasks.Where(t => t.PhaseId == phaseId).Select(t => t.Id).ToList();
var phaseSections = sections.Where(s => phaseTaskIds.Contains(s.TaskId) && s.Skill?.Name == skillName);
var phaseStatus = AggregateAssignmentStatus(phaseSections);
phaseStatuses.Add(phaseStatus);
}
// الآن تجمیع وضعیت‌های Phases
if (!phaseStatuses.Any())
return AssignmentStatus.Unassigned;
// اگر هر یکی Unassigned باشد → Unassigned
if (phaseStatuses.Any(s => s == AssignmentStatus.Unassigned))
return AssignmentStatus.Unassigned;
// اگر Unassigned نیست و هر یکی UserOnly باشد → UserOnly
if (phaseStatuses.Any(s => s == AssignmentStatus.UserOnly))
return AssignmentStatus.UserOnly;
// فقط اگر همه Assigned باشند → Assigned
return AssignmentStatus.Assigned;
}
private static AssignmentStatus AggregateAssignmentStatus(IEnumerable<TaskSection> sections)
{
var sectionList = sections.ToList();
if (!sectionList.Any())
return AssignmentStatus.Unassigned;
var statuses = sectionList.Select(GetAssignmentStatus).ToList();
// اگر هر یکی Unassigned باشد → Unassigned (بدترین وضعیت)
if (statuses.Any(s => s == AssignmentStatus.Unassigned))
return AssignmentStatus.Unassigned;
// اگر Unassigned نیست و هر یکی UserOnly باشد → UserOnly (وضعیت متوسط)
if (statuses.Any(s => s == AssignmentStatus.UserOnly))
return AssignmentStatus.UserOnly;
// فقط اگر همه Assigned باشند → Assigned (بهترین وضعیت)
return AssignmentStatus.Assigned;
}
}

View File

@@ -9,8 +9,8 @@ public class GetTaskDto
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; }
public TimeSpan TotalTime { get; set; }
// Task-specific fields
public TimeSpan SpentTime { get; init; }

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? ProjectName { 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)
@@ -41,9 +41,22 @@ 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.ProjectName))
{
queryable = queryable.Where(x=>x.Task.Name.Contains(request.ProjectName)
|| x.Task.Phase.Name.Contains(request.ProjectName)
|| x.Task.Phase.Project.Name.Contains(request.ProjectName));
}
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();
@@ -57,6 +70,9 @@ public class ProjectBoardListQueryHandler : IBaseQueryHandler<ProjectBoardListQu
.OrderByDescending(x => x.CurrentAssignedUserId == currentUserId)
.ThenByDescending(x=>x.Task.Priority)
.ThenBy(x => GetStatusOrder(x.Status))
.ThenBy(x=>x.Task.Phase.ProjectId)
.ThenBy(x=>x.Task.PhaseId)
.ThenBy(x=>x.TaskId)
.Select(x =>
{
// محاسبه یکبار برای هر Activity و Cache کردن نتیجه
@@ -67,7 +83,7 @@ public class ProjectBoardListQueryHandler : IBaseQueryHandler<ProjectBoardListQu
{
Activity = a,
TimeSpent = timeSpent,
TotalSeconds = timeSpent.TotalSeconds,
timeSpent.TotalSeconds,
FormattedTime = timeSpent.ToString(@"hh\:mm")
};
}).ToList();
@@ -117,6 +133,7 @@ public class ProjectBoardListQueryHandler : IBaseQueryHandler<ProjectBoardListQu
AssignedUser = x.CurrentAssignedUserId == x.OriginalAssignedUserId ? null
: users.GetValueOrDefault(x.CurrentAssignedUserId, "ناشناس"),
SkillName = x.Skill?.Name??"-",
TaskId = x.TaskId
};
}).ToList();

View File

@@ -14,6 +14,7 @@ public class ProjectBoardListResponse
public string OriginalUser { get; set; }
public string SkillName { get; set; }
public ProjectTaskPriority TaskPriority { get; set; }
public Guid TaskId { get; set; }
}
public class ProjectProgressDto

View File

@@ -21,7 +21,7 @@ public record ProjectDeployBoardDetailTaskItem(
TimeSpan DoneTimeSpan,
int Percentage,
List<ProjectDeployBoardDetailItemSkill> Skills)
: ProjectDeployBoardDetailPhaseItem(Name, TotalTimeSpan, DoneTimeSpan,Percentage);
: ProjectDeployBoardDetailPhaseItem(Name, TotalTimeSpan, DoneTimeSpan, Percentage);
public record ProjectDeployBoardDetailItemSkill(string OriginalUserFullName, string SkillName, int TimePercentage);
@@ -73,6 +73,7 @@ public class
var doneTime = t.Sections.Aggregate(TimeSpan.Zero,
(sum, next) => sum.Add(next.GetTotalTimeSpent()));
var skills = t.Sections
.Select(s =>
{
@@ -88,7 +89,17 @@ public class
skillName,
timePercentage);
}).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(
t.Name,

View File

@@ -26,6 +26,7 @@ public record ProjectSetTimeResponseSkill
public class ProjectSetTimeResponseSectionAdditionalTime
{
public Guid Id { get; set; }
public int Hours { get; init; }
public int Minutes { get; init; }
public string Description { get; init; }

View File

@@ -56,6 +56,7 @@ public class ProjectSetTimeDetailsQueryHandler
var skills = await _context.Skills
.AsNoTracking()
.OrderBy(x=>x.CreationDate)
.ToListAsync(cancellationToken);
var res = new ProjectSetTimeResponse(
@@ -69,6 +70,7 @@ public class ProjectSetTimeDetailsQueryHandler
AdditionalTimes = section?.AdditionalTimes
.Select(x => new ProjectSetTimeResponseSectionAdditionalTime
{
Id = x.Id,
Description = x.Reason ?? "",
Hours = (int)x.Hours.TotalHours,
Minutes = x.Hours.Minutes,
@@ -84,7 +86,7 @@ public class ProjectSetTimeDetailsQueryHandler
UserId = section?.OriginalAssignedUserId ?? 0,
SkillId = skill.Id,
};
}).OrderBy(x => x.SkillId).ToList(),
}).ToList(),
task.Id,
level);
@@ -114,6 +116,7 @@ public class ProjectSetTimeDetailsQueryHandler
var skills = await _context.Skills
.AsNoTracking()
.OrderBy(x=>x.CreationDate)
.ToListAsync(cancellationToken);
var res = new ProjectSetTimeResponse(
@@ -135,7 +138,7 @@ public class ProjectSetTimeDetailsQueryHandler
UserId = section?.UserId ?? 0,
SkillId = skill.Id,
};
}).OrderBy(x => x.SkillId).ToList(),
}).ToList(),
phase.Id,
level);
@@ -165,6 +168,7 @@ public class ProjectSetTimeDetailsQueryHandler
var skills = await _context.Skills
.AsNoTracking()
.OrderBy(x=>x.CreationDate)
.ToListAsync(cancellationToken);
var res = new ProjectSetTimeResponse(
@@ -186,7 +190,7 @@ public class ProjectSetTimeDetailsQueryHandler
UserId = section?.UserId ?? 0,
SkillId = skill.Id,
};
}).OrderBy(x => x.SkillId).ToList(),
}).ToList(),
project.Id,
level);

View File

@@ -0,0 +1,47 @@
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Domain._Common.Exceptions;
using GozareshgirProgramManager.Domain.TaskChatAgg.Repositories;
namespace GozareshgirProgramManager.Application.Modules.TaskChat.Commands.DeleteMessage;
public record DeleteMessageCommand(Guid MessageId) : IBaseCommand;
public class DeleteMessageCommandHandler : IBaseCommandHandler<DeleteMessageCommand>
{
private readonly ITaskChatMessageRepository _repository;
private readonly IAuthHelper _authHelper;
public DeleteMessageCommandHandler(ITaskChatMessageRepository repository, IAuthHelper authHelper)
{
_repository = repository;
_authHelper = authHelper;
}
public async Task<OperationResult> Handle(DeleteMessageCommand request, CancellationToken cancellationToken)
{
var currentUserId = _authHelper.GetCurrentUserId()??
throw new UnAuthorizedException("کاربر احراز هویت نشده است");
var message = await _repository.GetByIdAsync(request.MessageId);
if (message == null)
{
return OperationResult.NotFound("پیام یافت نشد");
}
try
{
message.DeleteMessage(currentUserId);
await _repository.UpdateAsync(message);
await _repository.SaveChangesAsync();
// TODO: SignalR notification
return OperationResult.Success();
}
catch (Exception ex)
{
return OperationResult.ValidationError(ex.Message);
}
}
}

View File

@@ -0,0 +1,51 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Domain._Common.Exceptions;
using GozareshgirProgramManager.Domain.TaskChatAgg.Repositories;
using MediatR;
namespace GozareshgirProgramManager.Application.Modules.TaskChat.Commands.EditMessage;
public record EditMessageCommand(
Guid MessageId,
string NewTextContent
) : IBaseCommand;
public class EditMessageCommandHandler : IBaseCommandHandler<EditMessageCommand>
{
private readonly ITaskChatMessageRepository _repository;
private readonly IAuthHelper _authHelper;
public EditMessageCommandHandler(ITaskChatMessageRepository repository, IAuthHelper authHelper)
{
_repository = repository;
_authHelper = authHelper;
}
public async Task<OperationResult> Handle(EditMessageCommand request, CancellationToken cancellationToken)
{
var currentUserId = _authHelper.GetCurrentUserId()??
throw new UnAuthorizedException("کاربر احراز هویت نشده است");
var message = await _repository.GetByIdAsync(request.MessageId);
if (message == null)
{
return OperationResult.NotFound("پیام یافت نشد");
}
try
{
message.EditMessage(request.NewTextContent, currentUserId);
await _repository.UpdateAsync(message);
await _repository.SaveChangesAsync();
// TODO: SignalR notification
return OperationResult.Success();
}
catch (Exception ex)
{
return OperationResult.ValidationError(ex.Message);
}
}
}

View File

@@ -0,0 +1,46 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Domain._Common.Exceptions;
using GozareshgirProgramManager.Domain.TaskChatAgg.Repositories;
using MediatR;
namespace GozareshgirProgramManager.Application.Modules.TaskChat.Commands.PinMessage;
public record PinMessageCommand(Guid MessageId) : IBaseCommand;
public class PinMessageCommandHandler : IBaseCommandHandler<PinMessageCommand>
{
private readonly ITaskChatMessageRepository _repository;
private readonly IAuthHelper _authHelper;
public PinMessageCommandHandler(ITaskChatMessageRepository repository, IAuthHelper authHelper)
{
_repository = repository;
_authHelper = authHelper;
}
public async Task<OperationResult> Handle(PinMessageCommand request, CancellationToken cancellationToken)
{
var currentUserId = _authHelper.GetCurrentUserId()??
throw new UnAuthorizedException("کاربر احراز هویت نشده است");
var message = await _repository.GetByIdAsync(request.MessageId);
if (message == null)
{
return OperationResult.NotFound("پیام یافت نشد");
}
try
{
message.PinMessage(currentUserId);
await _repository.UpdateAsync(message);
await _repository.SaveChangesAsync();
return OperationResult.Success();
}
catch (Exception ex)
{
return OperationResult.ValidationError(ex.Message);
}
}
}

View File

@@ -0,0 +1,157 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Application.Modules.TaskChat.DTOs;
using GozareshgirProgramManager.Application.Services.FileManagement;
using GozareshgirProgramManager.Domain._Common.Exceptions;
using GozareshgirProgramManager.Domain.TaskChatAgg.Entities;
using GozareshgirProgramManager.Domain.TaskChatAgg.Repositories;
using GozareshgirProgramManager.Domain.TaskChatAgg.Enums;
using GozareshgirProgramManager.Domain.FileManagementAgg.Entities;
using GozareshgirProgramManager.Domain.FileManagementAgg.Repositories;
using GozareshgirProgramManager.Domain.FileManagementAgg.Enums;
using GozareshgirProgramManager.Domain.ProjectAgg.Repositories;
using MediatR;
using Microsoft.AspNetCore.Http;
namespace GozareshgirProgramManager.Application.Modules.TaskChat.Commands.SendMessage;
public record SendMessageCommand(
Guid TaskId,
MessageType MessageType,
string? TextContent,
IFormFile? File,
Guid? ReplyToMessageId
) : IBaseCommand<MessageDto>;
public class SendMessageCommandHandler : IBaseCommandHandler<SendMessageCommand, MessageDto>
{
private readonly ITaskChatMessageRepository _messageRepository;
private readonly IUploadedFileRepository _fileRepository;
private readonly IProjectTaskRepository _taskRepository;
private readonly IFileUploadService _fileUploadService;
private readonly IAuthHelper _authHelper;
public SendMessageCommandHandler(
ITaskChatMessageRepository messageRepository,
IUploadedFileRepository fileRepository,
IProjectTaskRepository taskRepository,
IAuthHelper authHelper,
IFileUploadService fileUploadService)
{
_messageRepository = messageRepository;
_fileRepository = fileRepository;
_taskRepository = taskRepository;
_authHelper = authHelper;
_fileUploadService = fileUploadService;
}
public async Task<OperationResult<MessageDto>> Handle(SendMessageCommand request,
CancellationToken cancellationToken)
{
var currentUserId = _authHelper.GetCurrentUserId()
?? throw new UnAuthorizedException("کاربر احراز هویت نشده است");
var task = await _taskRepository.GetByIdAsync(request.TaskId, cancellationToken);
if (task == null)
{
return OperationResult<MessageDto>.NotFound("تسک یافت نشد");
}
Guid? uploadedFileId = null;
if (request.File != null)
{
var uploadedFile = await _fileUploadService.UploadFileAsync
(
request.File,
FileCategory.TaskChatMessage,
currentUserId
);
if (!uploadedFile.IsSuccess)
{
return OperationResult<MessageDto>.Failure(uploadedFile.ErrorMessage ?? "خطا در آپلود فایل");
}
uploadedFileId = uploadedFile.FileId!.Value;
}
var message = new TaskChatMessage(
taskId: request.TaskId,
senderUserId: currentUserId,
messageType: request.MessageType,
textContent: request.TextContent,
uploadedFileId
);
if (request.ReplyToMessageId.HasValue)
{
message.SetReplyTo(request.ReplyToMessageId.Value);
}
await _messageRepository.AddAsync(message);
await _messageRepository.SaveChangesAsync();
if (uploadedFileId.HasValue)
{
var file = await _fileRepository.GetByIdAsync(uploadedFileId.Value);
if (file != null)
{
file.SetReference("TaskChatMessage", message.Id.ToString());
await _fileRepository.UpdateAsync(file);
await _fileRepository.SaveChangesAsync();
}
}
var dto = new MessageDto
{
Id = message.Id,
TaskId = message.TaskId,
SenderUserId = message.SenderUserId,
SenderName = "کاربر",
MessageType = message.MessageType.ToString(),
TextContent = message.TextContent,
ReplyToMessageId = message.ReplyToMessageId,
IsEdited = message.IsEdited,
IsPinned = message.IsPinned,
CreationDate = message.CreationDate,
IsMine = true
};
if (uploadedFileId.HasValue)
{
var file = await _fileRepository.GetByIdAsync(uploadedFileId.Value);
if (file != null)
{
dto.File = new MessageFileDto
{
Id = file.Id,
FileName = file.OriginalFileName,
FileUrl = file.StorageUrl ?? "",
FileSizeBytes = file.FileSizeBytes,
FileType = file.FileType.ToString(),
ThumbnailUrl = file.ThumbnailUrl,
ImageWidth = file.ImageWidth,
ImageHeight = file.ImageHeight,
DurationSeconds = file.DurationSeconds
};
}
}
return OperationResult<MessageDto>.Success(dto);
}
private FileType DetectFileType(string mimeType, string extension)
{
if (mimeType.StartsWith("image/", StringComparison.OrdinalIgnoreCase))
return FileType.Image;
if (mimeType.StartsWith("video/", StringComparison.OrdinalIgnoreCase))
return FileType.Video;
if (mimeType.StartsWith("audio/", StringComparison.OrdinalIgnoreCase))
return FileType.Audio;
if (new[] { ".zip", ".rar", ".7z", ".tar", ".gz" }.Contains(extension.ToLower()))
return FileType.Archive;
return FileType.Document;
}
}

View File

@@ -0,0 +1,46 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Domain._Common.Exceptions;
using GozareshgirProgramManager.Domain.TaskChatAgg.Repositories;
using MediatR;
namespace GozareshgirProgramManager.Application.Modules.TaskChat.Commands.UnpinMessage;
public record UnpinMessageCommand(Guid MessageId) : IBaseCommand;
public class UnpinMessageCommandHandler : IBaseCommandHandler<UnpinMessageCommand>
{
private readonly ITaskChatMessageRepository _repository;
private readonly IAuthHelper _authHelper;
public UnpinMessageCommandHandler(ITaskChatMessageRepository repository, IAuthHelper authHelper)
{
_repository = repository;
_authHelper = authHelper;
}
public async Task<OperationResult> Handle(UnpinMessageCommand request, CancellationToken cancellationToken)
{
var currentUserId = _authHelper.GetCurrentUserId()??
throw new UnAuthorizedException("کاربر احراز هویت نشده است");
var message = await _repository.GetByIdAsync(request.MessageId);
if (message == null)
{
return OperationResult.NotFound("پیام یافت نشد");
}
try
{
message.UnpinMessage(currentUserId);
await _repository.UpdateAsync(message);
await _repository.SaveChangesAsync();
return OperationResult.Success();
}
catch (Exception ex)
{
return OperationResult.ValidationError(ex.Message);
}
}
}

View File

@@ -0,0 +1,63 @@
namespace GozareshgirProgramManager.Application.Modules.TaskChat.DTOs;
public class SendMessageDto
{
public Guid TaskId { get; set; }
public string MessageType { get; set; } = string.Empty; // "Text", "File", "Image", "Voice", "Video"
public string? TextContent { get; set; }
public Guid? FileId { get; set; }
public Guid? ReplyToMessageId { get; set; }
}
public class MessageDto
{
public Guid Id { get; set; }
public Guid TaskId { get; set; }
public long SenderUserId { get; set; }
public string SenderName { get; set; } = string.Empty;
public string MessageType { get; set; } = string.Empty;
public string? TextContent { get; set; }
public MessageFileDto? File { get; set; }
public Guid? ReplyToMessageId { get; set; }
public MessageDto? ReplyToMessage { get; set; }
public bool IsEdited { get; set; }
public DateTime? EditedDate { get; set; }
public bool IsPinned { get; set; }
public DateTime? PinnedDate { get; set; }
public long? PinnedByUserId { get; set; }
public DateTime CreationDate { get; set; }
public bool IsMine { get; set; }
}
public class MessageFileDto
{
public Guid Id { get; set; }
public string FileName { get; set; } = string.Empty;
public string FileUrl { get; set; } = string.Empty;
public long FileSizeBytes { get; set; }
public string FileType { get; set; } = string.Empty;
public string? ThumbnailUrl { get; set; }
public int? ImageWidth { get; set; }
public int? ImageHeight { get; set; }
public int? DurationSeconds { get; set; }
public string FileSizeFormatted
{
get
{
const long kb = 1024;
const long mb = kb * 1024;
const long gb = mb * 1024;
if (FileSizeBytes >= gb)
return $"{FileSizeBytes / (double)gb:F2} GB";
if (FileSizeBytes >= mb)
return $"{FileSizeBytes / (double)mb:F2} MB";
if (FileSizeBytes >= kb)
return $"{FileSizeBytes / (double)kb:F2} KB";
return $"{FileSizeBytes} Bytes";
}
}
}

View File

@@ -0,0 +1,216 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Application.Modules.TaskChat.DTOs;
using GozareshgirProgramManager.Domain.TaskChatAgg.Enums;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace GozareshgirProgramManager.Application.Modules.TaskChat.Queries.GetMessages;
public record GetMessagesQuery(
Guid TaskId,
MessageType? MessageType,
int Page = 1,
int PageSize = 50
) : IBaseQuery<PaginationResult<MessageDto>>;
public class GetMessagesQueryHandler : IBaseQueryHandler<GetMessagesQuery, PaginationResult<MessageDto>>
{
private readonly IProgramManagerDbContext _context;
private readonly IAuthHelper _authHelper;
public GetMessagesQueryHandler(IProgramManagerDbContext context, IAuthHelper authHelper)
{
_context = context;
_authHelper = authHelper;
}
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();
var skip = (request.Page - 1) * request.PageSize;
var query = _context.TaskChatMessages
.Where(m => m.TaskId == request.TaskId && !m.IsDeleted)
.Include(m => m.ReplyToMessage)
.OrderBy(m => m.CreationDate).AsQueryable();
if (request.MessageType.HasValue)
{
query = query.Where(m => m.MessageType == request.MessageType.Value);
}
var totalCount = await query.CountAsync(cancellationToken);
var messages = await query
.Skip(skip)
.Take(request.PageSize)
.ToListAsync(cancellationToken);
// ✅ گرفتن تمامی کاربران برای نمایش نام کامل فرستنده به جای "کاربر"
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) برای نمایش به صورت نوت
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.GetValueOrDefault(message.SenderUserId, "کاربر ناشناس");
var dto = new MessageDto
{
Id = message.Id,
TaskId = message.TaskId,
SenderUserId = message.SenderUserId,
SenderName = senderName,
MessageType = message.MessageType.ToString(),
TextContent = message.TextContent,
ReplyToMessageId = message.ReplyToMessageId,
IsEdited = message.IsEdited,
EditedDate = message.EditedDate,
IsPinned = message.IsPinned,
PinnedDate = message.PinnedDate,
PinnedByUserId = message.PinnedByUserId,
CreationDate = message.CreationDate,
IsMine = message.SenderUserId == currentUserId
};
if (message.ReplyToMessage != null)
{
var replySenderName = users.GetValueOrDefault(message.ReplyToMessage.SenderUserId, "کاربر ناشناس");
dto.ReplyToMessage = new MessageDto
{
Id = message.ReplyToMessage.Id,
SenderUserId = message.ReplyToMessage.SenderUserId,
SenderName = replySenderName,
TextContent = message.ReplyToMessage.TextContent,
CreationDate = message.ReplyToMessage.CreationDate
};
}
if (message.FileId.HasValue)
{
var file = await _context.UploadedFiles.FirstOrDefaultAsync(f => f.Id == message.FileId.Value, cancellationToken);
if (file != null)
{
dto.File = new MessageFileDto
{
Id = file.Id,
FileName = file.OriginalFileName,
FileUrl = file.StorageUrl ?? "",
FileSizeBytes = file.FileSizeBytes,
FileType = file.FileType.ToString(),
ThumbnailUrl = file.ThumbnailUrl,
ImageWidth = file.ImageWidth,
ImageHeight = file.ImageHeight,
DurationSeconds = file.DurationSeconds
};
}
}
messageDtos.Add(dto);
// ✅ پیدا کردن پیام بعدی (اگر وجود داشته باشد)
var currentIndex = messages.IndexOf(message);
var nextMessage = currentIndex < messages.Count - 1 ? messages[currentIndex + 1] : null;
if (nextMessage != null)
{
// ✅ زمان‌های اضافی بین این پیام و پیام بعدی
var additionalTimesBetween = allAdditionalTimes
.Where(at => at.CreationDate > message.CreationDate && at.CreationDate < nextMessage.CreationDate)
.ToList();
messageDtos.AddRange(CreateAdditionalTimeNotes(additionalTimesBetween, users, request.TaskId));
}
else
{
// ✅ این آخرین پیام است، زمان‌های اضافی بعد از آن را اضافه کن
var additionalTimesAfterLastMessage = allAdditionalTimes
.Where(at => at.CreationDate > message.CreationDate)
.ToList();
messageDtos.AddRange(CreateAdditionalTimeNotes(additionalTimesAfterLastMessage, users, request.TaskId));
}
}
// ✅ مرتب کردن نهایی تمام پیام‌ها (معمولی + نوت‌ها) بر اساس زمان ایجاد
messageDtos = messageDtos.OrderBy(m => m.CreationDate).ToList();
var response = new PaginationResult<MessageDto>()
{
List = messageDtos,
TotalCount = totalCount,
};
return OperationResult<PaginationResult<MessageDto>>.Success(response);
}
}

View File

@@ -0,0 +1,82 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Application.Modules.TaskChat.DTOs;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace GozareshgirProgramManager.Application.Modules.TaskChat.Queries.GetPinnedMessages;
public record GetPinnedMessagesQuery(Guid TaskId) : IBaseQuery<List<MessageDto>>;
public class GetPinnedMessagesQueryHandler : IBaseQueryHandler<GetPinnedMessagesQuery, List<MessageDto>>
{
private readonly IProgramManagerDbContext _context;
private readonly IAuthHelper _authHelper;
public GetPinnedMessagesQueryHandler(IProgramManagerDbContext context, IAuthHelper authHelper)
{
_context = context;
_authHelper = authHelper;
}
public async Task<OperationResult<List<MessageDto>>> Handle(GetPinnedMessagesQuery request, CancellationToken cancellationToken)
{
var currentUserId = _authHelper.GetCurrentUserId();
var messages = await _context.TaskChatMessages
.Where(m => m.TaskId == request.TaskId && m.IsPinned && !m.IsDeleted)
.Include(m => m.ReplyToMessage)
.OrderByDescending(m => m.PinnedDate)
.ToListAsync(cancellationToken);
// ✅ گرفتن تمامی کاربران برای نمایش نام کامل فرستنده
var senderUserIds = messages.Select(m => m.SenderUserId).Distinct().ToList();
var users = await _context.Users
.Where(u => senderUserIds.Contains(u.Id))
.ToDictionaryAsync(u => u.Id, u => u.FullName, cancellationToken);
var messageDtos = new List<MessageDto>();
foreach (var message in messages)
{
// ✅ نام فرستنده را از User واقعی بگیر (به جای "کاربر" ثابت)
var senderName = users.GetValueOrDefault(message.SenderUserId, "کاربر ناشناس");
var dto = new MessageDto
{
Id = message.Id,
TaskId = message.TaskId,
SenderUserId = message.SenderUserId,
SenderName = senderName,
MessageType = message.MessageType.ToString(),
TextContent = message.TextContent,
IsPinned = message.IsPinned,
PinnedDate = message.PinnedDate,
PinnedByUserId = message.PinnedByUserId,
CreationDate = message.CreationDate,
IsMine = message.SenderUserId == currentUserId
};
if (message.FileId.HasValue)
{
var file = await _context.UploadedFiles.FirstOrDefaultAsync(f => f.Id == message.FileId.Value, cancellationToken);
if (file != null)
{
dto.File = new MessageFileDto
{
Id = file.Id,
FileName = file.OriginalFileName,
FileUrl = file.StorageUrl ?? "",
FileSizeBytes = file.FileSizeBytes,
FileType = file.FileType.ToString(),
ThumbnailUrl = file.ThumbnailUrl
};
}
}
messageDtos.Add(dto);
}
return OperationResult<List<MessageDto>>.Success(messageDtos);
}
}

View File

@@ -0,0 +1,72 @@
using GozareshgirProgramManager.Application._Common.Interfaces;
using GozareshgirProgramManager.Application._Common.Models;
using GozareshgirProgramManager.Application.Modules.TaskChat.DTOs;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace GozareshgirProgramManager.Application.Modules.TaskChat.Queries.SearchMessages;
public record SearchMessagesQuery(
Guid TaskId,
string SearchText,
int Page = 1,
int PageSize = 20
) : IBaseQuery<List<MessageDto>>;
public class SearchMessagesQueryHandler : IBaseQueryHandler<SearchMessagesQuery, List<MessageDto>>
{
private readonly IProgramManagerDbContext _context;
private readonly IAuthHelper _authHelper;
public SearchMessagesQueryHandler(IProgramManagerDbContext context, IAuthHelper authHelper)
{
_context = context;
_authHelper = authHelper;
}
public async Task<OperationResult<List<MessageDto>>> Handle(SearchMessagesQuery request, CancellationToken cancellationToken)
{
var currentUserId = _authHelper.GetCurrentUserId();
var skip = (request.Page - 1) * request.PageSize;
var messages = await _context.TaskChatMessages
.Where(m => m.TaskId == request.TaskId &&
m.TextContent != null &&
m.TextContent.Contains(request.SearchText) &&
!m.IsDeleted)
.Include(m => m.ReplyToMessage)
.OrderByDescending(m => m.CreationDate)
.Skip(skip)
.Take(request.PageSize)
.ToListAsync(cancellationToken);
// ✅ گرفتن تمامی کاربران برای نمایش نام کامل فرستنده
var senderUserIds = messages.Select(m => m.SenderUserId).Distinct().ToList();
var users = await _context.Users
.Where(u => senderUserIds.Contains(u.Id))
.ToDictionaryAsync(u => u.Id, u => u.FullName, cancellationToken);
var messageDtos = new List<MessageDto>();
foreach (var message in messages)
{
// ✅ نام فرستنده را از User واقعی بگیر
var senderName = users.GetValueOrDefault(message.SenderUserId, "کاربر ناشناس");
var dto = new MessageDto
{
Id = message.Id,
TaskId = message.TaskId,
SenderUserId = message.SenderUserId,
SenderName = senderName,
MessageType = message.MessageType.ToString(),
TextContent = message.TextContent,
CreationDate = message.CreationDate,
IsMine = message.SenderUserId == currentUserId
};
messageDtos.Add(dto);
}
return OperationResult<List<MessageDto>>.Success(messageDtos);
}
}

View File

@@ -0,0 +1,38 @@
using GozareshgirProgramManager.Domain.FileManagementAgg.Entities;
namespace GozareshgirProgramManager.Application.Services.FileManagement;
/// <summary>
/// سرویس ذخیره‌سازی فایل
/// </summary>
public interface IFileStorageService
{
/// <summary>
/// آپلود فایل
/// </summary>
Task<(string StoragePath, string StorageUrl)> UploadAsync(
Stream fileStream,
string uniqueFileName,
string category);
/// <summary>
/// حذف فایل
/// </summary>
Task DeleteAsync(string storagePath);
/// <summary>
/// دریافت فایل
/// </summary>
Task<Stream?> GetFileStreamAsync(string storagePath);
/// <summary>
/// بررسی وجود فایل
/// </summary>
Task<bool> ExistsAsync(string storagePath);
/// <summary>
/// دریافت URL فایل
/// </summary>
string GetFileUrl(string storagePath);
}

View File

@@ -0,0 +1,69 @@
using GozareshgirProgramManager.Domain.FileManagementAgg.Enums;
using Microsoft.AspNetCore.Http;
namespace GozareshgirProgramManager.Application.Services.FileManagement;
/// <summary>
/// سرویس آپلود و مدیریت کامل فایل
/// این سرویس تمام مراحل آپلود، ذخیره، تولید thumbnail و... را انجام می‌دهد
/// </summary>
public interface IFileUploadService
{
/// <summary>
/// آپلود فایل با تمام مراحل پردازش
/// </summary>
/// <param name="file">فایل برای آپلود</param>
/// <param name="category">دسته‌بندی فایل</param>
/// <param name="uploadedByUserId">شناسه کاربر آپلودکننده</param>
/// <param name="maxFileSizeBytes">حداکثر حجم مجاز فایل (پیش‌فرض: 100MB)</param>
/// <returns>شناسه فایل آپلود شده یا null در صورت خطا</returns>
Task<FileUploadResult> UploadFileAsync(
IFormFile file,
FileCategory category,
long uploadedByUserId,
long maxFileSizeBytes = 100 * 1024 * 1024);
/// <summary>
/// آپلود فایل با Stream
/// </summary>
Task<FileUploadResult> UploadFileFromStreamAsync(
Stream fileStream,
string fileName,
string contentType,
FileCategory category,
long uploadedByUserId,
long maxFileSizeBytes = 100 * 1024 * 1024);
}
/// <summary>
/// نتیجه عملیات آپلود فایل
/// </summary>
public class FileUploadResult
{
public bool IsSuccess { get; set; }
public Guid? FileId { get; set; }
public string? ErrorMessage { get; set; }
public string? StorageUrl { get; set; }
public string? ThumbnailUrl { get; set; }
public static FileUploadResult Success(Guid fileId, string storageUrl, string? thumbnailUrl = null)
{
return new FileUploadResult
{
IsSuccess = true,
FileId = fileId,
StorageUrl = storageUrl,
ThumbnailUrl = thumbnailUrl
};
}
public static FileUploadResult Failed(string errorMessage)
{
return new FileUploadResult
{
IsSuccess = false,
ErrorMessage = errorMessage
};
}
}

Some files were not shown because too many files have changed in this diff Show More