From 582da511c67ebdd198de92797cc2c7b52d5a3537 Mon Sep 17 00:00:00 2001 From: mahan Date: Thu, 1 Jan 2026 19:25:28 +0330 Subject: [PATCH 1/4] feat: add progress percentage calculation to task sections --- .../Projects/DTOs/ProjectHierarchyDtos.cs | 1 + .../Extensions/ProjectMappingExtensions.cs | 2 ++ .../ProjectAgg/Entities/TaskSection.cs | 21 +++++++++++++++++++ 3 files changed, 24 insertions(+) diff --git a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/DTOs/ProjectHierarchyDtos.cs b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/DTOs/ProjectHierarchyDtos.cs index 3cbc8cec..4a3d9294 100644 --- a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/DTOs/ProjectHierarchyDtos.cs +++ b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/DTOs/ProjectHierarchyDtos.cs @@ -89,6 +89,7 @@ public class ProjectSectionDto public TimeSpan FinalEstimatedHours { get; set; } public TimeSpan TotalTimeSpent { get; set; } + public double ProgressPercentage { get; set; } public bool IsCompleted { get; set; } public bool IsInProgress { get; set; } diff --git a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Extensions/ProjectMappingExtensions.cs b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Extensions/ProjectMappingExtensions.cs index 67ce006e..2c689cad 100644 --- a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Extensions/ProjectMappingExtensions.cs +++ b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Extensions/ProjectMappingExtensions.cs @@ -166,6 +166,7 @@ public static class ProjectMappingExtensions CreationDate = section.CreationDate, FinalEstimatedHours = section.FinalEstimatedHours, TotalTimeSpent = section.GetTotalTimeSpent(), + ProgressPercentage = section.GetProgressPercentage(), IsCompleted = section.IsCompleted(), IsInProgress = section.IsInProgress(), Activities = section.Activities.Select(a => a.ToDto()).ToList(), @@ -188,6 +189,7 @@ public static class ProjectMappingExtensions CreationDate = section.CreationDate, FinalEstimatedHours = section.FinalEstimatedHours, TotalTimeSpent = section.GetTotalTimeSpent(), + ProgressPercentage = section.GetProgressPercentage(), IsCompleted = section.IsCompleted(), IsInProgress = section.IsInProgress() // No activities or additional times for summary diff --git a/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Entities/TaskSection.cs b/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Entities/TaskSection.cs index 7209dd00..e506d173 100644 --- a/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Entities/TaskSection.cs +++ b/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Entities/TaskSection.cs @@ -157,6 +157,27 @@ public class TaskSection : EntityBase return TimeSpan.FromTicks(_activities.Sum(a => a.GetTimeSpent().Ticks)); } + /// + /// محاسبه درصد پیشرفت بر اساس زمان مصرف شده به تایم برآورد شده + /// اگر وضعیت Completed باشد، همیشه 100 درصد برمی‌گرداند + /// + public double GetProgressPercentage() + { + // اگر تسک کامل شده، همیشه 100 درصد + if (Status == TaskSectionStatus.Completed) + return 100.0; + + // اگر تایم برآورد شده صفر است، درصد صفر است + if (FinalEstimatedHours.TotalHours <= 0) + return 0.0; + + var timeSpent = GetTotalTimeSpent(); + var percentage = (timeSpent.TotalHours / FinalEstimatedHours.TotalHours) * 100.0; + + // محدود کردن درصد به 100 (در صورتی که زمان مصرف شده بیشتر از تخمین باشد) + return Math.Min(percentage, 100.0); + } + public bool IsCompleted() { return Status == TaskSectionStatus.Completed; From 9d09ef60f827cbfd0cf7c21e430f7df7be2b2381 Mon Sep 17 00:00:00 2001 From: mahan Date: Tue, 6 Jan 2026 18:19:16 +0330 Subject: [PATCH 2/4] feat: add progress percentage calculations to project and task details --- .../GetProjectsListQueryHandler.cs | 15 +-------------- .../ProjectBoardListQueryHandler.cs | 1 + .../ProjectBoardListResponse.cs | 1 + .../ProjectDeployBoardDetailsQueryHandler.cs | 19 +++++++++++-------- .../ProjectDeployBoardListQueryHandler.cs | 4 +++- .../ProjectAgg/Entities/TaskSection.cs | 2 +- 6 files changed, 18 insertions(+), 24 deletions(-) diff --git a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectsList/GetProjectsListQueryHandler.cs b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectsList/GetProjectsListQueryHandler.cs index ceaf2e19..621ef025 100644 --- a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectsList/GetProjectsListQueryHandler.cs +++ b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectsList/GetProjectsListQueryHandler.cs @@ -296,20 +296,7 @@ public class GetProjectsListQueryHandler : IBaseQueryHandler a.GetTimeSpent().TotalHours)); - - if (totalEstimatedHours <= 0) - return (0, section.FinalEstimatedHours); - - var totalSpentHours = totalSpentTime.TotalHours; - - // محاسبه درصد (حداکثر 100%) - var percentage = (totalSpentHours / totalEstimatedHours) * 100; - return (Math.Min((int)Math.Round(percentage), 100), section.FinalEstimatedHours); + return ((int)section.GetProgressPercentage(),section.FinalEstimatedHours); } } diff --git a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/ProjectBoardList/ProjectBoardListQueryHandler.cs b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/ProjectBoardList/ProjectBoardListQueryHandler.cs index e6502494..1157a932 100644 --- a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/ProjectBoardList/ProjectBoardListQueryHandler.cs +++ b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/ProjectBoardList/ProjectBoardListQueryHandler.cs @@ -106,6 +106,7 @@ public class ProjectBoardListQueryHandler : IBaseQueryHandler a.TotalSeconds), Histories = mergedHistories }, diff --git a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/ProjectBoardList/ProjectBoardListResponse.cs b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/ProjectBoardList/ProjectBoardListResponse.cs index ab38750e..688a12c0 100644 --- a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/ProjectBoardList/ProjectBoardListResponse.cs +++ b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/ProjectBoardList/ProjectBoardListResponse.cs @@ -18,6 +18,7 @@ public class ProjectBoardListResponse public class ProjectProgressDto { public double CurrentSecond { get; set; } + public int Percentage { get; set; } public double CompleteSecond { get; set; } public List Histories { get; set; } } diff --git a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/ProjectDeployBoardDetail/ProjectDeployBoardDetailsQueryHandler.cs b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/ProjectDeployBoardDetail/ProjectDeployBoardDetailsQueryHandler.cs index 33a1caa6..ddff2247 100644 --- a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/ProjectDeployBoardDetail/ProjectDeployBoardDetailsQueryHandler.cs +++ b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/ProjectDeployBoardDetail/ProjectDeployBoardDetailsQueryHandler.cs @@ -12,14 +12,16 @@ public record ProjectDeployBoardDetailsResponse( public record ProjectDeployBoardDetailPhaseItem( string Name, TimeSpan TotalTimeSpan, - TimeSpan DoneTimeSpan); + TimeSpan DoneTimeSpan, + int Percentage); public record ProjectDeployBoardDetailTaskItem( string Name, TimeSpan TotalTimeSpan, TimeSpan DoneTimeSpan, + int Percentage, List Skills) - : ProjectDeployBoardDetailPhaseItem(Name, TotalTimeSpan, DoneTimeSpan); + : ProjectDeployBoardDetailPhaseItem(Name, TotalTimeSpan, DoneTimeSpan,Percentage); public record ProjectDeployBoardDetailItemSkill(string OriginalUserFullName, string SkillName, int TimePercentage); @@ -79,22 +81,20 @@ public class var skillName = s.Skill?.Name ?? "بدون مهارت"; - var totalTimeSpent = s.GetTotalTimeSpent(); - - var timePercentage = s.FinalEstimatedHours.Ticks > 0 - ? (int)((totalTimeSpent.Ticks / (double)s.FinalEstimatedHours.Ticks) * 100) - : 0; + var timePercentage = (int)s.GetProgressPercentage(); return new ProjectDeployBoardDetailItemSkill( originalUserFullName, skillName, timePercentage); }).ToList(); + var taskPercentage = (int)Math.Round(skills.Average(x => x.TimePercentage)); return new ProjectDeployBoardDetailTaskItem( t.Name, totalTime, doneTime, + taskPercentage, skills); }).ToList(); @@ -104,7 +104,10 @@ public class var doneTimeSpan = tasksRes.Aggregate(TimeSpan.Zero, (sum, next) => sum.Add(next.DoneTimeSpan)); - var phaseRes = new ProjectDeployBoardDetailPhaseItem(phase.Name, totalTimeSpan, doneTimeSpan); + var phasePercentage = tasksRes.Average(x => x.Percentage); + + var phaseRes = new ProjectDeployBoardDetailPhaseItem(phase.Name, totalTimeSpan, doneTimeSpan, + (int)phasePercentage); var res = new ProjectDeployBoardDetailsResponse(phaseRes, tasksRes); diff --git a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/ProjectDeployBoardList/ProjectDeployBoardListQueryHandler.cs b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/ProjectDeployBoardList/ProjectDeployBoardListQueryHandler.cs index 6a0a7cda..d8f6e39d 100644 --- a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/ProjectDeployBoardList/ProjectDeployBoardListQueryHandler.cs +++ b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/ProjectDeployBoardList/ProjectDeployBoardListQueryHandler.cs @@ -17,6 +17,7 @@ public record ProjectDeployBoardListItem() public int DoneTasks { get; set; } public TimeSpan TotalTimeSpan { get; set; } public TimeSpan DoneTimeSpan { get; set; } + public int Percentage { get; set; } public ProjectDeployStatus DeployStatus { get; set; } } public record GetProjectsDeployBoardListResponse(List Items); @@ -66,7 +67,8 @@ public class ProjectDeployBoardListQueryHandler:IBaseQueryHandler x.TaskId).Distinct().Count(), TotalTimeSpan = TimeSpan.FromTicks(g.Sum(x => x.InitialEstimatedHours.Ticks)), DoneTimeSpan = TimeSpan.FromTicks(g.Sum(x=>x.GetTotalTimeSpent().Ticks)), - DeployStatus = g.First().Task.Phase.DeployStatus + DeployStatus = g.First().Task.Phase.DeployStatus, + Percentage = (int)Math.Round(g.Average(x => x.GetProgressPercentage())) }).ToList(); var response = new GetProjectsDeployBoardListResponse(list); return OperationResult.Success(response); diff --git a/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Entities/TaskSection.cs b/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Entities/TaskSection.cs index e506d173..072422fe 100644 --- a/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Entities/TaskSection.cs +++ b/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Entities/TaskSection.cs @@ -172,7 +172,7 @@ public class TaskSection : EntityBase return 0.0; var timeSpent = GetTotalTimeSpent(); - var percentage = (timeSpent.TotalHours / FinalEstimatedHours.TotalHours) * 100.0; + var percentage = (timeSpent.TotalMinutes / FinalEstimatedHours.TotalMinutes) * 100.0; // محدود کردن درصد به 100 (در صورتی که زمان مصرف شده بیشتر از تخمین باشد) return Math.Min(percentage, 100.0); From 9a591fabff85fe575abfbfc8469974b2dc5edf89 Mon Sep 17 00:00:00 2001 From: SamSys Date: Tue, 6 Jan 2026 19:07:54 +0330 Subject: [PATCH 3/4] change WorningSms methoth --- .../InstitutionContractRepository.cs | 439 +++++++++--------- 1 file changed, 223 insertions(+), 216 deletions(-) diff --git a/CompanyManagment.EFCore/Repository/InstitutionContractRepository.cs b/CompanyManagment.EFCore/Repository/InstitutionContractRepository.cs index 34c71438..7c54aa1d 100644 --- a/CompanyManagment.EFCore/Repository/InstitutionContractRepository.cs +++ b/CompanyManagment.EFCore/Repository/InstitutionContractRepository.cs @@ -6334,268 +6334,275 @@ public class InstitutionContractRepository : RepositoryBase - ///دریافت لیست پیامک قرادا های آبی بدهکار + ///دریافت لیست پیامک قراداد های آبی بدهکار /// /// - //public async Task> GetBlueSmsListData() - //{ + public async Task> GetWarningSmsListData() + { - // var institutionContracts = await _context.InstitutionContractSet.AsQueryable().Select(x => new InstitutionContractViewModel - // { - // Id = x.id, - // ContractingPartyId = x.ContractingPartyId, - // ContractingPartyName = x.ContractingPartyName, - // ContractStartGr = x.ContractStartGr, - // ContractStartFa = x.ContractStartFa, - // ContractEndGr = x.ContractEndGr, - // ContractEndFa = x.ContractEndFa, - // IsActiveString = x.IsActiveString, - // ContractAmountDouble = x.ContractAmount, - // OfficialCompany = x.OfficialCompany, - // IsInstallment = x.IsInstallment, - // VerificationStatus = x.VerificationStatus, - // SigningType = x.SigningType, - // }).Where(x => x.IsActiveString == "blue" && - // x.ContractAmountDouble > 0).GroupBy(x => x.ContractingPartyId).Select(x => x.First()).ToListAsync(); + var institutionContracts = await _context.InstitutionContractSet.AsQueryable().Select(x => new InstitutionContractViewModel + { + Id = x.id, + ContractingPartyId = x.ContractingPartyId, + ContractingPartyName = x.ContractingPartyName, + ContractStartGr = x.ContractStartGr, + ContractStartFa = x.ContractStartFa, + ContractEndGr = x.ContractEndGr, + ContractEndFa = x.ContractEndFa, + IsActiveString = x.IsActiveString, + ContractAmountDouble = x.ContractAmount, + OfficialCompany = x.OfficialCompany, + IsInstallment = x.IsInstallment, + VerificationStatus = x.VerificationStatus, + SigningType = x.SigningType, + }).Where(x => x.IsActiveString == "blue" && + x.ContractAmountDouble > 0).GroupBy(x => x.ContractingPartyId).Select(x => x.First()).ToListAsync(); - // // قرارداد هایی که بطور یکجا پرداخت شده اند - // var paidInFull = institutionContracts.Where(x => - // x.SigningType != InstitutionContractSigningType.Legacy && x.IsInstallment == false && x.SigningType != null).ToList(); + var institutionContractsIds = institutionContracts.Select(x => x.id).ToList(); - // //حذف قراداد هایی که یکجا پرداخت شده اند از لیست ایجاد سند ماهانه - // institutionContracts = institutionContracts.Except(paidInFull).ToList(); + // قرارداد هایی که بطور یکجا پرداخت شده اند + var paidInFull = institutionContracts.Where(x => + x.SigningType != InstitutionContractSigningType.Legacy && x.IsInstallment == false && x.SigningType != null).ToList(); - // var contractingPartyList = await _context.PersonalContractingParties - // .Where(x => institutionContracts.Select(ins => ins.ContractingPartyId).Contains(x.id)).ToListAsync(); + //حذف قراداد هایی که یکجا پرداخت شده اند از لیست ایجاد سند ماهانه + institutionContracts = institutionContracts.Except(paidInFull).ToList(); - // var financialStatmentList = await _context.FinancialStatments.AsSplitQuery() - // .Where(x => institutionContracts.Select(ins => ins.ContractingPartyId).Contains(x.ContractingPartyId)) - // .Include(x => x.FinancialTransactionList).Where(x => x.FinancialTransactionList.Count > 0).ToListAsync(); + var contractingPartyList = await _context.PersonalContractingParties + .Where(x => institutionContracts.Select(ins => ins.ContractingPartyId).Contains(x.id)).ToListAsync(); - // var phoneNumberList = await _context.InstitutionContractContactInfos - // .Where(x => institutionContracts.Select(ins => ins.Id).Contains(x.InstitutionContractId)) - // .Where(x => x.SendSms && x.PhoneType == "شماره همراه" && !string.IsNullOrWhiteSpace(x.PhoneNumber) && - // x.PhoneNumber.Length == 11).ToListAsync(); - // var legalActionSentSms =await _context.SmsResults - // .Where(x => x.TypeOfSms == "اقدام قضایی").ToListAsync(); - // var warningSentSms = await _context.SmsResults.Where(x => x.TypeOfSms.Contains("هشدار")).ToListAsync(); + var financialStatmentList = await _context.FinancialStatments.AsSplitQuery() + .Where(x => institutionContracts.Select(ins => ins.ContractingPartyId).Contains(x.ContractingPartyId)) + .Include(x => x.FinancialTransactionList).Where(x => x.FinancialTransactionList.Count > 0).ToListAsync(); - // var oldInstitutionContract = institutionContracts.Where(x => x.IsInstallment == false).ToList(); - // var electronicInstitutionContract = institutionContracts.Where(x => x.IsInstallment == true).ToList(); + var phoneNumberList = await _context.InstitutionContractContactInfos + .Where(x => institutionContracts.Select(ins => ins.Id).Contains(x.InstitutionContractId)) + .Where(x => x.SendSms && x.PhoneType == "شماره همراه" && !string.IsNullOrWhiteSpace(x.PhoneNumber) && + x.PhoneNumber.Length == 11).ToListAsync(); + var legalActionSentSms = await _context.SmsResults + .Where(x => x.TypeOfSms == "اقدام قضایی").ToListAsync(); + var warningSentSms = await _context.SmsResults.Where(x => x.TypeOfSms.Contains("هشدار")).ToListAsync(); - // foreach (var item in oldInstitutionContract) - // { - // try - // { - // var contractingParty = GetDetails(item.ContractingPartyId); - // bool hasLegalActionSentSms = legalActionSentSms.Any(x => x.InstitutionContractId == item.Id); + var oldInstitutionContract = institutionContracts.Where(x => x.IsInstallment == false).ToList(); + var electronicInstitutionContract = institutionContracts.Where(x => x.IsInstallment == true).ToList(); - // if (!string.IsNullOrWhiteSpace(contractingParty.LName) && !hasLegalActionSentSms) - // { - // //Thread.Sleep(500); - // var partyName = contractingParty.LName; + foreach (var item in oldInstitutionContract) + { + try + { + var contractingParty = GetDetails(item.ContractingPartyId); + bool hasLegalActionSentSms = legalActionSentSms.Any(x => x.InstitutionContractId == item.Id); + int year = Convert.ToInt32(item.ContractEndFa.Substring(0, 4)); + int month = Convert.ToInt32(item.ContractEndFa.Substring(5, 2)); + var endOfContractNextMonthStart = new PersianDateTime(year, month, 1).AddMonths(1); + var endOfContractNextMonthEnd = (($"{endOfContractNextMonthStart}").FindeEndOfMonth()).ToGeorgianDateTime(); + var now = DateTime.Now - // if (partyName.Length > 25) partyName = $"{partyName.Substring(0, 25)}"; + if (!string.IsNullOrWhiteSpace(contractingParty.LName) && !hasLegalActionSentSms && now.Date <= endOfContractNextMonthEnd.Date) + { + //Thread.Sleep(500); + var partyName = contractingParty.LName; - // var isLegal = contractingParty.IsLegal == "حقوقی" ? true : false; - // var isBlock = contractingParty.IsBlock == "true" ? true : false; - // var isActive = contractingParty.IsActiveString == "true" ? true : false; - // if (!string.IsNullOrWhiteSpace(contractingParty.IsActiveString)) - // { - // var hasFinancialStatement = - // financialStatmentList.Any(x => x.ContractingPartyId == item.ContractingPartyId); + if (partyName.Length > 25) partyName = $"{partyName.Substring(0, 25)}"; - // var hasPhonNumber = phoneNumberList.Any(x => x.InstitutionContractId == item.Id); + var isLegal = contractingParty.IsLegal == "حقوقی" ? true : false; + var isBlock = contractingParty.IsBlock == "true" ? true : false; + var isActive = contractingParty.IsActiveString == "true" ? true : false; + if (!string.IsNullOrWhiteSpace(contractingParty.IsActiveString)) + { + var hasFinancialStatement = + financialStatmentList.Any(x => x.ContractingPartyId == item.ContractingPartyId); - // if (hasFinancialStatement && hasPhonNumber) - // { - // var phoneNumbers = phoneNumberList.Where(x => x.InstitutionContractId == item.Id) - // .Select(x => new CreateContactInfo - // { - // PhoneType = x.PhoneType, - // PhoneNumber = x.PhoneNumber, + var hasPhonNumber = phoneNumberList.Any(x => x.InstitutionContractId == item.Id); - // InstitutionContractId = x.InstitutionContractId, - // SendSms = x.SendSms - // }).Where(x => x.PhoneNumber.Length == 11).ToList(); + if (hasFinancialStatement && hasPhonNumber) + { + var phoneNumbers = phoneNumberList.Where(x => x.InstitutionContractId == item.Id) + .Select(x => new CreateContactInfo + { + PhoneType = x.PhoneType, + PhoneNumber = x.PhoneNumber, - // var transactions = financialStatmentList.FirstOrDefault(x => - // x.ContractingPartyId == item.ContractingPartyId); + InstitutionContractId = x.InstitutionContractId, + SendSms = x.SendSms + }).Where(x => x.PhoneNumber.Length == 11).ToList(); + + var transactions = financialStatmentList.FirstOrDefault(x => + x.ContractingPartyId == item.ContractingPartyId); - // var debtor = transactions.FinancialTransactionViewModels.Sum(x => x.Deptor); - // var creditor = transactions.FinancialTransactionViewModels.Sum(x => x.Creditor); - + var debtor = transactions.FinancialTransactionViewModels.Sum(x => x.Deptor); + var creditor = transactions.FinancialTransactionViewModels.Sum(x => x.Creditor); - // var id = $"{item.ContractingPartyId}"; - // var aprove = $"{transactions.Id}"; - // var balance = debtor - creditor; - // if (balance > 0) // اگر بدهکار بود - // { + var id = $"{item.ContractingPartyId}"; + var aprove = $"{transactions.Id}"; + var balance = debtor - creditor; - // if (isLegal) - // { - // if (item.OfficialCompany == "Official") // حقوقی بدهکار رسمی - // { - // var balanceToMoney = balance.ToMoney(); + if (balance > 0) // اگر بدهکار بود + { - // foreach (var number in phoneNumbers) - // { - - // var isLastAlarmSend = _context.SmsResults.Any(x => ( - // x.ContractingPatyId == contractingParty.Id && - // x.Mobile == number.PhoneNumber) && (x.TypeOfSms == "اقدام قضایی" || x.TypeOfSms == "هشدار دوم")); + if (isLegal) + { + if (item.OfficialCompany == "Official") // حقوقی بدهکار رسمی + { + var balanceToMoney = balance.ToMoney(); - // if (!string.IsNullOrWhiteSpace(number.PhoneNumber) && - // number.PhoneNumber.Length == 11 && !isSend && !isLastAlarmSend) - // { + foreach (var number in phoneNumbers) + { - // var smsResult = _smsService.MonthlyBill(number.PhoneNumber, 608443, - // partyName, - // balanceToMoney, id, aprove); - // Thread.Sleep(1000); - // if (smsResult.IsSuccedded) - // { - // var createSmsResult = new SmsResult(smsResult.MessageId, - // smsResult.Message, typeOfSms, partyName, number.PhoneNumber, - // item.ContractingPartyId, item.Id); - // _smsResultRepository.Create(createSmsResult); - // _smsResultRepository.SaveChanges(); - // Thread.Sleep(1000); - // } + var isLastAlarmSend = _context.SmsResults.Any(x => ( + x.ContractingPatyId == contractingParty.Id && + x.Mobile == number.PhoneNumber) && (x.TypeOfSms == "اقدام قضایی" || x.TypeOfSms == "هشدار دوم")); + var t = warningSentSms.Any(x=> x.) + if (!string.IsNullOrWhiteSpace(number.PhoneNumber) && + number.PhoneNumber.Length == 11 && !isSend && !isLastAlarmSend) + { - // } - // } + var smsResult = _smsService.MonthlyBill(number.PhoneNumber, 608443, + partyName, + balanceToMoney, id, aprove); + Thread.Sleep(1000); + if (smsResult.IsSuccedded) + { + var createSmsResult = new SmsResult(smsResult.MessageId, + smsResult.Message, typeOfSms, partyName, number.PhoneNumber, + item.ContractingPartyId, item.Id); + _smsResultRepository.Create(createSmsResult); + _smsResultRepository.SaveChanges(); + Thread.Sleep(1000); + } + + } + } - // } - // else if (item.OfficialCompany == "NotOfficial") // حقوقی بدهکار غیر رسمی - // { - // var balanceToMoney = balance.ToMoney(); - // foreach (var number in phoneNumbers) - // { - // var isSend = _context.SmsResults.Any(x => - // x.ContractingPatyId == contractingParty.Id && - // x.Mobile == number.PhoneNumber && x.TypeOfSms == typeOfSms); - // var isLastAlarmSend = _context.SmsResults.Any(x => ( - // x.ContractingPatyId == contractingParty.Id && - // x.Mobile == number.PhoneNumber) && (x.TypeOfSms == "اقدام قضایی" || x.TypeOfSms == "هشدار دوم")); - // if (!string.IsNullOrWhiteSpace(number.PhoneNumber) && - // number.PhoneNumber.Length == 11 && !isSend && !isLastAlarmSend) - // { + } + else if (item.OfficialCompany == "NotOfficial") // حقوقی بدهکار غیر رسمی + { + var balanceToMoney = balance.ToMoney(); + foreach (var number in phoneNumbers) + { + var isSend = _context.SmsResults.Any(x => + x.ContractingPatyId == contractingParty.Id && + x.Mobile == number.PhoneNumber && x.TypeOfSms == typeOfSms); + var isLastAlarmSend = _context.SmsResults.Any(x => ( + x.ContractingPatyId == contractingParty.Id && + x.Mobile == number.PhoneNumber) && (x.TypeOfSms == "اقدام قضایی" || x.TypeOfSms == "هشدار دوم")); + if (!string.IsNullOrWhiteSpace(number.PhoneNumber) && + number.PhoneNumber.Length == 11 && !isSend && !isLastAlarmSend) + { - // var smsResult = _smsService.MonthlyBill(number.PhoneNumber, 351691, - // partyName, - // balanceToMoney, id, aprove); - // Thread.Sleep(1000); - // if (smsResult.IsSuccedded) - // { - // var createSmsResult = new SmsResult(smsResult.MessageId, - // smsResult.Message, typeOfSms, partyName, number.PhoneNumber, - // item.ContractingPartyId, item.Id); - // _smsResultRepository.Create(createSmsResult); - // _smsResultRepository.SaveChanges(); - // Thread.Sleep(1000); - // } + var smsResult = _smsService.MonthlyBill(number.PhoneNumber, 351691, + partyName, + balanceToMoney, id, aprove); + Thread.Sleep(1000); + if (smsResult.IsSuccedded) + { + var createSmsResult = new SmsResult(smsResult.MessageId, + smsResult.Message, typeOfSms, partyName, number.PhoneNumber, + item.ContractingPartyId, item.Id); + _smsResultRepository.Create(createSmsResult); + _smsResultRepository.SaveChanges(); + Thread.Sleep(1000); + } - // } - // } + } + } - // } - // } - // else - // { - // if (item.OfficialCompany == "Official") // حقیقی بدهکار رسمی - // { - // var balanceToMoney = balance.ToMoney(); + } + } + else + { + if (item.OfficialCompany == "Official") // حقیقی بدهکار رسمی + { + var balanceToMoney = balance.ToMoney(); - // foreach (var number in phoneNumbers) - // { - // var isSend = _context.SmsResults.Any(x => - // x.ContractingPatyId == contractingParty.Id && - // x.Mobile == number.PhoneNumber && x.TypeOfSms == typeOfSms); - // var isLastAlarmSend = _context.SmsResults.Any(x => ( - // x.ContractingPatyId == contractingParty.Id && - // x.Mobile == number.PhoneNumber) && (x.TypeOfSms == "اقدام قضایی" || x.TypeOfSms == "هشدار دوم")); - // if (!string.IsNullOrWhiteSpace(number.PhoneNumber) && - // number.PhoneNumber.Length == 11 && !isSend && !isLastAlarmSend) - // { + foreach (var number in phoneNumbers) + { + var isSend = _context.SmsResults.Any(x => + x.ContractingPatyId == contractingParty.Id && + x.Mobile == number.PhoneNumber && x.TypeOfSms == typeOfSms); + var isLastAlarmSend = _context.SmsResults.Any(x => ( + x.ContractingPatyId == contractingParty.Id && + x.Mobile == number.PhoneNumber) && (x.TypeOfSms == "اقدام قضایی" || x.TypeOfSms == "هشدار دوم")); + if (!string.IsNullOrWhiteSpace(number.PhoneNumber) && + number.PhoneNumber.Length == 11 && !isSend && !isLastAlarmSend) + { - // var smsResult = _smsService.MonthlyBill(number.PhoneNumber, 190430, - // partyName, - // balanceToMoney, id, aprove); - // Thread.Sleep(1000); - // if (smsResult.IsSuccedded) - // { - // var createSmsResult = new SmsResult(smsResult.MessageId, - // smsResult.Message, typeOfSms, partyName, number.PhoneNumber, - // item.ContractingPartyId, item.Id); - // _smsResultRepository.Create(createSmsResult); - // _smsResultRepository.SaveChanges(); - // Thread.Sleep(1000); - // } - // } - // } + var smsResult = _smsService.MonthlyBill(number.PhoneNumber, 190430, + partyName, + balanceToMoney, id, aprove); + Thread.Sleep(1000); + if (smsResult.IsSuccedded) + { + var createSmsResult = new SmsResult(smsResult.MessageId, + smsResult.Message, typeOfSms, partyName, number.PhoneNumber, + item.ContractingPartyId, item.Id); + _smsResultRepository.Create(createSmsResult); + _smsResultRepository.SaveChanges(); + Thread.Sleep(1000); + } + } + } - // } - // else if (item.OfficialCompany == "NotOfficial") // حقیقی بدهکار غیر رسمی - // { - // var balanceToMoney = balance.ToMoney(); + } + else if (item.OfficialCompany == "NotOfficial") // حقیقی بدهکار غیر رسمی + { + var balanceToMoney = balance.ToMoney(); - // foreach (var number in phoneNumbers) - // { - // var isSend = _context.SmsResults.Any(x => - // x.ContractingPatyId == contractingParty.Id && - // x.Mobile == number.PhoneNumber && x.TypeOfSms == typeOfSms); - // var isLastAlarmSend = _context.SmsResults.Any(x => ( - // x.ContractingPatyId == contractingParty.Id && - // x.Mobile == number.PhoneNumber) && (x.TypeOfSms == "اقدام قضایی" || x.TypeOfSms == "هشدار دوم")); - // if (!string.IsNullOrWhiteSpace(number.PhoneNumber) && - // number.PhoneNumber.Length == 11 && !isSend && !isLastAlarmSend) - // { - // var smsResult = _smsService.MonthlyBill(number.PhoneNumber, 412829, - // partyName, - // balanceToMoney, id, aprove); - // Thread.Sleep(1000); - // if (smsResult.IsSuccedded) - // { - // var createSmsResult = new SmsResult(smsResult.MessageId, - // smsResult.Message, typeOfSms, partyName, number.PhoneNumber, - // item.ContractingPartyId, item.Id); - // _smsResultRepository.Create(createSmsResult); - // _smsResultRepository.SaveChanges(); - // Thread.Sleep(1000); - // } + foreach (var number in phoneNumbers) + { + var isSend = _context.SmsResults.Any(x => + x.ContractingPatyId == contractingParty.Id && + x.Mobile == number.PhoneNumber && x.TypeOfSms == typeOfSms); + var isLastAlarmSend = _context.SmsResults.Any(x => ( + x.ContractingPatyId == contractingParty.Id && + x.Mobile == number.PhoneNumber) && (x.TypeOfSms == "اقدام قضایی" || x.TypeOfSms == "هشدار دوم")); + if (!string.IsNullOrWhiteSpace(number.PhoneNumber) && + number.PhoneNumber.Length == 11 && !isSend && !isLastAlarmSend) + { + var smsResult = _smsService.MonthlyBill(number.PhoneNumber, 412829, + partyName, + balanceToMoney, id, aprove); + Thread.Sleep(1000); + if (smsResult.IsSuccedded) + { + var createSmsResult = new SmsResult(smsResult.MessageId, + smsResult.Message, typeOfSms, partyName, number.PhoneNumber, + item.ContractingPartyId, item.Id); + _smsResultRepository.Create(createSmsResult); + _smsResultRepository.SaveChanges(); + Thread.Sleep(1000); + } - // } - // } + } + } - // } - // } - // } - // } - // } - // } - // } - // catch (Exception e) - // { + } + } + } + } + } + } + } + catch (Exception e) + { - // string name = item.ContractingPartyName.Length > 18 ? item.ContractingPartyName.Substring(0, 18) : item.ContractingPartyName; - // string errMess = $"{name}-خطا"; - // _smsService.Alarm("09114221321", errMess); - // _logger.LogError(e, "BlueWarningSms"); - // } - // } - //} + string name = item.ContractingPartyName.Length > 18 ? item.ContractingPartyName.Substring(0, 18) : item.ContractingPartyName; + string errMess = $"{name}-خطا"; + _smsService.Alarm("09114221321", errMess); + _logger.LogError(e, "BlueWarningSms"); + } + } + } #endregion From 3da7453ece4497979cfae2c49ad82bba07b5ce34 Mon Sep 17 00:00:00 2001 From: mahan Date: Tue, 6 Jan 2026 21:47:22 +0330 Subject: [PATCH 4/4] feat: add assignment status tracking for projects, phases, and tasks --- .../GetProjectsList/GetProjectListDto.cs | 20 +- .../GetProjectsListQueryHandler.cs | 273 +++++++++++------- .../GetProjectsListResponse.cs | 5 +- .../Queries/GetProjectsList/GetTaskListDto.cs | 31 ++ .../ProjectAgg/Enums/AssignmentStatus.cs | 23 ++ ServiceHost/Program.cs | 8 +- 6 files changed, 237 insertions(+), 123 deletions(-) create mode 100644 ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectsList/GetTaskListDto.cs create mode 100644 ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Enums/AssignmentStatus.cs diff --git a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectsList/GetProjectListDto.cs b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectsList/GetProjectListDto.cs index 7066148e..91a82c28 100644 --- a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectsList/GetProjectListDto.cs +++ b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectsList/GetProjectListDto.cs @@ -1,20 +1,28 @@ using GozareshgirProgramManager.Domain.ProjectAgg.Enums; namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectsList; -public record GetProjectListDto + +// Base DTO shared across project, phase, and task +public class GetProjectItemDto { public Guid Id { get; init; } public string Name { get; init; } = string.Empty; public int Percentage { get; init; } public ProjectHierarchyLevel Level { get; init; } public Guid? ParentId { get; init; } - public bool HasFront { get; set; } - public bool HasBackend { get; set; } - public bool HasDesign { get; set; } public int TotalHours { get; set; } public int Minutes { get; set; } - + public AssignmentStatus Front { get; set; } + public AssignmentStatus Backend { get; set; } + public AssignmentStatus Design { get; set; } } +// Project DTO (no extra fields; inherits from base) +public class GetProjectDto : GetProjectItemDto +{ +} - +// Phase DTO (no extra fields; inherits from base) +public class GetPhaseDto : GetProjectItemDto +{ +} diff --git a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectsList/GetProjectsListQueryHandler.cs b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectsList/GetProjectsListQueryHandler.cs index 621ef025..d26a06b1 100644 --- a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectsList/GetProjectsListQueryHandler.cs +++ b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectsList/GetProjectsListQueryHandler.cs @@ -3,6 +3,7 @@ using GozareshgirProgramManager.Application._Common.Models; using GozareshgirProgramManager.Domain.ProjectAgg.Entities; using GozareshgirProgramManager.Domain.ProjectAgg.Enums; using Microsoft.EntityFrameworkCore; +using System.Linq; namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectsList; @@ -17,47 +18,47 @@ public class GetProjectsListQueryHandler : IBaseQueryHandler> Handle(GetProjectsListQuery request, CancellationToken cancellationToken) { - List projects; + var projects = new List(); + var phases = new List(); + var tasks = new List(); switch (request.HierarchyLevel) { case ProjectHierarchyLevel.Project: projects = await GetProjects(request.ParentId, cancellationToken); + await SetSkillFlags(projects, cancellationToken); break; case ProjectHierarchyLevel.Phase: - projects = await GetPhases(request.ParentId, cancellationToken); + phases = await GetPhases(request.ParentId, cancellationToken); + await SetSkillFlags(phases, cancellationToken); break; case ProjectHierarchyLevel.Task: - projects = await GetTasks(request.ParentId, cancellationToken); + tasks = await GetTasks(request.ParentId, cancellationToken); + // Tasks don't need SetSkillFlags because they have Sections list break; default: return OperationResult.Failure("سطح سلسله مراتب نامعتبر است"); } - await SetSkillFlags(projects, cancellationToken); - var response = new GetProjectsListResponse(projects); + var response = new GetProjectsListResponse(projects, phases, tasks); return OperationResult.Success(response); } - private async Task> GetProjects(Guid? parentId, CancellationToken cancellationToken) + private async Task> GetProjects(Guid? parentId, CancellationToken cancellationToken) { var query = _context.Projects.AsQueryable(); - - // پروژه‌ها سطح بالا هستند و parentId ندارند، فقط در صورت null بودن parentId نمایش داده می‌شوند if (parentId.HasValue) { - return new List(); // پروژه‌ها parent ندارند + return new List(); } - - var projects = await query + var entities = await query .OrderByDescending(p => p.CreationDate) .ToListAsync(cancellationToken); - var result = new List(); - - foreach (var project in projects) + var result = new List(); + foreach (var project in entities) { var (percentage, totalTime) = await CalculateProjectPercentage(project, cancellationToken); - result.Add(new GetProjectListDto + result.Add(new GetProjectDto { Id = project.Id, Name = project.Name, @@ -68,28 +69,24 @@ public class GetProjectsListQueryHandler : IBaseQueryHandler> GetPhases(Guid? parentId, CancellationToken cancellationToken) + private async Task> GetPhases(Guid? parentId, CancellationToken cancellationToken) { var query = _context.ProjectPhases.AsQueryable(); - if (parentId.HasValue) { query = query.Where(x => x.ProjectId == parentId); } - - var phases = await query + var entities = await query .OrderByDescending(p => p.CreationDate) .ToListAsync(cancellationToken); - var result = new List(); - - foreach (var phase in phases) + var result = new List(); + foreach (var phase in entities) { var (percentage, totalTime) = await CalculatePhasePercentage(phase, cancellationToken); - result.Add(new GetProjectListDto + result.Add(new GetPhaseDto { Id = phase.Id, Name = phase.Name, @@ -100,28 +97,87 @@ public class GetProjectsListQueryHandler : IBaseQueryHandler> GetTasks(Guid? parentId, CancellationToken cancellationToken) + private async Task> GetTasks(Guid? parentId, CancellationToken cancellationToken) { var query = _context.ProjectTasks.AsQueryable(); - if (parentId.HasValue) { query = query.Where(x => x.PhaseId == parentId); } - - var tasks = await query + var entities = await query .OrderByDescending(t => t.CreationDate) .ToListAsync(cancellationToken); - var result = new List(); + var result = new List(); + // دریافت تمام Skills + var allSkills = await _context.Skills + .Select(s => new { s.Id, s.Name }) + .ToListAsync(cancellationToken); - foreach (var task in tasks) + foreach (var task in entities) { var (percentage, totalTime) = await CalculateTaskPercentage(task, cancellationToken); - result.Add(new GetProjectListDto + var sections = await _context.TaskSections + .Include(s => s.Activities) + .Include(s => s.Skill) + .Where(s => s.TaskId == task.Id) + .ToListAsync(cancellationToken); + + // جمع‌آوری تمام userId های مورد نیاز + var userIds = sections + .Where(s => s.CurrentAssignedUserId > 0) + .Select(s => s.CurrentAssignedUserId) + .Distinct() + .ToList(); + + // دریافت اطلاعات کاربران + var users = await _context.Users + .Where(u => userIds.Contains(u.Id)) + .Select(u => new { u.Id, u.FullName }) + .ToDictionaryAsync(u => u.Id, u => u.FullName, cancellationToken); + + // محاسبه SpentTime و RemainingTime + var spentTime = TimeSpan.FromTicks(sections.Sum(s => s.Activities.Sum(a => a.GetTimeSpent().Ticks))); + var remainingTime = totalTime - spentTime; + + // ساخت section DTOs برای تمام Skills + var sectionDtos = allSkills.Select(skill => + { + var section = sections.FirstOrDefault(s => s.SkillId == skill.Id); + + if (section == null) + { + // اگر section وجود نداشت، یک DTO با وضعیت Unassigned برمی‌گردانیم + return new GetTaskSectionDto + { + Id = Guid.Empty, + SkillName = skill.Name ?? string.Empty, + SpentTime = TimeSpan.Zero, + TotalTime = TimeSpan.Zero, + Percentage = 0, + UserFullName = string.Empty, + AssignmentStatus = AssignmentStatus.Unassigned + }; + } + + // اگر section وجود داشت + return new GetTaskSectionDto + { + Id = section.Id, + SkillName = skill.Name ?? string.Empty, + SpentTime = TimeSpan.FromTicks(section.Activities.Sum(a => a.GetTimeSpent().Ticks)), + TotalTime = section.FinalEstimatedHours, + Percentage = (int)section.GetProgressPercentage(), + UserFullName = section.CurrentAssignedUserId > 0 && users.ContainsKey(section.CurrentAssignedUserId) + ? users[section.CurrentAssignedUserId] + : string.Empty, + AssignmentStatus = GetAssignmentStatus(section) + }; + }).ToList(); + + result.Add(new GetTaskDto { Id = task.Id, Name = task.Name, @@ -129,167 +185,144 @@ public class GetProjectsListQueryHandler : IBaseQueryHandler projects, CancellationToken cancellationToken) + private async Task SetSkillFlags(List items, CancellationToken cancellationToken) where TItem : GetProjectItemDto { - if (!projects.Any()) + if (!items.Any()) return; - - var projectIds = projects.Select(x => x.Id).ToList(); - var hierarchyLevel = projects.First().Level; - + var ids = items.Select(x => x.Id).ToList(); + var hierarchyLevel = items.First().Level; switch (hierarchyLevel) { case ProjectHierarchyLevel.Project: - await SetSkillFlagsForProjects(projects, projectIds, cancellationToken); + await SetSkillFlagsForProjects(items, ids, cancellationToken); break; - case ProjectHierarchyLevel.Phase: - await SetSkillFlagsForPhases(projects, projectIds, cancellationToken); - break; - - case ProjectHierarchyLevel.Task: - await SetSkillFlagsForTasks(projects, projectIds, cancellationToken); + await SetSkillFlagsForPhases(items, ids, cancellationToken); break; } } - private async Task SetSkillFlagsForProjects(List projects, List projectIds, CancellationToken cancellationToken) + + private async Task SetSkillFlagsForProjects(List items, List projectIds, CancellationToken cancellationToken) where TItem : GetProjectItemDto { - var projectSections = await _context.ProjectSections - .Include(x => x.Skill) - .Where(s => projectIds.Contains(s.ProjectId)) + // For projects: gather all phases, then tasks, then sections + var phases = await _context.ProjectPhases + .Where(ph => projectIds.Contains(ph.ProjectId)) + .Select(ph => ph.Id) + .ToListAsync(cancellationToken); + var tasks = await _context.ProjectTasks + .Where(t => phases.Contains(t.PhaseId)) + .Select(t => t.Id) + .ToListAsync(cancellationToken); + var sections = await _context.TaskSections + .Include(s => s.Skill) + .Where(s => tasks.Contains(s.TaskId)) .ToListAsync(cancellationToken); - if (!projectSections.Any()) - return; - - foreach (var project in projects) + foreach (var item in items) { - var sections = projectSections.Where(s => s.ProjectId == project.Id).ToList(); - project.HasBackend = sections.Any(x => x.Skill?.Name == "Backend"); - project.HasFront = sections.Any(x => x.Skill?.Name == "Frontend"); - project.HasDesign = sections.Any(x => x.Skill?.Name == "UI/UX Design"); + var relatedPhases = phases; // used for filtering tasks by project + var relatedTasks = await _context.ProjectTasks + .Where(t => t.PhaseId != Guid.Empty && relatedPhases.Contains(t.PhaseId)) + .Select(t => t.Id) + .ToListAsync(cancellationToken); + var itemSections = sections.Where(s => relatedTasks.Contains(s.TaskId)); + item.Backend = GetAssignmentStatus(itemSections.FirstOrDefault(x => x.Skill?.Name == "Backend")); + item.Front = GetAssignmentStatus(itemSections.FirstOrDefault(x => x.Skill?.Name == "Frontend")); + item.Design = GetAssignmentStatus(itemSections.FirstOrDefault(x => x.Skill?.Name == "UI/UX Design")); } } - private async Task SetSkillFlagsForPhases(List projects, List phaseIds, CancellationToken cancellationToken) + private async Task SetSkillFlagsForPhases(List items, List phaseIds, CancellationToken cancellationToken) where TItem : GetProjectItemDto { - var phaseSections = await _context.PhaseSections - .Include(x => x.Skill) - .Where(s => phaseIds.Contains(s.PhaseId)) + // For phases: gather tasks, then sections + var tasks = await _context.ProjectTasks + .Where(t => phaseIds.Contains(t.PhaseId)) + .Select(t => t.Id) + .ToListAsync(cancellationToken); + var sections = await _context.TaskSections + .Include(s => s.Skill) + .Where(s => tasks.Contains(s.TaskId)) .ToListAsync(cancellationToken); - if (!phaseSections.Any()) - return; - - foreach (var phase in projects) + foreach (var item in items) { - var sections = phaseSections.Where(s => s.PhaseId == phase.Id).ToList(); - phase.HasBackend = sections.Any(x => x.Skill?.Name == "Backend"); - phase.HasFront = sections.Any(x => x.Skill?.Name == "Frontend"); - phase.HasDesign = sections.Any(x => x.Skill?.Name == "UI/UX Design"); - } - } - - private async Task SetSkillFlagsForTasks(List projects, List taskIds, CancellationToken cancellationToken) - { - var taskSections = await _context.TaskSections - .Include(x => x.Skill) - .Where(s => taskIds.Contains(s.TaskId)) - .ToListAsync(cancellationToken); - - if (!taskSections.Any()) - return; - - foreach (var task in projects) - { - var sections = taskSections.Where(s => s.TaskId == task.Id).ToList(); - task.HasBackend = sections.Any(x => x.Skill?.Name == "Backend"); - task.HasFront = sections.Any(x => x.Skill?.Name == "Frontend"); - task.HasDesign = sections.Any(x => x.Skill?.Name == "UI/UX Design"); + // Filter tasks for this phase + var phaseTaskIds = await _context.ProjectTasks + .Where(t => t.PhaseId == item.Id) + .Select(t => t.Id) + .ToListAsync(cancellationToken); + var itemSections = sections.Where(s => phaseTaskIds.Contains(s.TaskId)); + item.Backend = GetAssignmentStatus(itemSections.FirstOrDefault(x => x.Skill?.Name == "Backend")); + item.Front = GetAssignmentStatus(itemSections.FirstOrDefault(x => x.Skill?.Name == "Frontend")); + item.Design = GetAssignmentStatus(itemSections.FirstOrDefault(x => x.Skill?.Name == "UI/UX Design")); } } private async Task<(int Percentage, TimeSpan TotalTime)> CalculateProjectPercentage(Project project, CancellationToken cancellationToken) { - // گرفتن تمام فازهای پروژه var phases = await _context.ProjectPhases .Where(ph => ph.ProjectId == project.Id) .ToListAsync(cancellationToken); - if (!phases.Any()) return (0, TimeSpan.Zero); - - // محاسبه درصد هر فاز و میانگین‌گیری var phasePercentages = new List(); var totalTime = TimeSpan.Zero; - foreach (var phase in phases) { var (phasePercentage, phaseTime) = await CalculatePhasePercentage(phase, cancellationToken); phasePercentages.Add(phasePercentage); totalTime += phaseTime; } - var averagePercentage = phasePercentages.Any() ? (int)phasePercentages.Average() : 0; return (averagePercentage, totalTime); } private async Task<(int Percentage, TimeSpan TotalTime)> CalculatePhasePercentage(ProjectPhase phase, CancellationToken cancellationToken) { - // گرفتن تمام تسک‌های فاز var tasks = await _context.ProjectTasks .Where(t => t.PhaseId == phase.Id) .ToListAsync(cancellationToken); - if (!tasks.Any()) return (0, TimeSpan.Zero); - - // محاسبه درصد هر تسک و میانگین‌گیری var taskPercentages = new List(); var totalTime = TimeSpan.Zero; - foreach (var task in tasks) { var (taskPercentage, taskTime) = await CalculateTaskPercentage(task, cancellationToken); taskPercentages.Add(taskPercentage); totalTime += taskTime; } - var averagePercentage = taskPercentages.Any() ? (int)taskPercentages.Average() : 0; return (averagePercentage, totalTime); } private async Task<(int Percentage, TimeSpan TotalTime)> CalculateTaskPercentage(ProjectTask task, CancellationToken cancellationToken) { - // گرفتن تمام سکشن‌های تسک با activities var sections = await _context.TaskSections .Include(s => s.Activities) .Include(x=>x.AdditionalTimes) .Where(s => s.TaskId == task.Id) .ToListAsync(cancellationToken); - if (!sections.Any()) return (0, TimeSpan.Zero); - - // محاسبه درصد هر سکشن و میانگین‌گیری var sectionPercentages = new List(); var totalTime = TimeSpan.Zero; - foreach (var section in sections) { var (sectionPercentage, sectionTime) = CalculateSectionPercentage(section); sectionPercentages.Add(sectionPercentage); totalTime += sectionTime; } - var averagePercentage = sectionPercentages.Any() ? (int)sectionPercentages.Average() : 0; return (averagePercentage, totalTime); } @@ -298,5 +331,29 @@ public class GetProjectsListQueryHandler : IBaseQueryHandler 0; + + // بررسی وجود time (InitialEstimatedHours بزرگتر از صفر باشد) + bool hasTime = section.InitialEstimatedHours > TimeSpan.Zero; + + // تعیین تکلیف شده: هم user و هم time تعیین شده + if (hasUser && hasTime) + return AssignmentStatus.Assigned; + + // فقط کاربر تعیین شده: user دارد ولی time ندارد + if (hasUser && !hasTime) + return AssignmentStatus.UserOnly; + + // تعیین تکلیف نشده: نه user دارد نه time + return AssignmentStatus.Unassigned; + } } diff --git a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectsList/GetProjectsListResponse.cs b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectsList/GetProjectsListResponse.cs index 928501dd..f4934501 100644 --- a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectsList/GetProjectsListResponse.cs +++ b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectsList/GetProjectsListResponse.cs @@ -1,5 +1,6 @@ namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectsList; public record GetProjectsListResponse( - List Projects); - + List Projects, + List Phases, + List Tasks); diff --git a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectsList/GetTaskListDto.cs b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectsList/GetTaskListDto.cs new file mode 100644 index 00000000..d48493d7 --- /dev/null +++ b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/GetProjectsList/GetTaskListDto.cs @@ -0,0 +1,31 @@ +using GozareshgirProgramManager.Domain.ProjectAgg.Enums; + +namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectsList; + +public class GetTaskDto +{ + public Guid Id { get; init; } + public string Name { get; init; } = string.Empty; + public int Percentage { get; init; } + public ProjectHierarchyLevel Level { get; init; } + public Guid? ParentId { get; init; } + public int TotalHours { get; set; } + public int Minutes { get; set; } + + // Task-specific fields + public TimeSpan SpentTime { get; init; } + public TimeSpan RemainingTime { get; init; } + public List Sections { get; init; } +} + +public class GetTaskSectionDto +{ + public Guid Id { get; init; } + public string SkillName { get; init; } = string.Empty; + public TimeSpan SpentTime { get; init; } + public TimeSpan TotalTime { get; init; } + public int Percentage { get; init; } + public string UserFullName{ get; init; } = string.Empty; + public AssignmentStatus AssignmentStatus { get; set; } + +} diff --git a/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Enums/AssignmentStatus.cs b/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Enums/AssignmentStatus.cs new file mode 100644 index 00000000..91cbeffd --- /dev/null +++ b/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Enums/AssignmentStatus.cs @@ -0,0 +1,23 @@ +namespace GozareshgirProgramManager.Domain.ProjectAgg.Enums; + +/// +/// وضعیت تکلیف دهی برای بخش‌های مختلف پروژه +/// +public enum AssignmentStatus +{ + /// + /// تعیین تکلیف نشده + /// + Unassigned = 0, + + /// + /// تعیین تکلیف شده + /// + Assigned = 1, + + /// + /// فقط کاربر تعیین شده + /// + UserOnly = 2, +} + diff --git a/ServiceHost/Program.cs b/ServiceHost/Program.cs index 73ff3d88..012f1a0a 100644 --- a/ServiceHost/Program.cs +++ b/ServiceHost/Program.cs @@ -380,13 +380,7 @@ builder.Host.UseSerilog((context, services, configuration) => .ReadFrom.Configuration(context.Configuration) .ReadFrom.Services(services) .Enrich.FromLogContext(); - - // در محیط Development، EF Core Commands را هم لاگ می‌کنیم - if (context.HostingEnvironment.IsDevelopment()) - { - logConfig - .MinimumLevel.Override("Microsoft.EntityFrameworkCore.Database.Command", LogEventLevel.Information); - } + logConfig.WriteTo.File( path: Path.Combine(logDirectory, "gozareshgir_log.txt"),