From e661bc2dcbdebd17a7324cbcf99fe6dea15323fe Mon Sep 17 00:00:00 2001 From: mahan Date: Tue, 23 Dec 2025 14:52:41 +0330 Subject: [PATCH 01/15] add: enhance ProjectBoardListQueryHandler and ProjectPhase to manage task section statuses and deployment states --- .../ProjectBoardListQueryHandler.cs | 2 +- .../ProjectAgg/Entities/ProjectPhase.cs | 25 +++++++++++++++++++ .../ProjectAgg/Enums/TaskSectionStatus.cs | 3 ++- 3 files changed, 28 insertions(+), 2 deletions(-) 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 5a085060..18a52853 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 @@ -23,7 +23,7 @@ public class ProjectBoardListQueryHandler : IBaseQueryHandler x.InitialEstimatedHours > TimeSpan.Zero) + .Where(x => x.InitialEstimatedHours > TimeSpan.Zero && x.Status != TaskSectionStatus.Completed) .Include(x => x.Task) .ThenInclude(x => x.Phase) .ThenInclude(x => x.Project) diff --git a/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Entities/ProjectPhase.cs b/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Entities/ProjectPhase.cs index 539ac278..8d7583bc 100644 --- a/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Entities/ProjectPhase.cs +++ b/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Entities/ProjectPhase.cs @@ -23,6 +23,7 @@ public class ProjectPhase : ProjectHierarchyNode ProjectId = projectId; _tasks = new List(); _phaseSections = new List(); + DeployStatus = ProjectDeployStatus.NoTCompleted; AddDomainEvent(new PhaseCreatedEvent(Id, projectId, name)); } @@ -36,6 +37,8 @@ public class ProjectPhase : ProjectHierarchyNode public DateTime? StartDate { get; private set; } public DateTime? EndDate { get; private set; } public int OrderIndex { get; private set; } + public bool IsArchived { get; set; } + public ProjectDeployStatus DeployStatus { get; set; } #region Task Management @@ -196,4 +199,26 @@ public class ProjectPhase : ProjectHierarchyNode } #endregion + + public void SetArchived() + { + IsArchived = true; + } + public void SetUnarchived() + { + IsArchived = false; + } + public void UpdateDeployStatus(ProjectDeployStatus status) + { + DeployStatus = status; + } +} + +public enum ProjectDeployStatus +{ + NoTCompleted, + PendingDevDeploy, + DevDeployed, + PendingDeploy, + Deployed } diff --git a/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Enums/TaskSectionStatus.cs b/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Enums/TaskSectionStatus.cs index d5208bfb..9e63c1f6 100644 --- a/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Enums/TaskSectionStatus.cs +++ b/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Enums/TaskSectionStatus.cs @@ -6,5 +6,6 @@ public enum TaskSectionStatus ReadyToStart = 1, // آماده شروع InProgress = 2, // درحال انجام Incomplete = 3, // ناتمام شده - Completed = 4 // تکمیل شده + PendingForCompletion = 5, // در انتظار تکمیل + Completed = 5 // تکمیل شده } \ No newline at end of file From b9e271de1ac5e46c552d2c8ae69f50e0a4da02c7 Mon Sep 17 00:00:00 2001 From: mahan Date: Tue, 23 Dec 2025 15:00:18 +0330 Subject: [PATCH 02/15] fix: update OccurredOn property to use DateTime.Now instead of DateTime.UtcNow in event records --- .../ProjectAgg/Events/ProjectEvents.cs | 66 +++++++++---------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Events/ProjectEvents.cs b/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Events/ProjectEvents.cs index fb6ca424..155fec66 100644 --- a/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Events/ProjectEvents.cs +++ b/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Events/ProjectEvents.cs @@ -8,171 +8,171 @@ namespace GozareshgirProgramManager.Domain.ProjectAgg.Events; // Project Events public record ProjectCreatedEvent(Guid ProjectId, string Name) : IDomainEvent { - public DateTime OccurredOn { get; init; } = DateTime.UtcNow; + public DateTime OccurredOn { get; init; } = DateTime.Now; } public record ProjectStatusUpdatedEvent(Guid ProjectId, ProjectStatus Status) : IDomainEvent { - public DateTime OccurredOn { get; init; } = DateTime.UtcNow; + public DateTime OccurredOn { get; init; } = DateTime.Now; } public record ProjectAssignedEvent(Guid ProjectId, long UserId) : IDomainEvent { - public DateTime OccurredOn { get; init; } = DateTime.UtcNow; + public DateTime OccurredOn { get; init; } = DateTime.Now; } public record ProjectUnassignedEvent(Guid ProjectId) : IDomainEvent { - public DateTime OccurredOn { get; init; } = DateTime.UtcNow; + public DateTime OccurredOn { get; init; } = DateTime.Now; } // Phase Events public record PhaseCreatedEvent(Guid PhaseId, Guid ProjectId, string Name) : IDomainEvent { - public DateTime OccurredOn { get; init; } = DateTime.UtcNow; + public DateTime OccurredOn { get; init; } = DateTime.Now; } public record PhaseAddedEvent(Guid PhaseId, Guid ProjectId, string Name) : IDomainEvent { - public DateTime OccurredOn { get; init; } = DateTime.UtcNow; + public DateTime OccurredOn { get; init; } = DateTime.Now; } public record PhaseRemovedEvent(Guid PhaseId, Guid ProjectId) : IDomainEvent { - public DateTime OccurredOn { get; init; } = DateTime.UtcNow; + public DateTime OccurredOn { get; init; } = DateTime.Now; } public record PhaseStatusUpdatedEvent(Guid PhaseId, PhaseStatus Status) : IDomainEvent { - public DateTime OccurredOn { get; init; } = DateTime.UtcNow; + public DateTime OccurredOn { get; init; } = DateTime.Now; } public record PhaseAssignedEvent(Guid PhaseId, long UserId) : IDomainEvent { - public DateTime OccurredOn { get; init; } = DateTime.UtcNow; + public DateTime OccurredOn { get; init; } = DateTime.Now; } public record PhaseUnassignedEvent(Guid PhaseId) : IDomainEvent { - public DateTime OccurredOn { get; init; } = DateTime.UtcNow; + public DateTime OccurredOn { get; init; } = DateTime.Now; } // Task Events public record TaskCreatedEvent(Guid TaskId, Guid PhaseId, string Name) : IDomainEvent { - public DateTime OccurredOn { get; init; } = DateTime.UtcNow; + public DateTime OccurredOn { get; init; } = DateTime.Now; } public record TaskAddedEvent(Guid TaskId, Guid PhaseId, string Name) : IDomainEvent { - public DateTime OccurredOn { get; init; } = DateTime.UtcNow; + public DateTime OccurredOn { get; init; } = DateTime.Now; } public record TaskRemovedEvent(Guid TaskId, Guid PhaseId) : IDomainEvent { - public DateTime OccurredOn { get; init; } = DateTime.UtcNow; + public DateTime OccurredOn { get; init; } = DateTime.Now; } public record TaskStatusUpdatedEvent(Guid TaskId, TaskStatus Status) : IDomainEvent { - public DateTime OccurredOn { get; init; } = DateTime.UtcNow; + public DateTime OccurredOn { get; init; } = DateTime.Now; } public record TaskPriorityUpdatedEvent(Guid TaskId, TaskPriority Priority) : IDomainEvent { - public DateTime OccurredOn { get; init; } = DateTime.UtcNow; + public DateTime OccurredOn { get; init; } = DateTime.Now; } public record TaskAssignedEvent(Guid TaskId, long UserId) : IDomainEvent { - public DateTime OccurredOn { get; init; } = DateTime.UtcNow; + public DateTime OccurredOn { get; init; } = DateTime.Now; } public record TaskUnassignedEvent(Guid TaskId) : IDomainEvent { - public DateTime OccurredOn { get; init; } = DateTime.UtcNow; + public DateTime OccurredOn { get; init; } = DateTime.Now; } public record TaskSectionAddedEvent(Guid TaskId, Guid SectionId, Guid SkillId) : IDomainEvent { - public DateTime OccurredOn { get; init; } = DateTime.UtcNow; + public DateTime OccurredOn { get; init; } = DateTime.Now; } public record TaskSectionRemovedEvent(Guid TaskId, Guid SectionId) : IDomainEvent { - public DateTime OccurredOn { get; init; } = DateTime.UtcNow; + public DateTime OccurredOn { get; init; } = DateTime.Now; } // TaskSection Events public record TaskSectionStatusChangedEvent(Guid SectionId, TaskSectionStatus OldStatus, TaskSectionStatus NewStatus,long UserId) : IDomainEvent { - public DateTime OccurredOn { get; init; } = DateTime.UtcNow; + public DateTime OccurredOn { get; init; } = DateTime.Now; } public record TaskSectionAssignedEvent(Guid SectionId, long UserId) : IDomainEvent { - public DateTime OccurredOn { get; init; } = DateTime.UtcNow; + public DateTime OccurredOn { get; init; } = DateTime.Now; } public record TaskSectionTransferredEvent(Guid SectionId, long FromUserId, long ToUserId) : IDomainEvent { - public DateTime OccurredOn { get; init; } = DateTime.UtcNow; + public DateTime OccurredOn { get; init; } = DateTime.Now; } // Section Events (Legacy - keeping for backward compatibility) public record ProjectPhaseAddedEvent(Guid ProjectId, Guid PhaseId, string PhaseName) : IDomainEvent { - public DateTime OccurredOn { get; init; } = DateTime.UtcNow; + public DateTime OccurredOn { get; init; } = DateTime.Now; } public record ProjectTaskStatusChangedEvent(Guid SectionId, TaskStatus OldStatus, TaskStatus NewStatus) : IDomainEvent { - public DateTime OccurredOn { get; init; } = DateTime.UtcNow; + public DateTime OccurredOn { get; init; } = DateTime.Now; } public record ProjectSectionAddedEvent(Guid SectionId, Guid TaskId) : IDomainEvent { - public DateTime OccurredOn { get; init; } = DateTime.UtcNow; + public DateTime OccurredOn { get; init; } = DateTime.Now; } public record ProjectSectionAssignedEvent(Guid SectionId, long UserId) : IDomainEvent { - public DateTime OccurredOn { get; init; } = DateTime.UtcNow; + public DateTime OccurredOn { get; init; } = DateTime.Now; } public record ProjectSectionTransferredEvent(Guid SectionId, long FromUserId, long ToUserId) : IDomainEvent { - public DateTime OccurredOn { get; init; } = DateTime.UtcNow; + public DateTime OccurredOn { get; init; } = DateTime.Now; } public record WorkStartedEvent(Guid SectionId, long UserId, DateTime StartTime, string? Notes) : IDomainEvent { - public DateTime OccurredOn { get; init; } = DateTime.UtcNow; + public DateTime OccurredOn { get; init; } = DateTime.Now; } public record WorkStoppedEvent(Guid SectionId, long UserId, DateTime StartTime, DateTime EndTime, TimeSpan Duration, string? Notes) : IDomainEvent { - public DateTime OccurredOn { get; init; } = DateTime.UtcNow; + public DateTime OccurredOn { get; init; } = DateTime.Now; } public record ProjectSectionCompletedEvent(Guid ProjectId, long UserId, TimeSpan TotalTimeSpent, string? Notes) : IDomainEvent { - public DateTime OccurredOn { get; init; } = DateTime.UtcNow; + public DateTime OccurredOn { get; init; } = DateTime.Now; } public record AdditionalTimeAddedEvent(Guid ProjectId, Guid AdditionalTimeId, TimeSpan Hours, string? Reason, long? AddedByUserId) : IDomainEvent { - public DateTime OccurredOn { get; init; } = DateTime.UtcNow; + public DateTime OccurredOn { get; init; } = DateTime.Now; } public record AdditionalTimeRemovedEvent(Guid ProjectId, Guid AdditionalTimeId, TimeSpan RemovedHours) : IDomainEvent { - public DateTime OccurredOn { get; init; } = DateTime.UtcNow; + public DateTime OccurredOn { get; init; } = DateTime.Now; } public record InitialEstimatedTimeUpdatedEvent(Guid ProjectId, TimeSpan OldEstimate, TimeSpan NewEstimate) : IDomainEvent { - public DateTime OccurredOn { get; init; } = DateTime.UtcNow; + public DateTime OccurredOn { get; init; } = DateTime.Now; } From 39a5918a1150611433abd9806ccd2f34f1a67fcb Mon Sep 17 00:00:00 2001 From: mahan Date: Wed, 24 Dec 2025 18:55:01 +0330 Subject: [PATCH 03/15] add: implement ProjectDeployBoardListQueryHandler and initialize GetProjectsListQueryHandler structure --- .../ProjectBoardList/ProjectBoardListQueryHandler.cs | 1 + .../ProjectDeployBoardListQueryHandler.cs | 10 ++++++++++ 2 files changed, 11 insertions(+) create mode 100644 ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/ProjectDeployBoardList/ProjectDeployBoardListQueryHandler.cs 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 18a52853..69e82940 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 @@ -22,6 +22,7 @@ public class ProjectBoardListQueryHandler : IBaseQueryHandler x.InitialEstimatedHours > TimeSpan.Zero && x.Status != TaskSectionStatus.Completed) .Include(x => x.Task) 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 new file mode 100644 index 00000000..e040c52c --- /dev/null +++ b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/ProjectDeployBoardList/ProjectDeployBoardListQueryHandler.cs @@ -0,0 +1,10 @@ +namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.ProjectDeployBoardList; + +public record GetProjectsListQueryHandler : IBaseQueryHandler; + +public record GetProjectsListResponse(); + +public class ProjectDeployBoardListQueryHandler +{ + +} \ No newline at end of file From 4c638cbdae3d7973c5cbc0194c7c384c3af5e25b Mon Sep 17 00:00:00 2001 From: mahan Date: Sat, 27 Dec 2025 13:59:37 +0330 Subject: [PATCH 04/15] add: implement DeployStatus and IsArchived properties in ProjectPhase, and create ProjectDeployBoardListQueryHandler for project deployment management --- .../ProjectDeployBoardListQueryHandler.cs | 67 +- .../ProjectAgg/Enums/TaskSectionStatus.cs | 2 +- ... deploy status and is archived.Designer.cs | 865 ++++++++++++++++++ ...add phase deploy status and is archived.cs | 41 + .../Migrations/AppDbContextModelSnapshot.cs | 12 +- .../Mappings/ProjectPhaseMapping.cs | 3 + .../ProgramManager/ProjectController.cs | 8 + 7 files changed, 990 insertions(+), 8 deletions(-) create mode 100644 ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Migrations/20251227094008_add phase deploy status and is archived.Designer.cs create mode 100644 ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Migrations/20251227094008_add phase deploy status and is archived.cs 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 e040c52c..742e3455 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 @@ -1,10 +1,67 @@ +using System.Xml.Schema; +using GozareshgirProgramManager.Application._Common.Interfaces; +using GozareshgirProgramManager.Application._Common.Models; +using GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectsList; +using GozareshgirProgramManager.Domain.ProjectAgg.Entities; +using GozareshgirProgramManager.Domain.ProjectAgg.Enums; +using Microsoft.EntityFrameworkCore; + namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.ProjectDeployBoardList; -public record GetProjectsListQueryHandler : IBaseQueryHandler; - -public record GetProjectsListResponse(); - -public class ProjectDeployBoardListQueryHandler +public record ProjectDeployBoardListItem() { + public string ProjectName { get; set; } + public string PhaseName { get; set; } + public int TotalTasks { get; set; } + public int DoneTasks { get; set; } + public TimeSpan TotalTimeSpan { get; set; } + public TimeSpan DoneTimeSpan { get; set; } +} +public record GetProjectsDeployBoardListResponse(List Items); + + +public record GetProjectDeployBoardListQuery():IBaseQuery; + +public class ProjectDeployBoardListQueryHandler:IBaseQueryHandler +{ + private readonly IProgramManagerDbContext _dbContext; + private readonly IAuthHelper _authHelper; + + public ProjectDeployBoardListQueryHandler(IProgramManagerDbContext dbContext, IAuthHelper authHelper) + { + _dbContext = dbContext; + _authHelper = authHelper; + } + + public async Task> Handle(GetProjectDeployBoardListQuery request, CancellationToken cancellationToken) + { + var userId = _authHelper.GetCurrentUserId(); + if (userId == null) + { + return OperationResult.NotFound("کاربر یافت نشد"); + } + + var query =await _dbContext.TaskSections + .Include(x=>x.Task) + .ThenInclude(x => x.Phase) + .ThenInclude(x => x.Project) + .AsNoTracking() + .Where(x => x.Status == TaskSectionStatus.Completed + || x.Status == TaskSectionStatus.PendingForCompletion + || x.Task.Phase.DeployStatus != ProjectDeployStatus.NoTCompleted) + .GroupBy(x=>x.Task.PhaseId).ToListAsync(cancellationToken: cancellationToken); + + var list = query.Select(g => new ProjectDeployBoardListItem + { + ProjectName = g.First().Task.Phase.Project.Name, + PhaseName = g.First().Task.Phase.Name, + TotalTasks = g.Select(x => x.TaskId).Distinct().Count(), + DoneTasks = g.Where(x => x.Status == TaskSectionStatus.Completed).Select(x => x.TaskId).Distinct().Count(), + TotalTimeSpan = TimeSpan.FromHours(g.Sum(x => x.InitialEstimatedHours.TotalHours)), + DoneTimeSpan = TimeSpan.FromHours(g.Where(x => x.Status == TaskSectionStatus.Completed).Sum(x => x.InitialEstimatedHours.TotalHours)) + }).ToList(); + var response = new GetProjectsDeployBoardListResponse(list); + return OperationResult.Success(response); + } } \ No newline at end of file diff --git a/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Enums/TaskSectionStatus.cs b/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Enums/TaskSectionStatus.cs index 9e63c1f6..5c5a019e 100644 --- a/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Enums/TaskSectionStatus.cs +++ b/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Enums/TaskSectionStatus.cs @@ -6,6 +6,6 @@ public enum TaskSectionStatus ReadyToStart = 1, // آماده شروع InProgress = 2, // درحال انجام Incomplete = 3, // ناتمام شده - PendingForCompletion = 5, // در انتظار تکمیل + PendingForCompletion = 4, // در انتظار تکمیل Completed = 5 // تکمیل شده } \ No newline at end of file diff --git a/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Migrations/20251227094008_add phase deploy status and is archived.Designer.cs b/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Migrations/20251227094008_add phase deploy status and is archived.Designer.cs new file mode 100644 index 00000000..74452d75 --- /dev/null +++ b/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Migrations/20251227094008_add phase deploy status and is archived.Designer.cs @@ -0,0 +1,865 @@ +// +using System; +using GozareshgirProgramManager.Infrastructure.Persistence.Context; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace GozareshgirProgramManager.Infrastructure.Migrations +{ + [DbContext(typeof(ProgramManagerDbContext))] + [Migration("20251227094008_add phase deploy status and is archived")] + partial class addphasedeploystatusandisarchived + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.1") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("GozareshgirProgramManager.Domain.CheckoutAgg.Entities.Checkout", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CheckoutEndDate") + .HasColumnType("datetime2"); + + b.Property("CheckoutStartDate") + .HasColumnType("datetime2"); + + b.Property("CreationDate") + .HasColumnType("datetime2"); + + b.Property("DeductionFromSalary") + .HasColumnType("float"); + + b.Property("FullName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("MandatoryHours") + .HasColumnType("int"); + + b.Property("Month") + .HasColumnType("int"); + + b.Property("MonthlySalaryDefined") + .HasColumnType("float"); + + b.Property("MonthlySalaryPay") + .HasColumnType("float"); + + b.Property("RemainingHours") + .HasColumnType("int"); + + b.Property("TotalDaysWorked") + .HasColumnType("int"); + + b.Property("TotalHoursWorked") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.Property("Year") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Checkouts", (string)null); + }); + + modelBuilder.Entity("GozareshgirProgramManager.Domain.CustomerAgg.Customer", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreationDate") + .HasColumnType("datetime2"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.ToTable("Customers", (string)null); + }); + + modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.PhaseSection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreationDate") + .HasColumnType("datetime2"); + + b.Property("PhaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("SkillId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("PhaseId"); + + b.HasIndex("SkillId"); + + b.ToTable("PhaseSections"); + }); + + modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.Project", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CreationDate") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("EndDate") + .HasColumnType("datetime2"); + + b.Property("HasAssignmentOverride") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("PlannedEndDate") + .HasColumnType("datetime2"); + + b.Property("PlannedStartDate") + .HasColumnType("datetime2"); + + b.Property("StartDate") + .HasColumnType("datetime2"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("Id"); + + b.ToTable("Projects", (string)null); + }); + + modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.ProjectPhase", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CreationDate") + .HasColumnType("datetime2"); + + b.Property("DeployStatus") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("EndDate") + .HasColumnType("datetime2"); + + b.Property("HasAssignmentOverride") + .HasColumnType("bit"); + + b.Property("IsArchived") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("OrderIndex") + .HasColumnType("int"); + + b.Property("ProjectId") + .HasColumnType("uniqueidentifier"); + + b.Property("StartDate") + .HasColumnType("datetime2"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.ToTable("ProjectPhases", (string)null); + }); + + modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.ProjectSection", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CreationDate") + .HasColumnType("datetime2"); + + b.Property("ProjectId") + .HasColumnType("uniqueidentifier"); + + b.Property("SkillId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.HasIndex("SkillId"); + + b.ToTable("ProjectSections"); + }); + + modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.ProjectTask", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("AllocatedTime") + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b.Property("CreationDate") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("DueDate") + .HasColumnType("datetime2"); + + b.Property("EndDate") + .HasColumnType("datetime2"); + + b.Property("HasAssignmentOverride") + .HasColumnType("bit"); + + b.Property("HasTimeOverride") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("OrderIndex") + .HasColumnType("int"); + + b.Property("PhaseId") + .HasColumnType("uniqueidentifier"); + + b.Property("Priority") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("StartDate") + .HasColumnType("datetime2"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("Id"); + + b.HasIndex("PhaseId"); + + b.ToTable("ProjectTasks", (string)null); + }); + + modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.TaskSection", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CreationDate") + .HasColumnType("datetime2"); + + b.Property("CurrentAssignedUserId") + .HasColumnType("bigint"); + + b.Property("InitialDescription") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("InitialEstimatedHours") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b.Property("OriginalAssignedUserId") + .HasColumnType("bigint"); + + b.Property("SkillId") + .HasColumnType("uniqueidentifier"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("TaskId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("SkillId"); + + b.HasIndex("TaskId"); + + b.ToTable("TaskSections", (string)null); + }); + + modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.TaskSectionActivity", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CreationDate") + .HasColumnType("datetime2"); + + b.Property("EndDate") + .HasColumnType("datetime2"); + + b.Property("EndNotes") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("SectionId") + .HasColumnType("uniqueidentifier"); + + b.Property("StartDate") + .HasColumnType("datetime2"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("SectionId"); + + b.ToTable("TaskSectionActivities", (string)null); + }); + + modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.TaskSectionAdditionalTime", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("AddedAt") + .HasColumnType("datetime2"); + + b.Property("AddedByUserId") + .HasColumnType("bigint"); + + b.Property("CreationDate") + .HasColumnType("datetime2"); + + b.Property("Hours") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b.Property("Reason") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("TaskSectionId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("TaskSectionId"); + + b.ToTable("TaskSectionAdditionalTimes", (string)null); + }); + + modelBuilder.Entity("GozareshgirProgramManager.Domain.RoleAgg.Entities.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("datetime2"); + + b.Property("GozareshgirRoleId") + .HasColumnType("bigint"); + + b.Property("RoleName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("PmRoles", (string)null); + }); + + modelBuilder.Entity("GozareshgirProgramManager.Domain.SalaryPaymentSettingAgg.Entities.SalaryPaymentSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("datetime2"); + + b.Property("EndSettingDate") + .HasColumnType("datetime2"); + + b.Property("HolidayWorking") + .HasColumnType("bit"); + + b.Property("MonthlySalary") + .HasColumnType("float"); + + b.Property("StartSettingDate") + .HasColumnType("datetime2"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.ToTable("SalaryPaymentSetting", (string)null); + }); + + modelBuilder.Entity("GozareshgirProgramManager.Domain.SkillAgg.Entities.Skill", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CreationDate") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.ToTable("Skills", (string)null); + }); + + modelBuilder.Entity("GozareshgirProgramManager.Domain.UserAgg.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AccountId") + .HasColumnType("bigint"); + + b.Property("CreationDate") + .HasColumnType("datetime2"); + + b.Property("Email") + .HasMaxLength(150) + .HasColumnType("nvarchar(150)"); + + b.Property("FullName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("Mobile") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Password") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("ProfilePhotoPath") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UserName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("VerifyCode") + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.HasKey("Id"); + + b.ToTable("Users", (string)null); + }); + + modelBuilder.Entity("GozareshgirProgramManager.Domain.UserAgg.Entities.UserRefreshToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreationDate") + .HasColumnType("datetime2"); + + b.Property("ExpiresAt") + .HasColumnType("datetime2"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("RevokedAt") + .HasColumnType("datetime2"); + + b.Property("Token") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UserAgent") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ExpiresAt"); + + b.HasIndex("Token") + .IsUnique(); + + b.HasIndex("UserId"); + + b.ToTable("UserRefreshTokens", (string)null); + }); + + modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.PhaseSection", b => + { + b.HasOne("GozareshgirProgramManager.Domain.ProjectAgg.Entities.ProjectPhase", "Phase") + .WithMany("PhaseSections") + .HasForeignKey("PhaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GozareshgirProgramManager.Domain.SkillAgg.Entities.Skill", "Skill") + .WithMany() + .HasForeignKey("SkillId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Phase"); + + b.Navigation("Skill"); + }); + + modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.ProjectPhase", b => + { + b.HasOne("GozareshgirProgramManager.Domain.ProjectAgg.Entities.Project", "Project") + .WithMany("Phases") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.ProjectSection", b => + { + b.HasOne("GozareshgirProgramManager.Domain.ProjectAgg.Entities.Project", "Project") + .WithMany("ProjectSections") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GozareshgirProgramManager.Domain.SkillAgg.Entities.Skill", "Skill") + .WithMany() + .HasForeignKey("SkillId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Project"); + + b.Navigation("Skill"); + }); + + modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.ProjectTask", b => + { + b.HasOne("GozareshgirProgramManager.Domain.ProjectAgg.Entities.ProjectPhase", "Phase") + .WithMany("Tasks") + .HasForeignKey("PhaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Phase"); + }); + + modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.TaskSection", b => + { + b.HasOne("GozareshgirProgramManager.Domain.SkillAgg.Entities.Skill", "Skill") + .WithMany("Sections") + .HasForeignKey("SkillId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GozareshgirProgramManager.Domain.ProjectAgg.Entities.ProjectTask", "Task") + .WithMany("Sections") + .HasForeignKey("TaskId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Skill"); + + b.Navigation("Task"); + }); + + modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.TaskSectionActivity", b => + { + b.HasOne("GozareshgirProgramManager.Domain.ProjectAgg.Entities.TaskSection", "Section") + .WithMany("Activities") + .HasForeignKey("SectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Section"); + }); + + modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.TaskSectionAdditionalTime", b => + { + b.HasOne("GozareshgirProgramManager.Domain.ProjectAgg.Entities.TaskSection", null) + .WithMany("AdditionalTimes") + .HasForeignKey("TaskSectionId"); + }); + + modelBuilder.Entity("GozareshgirProgramManager.Domain.RoleAgg.Entities.Role", b => + { + b.OwnsMany("GozareshgirProgramManager.Domain.PermissionAgg.Entities.Permission", "Permissions", b1 => + { + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id")); + + b1.Property("Code") + .HasColumnType("int"); + + b1.Property("RoleId") + .HasColumnType("bigint"); + + b1.HasKey("Id"); + + b1.HasIndex("RoleId"); + + b1.ToTable("PmRolePermissions", (string)null); + + b1.WithOwner("Role") + .HasForeignKey("RoleId"); + + b1.Navigation("Role"); + }); + + b.Navigation("Permissions"); + }); + + modelBuilder.Entity("GozareshgirProgramManager.Domain.SalaryPaymentSettingAgg.Entities.SalaryPaymentSetting", b => + { + b.OwnsMany("GozareshgirProgramManager.Domain.SalaryPaymentSettingAgg.Entities.WorkingHours", "WorkingHoursList", b1 => + { + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id")); + + b1.Property("EndShiftOne") + .HasColumnType("time(0)"); + + b1.Property("EndShiftTwo") + .HasColumnType("time(0)"); + + b1.Property("HasRestTime") + .HasColumnType("bit"); + + b1.Property("HasShiftOne") + .HasColumnType("bit"); + + b1.Property("HasShiftTow") + .HasColumnType("bit"); + + b1.Property("IsActiveDay") + .HasColumnType("bit"); + + b1.Property("PersianDayOfWeek") + .HasColumnType("int"); + + b1.Property("RestTime") + .HasColumnType("time(0)"); + + b1.Property("SalaryPaymentSettingId") + .HasColumnType("bigint"); + + b1.Property("ShiftDurationInMinutes") + .HasColumnType("int"); + + b1.Property("StartShiftOne") + .HasColumnType("time(0)"); + + b1.Property("StartShiftTwo") + .HasColumnType("time(0)"); + + b1.HasKey("Id"); + + b1.HasIndex("SalaryPaymentSettingId"); + + b1.ToTable("WorkingHours", (string)null); + + b1.WithOwner("SalaryPaymentSetting") + .HasForeignKey("SalaryPaymentSettingId"); + + b1.Navigation("SalaryPaymentSetting"); + }); + + b.Navigation("WorkingHoursList"); + }); + + modelBuilder.Entity("GozareshgirProgramManager.Domain.UserAgg.Entities.User", b => + { + b.OwnsMany("GozareshgirProgramManager.Domain.RoleUserAgg.RoleUser", "RoleUser", b1 => + { + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id")); + + b1.Property("RoleId") + .HasColumnType("bigint"); + + b1.Property("UserId") + .HasColumnType("bigint"); + + b1.HasKey("Id"); + + b1.HasIndex("UserId"); + + b1.ToTable("RoleUsers", (string)null); + + b1.WithOwner("User") + .HasForeignKey("UserId"); + + b1.Navigation("User"); + }); + + b.Navigation("RoleUser"); + }); + + modelBuilder.Entity("GozareshgirProgramManager.Domain.UserAgg.Entities.UserRefreshToken", b => + { + b.HasOne("GozareshgirProgramManager.Domain.UserAgg.Entities.User", "User") + .WithMany("RefreshTokens") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.Project", b => + { + b.Navigation("Phases"); + + b.Navigation("ProjectSections"); + }); + + modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.ProjectPhase", b => + { + b.Navigation("PhaseSections"); + + b.Navigation("Tasks"); + }); + + modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.ProjectTask", b => + { + b.Navigation("Sections"); + }); + + modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.TaskSection", b => + { + b.Navigation("Activities"); + + b.Navigation("AdditionalTimes"); + }); + + modelBuilder.Entity("GozareshgirProgramManager.Domain.SkillAgg.Entities.Skill", b => + { + b.Navigation("Sections"); + }); + + modelBuilder.Entity("GozareshgirProgramManager.Domain.UserAgg.Entities.User", b => + { + b.Navigation("RefreshTokens"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Migrations/20251227094008_add phase deploy status and is archived.cs b/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Migrations/20251227094008_add phase deploy status and is archived.cs new file mode 100644 index 00000000..7dce42cd --- /dev/null +++ b/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Migrations/20251227094008_add phase deploy status and is archived.cs @@ -0,0 +1,41 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace GozareshgirProgramManager.Infrastructure.Migrations +{ + /// + public partial class addphasedeploystatusandisarchived : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "DeployStatus", + table: "ProjectPhases", + type: "nvarchar(30)", + maxLength: 30, + nullable: false, + defaultValue: ""); + + migrationBuilder.AddColumn( + name: "IsArchived", + table: "ProjectPhases", + type: "bit", + nullable: false, + defaultValue: false); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "DeployStatus", + table: "ProjectPhases"); + + migrationBuilder.DropColumn( + name: "IsArchived", + table: "ProjectPhases"); + } + } +} diff --git a/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Migrations/AppDbContextModelSnapshot.cs b/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Migrations/AppDbContextModelSnapshot.cs index 25e60639..4382f61b 100644 --- a/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Migrations/AppDbContextModelSnapshot.cs +++ b/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Migrations/AppDbContextModelSnapshot.cs @@ -126,7 +126,7 @@ namespace GozareshgirProgramManager.Infrastructure.Migrations b.HasIndex("SkillId"); - b.ToTable("PhaseSections", (string)null); + b.ToTable("PhaseSections"); }); modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.Project", b => @@ -179,6 +179,11 @@ namespace GozareshgirProgramManager.Infrastructure.Migrations b.Property("CreationDate") .HasColumnType("datetime2"); + b.Property("DeployStatus") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + b.Property("Description") .HasMaxLength(1000) .HasColumnType("nvarchar(1000)"); @@ -189,6 +194,9 @@ namespace GozareshgirProgramManager.Infrastructure.Migrations b.Property("HasAssignmentOverride") .HasColumnType("bit"); + b.Property("IsArchived") + .HasColumnType("bit"); + b.Property("Name") .IsRequired() .HasMaxLength(200) @@ -238,7 +246,7 @@ namespace GozareshgirProgramManager.Infrastructure.Migrations b.HasIndex("SkillId"); - b.ToTable("ProjectSections", (string)null); + b.ToTable("ProjectSections"); }); modelBuilder.Entity("GozareshgirProgramManager.Domain.ProjectAgg.Entities.ProjectTask", b => diff --git a/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Persistence/Mappings/ProjectPhaseMapping.cs b/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Persistence/Mappings/ProjectPhaseMapping.cs index 838b7f0e..01a43077 100644 --- a/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Persistence/Mappings/ProjectPhaseMapping.cs +++ b/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Persistence/Mappings/ProjectPhaseMapping.cs @@ -48,6 +48,9 @@ public class ProjectPhaseMapping : IEntityTypeConfiguration builder.Property(ph => ph.HasAssignmentOverride) .IsRequired(); + builder.Property(x => x.DeployStatus) + .HasConversion().HasMaxLength(30); + // Relationship with Project builder.HasOne(ph => ph.Project) .WithMany(p => p.Phases) diff --git a/ServiceHost/Areas/Admin/Controllers/ProgramManager/ProjectController.cs b/ServiceHost/Areas/Admin/Controllers/ProgramManager/ProjectController.cs index 225a1e9b..1aa439e8 100644 --- a/ServiceHost/Areas/Admin/Controllers/ProgramManager/ProjectController.cs +++ b/ServiceHost/Areas/Admin/Controllers/ProgramManager/ProjectController.cs @@ -12,6 +12,7 @@ using GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectA using GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectsList; using GozareshgirProgramManager.Application.Modules.Projects.Queries.ProjectBoardDetail; using GozareshgirProgramManager.Application.Modules.Projects.Queries.ProjectBoardList; +using GozareshgirProgramManager.Application.Modules.Projects.Queries.ProjectDeployBoardList; using GozareshgirProgramManager.Application.Modules.Projects.Queries.ProjectSetTimeDetails; using MediatR; using Microsoft.AspNetCore.Mvc; @@ -112,4 +113,11 @@ public class ProjectController : ProgramManagerBaseController var res = await _mediator.Send(query); return res; } + + [HttpGet("deploy-board")] + public async Task>> GetProjectDeployBoard() + { + var request = new GetProjectDeployBoardListQuery(); + return await _mediator.Send(request); + } } \ No newline at end of file From 7ce7854091b8737183d526c025bf4a947fa868f3 Mon Sep 17 00:00:00 2001 From: mahan Date: Sat, 27 Dec 2025 16:26:07 +0330 Subject: [PATCH 05/15] add: include DeployStatus in ProjectDeployBoardListItem for deployment status tracking --- .../ProjectDeployBoardListQueryHandler.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 742e3455..3cbac6e0 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 @@ -16,6 +16,7 @@ public record ProjectDeployBoardListItem() public int DoneTasks { get; set; } public TimeSpan TotalTimeSpan { get; set; } public TimeSpan DoneTimeSpan { get; set; } + public ProjectDeployStatus DeployStatus { get; set; } } public record GetProjectsDeployBoardListResponse(List Items); @@ -59,7 +60,9 @@ public class ProjectDeployBoardListQueryHandler:IBaseQueryHandler x.TaskId).Distinct().Count(), DoneTasks = g.Where(x => x.Status == TaskSectionStatus.Completed).Select(x => x.TaskId).Distinct().Count(), TotalTimeSpan = TimeSpan.FromHours(g.Sum(x => x.InitialEstimatedHours.TotalHours)), - DoneTimeSpan = TimeSpan.FromHours(g.Where(x => x.Status == TaskSectionStatus.Completed).Sum(x => x.InitialEstimatedHours.TotalHours)) + DoneTimeSpan = TimeSpan.FromHours(g.Where(x => x.Status == TaskSectionStatus.Completed) + .Sum(x => x.InitialEstimatedHours.TotalHours)), + DeployStatus = g.First().Task.Phase.DeployStatus }).ToList(); var response = new GetProjectsDeployBoardListResponse(list); return OperationResult.Success(response); From 21599016143290a34521a7878751579a7540e807 Mon Sep 17 00:00:00 2001 From: mahan Date: Sat, 27 Dec 2025 19:42:13 +0330 Subject: [PATCH 06/15] add: implement ProjectDeployBoardDetailsQuery and its handler with validation for project phase details --- .../ProjectDeployBoardDetailsQueryHandler.cs | 101 ++++++++++++++++++ ...ProjectDeployBoardDetailsQueryValidator.cs | 11 ++ 2 files changed, 112 insertions(+) create mode 100644 ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/ProjectDeployBoardDetail/ProjectDeployBoardDetailsQueryHandler.cs create mode 100644 ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/ProjectDeployBoardDetail/ProjectDeployBoardDetailsQueryValidator.cs 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 new file mode 100644 index 00000000..3e3657d9 --- /dev/null +++ b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/ProjectDeployBoardDetail/ProjectDeployBoardDetailsQueryHandler.cs @@ -0,0 +1,101 @@ +using System.Security.AccessControl; +using GozareshgirProgramManager.Application._Common.Interfaces; +using GozareshgirProgramManager.Application._Common.Models; +using Microsoft.EntityFrameworkCore; + +namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.ProjectDeployBoardDetail; + +public record ProjectDeployBoardDetailsResponse( + ProjectDeployBoardDetailPhaseItem Phase, + List Tasks); + +public record ProjectDeployBoardDetailPhaseItem( + string Name, + TimeSpan TotalTimeSpan, + TimeSpan DoneTimeSpan); + +public record ProjectDeployBoardDetailTaskItem( + string Name, + TimeSpan TotalTimeSpan, + TimeSpan DoneTimeSpan, + List Skills) + : ProjectDeployBoardDetailPhaseItem(Name, TotalTimeSpan, DoneTimeSpan); + +public record ProjectDeployBoardDetailItemSkill(string OriginalUserFullName, string SkillName, int TimePercentage); + +public record ProjectDeployBoardDetailsQuery(Guid PhaseId) : IBaseQuery; + +public class + ProjectDeployBoardDetailsQueryHandler : IBaseQueryHandler +{ + private readonly IProgramManagerDbContext _dbContext; + + public ProjectDeployBoardDetailsQueryHandler(IProgramManagerDbContext dbContext) + { + _dbContext = dbContext; + } + + public async Task> Handle(ProjectDeployBoardDetailsQuery request, + CancellationToken cancellationToken) + { + var phase = await _dbContext.ProjectPhases + .Include(x => x.Tasks) + .ThenInclude(x => x.Sections) + .ThenInclude(x => x.Activities) + .Include(x => x.Tasks) + .ThenInclude(x => x.Sections) + .ThenInclude(x => x.AdditionalTimes) + .Include(x => x.Tasks) + .ThenInclude(x => x.Sections) + .ThenInclude(x => x.Skill) + .FirstOrDefaultAsync(x => x.Id == request.PhaseId, cancellationToken); + + if (phase == null) + return OperationResult.NotFound("بخش اصلی مورد نظر یافت نشد"); + + var userIds = phase.Tasks + .SelectMany(t => t.Sections) + .Select(s => s.OriginalAssignedUserId) + .Distinct() + .ToList(); + + var usersDict = await _dbContext.Users + .Where(x => userIds.Contains(x.Id)) + .ToDictionaryAsync(x => x.Id, x => x.FullName, cancellationToken); + + var tasksRes = phase.Tasks.Select(t => + { + var totalTime = t.Sections.Select(s => s.FinalEstimatedHours) + .Aggregate(TimeSpan.Zero, (sum, next) => sum.Add(next)); + + var doneTime = t.Sections.Aggregate(TimeSpan.Zero, + (sum, next) => sum.Add(next.GetTotalTimeSpent())); + var skills = t.Sections + .Select(s => new ProjectDeployBoardDetailItemSkill( + usersDict.GetValueOrDefault(s.OriginalAssignedUserId, "کاربر ناشناس"), + s.Skill?.Name ?? "بدون مهارت", + totalTime.TotalSeconds > 0 + ? (int)((doneTime.TotalSeconds / totalTime.TotalSeconds) * 100) + : 0)).ToList(); + + return new ProjectDeployBoardDetailTaskItem( + t.Name, + totalTime, + doneTime, + skills); + }).ToList(); + + var totalTimeSpan = tasksRes.Aggregate(TimeSpan.Zero, + (sum, next) => sum.Add(next.TotalTimeSpan)); + + var doneTimeSpan = tasksRes.Aggregate(TimeSpan.Zero, + (sum, next) => sum.Add(next.DoneTimeSpan)); + + var phaseRes = new ProjectDeployBoardDetailPhaseItem(phase.Name, totalTimeSpan, doneTimeSpan); + + var res = new ProjectDeployBoardDetailsResponse(phaseRes, tasksRes); + + return OperationResult.Success(res); + } +} \ No newline at end of file diff --git a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/ProjectDeployBoardDetail/ProjectDeployBoardDetailsQueryValidator.cs b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/ProjectDeployBoardDetail/ProjectDeployBoardDetailsQueryValidator.cs new file mode 100644 index 00000000..0cbf5cd9 --- /dev/null +++ b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Queries/ProjectDeployBoardDetail/ProjectDeployBoardDetailsQueryValidator.cs @@ -0,0 +1,11 @@ +using FluentValidation; + +namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.ProjectDeployBoardDetail; + +public class ProjectDeployBoardDetailsQueryValidator:AbstractValidator +{ + public ProjectDeployBoardDetailsQueryValidator() + { + RuleFor(x=>x.PhaseId).NotNull().WithMessage("شناسه بخش اصلی نمی‌تواند خالی باشد"); + } +} \ No newline at end of file From 9eefdd8fd17ca7b67c138b1b77a1bf0be93fd11d Mon Sep 17 00:00:00 2001 From: mahan Date: Mon, 29 Dec 2025 10:27:23 +0330 Subject: [PATCH 07/15] add: implement GetProjectDeployBoardDetails endpoint for retrieving project board details --- .../Admin/Controllers/ProgramManager/ProjectController.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ServiceHost/Areas/Admin/Controllers/ProgramManager/ProjectController.cs b/ServiceHost/Areas/Admin/Controllers/ProgramManager/ProjectController.cs index 1aa439e8..31c91ecb 100644 --- a/ServiceHost/Areas/Admin/Controllers/ProgramManager/ProjectController.cs +++ b/ServiceHost/Areas/Admin/Controllers/ProgramManager/ProjectController.cs @@ -120,4 +120,11 @@ public class ProjectController : ProgramManagerBaseController var request = new GetProjectDeployBoardListQuery(); return await _mediator.Send(request); } + [HttpGet("deploy-board/details")] + public async Task>> GetProjectDeployBoardDetails(Guid id) + { + var query = new ProjectBoardDetailQuery(id); + var res = await _mediator.Send(query); + return res; + } } \ No newline at end of file From 9e5e8d8e5d4741d8c3c8aece66b5534c59250916 Mon Sep 17 00:00:00 2001 From: mahan Date: Mon, 29 Dec 2025 11:45:32 +0330 Subject: [PATCH 08/15] refactor: improve code formatting and structure in ProjectController --- .../ProgramManager/ProjectController.cs | 63 +++++++++++-------- 1 file changed, 36 insertions(+), 27 deletions(-) diff --git a/ServiceHost/Areas/Admin/Controllers/ProgramManager/ProjectController.cs b/ServiceHost/Areas/Admin/Controllers/ProgramManager/ProjectController.cs index 31c91ecb..39b40a19 100644 --- a/ServiceHost/Areas/Admin/Controllers/ProgramManager/ProjectController.cs +++ b/ServiceHost/Areas/Admin/Controllers/ProgramManager/ProjectController.cs @@ -23,70 +23,75 @@ namespace ServiceHost.Areas.Admin.Controllers.ProgramManager; public class ProjectController : ProgramManagerBaseController { private readonly IMediator _mediator; - + public ProjectController(IMediator mediator) { _mediator = mediator; } [HttpGet] - public async Task>> Get([FromQuery]GetProjectsListQuery query) + public async Task>> Get( + [FromQuery] GetProjectsListQuery query) { - var res=await _mediator.Send(query); + var res = await _mediator.Send(query); return res; } - + [HttpPost] public async Task> Create([FromBody] CreateProjectCommand command) { - var res=await _mediator.Send(command); + var res = await _mediator.Send(command); return res; } - + [HttpPut] public async Task> Edit([FromBody] EditProjectCommand command) { - var res=await _mediator.Send(command); + var res = await _mediator.Send(command); return res; } - + [HttpDelete] public async Task> Delete([FromQuery] DeleteProjectCommand command) { - var res=await _mediator.Send(command); + var res = await _mediator.Send(command); return res; } - + [HttpGet("assign")] - public async Task>> GetAssignableProjects(GetProjectAssignDetailsQuery query) + public async Task>> GetAssignableProjects( + GetProjectAssignDetailsQuery query) { - var res=await _mediator.Send(query); + var res = await _mediator.Send(query); return res; } - + [HttpPost("assign")] public async Task> Assign(AssignProjectCommand command) { - var res=await _mediator.Send(command); + var res = await _mediator.Send(command); return res; } + [HttpGet("set-time")] - public async Task>> GetSetTimeProjectDetails(ProjectSetTimeDetailsQuery query) + public async Task>> GetSetTimeProjectDetails( + ProjectSetTimeDetailsQuery query) { - var res=await _mediator.Send(query); + var res = await _mediator.Send(query); return res; } + [HttpPost("set-time")] public async Task> SetTimeProject(SetTimeProjectCommand command) { - var res=await _mediator.Send(command); + var res = await _mediator.Send(command); return res; } [HttpPost("change-status")] public async Task> ChangeStatus(ChangeStatusSectionCommand command) { - var res = await _mediator.Send(command); + var res = await _mediator.Send(command); return res; } @@ -98,14 +103,16 @@ public class ProjectController : ProgramManagerBaseController } [HttpGet("board")] - public async Task>>> GetProjectBoard([FromQuery] ProjectBoardListQuery query) + public async Task>>> GetProjectBoard( + [FromQuery] ProjectBoardListQuery query) { // اجرای Command برای متوقف کردن تسک‌های overtime قبل از نمایش await _mediator.Send(new AutoStopOverTimeTaskSectionsCommand()); - + var res = await _mediator.Send(query); return res; } + [HttpGet("board/details")] public async Task>> GetProjectBoardDetails(Guid id) { @@ -117,14 +124,16 @@ public class ProjectController : ProgramManagerBaseController [HttpGet("deploy-board")] public async Task>> GetProjectDeployBoard() { + var request = new GetProjectDeployBoardListQuery(); return await _mediator.Send(request); } - [HttpGet("deploy-board/details")] - public async Task>> GetProjectDeployBoardDetails(Guid id) - { - var query = new ProjectBoardDetailQuery(id); - var res = await _mediator.Send(query); - return res; - } + + [HttpGet("deploy-board/details")] + public async Task>> GetProjectDeployBoardDetails(Guid id) + { + var query = new ProjectBoardDetailQuery(id); + var res = await _mediator.Send(query); + return res; + } } \ No newline at end of file From d855684cd71ac1af9cec270b9bfca0a36c65216f Mon Sep 17 00:00:00 2001 From: mahan Date: Mon, 29 Dec 2025 12:16:51 +0330 Subject: [PATCH 09/15] add: implement AutoUpdateDeployStatusCommand for automatic deployment status updates --- .../AutoUpdateDeployStatusCommand.cs | 52 +++++++++++++++++++ .../ProgramManager/ProjectController.cs | 4 +- 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Commands/AutoUpdateDeployStatus/AutoUpdateDeployStatusCommand.cs diff --git a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Commands/AutoUpdateDeployStatus/AutoUpdateDeployStatusCommand.cs b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Commands/AutoUpdateDeployStatus/AutoUpdateDeployStatusCommand.cs new file mode 100644 index 00000000..5164a0c0 --- /dev/null +++ b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Commands/AutoUpdateDeployStatus/AutoUpdateDeployStatusCommand.cs @@ -0,0 +1,52 @@ +using System.Linq; +using GozareshgirProgramManager.Application._Common.Interfaces; +using GozareshgirProgramManager.Application._Common.Models; +using GozareshgirProgramManager.Domain.ProjectAgg.Entities; +using GozareshgirProgramManager.Domain.ProjectAgg.Enums; +using Microsoft.EntityFrameworkCore; + +namespace GozareshgirProgramManager.Application.Modules.Projects.Commands.AutoUpdateDeployStatus; + +public record AutoUpdateDeployStatusCommand : IBaseCommand; + +public class AutoUpdateDeployStatusCommandHandler : IBaseCommandHandler +{ + private readonly IProgramManagerDbContext _dbContext; + + public AutoUpdateDeployStatusCommandHandler(IProgramManagerDbContext dbContext) + { + _dbContext = dbContext; + } + + public async Task Handle(AutoUpdateDeployStatusCommand request, + CancellationToken cancellationToken) + { + // Fetch all sections whose phase is still marked as not completed + var sections = await _dbContext.TaskSections + .Include(ts => ts.Task) + .ThenInclude(t => t.Phase) + .Where(ts => ts.Task.Phase.DeployStatus == ProjectDeployStatus.NoTCompleted) + .ToListAsync(cancellationToken); + + if (sections.Count == 0) + return OperationResult.Success(); + + var phasesToUpdate = sections + .GroupBy(ts => ts.Task.PhaseId) + .Where(g => g.All(s => s.Status == TaskSectionStatus.Completed)) + .Select(g => g.First().Task.Phase) + .Distinct() + .ToList(); + + if (phasesToUpdate.Count == 0) + return OperationResult.Success(); + + foreach (var phase in phasesToUpdate) + { + phase.UpdateDeployStatus(ProjectDeployStatus.PendingDevDeploy); + } + + await _dbContext.SaveChangesAsync(cancellationToken); + return OperationResult.Success(); + } +} diff --git a/ServiceHost/Areas/Admin/Controllers/ProgramManager/ProjectController.cs b/ServiceHost/Areas/Admin/Controllers/ProgramManager/ProjectController.cs index 39b40a19..a0fe2e7c 100644 --- a/ServiceHost/Areas/Admin/Controllers/ProgramManager/ProjectController.cs +++ b/ServiceHost/Areas/Admin/Controllers/ProgramManager/ProjectController.cs @@ -2,6 +2,7 @@ using GozareshgirProgramManager.Application._Common.Models; using GozareshgirProgramManager.Application.Modules.Projects.Commands.AssignProject; using GozareshgirProgramManager.Application.Modules.Projects.Commands.AutoStopOverTimeTaskSections; +using GozareshgirProgramManager.Application.Modules.Projects.Commands.AutoUpdateDeployStatus; using GozareshgirProgramManager.Application.Modules.Projects.Commands.ChangeStatusSection; using GozareshgirProgramManager.Application.Modules.Projects.Commands.CreateProject; using GozareshgirProgramManager.Application.Modules.Projects.Commands.DeleteProject; @@ -124,7 +125,8 @@ public class ProjectController : ProgramManagerBaseController [HttpGet("deploy-board")] public async Task>> GetProjectDeployBoard() { - + // قبل از دریافت دیتا، وضعیت دیپلوی را بر اساس تکمیل شدن تمام سکشن‌ها به‌روزرسانی می‌کنیم + await _mediator.Send(new AutoUpdateDeployStatusCommand()); var request = new GetProjectDeployBoardListQuery(); return await _mediator.Send(request); } From 1e733f3f20632a2c242088044c78ba69f056c02f Mon Sep 17 00:00:00 2001 From: mahan Date: Tue, 30 Dec 2025 11:10:40 +0330 Subject: [PATCH 10/15] add: implement ChangeDeployStatusProject command and handler for updating project deployment status --- .../AutoUpdateDeployStatusCommand.cs | 2 +- .../ChangeDeployeStatusProjectComand.cs | 39 +++++++++++++++++++ .../ProjectDeployBoardListQueryHandler.cs | 2 +- .../ProjectAgg/Entities/ProjectPhase.cs | 4 +- .../_Common/IRepository.cs | 2 +- .../Persistence/_Common/RepositoryBase.cs | 4 +- .../ProgramManager/ProjectController.cs | 8 ++++ 7 files changed, 54 insertions(+), 7 deletions(-) create mode 100644 ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Commands/ChangeDeployStatusProject/ChangeDeployeStatusProjectComand.cs diff --git a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Commands/AutoUpdateDeployStatus/AutoUpdateDeployStatusCommand.cs b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Commands/AutoUpdateDeployStatus/AutoUpdateDeployStatusCommand.cs index 5164a0c0..66fce572 100644 --- a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Commands/AutoUpdateDeployStatus/AutoUpdateDeployStatusCommand.cs +++ b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Commands/AutoUpdateDeployStatus/AutoUpdateDeployStatusCommand.cs @@ -25,7 +25,7 @@ public class AutoUpdateDeployStatusCommandHandler : IBaseCommandHandler ts.Task) .ThenInclude(t => t.Phase) - .Where(ts => ts.Task.Phase.DeployStatus == ProjectDeployStatus.NoTCompleted) + .Where(ts => ts.Task.Phase.DeployStatus == ProjectDeployStatus.NotCompleted) .ToListAsync(cancellationToken); if (sections.Count == 0) diff --git a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Commands/ChangeDeployStatusProject/ChangeDeployeStatusProjectComand.cs b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Commands/ChangeDeployStatusProject/ChangeDeployeStatusProjectComand.cs new file mode 100644 index 00000000..68c336cd --- /dev/null +++ b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Commands/ChangeDeployStatusProject/ChangeDeployeStatusProjectComand.cs @@ -0,0 +1,39 @@ +using GozareshgirProgramManager.Application._Common.Interfaces; +using GozareshgirProgramManager.Application._Common.Models; +using GozareshgirProgramManager.Domain._Common; +using GozareshgirProgramManager.Domain.ProjectAgg.Entities; +using GozareshgirProgramManager.Domain.ProjectAgg.Repositories; + +namespace GozareshgirProgramManager.Application.Modules.Projects.Commands.ChangeDeployStatusProject; + +public record ChangeDeployStatusProjectCommand(Guid PhaseId, ProjectDeployStatus Status):IBaseCommand; + +public class ChangeDeployStatusProjectCommandHandler : IBaseCommandHandler +{ + private readonly IProjectRepository _projectRepository; + private readonly IProjectPhaseRepository _projectPhaseRepository; + private readonly IUnitOfWork _unitOfWork; + + public ChangeDeployStatusProjectCommandHandler(IProjectRepository projectRepository, IUnitOfWork unitOfWork, IProjectPhaseRepository projectPhaseRepository) + { + _projectRepository = projectRepository; + _unitOfWork = unitOfWork; + _projectPhaseRepository = projectPhaseRepository; + } + + public async Task Handle(ChangeDeployStatusProjectCommand request, CancellationToken cancellationToken) + { + var project = await _projectPhaseRepository.GetByIdAsync(request.PhaseId, cancellationToken); + if (project == null) + return OperationResult.NotFound("بخش مورد نظر یافت نشد"); + + if (project.DeployStatus == ProjectDeployStatus.NotCompleted) + { + return OperationResult.Failure("وضعیت استقرار نمی‌تواند از حالت 'تایید نشده' تغییر کند."); + } + project.UpdateDeployStatus(request.Status); + + await _unitOfWork.SaveChangesAsync(cancellationToken); + return OperationResult.Success(); + } +} \ No newline at end of file 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 3cbac6e0..7ee495b4 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 @@ -50,7 +50,7 @@ public class ProjectDeployBoardListQueryHandler:IBaseQueryHandler x.Status == TaskSectionStatus.Completed || x.Status == TaskSectionStatus.PendingForCompletion - || x.Task.Phase.DeployStatus != ProjectDeployStatus.NoTCompleted) + || x.Task.Phase.DeployStatus != ProjectDeployStatus.NotCompleted) .GroupBy(x=>x.Task.PhaseId).ToListAsync(cancellationToken: cancellationToken); var list = query.Select(g => new ProjectDeployBoardListItem diff --git a/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Entities/ProjectPhase.cs b/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Entities/ProjectPhase.cs index 8d7583bc..ec80aaa9 100644 --- a/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Entities/ProjectPhase.cs +++ b/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Entities/ProjectPhase.cs @@ -23,7 +23,7 @@ public class ProjectPhase : ProjectHierarchyNode ProjectId = projectId; _tasks = new List(); _phaseSections = new List(); - DeployStatus = ProjectDeployStatus.NoTCompleted; + DeployStatus = ProjectDeployStatus.NotCompleted; AddDomainEvent(new PhaseCreatedEvent(Id, projectId, name)); } @@ -216,7 +216,7 @@ public class ProjectPhase : ProjectHierarchyNode public enum ProjectDeployStatus { - NoTCompleted, + NotCompleted, PendingDevDeploy, DevDeployed, PendingDeploy, diff --git a/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/_Common/IRepository.cs b/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/_Common/IRepository.cs index cd8a9dcc..053ba51b 100644 --- a/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/_Common/IRepository.cs +++ b/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/_Common/IRepository.cs @@ -5,7 +5,7 @@ namespace GozareshgirProgramManager.Domain._Common; public interface IRepository where T:class { T Get(TKey id); - Task GetByIdAsync(TKey id); + Task GetByIdAsync(TKey id, CancellationToken cancellationToken = default); List Get(); IQueryable GetQueryable(); void Create(T entity); diff --git a/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Persistence/_Common/RepositoryBase.cs b/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Persistence/_Common/RepositoryBase.cs index def0cf17..3a016ae8 100644 --- a/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Persistence/_Common/RepositoryBase.cs +++ b/ProgramManager/src/Infrastructure/GozareshgirProgramManager.Infrastructure/Persistence/_Common/RepositoryBase.cs @@ -43,9 +43,9 @@ public class RepositoryBase : IRepository where T : class return _context.Find(id); } - public async Task GetByIdAsync(TKey id) + public async Task GetByIdAsync(TKey id, CancellationToken cancellationToken = default) { - return await _context.Set().FindAsync(id); + return await _context.Set().FindAsync([id], cancellationToken); } public List Get() diff --git a/ServiceHost/Areas/Admin/Controllers/ProgramManager/ProjectController.cs b/ServiceHost/Areas/Admin/Controllers/ProgramManager/ProjectController.cs index a0fe2e7c..e9c5e60f 100644 --- a/ServiceHost/Areas/Admin/Controllers/ProgramManager/ProjectController.cs +++ b/ServiceHost/Areas/Admin/Controllers/ProgramManager/ProjectController.cs @@ -3,6 +3,7 @@ using GozareshgirProgramManager.Application._Common.Models; using GozareshgirProgramManager.Application.Modules.Projects.Commands.AssignProject; using GozareshgirProgramManager.Application.Modules.Projects.Commands.AutoStopOverTimeTaskSections; using GozareshgirProgramManager.Application.Modules.Projects.Commands.AutoUpdateDeployStatus; +using GozareshgirProgramManager.Application.Modules.Projects.Commands.ChangeDeployStatusProject; using GozareshgirProgramManager.Application.Modules.Projects.Commands.ChangeStatusSection; using GozareshgirProgramManager.Application.Modules.Projects.Commands.CreateProject; using GozareshgirProgramManager.Application.Modules.Projects.Commands.DeleteProject; @@ -138,4 +139,11 @@ public class ProjectController : ProgramManagerBaseController var res = await _mediator.Send(query); return res; } + + [HttpPost("deploy-board/change-status")] + public async Task> ChangeDeployStatus(ChangeDeployStatusProjectCommand command) + { + var res = await _mediator.Send(command); + return res; + } } \ No newline at end of file From fb5b98bf25fdfed1efe53f1ce65a4836c84cebb1 Mon Sep 17 00:00:00 2001 From: mahan Date: Tue, 30 Dec 2025 11:54:56 +0330 Subject: [PATCH 11/15] add: update UpdateDeployStatus method to archive project phase upon deployment --- .../ProjectAgg/Entities/ProjectPhase.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Entities/ProjectPhase.cs b/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Entities/ProjectPhase.cs index ec80aaa9..3534c50f 100644 --- a/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Entities/ProjectPhase.cs +++ b/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Entities/ProjectPhase.cs @@ -211,6 +211,10 @@ public class ProjectPhase : ProjectHierarchyNode public void UpdateDeployStatus(ProjectDeployStatus status) { DeployStatus = status; + if (status == ProjectDeployStatus.Deployed) + { + IsArchived = true; + } } } From 656bb49fab26bfb8442b13845781fca4ba53a2c1 Mon Sep 17 00:00:00 2001 From: mahan Date: Tue, 30 Dec 2025 12:30:06 +0330 Subject: [PATCH 12/15] add: remove GetByIdAsync method and add Id property to ProjectDeployBoardListItem --- .../ProjectDeployBoardListQueryHandler.cs | 2 ++ .../ProjectAgg/Repositories/ITaskSectionActivityRepository.cs | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) 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 7ee495b4..d0b7e6e5 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 @@ -10,6 +10,7 @@ namespace GozareshgirProgramManager.Application.Modules.Projects.Queries.Project public record ProjectDeployBoardListItem() { + public Guid Id { get; set; } public string ProjectName { get; set; } public string PhaseName { get; set; } public int TotalTasks { get; set; } @@ -55,6 +56,7 @@ public class ProjectDeployBoardListQueryHandler:IBaseQueryHandler new ProjectDeployBoardListItem { + Id = g.Key, ProjectName = g.First().Task.Phase.Project.Name, PhaseName = g.First().Task.Phase.Name, TotalTasks = g.Select(x => x.TaskId).Distinct().Count(), diff --git a/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Repositories/ITaskSectionActivityRepository.cs b/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Repositories/ITaskSectionActivityRepository.cs index d20ef6e0..55eff1be 100644 --- a/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Repositories/ITaskSectionActivityRepository.cs +++ b/ProgramManager/src/Domain/GozareshgirProgramManager.Domain/ProjectAgg/Repositories/ITaskSectionActivityRepository.cs @@ -5,7 +5,6 @@ namespace GozareshgirProgramManager.Domain.ProjectAgg.Repositories; public interface ITaskSectionActivityRepository:IRepository { - Task GetByIdAsync(Guid id); Task> GetBySectionIdAsync(Guid sectionId); Task> GetByUserIdAsync(long userId); Task> GetActiveByUserAsync(long userId); From 94955ea1b4f1453ec86fb082eac82a1410597259 Mon Sep 17 00:00:00 2001 From: mahan Date: Tue, 30 Dec 2025 12:54:53 +0330 Subject: [PATCH 13/15] add: update GetProjectDeployBoardDetails method to use ProjectDeployBoardDetailsQuery --- .../Admin/Controllers/ProgramManager/ProjectController.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ServiceHost/Areas/Admin/Controllers/ProgramManager/ProjectController.cs b/ServiceHost/Areas/Admin/Controllers/ProgramManager/ProjectController.cs index e9c5e60f..e13bcc97 100644 --- a/ServiceHost/Areas/Admin/Controllers/ProgramManager/ProjectController.cs +++ b/ServiceHost/Areas/Admin/Controllers/ProgramManager/ProjectController.cs @@ -14,6 +14,7 @@ using GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectA using GozareshgirProgramManager.Application.Modules.Projects.Queries.GetProjectsList; using GozareshgirProgramManager.Application.Modules.Projects.Queries.ProjectBoardDetail; using GozareshgirProgramManager.Application.Modules.Projects.Queries.ProjectBoardList; +using GozareshgirProgramManager.Application.Modules.Projects.Queries.ProjectDeployBoardDetail; using GozareshgirProgramManager.Application.Modules.Projects.Queries.ProjectDeployBoardList; using GozareshgirProgramManager.Application.Modules.Projects.Queries.ProjectSetTimeDetails; using MediatR; @@ -133,9 +134,9 @@ public class ProjectController : ProgramManagerBaseController } [HttpGet("deploy-board/details")] - public async Task>> GetProjectDeployBoardDetails(Guid id) + public async Task>> GetProjectDeployBoardDetails(Guid id) { - var query = new ProjectBoardDetailQuery(id); + var query = new ProjectDeployBoardDetailsQuery(id); var res = await _mediator.Send(query); return res; } From 45615684ed0854b5cf697c80dfcc85178da5855d Mon Sep 17 00:00:00 2001 From: mahan Date: Tue, 30 Dec 2025 15:55:57 +0330 Subject: [PATCH 14/15] add: enhance skill calculation in ProjectDeployBoardDetailsQueryHandler and refine query conditions in ProjectDeployBoardListQueryHandler --- .../ProjectDeployBoardDetailsQueryHandler.cs | 24 ++++++++++++++----- .../ProjectDeployBoardListQueryHandler.cs | 11 +++++---- 2 files changed, 24 insertions(+), 11 deletions(-) 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 3e3657d9..33a1caa6 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 @@ -72,12 +72,24 @@ public class var doneTime = t.Sections.Aggregate(TimeSpan.Zero, (sum, next) => sum.Add(next.GetTotalTimeSpent())); var skills = t.Sections - .Select(s => new ProjectDeployBoardDetailItemSkill( - usersDict.GetValueOrDefault(s.OriginalAssignedUserId, "کاربر ناشناس"), - s.Skill?.Name ?? "بدون مهارت", - totalTime.TotalSeconds > 0 - ? (int)((doneTime.TotalSeconds / totalTime.TotalSeconds) * 100) - : 0)).ToList(); + .Select(s => + { + var originalUserFullName = usersDict.GetValueOrDefault(s.OriginalAssignedUserId, + "کاربر ناشناس"); + + var skillName = s.Skill?.Name ?? "بدون مهارت"; + + var totalTimeSpent = s.GetTotalTimeSpent(); + + var timePercentage = s.FinalEstimatedHours.Ticks > 0 + ? (int)((totalTimeSpent.Ticks / (double)s.FinalEstimatedHours.Ticks) * 100) + : 0; + + return new ProjectDeployBoardDetailItemSkill( + originalUserFullName, + skillName, + timePercentage); + }).ToList(); return new ProjectDeployBoardDetailTaskItem( t.Name, 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 d0b7e6e5..380e08ea 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 @@ -51,7 +51,8 @@ public class ProjectDeployBoardListQueryHandler:IBaseQueryHandler x.Status == TaskSectionStatus.Completed || x.Status == TaskSectionStatus.PendingForCompletion - || x.Task.Phase.DeployStatus != ProjectDeployStatus.NotCompleted) + || (x.Task.Phase.DeployStatus != ProjectDeployStatus.NotCompleted + && x.InitialEstimatedHours>TimeSpan.Zero)) .GroupBy(x=>x.Task.PhaseId).ToListAsync(cancellationToken: cancellationToken); var list = query.Select(g => new ProjectDeployBoardListItem @@ -60,10 +61,10 @@ public class ProjectDeployBoardListQueryHandler:IBaseQueryHandler x.TaskId).Distinct().Count(), - DoneTasks = g.Where(x => x.Status == TaskSectionStatus.Completed).Select(x => x.TaskId).Distinct().Count(), - TotalTimeSpan = TimeSpan.FromHours(g.Sum(x => x.InitialEstimatedHours.TotalHours)), - DoneTimeSpan = TimeSpan.FromHours(g.Where(x => x.Status == TaskSectionStatus.Completed) - .Sum(x => x.InitialEstimatedHours.TotalHours)), + DoneTasks = g.Where(x => x.Status == TaskSectionStatus.Completed) + .Select(x => x.TaskId).Distinct().Count(), + TotalTimeSpan = TimeSpan.FromTicks(g.Sum(x => x.InitialEstimatedHours.Ticks)), + DoneTimeSpan = TimeSpan.FromTicks(g.Sum(x=>x.GetTotalTimeSpent().Ticks)), DeployStatus = g.First().Task.Phase.DeployStatus }).ToList(); var response = new GetProjectsDeployBoardListResponse(list); From aa37ca4b2898fd7ed8eb047104e5895dc1e0e0a3 Mon Sep 17 00:00:00 2001 From: mahan Date: Tue, 30 Dec 2025 17:19:59 +0330 Subject: [PATCH 15/15] add: enhance deployment status validation in ChangeDeployStatusProject command and include Activities in ProjectDeployBoardList query --- .../ChangeDeployeStatusProjectComand.cs | 5 +++++ .../ProjectDeployBoardListQueryHandler.cs | 1 + 2 files changed, 6 insertions(+) diff --git a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Commands/ChangeDeployStatusProject/ChangeDeployeStatusProjectComand.cs b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Commands/ChangeDeployStatusProject/ChangeDeployeStatusProjectComand.cs index 68c336cd..06c75b4a 100644 --- a/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Commands/ChangeDeployStatusProject/ChangeDeployeStatusProjectComand.cs +++ b/ProgramManager/src/Application/GozareshgirProgramManager.Application/Modules/Projects/Commands/ChangeDeployStatusProject/ChangeDeployeStatusProjectComand.cs @@ -31,6 +31,11 @@ public class ChangeDeployStatusProjectCommandHandler : IBaseCommandHandlerx.Activities) .Include(x=>x.Task) .ThenInclude(x => x.Phase) .ThenInclude(x => x.Project)