Compare commits
	
		
			58 Commits
		
	
	
		
			d12f8ed0fb
			...
			8c691c5d3e
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 8c691c5d3e | |||
| 2128e630d5 | |||
| 9149c4e546 | |||
| 70c1c6da9d | |||
| e3ddcee8b3 | |||
| 0d49163d7b | |||
| b94f4bdb63 | |||
| 2ed0e6e5b6 | |||
| 72a92417b5 | |||
| cb2856b2df | |||
| 82ebd07d61 | |||
| b78c5c07c5 | |||
| 64a7cde69c | |||
| a6a842bf10 | |||
| c3f5fe8e34 | |||
| c9bb18d8e5 | |||
| 2e925efcf7 | |||
| 5bc13e215d | |||
| 
						 | 
					f20b4a42a1 | ||
| 99818c42b0 | |||
| 5d5579882f | |||
| 3dfde6d9a5 | |||
| aa2bc674eb | |||
| 4164c7d761 | |||
| 
						 | 
					692b1bfef3 | ||
| 
						 | 
					1c58265a9f | ||
| 303f326773 | |||
| e2956c0c8c | |||
| 39378f3a88 | |||
| caacb43aa8 | |||
| 29ea1698bc | |||
| 793877b8f8 | |||
| f47586710b | |||
| b21d30c18e | |||
| d78a2fe3b2 | |||
| 6ebc74499f | |||
| ff9c7c9434 | |||
| e391c82659 | |||
| 1f56143142 | |||
| dc21b9d2c6 | |||
| 17ae02a0b3 | |||
| 9d5535edf1 | |||
| c689f2dfd8 | |||
| 5c019a2ff6 | |||
| cb185db4f3 | |||
| 1a51860517 | |||
| f275d08215 | |||
| 4ccc690560 | |||
| 82f3fdbc23 | |||
| 790e9f63e1 | |||
| 0636c8aedd | |||
| 8f2c828282 | |||
| 169e7f6601 | |||
| 691a670a28 | |||
| 76b6ac6581 | |||
| abe7870ad5 | |||
| 712c5e2a0a | |||
| 8814dc59d9 | 
@ -44,6 +44,7 @@ namespace Marco.Pms.DataAccess.Data
 | 
				
			|||||||
        public DbSet<TaskAllocation> TaskAllocations { get; set; }
 | 
					        public DbSet<TaskAllocation> TaskAllocations { get; set; }
 | 
				
			||||||
        public DbSet<TaskComment> TaskComments { get; set; }
 | 
					        public DbSet<TaskComment> TaskComments { get; set; }
 | 
				
			||||||
        public DbSet<TaskMembers> TaskMembers { get; set; }
 | 
					        public DbSet<TaskMembers> TaskMembers { get; set; }
 | 
				
			||||||
 | 
					        public DbSet<TaskAttachment> TaskAttachments { get; set; }
 | 
				
			||||||
        public DbSet<Attendance> Attendes { get; set; }
 | 
					        public DbSet<Attendance> Attendes { get; set; }
 | 
				
			||||||
        public DbSet<AttendanceLog> AttendanceLogs { get; set; }
 | 
					        public DbSet<AttendanceLog> AttendanceLogs { get; set; }
 | 
				
			||||||
        public DbSet<Employee> Employees { get; set; }
 | 
					        public DbSet<Employee> Employees { get; set; }
 | 
				
			||||||
@ -68,6 +69,7 @@ namespace Marco.Pms.DataAccess.Data
 | 
				
			|||||||
        public DbSet<Document> Documents { get; set; }
 | 
					        public DbSet<Document> Documents { get; set; }
 | 
				
			||||||
        public DbSet<TicketTag> TicketTags { get; set; }
 | 
					        public DbSet<TicketTag> TicketTags { get; set; }
 | 
				
			||||||
        public DbSet<WorkCategoryMaster> WorkCategoryMasters { get; set; }
 | 
					        public DbSet<WorkCategoryMaster> WorkCategoryMasters { get; set; }
 | 
				
			||||||
 | 
					        public DbSet<WorkStatusMaster> WorkStatusMasters { get; set; }
 | 
				
			||||||
        public DbSet<Contact> Contacts { get; set; }
 | 
					        public DbSet<Contact> Contacts { get; set; }
 | 
				
			||||||
        public DbSet<ContactCategoryMaster> ContactCategoryMasters { get; set; }
 | 
					        public DbSet<ContactCategoryMaster> ContactCategoryMasters { get; set; }
 | 
				
			||||||
        public DbSet<ContactEmail> ContactsEmails { get; set; }
 | 
					        public DbSet<ContactEmail> ContactsEmails { get; set; }
 | 
				
			||||||
@ -160,14 +162,19 @@ namespace Marco.Pms.DataAccess.Data
 | 
				
			|||||||
                },
 | 
					                },
 | 
				
			||||||
                 new StatusMaster
 | 
					                 new StatusMaster
 | 
				
			||||||
                 {
 | 
					                 {
 | 
				
			||||||
                     Id = new Guid("603e994b-a27f-4e5d-a251-f3d69b0498ba"),
 | 
					                     Id = new Guid("cdad86aa-8a56-4ff4-b633-9c629057dfef"),
 | 
				
			||||||
                     Status = "In Progress",
 | 
					                     Status = "In Progress",
 | 
				
			||||||
                     TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
 | 
					                     TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
 | 
				
			||||||
 | 
					                 }, new StatusMaster
 | 
				
			||||||
 | 
					                 {
 | 
				
			||||||
 | 
					                     Id = new Guid("603e994b-a27f-4e5d-a251-f3d69b0498ba"),
 | 
				
			||||||
 | 
					                     Status = "On Hold",
 | 
				
			||||||
 | 
					                     TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
 | 
				
			||||||
                 },
 | 
					                 },
 | 
				
			||||||
                  new StatusMaster
 | 
					                  new StatusMaster
 | 
				
			||||||
                  {
 | 
					                  {
 | 
				
			||||||
                      Id = new Guid("ef1c356e-0fe0-42df-a5d3-8daee355492d"),
 | 
					                      Id = new Guid("ef1c356e-0fe0-42df-a5d3-8daee355492d"),
 | 
				
			||||||
                      Status = "On Hold",
 | 
					                      Status = "In Active",
 | 
				
			||||||
                      TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
 | 
					                      TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
 | 
				
			||||||
                  },
 | 
					                  },
 | 
				
			||||||
                   new StatusMaster
 | 
					                   new StatusMaster
 | 
				
			||||||
@ -428,6 +435,32 @@ namespace Marco.Pms.DataAccess.Data
 | 
				
			|||||||
                    TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
 | 
					                    TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                );
 | 
					                );
 | 
				
			||||||
 | 
					            modelBuilder.Entity<WorkStatusMaster>().HasData(
 | 
				
			||||||
 | 
					                new WorkStatusMaster
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    Id = new Guid("030bb085-e230-4370-aec7-9a74d652864e"),
 | 
				
			||||||
 | 
					                    Name = "Approve",
 | 
				
			||||||
 | 
					                    Description = "Confirm the tasks are actually finished as reported",
 | 
				
			||||||
 | 
					                    IsSystem = true,
 | 
				
			||||||
 | 
					                    TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                new WorkStatusMaster
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    Id = new Guid("2a1a5b96-cf93-4111-b4b1-76c19d6333b4"),
 | 
				
			||||||
 | 
					                    Name = "Partially Approve",
 | 
				
			||||||
 | 
					                    Description = "Not all tasks are actually finished as reported",
 | 
				
			||||||
 | 
					                    IsSystem = true,
 | 
				
			||||||
 | 
					                    TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                new WorkStatusMaster
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    Id = new Guid("00a062e6-62e6-42c5-b6b1-024328651b72"),
 | 
				
			||||||
 | 
					                    Name = "NCR",
 | 
				
			||||||
 | 
					                    Description = "Tasks are not finished as reported or have any issues in al the tasks",
 | 
				
			||||||
 | 
					                    IsSystem = true,
 | 
				
			||||||
 | 
					                    TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										3269
									
								
								Marco.Pms.DataAccess/Migrations/20250612094243_Added_TaskAttachments_Table.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										3269
									
								
								Marco.Pms.DataAccess/Migrations/20250612094243_Added_TaskAttachments_Table.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					using System;
 | 
				
			||||||
 | 
					using Microsoft.EntityFrameworkCore.Migrations;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#nullable disable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Marco.Pms.DataAccess.Migrations
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /// <inheritdoc />
 | 
				
			||||||
 | 
					    public partial class Added_TaskAttachments_Table : Migration
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        /// <inheritdoc />
 | 
				
			||||||
 | 
					        protected override void Up(MigrationBuilder migrationBuilder)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            migrationBuilder.CreateTable(
 | 
				
			||||||
 | 
					                name: "TaskAttachments",
 | 
				
			||||||
 | 
					                columns: table => new
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
 | 
				
			||||||
 | 
					                    ReferenceId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
 | 
				
			||||||
 | 
					                    DocumentId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci")
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                constraints: table =>
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    table.PrimaryKey("PK_TaskAttachments", x => x.Id);
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					                .Annotation("MySql:CharSet", "utf8mb4");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <inheritdoc />
 | 
				
			||||||
 | 
					        protected override void Down(MigrationBuilder migrationBuilder)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            migrationBuilder.DropTable(
 | 
				
			||||||
 | 
					                name: "TaskAttachments");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										3375
									
								
								Marco.Pms.DataAccess/Migrations/20250616064217_Added_Apporved_By_In_TaskAllocation_Table.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										3375
									
								
								Marco.Pms.DataAccess/Migrations/20250616064217_Added_Apporved_By_In_TaskAllocation_Table.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -0,0 +1,188 @@
 | 
				
			|||||||
 | 
					using System;
 | 
				
			||||||
 | 
					using Microsoft.EntityFrameworkCore.Migrations;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#nullable disable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Marco.Pms.DataAccess.Migrations
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /// <inheritdoc />
 | 
				
			||||||
 | 
					    public partial class Added_Apporved_By_In_TaskAllocation_Table : Migration
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        /// <inheritdoc />
 | 
				
			||||||
 | 
					        protected override void Up(MigrationBuilder migrationBuilder)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            migrationBuilder.AddColumn<Guid>(
 | 
				
			||||||
 | 
					                name: "ApprovedById",
 | 
				
			||||||
 | 
					                table: "TaskAllocations",
 | 
				
			||||||
 | 
					                type: "char(36)",
 | 
				
			||||||
 | 
					                nullable: true,
 | 
				
			||||||
 | 
					                collation: "ascii_general_ci");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.AddColumn<DateTime>(
 | 
				
			||||||
 | 
					                name: "ApprovedDate",
 | 
				
			||||||
 | 
					                table: "TaskAllocations",
 | 
				
			||||||
 | 
					                type: "datetime(6)",
 | 
				
			||||||
 | 
					                nullable: true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.AddColumn<Guid>(
 | 
				
			||||||
 | 
					                name: "ParentTaskId",
 | 
				
			||||||
 | 
					                table: "TaskAllocations",
 | 
				
			||||||
 | 
					                type: "char(36)",
 | 
				
			||||||
 | 
					                nullable: true,
 | 
				
			||||||
 | 
					                collation: "ascii_general_ci");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.AddColumn<Guid>(
 | 
				
			||||||
 | 
					                name: "ReportedById",
 | 
				
			||||||
 | 
					                table: "TaskAllocations",
 | 
				
			||||||
 | 
					                type: "char(36)",
 | 
				
			||||||
 | 
					                nullable: true,
 | 
				
			||||||
 | 
					                collation: "ascii_general_ci");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.AddColumn<double>(
 | 
				
			||||||
 | 
					                name: "ReportedTask",
 | 
				
			||||||
 | 
					                table: "TaskAllocations",
 | 
				
			||||||
 | 
					                type: "double",
 | 
				
			||||||
 | 
					                nullable: false,
 | 
				
			||||||
 | 
					                defaultValue: 0.0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.AddColumn<Guid>(
 | 
				
			||||||
 | 
					                name: "WorkStatusId",
 | 
				
			||||||
 | 
					                table: "TaskAllocations",
 | 
				
			||||||
 | 
					                type: "char(36)",
 | 
				
			||||||
 | 
					                nullable: true,
 | 
				
			||||||
 | 
					                collation: "ascii_general_ci");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.CreateTable(
 | 
				
			||||||
 | 
					                name: "WorkStatusMasters",
 | 
				
			||||||
 | 
					                columns: table => new
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
 | 
				
			||||||
 | 
					                    Name = table.Column<string>(type: "longtext", nullable: false)
 | 
				
			||||||
 | 
					                        .Annotation("MySql:CharSet", "utf8mb4"),
 | 
				
			||||||
 | 
					                    Description = table.Column<string>(type: "longtext", nullable: false)
 | 
				
			||||||
 | 
					                        .Annotation("MySql:CharSet", "utf8mb4"),
 | 
				
			||||||
 | 
					                    IsSystem = table.Column<bool>(type: "tinyint(1)", nullable: false),
 | 
				
			||||||
 | 
					                    TenantId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci")
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                constraints: table =>
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    table.PrimaryKey("PK_WorkStatusMasters", x => x.Id);
 | 
				
			||||||
 | 
					                    table.ForeignKey(
 | 
				
			||||||
 | 
					                        name: "FK_WorkStatusMasters_Tenants_TenantId",
 | 
				
			||||||
 | 
					                        column: x => x.TenantId,
 | 
				
			||||||
 | 
					                        principalTable: "Tenants",
 | 
				
			||||||
 | 
					                        principalColumn: "Id",
 | 
				
			||||||
 | 
					                        onDelete: ReferentialAction.Cascade);
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					                .Annotation("MySql:CharSet", "utf8mb4");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.InsertData(
 | 
				
			||||||
 | 
					                table: "WorkStatusMasters",
 | 
				
			||||||
 | 
					                columns: new[] { "Id", "Description", "IsSystem", "Name", "TenantId" },
 | 
				
			||||||
 | 
					                values: new object[,]
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    { new Guid("00a062e6-62e6-42c5-b6b1-024328651b72"), "Tasks are not finished as reported or have any issues in al the tasks", true, "NCR", new Guid("b3466e83-7e11-464c-b93a-daf047838b26") },
 | 
				
			||||||
 | 
					                    { new Guid("030bb085-e230-4370-aec7-9a74d652864e"), "Confirm the tasks are actually finished as reported", true, "Approve", new Guid("b3466e83-7e11-464c-b93a-daf047838b26") },
 | 
				
			||||||
 | 
					                    { new Guid("2a1a5b96-cf93-4111-b4b1-76c19d6333b4"), "Not all tasks are actually finished as reported", true, "Partially Approve", new Guid("b3466e83-7e11-464c-b93a-daf047838b26") }
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.CreateIndex(
 | 
				
			||||||
 | 
					                name: "IX_TaskAllocations_ApprovedById",
 | 
				
			||||||
 | 
					                table: "TaskAllocations",
 | 
				
			||||||
 | 
					                column: "ApprovedById");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.CreateIndex(
 | 
				
			||||||
 | 
					                name: "IX_TaskAllocations_ReportedById",
 | 
				
			||||||
 | 
					                table: "TaskAllocations",
 | 
				
			||||||
 | 
					                column: "ReportedById");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.CreateIndex(
 | 
				
			||||||
 | 
					                name: "IX_TaskAllocations_WorkStatusId",
 | 
				
			||||||
 | 
					                table: "TaskAllocations",
 | 
				
			||||||
 | 
					                column: "WorkStatusId");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.CreateIndex(
 | 
				
			||||||
 | 
					                name: "IX_WorkStatusMasters_TenantId",
 | 
				
			||||||
 | 
					                table: "WorkStatusMasters",
 | 
				
			||||||
 | 
					                column: "TenantId");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.AddForeignKey(
 | 
				
			||||||
 | 
					                name: "FK_TaskAllocations_Employees_ApprovedById",
 | 
				
			||||||
 | 
					                table: "TaskAllocations",
 | 
				
			||||||
 | 
					                column: "ApprovedById",
 | 
				
			||||||
 | 
					                principalTable: "Employees",
 | 
				
			||||||
 | 
					                principalColumn: "Id");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.AddForeignKey(
 | 
				
			||||||
 | 
					                name: "FK_TaskAllocations_Employees_ReportedById",
 | 
				
			||||||
 | 
					                table: "TaskAllocations",
 | 
				
			||||||
 | 
					                column: "ReportedById",
 | 
				
			||||||
 | 
					                principalTable: "Employees",
 | 
				
			||||||
 | 
					                principalColumn: "Id");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.AddForeignKey(
 | 
				
			||||||
 | 
					                name: "FK_TaskAllocations_WorkStatusMasters_WorkStatusId",
 | 
				
			||||||
 | 
					                table: "TaskAllocations",
 | 
				
			||||||
 | 
					                column: "WorkStatusId",
 | 
				
			||||||
 | 
					                principalTable: "WorkStatusMasters",
 | 
				
			||||||
 | 
					                principalColumn: "Id");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <inheritdoc />
 | 
				
			||||||
 | 
					        protected override void Down(MigrationBuilder migrationBuilder)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            migrationBuilder.DropForeignKey(
 | 
				
			||||||
 | 
					                name: "FK_TaskAllocations_Employees_ApprovedById",
 | 
				
			||||||
 | 
					                table: "TaskAllocations");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.DropForeignKey(
 | 
				
			||||||
 | 
					                name: "FK_TaskAllocations_Employees_ReportedById",
 | 
				
			||||||
 | 
					                table: "TaskAllocations");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.DropForeignKey(
 | 
				
			||||||
 | 
					                name: "FK_TaskAllocations_WorkStatusMasters_WorkStatusId",
 | 
				
			||||||
 | 
					                table: "TaskAllocations");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.DropTable(
 | 
				
			||||||
 | 
					                name: "WorkStatusMasters");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.DropIndex(
 | 
				
			||||||
 | 
					                name: "IX_TaskAllocations_ApprovedById",
 | 
				
			||||||
 | 
					                table: "TaskAllocations");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.DropIndex(
 | 
				
			||||||
 | 
					                name: "IX_TaskAllocations_ReportedById",
 | 
				
			||||||
 | 
					                table: "TaskAllocations");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.DropIndex(
 | 
				
			||||||
 | 
					                name: "IX_TaskAllocations_WorkStatusId",
 | 
				
			||||||
 | 
					                table: "TaskAllocations");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.DropColumn(
 | 
				
			||||||
 | 
					                name: "ApprovedById",
 | 
				
			||||||
 | 
					                table: "TaskAllocations");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.DropColumn(
 | 
				
			||||||
 | 
					                name: "ApprovedDate",
 | 
				
			||||||
 | 
					                table: "TaskAllocations");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.DropColumn(
 | 
				
			||||||
 | 
					                name: "ParentTaskId",
 | 
				
			||||||
 | 
					                table: "TaskAllocations");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.DropColumn(
 | 
				
			||||||
 | 
					                name: "ReportedById",
 | 
				
			||||||
 | 
					                table: "TaskAllocations");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.DropColumn(
 | 
				
			||||||
 | 
					                name: "ReportedTask",
 | 
				
			||||||
 | 
					                table: "TaskAllocations");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.DropColumn(
 | 
				
			||||||
 | 
					                name: "WorkStatusId",
 | 
				
			||||||
 | 
					                table: "TaskAllocations");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										3381
									
								
								Marco.Pms.DataAccess/Migrations/20250618112021_EnhancedWorkItemForParentId_Description.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										3381
									
								
								Marco.Pms.DataAccess/Migrations/20250618112021_EnhancedWorkItemForParentId_Description.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -0,0 +1,41 @@
 | 
				
			|||||||
 | 
					using System;
 | 
				
			||||||
 | 
					using Microsoft.EntityFrameworkCore.Migrations;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#nullable disable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Marco.Pms.DataAccess.Migrations
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /// <inheritdoc />
 | 
				
			||||||
 | 
					    public partial class EnhancedWorkItemForParentId_Description : Migration
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        /// <inheritdoc />
 | 
				
			||||||
 | 
					        protected override void Up(MigrationBuilder migrationBuilder)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            migrationBuilder.AddColumn<string>(
 | 
				
			||||||
 | 
					                name: "Description",
 | 
				
			||||||
 | 
					                table: "WorkItems",
 | 
				
			||||||
 | 
					                type: "longtext",
 | 
				
			||||||
 | 
					                nullable: true)
 | 
				
			||||||
 | 
					                .Annotation("MySql:CharSet", "utf8mb4");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.AddColumn<Guid>(
 | 
				
			||||||
 | 
					                name: "ParentTaskId",
 | 
				
			||||||
 | 
					                table: "WorkItems",
 | 
				
			||||||
 | 
					                type: "char(36)",
 | 
				
			||||||
 | 
					                nullable: true,
 | 
				
			||||||
 | 
					                collation: "ascii_general_ci");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <inheritdoc />
 | 
				
			||||||
 | 
					        protected override void Down(MigrationBuilder migrationBuilder)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            migrationBuilder.DropColumn(
 | 
				
			||||||
 | 
					                name: "Description",
 | 
				
			||||||
 | 
					                table: "WorkItems");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.DropColumn(
 | 
				
			||||||
 | 
					                name: "ParentTaskId",
 | 
				
			||||||
 | 
					                table: "WorkItems");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										3275
									
								
								Marco.Pms.DataAccess/Migrations/20250619060620_Added_New_Status_Master_In_Progress.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										3275
									
								
								Marco.Pms.DataAccess/Migrations/20250619060620_Added_New_Status_Master_In_Progress.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -0,0 +1,57 @@
 | 
				
			|||||||
 | 
					using System;
 | 
				
			||||||
 | 
					using Microsoft.EntityFrameworkCore.Migrations;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#nullable disable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Marco.Pms.DataAccess.Migrations
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /// <inheritdoc />
 | 
				
			||||||
 | 
					    public partial class Added_New_Status_Master_In_Progress : Migration
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        /// <inheritdoc />
 | 
				
			||||||
 | 
					        protected override void Up(MigrationBuilder migrationBuilder)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            migrationBuilder.UpdateData(
 | 
				
			||||||
 | 
					                table: "StatusMasters",
 | 
				
			||||||
 | 
					                keyColumn: "Id",
 | 
				
			||||||
 | 
					                keyValue: new Guid("603e994b-a27f-4e5d-a251-f3d69b0498ba"),
 | 
				
			||||||
 | 
					                column: "Status",
 | 
				
			||||||
 | 
					                value: "On Hold");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.UpdateData(
 | 
				
			||||||
 | 
					                table: "StatusMasters",
 | 
				
			||||||
 | 
					                keyColumn: "Id",
 | 
				
			||||||
 | 
					                keyValue: new Guid("ef1c356e-0fe0-42df-a5d3-8daee355492d"),
 | 
				
			||||||
 | 
					                column: "Status",
 | 
				
			||||||
 | 
					                value: "In Active");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.InsertData(
 | 
				
			||||||
 | 
					                table: "StatusMasters",
 | 
				
			||||||
 | 
					                columns: new[] { "Id", "Status", "TenantId" },
 | 
				
			||||||
 | 
					                values: new object[] { new Guid("cdad86aa-8a56-4ff4-b633-9c629057dfef"), "In Progress", new Guid("b3466e83-7e11-464c-b93a-daf047838b26") });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <inheritdoc />
 | 
				
			||||||
 | 
					        protected override void Down(MigrationBuilder migrationBuilder)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            migrationBuilder.DeleteData(
 | 
				
			||||||
 | 
					                table: "StatusMasters",
 | 
				
			||||||
 | 
					                keyColumn: "Id",
 | 
				
			||||||
 | 
					                keyValue: new Guid("cdad86aa-8a56-4ff4-b633-9c629057dfef"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.UpdateData(
 | 
				
			||||||
 | 
					                table: "StatusMasters",
 | 
				
			||||||
 | 
					                keyColumn: "Id",
 | 
				
			||||||
 | 
					                keyValue: new Guid("603e994b-a27f-4e5d-a251-f3d69b0498ba"),
 | 
				
			||||||
 | 
					                column: "Status",
 | 
				
			||||||
 | 
					                value: "In Progress");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.UpdateData(
 | 
				
			||||||
 | 
					                table: "StatusMasters",
 | 
				
			||||||
 | 
					                keyColumn: "Id",
 | 
				
			||||||
 | 
					                keyValue: new Guid("ef1c356e-0fe0-42df-a5d3-8daee355492d"),
 | 
				
			||||||
 | 
					                column: "Status",
 | 
				
			||||||
 | 
					                value: "On Hold");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -28,6 +28,12 @@ namespace Marco.Pms.DataAccess.Migrations
 | 
				
			|||||||
                        .ValueGeneratedOnAdd()
 | 
					                        .ValueGeneratedOnAdd()
 | 
				
			||||||
                        .HasColumnType("char(36)");
 | 
					                        .HasColumnType("char(36)");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<Guid?>("ApprovedById")
 | 
				
			||||||
 | 
					                        .HasColumnType("char(36)");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<DateTime?>("ApprovedDate")
 | 
				
			||||||
 | 
					                        .HasColumnType("datetime(6)");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<Guid>("AssignedBy")
 | 
					                    b.Property<Guid>("AssignedBy")
 | 
				
			||||||
                        .HasColumnType("char(36)");
 | 
					                        .HasColumnType("char(36)");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -40,29 +46,64 @@ namespace Marco.Pms.DataAccess.Migrations
 | 
				
			|||||||
                    b.Property<string>("Description")
 | 
					                    b.Property<string>("Description")
 | 
				
			||||||
                        .HasColumnType("longtext");
 | 
					                        .HasColumnType("longtext");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<Guid?>("ParentTaskId")
 | 
				
			||||||
 | 
					                        .HasColumnType("char(36)");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<double>("PlannedTask")
 | 
					                    b.Property<double>("PlannedTask")
 | 
				
			||||||
                        .HasColumnType("double");
 | 
					                        .HasColumnType("double");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<Guid?>("ReportedById")
 | 
				
			||||||
 | 
					                        .HasColumnType("char(36)");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<DateTime?>("ReportedDate")
 | 
					                    b.Property<DateTime?>("ReportedDate")
 | 
				
			||||||
                        .HasColumnType("datetime(6)");
 | 
					                        .HasColumnType("datetime(6)");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<double>("ReportedTask")
 | 
				
			||||||
 | 
					                        .HasColumnType("double");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<Guid>("TenantId")
 | 
					                    b.Property<Guid>("TenantId")
 | 
				
			||||||
                        .HasColumnType("char(36)");
 | 
					                        .HasColumnType("char(36)");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<Guid>("WorkItemId")
 | 
					                    b.Property<Guid>("WorkItemId")
 | 
				
			||||||
                        .HasColumnType("char(36)");
 | 
					                        .HasColumnType("char(36)");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<Guid?>("WorkStatusId")
 | 
				
			||||||
 | 
					                        .HasColumnType("char(36)");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.HasKey("Id");
 | 
					                    b.HasKey("Id");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.HasIndex("ApprovedById");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.HasIndex("AssignedBy");
 | 
					                    b.HasIndex("AssignedBy");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.HasIndex("ReportedById");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.HasIndex("TenantId");
 | 
					                    b.HasIndex("TenantId");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.HasIndex("WorkItemId");
 | 
					                    b.HasIndex("WorkItemId");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.HasIndex("WorkStatusId");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.ToTable("TaskAllocations");
 | 
					                    b.ToTable("TaskAllocations");
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            modelBuilder.Entity("Marco.Pms.Model.Activities.TaskAttachment", b =>
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    b.Property<Guid>("Id")
 | 
				
			||||||
 | 
					                        .ValueGeneratedOnAdd()
 | 
				
			||||||
 | 
					                        .HasColumnType("char(36)");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<Guid>("DocumentId")
 | 
				
			||||||
 | 
					                        .HasColumnType("char(36)");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<Guid>("ReferenceId")
 | 
				
			||||||
 | 
					                        .HasColumnType("char(36)");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.HasKey("Id");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.ToTable("TaskAttachments");
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            modelBuilder.Entity("Marco.Pms.Model.Activities.TaskComment", b =>
 | 
					            modelBuilder.Entity("Marco.Pms.Model.Activities.TaskComment", b =>
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    b.Property<Guid>("Id")
 | 
					                    b.Property<Guid>("Id")
 | 
				
			||||||
@ -1658,17 +1699,23 @@ namespace Marco.Pms.DataAccess.Migrations
 | 
				
			|||||||
                        },
 | 
					                        },
 | 
				
			||||||
                        new
 | 
					                        new
 | 
				
			||||||
                        {
 | 
					                        {
 | 
				
			||||||
                            Id = new Guid("603e994b-a27f-4e5d-a251-f3d69b0498ba"),
 | 
					                            Id = new Guid("cdad86aa-8a56-4ff4-b633-9c629057dfef"),
 | 
				
			||||||
                            Status = "In Progress",
 | 
					                            Status = "In Progress",
 | 
				
			||||||
                            TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
 | 
					                            TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
 | 
				
			||||||
                        },
 | 
					                        },
 | 
				
			||||||
                        new
 | 
					                        new
 | 
				
			||||||
                        {
 | 
					                        {
 | 
				
			||||||
                            Id = new Guid("ef1c356e-0fe0-42df-a5d3-8daee355492d"),
 | 
					                            Id = new Guid("603e994b-a27f-4e5d-a251-f3d69b0498ba"),
 | 
				
			||||||
                            Status = "On Hold",
 | 
					                            Status = "On Hold",
 | 
				
			||||||
                            TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
 | 
					                            TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
 | 
				
			||||||
                        },
 | 
					                        },
 | 
				
			||||||
                        new
 | 
					                        new
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            Id = new Guid("ef1c356e-0fe0-42df-a5d3-8daee355492d"),
 | 
				
			||||||
 | 
					                            Status = "In Active",
 | 
				
			||||||
 | 
					                            TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                        new
 | 
				
			||||||
                        {
 | 
					                        {
 | 
				
			||||||
                            Id = new Guid("33deaef9-9af1-4f2a-b443-681ea0d04f81"),
 | 
					                            Id = new Guid("33deaef9-9af1-4f2a-b443-681ea0d04f81"),
 | 
				
			||||||
                            Status = "Completed",
 | 
					                            Status = "Completed",
 | 
				
			||||||
@ -1919,6 +1966,59 @@ namespace Marco.Pms.DataAccess.Migrations
 | 
				
			|||||||
                        });
 | 
					                        });
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            modelBuilder.Entity("Marco.Pms.Model.Master.WorkStatusMaster", b =>
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    b.Property<Guid>("Id")
 | 
				
			||||||
 | 
					                        .ValueGeneratedOnAdd()
 | 
				
			||||||
 | 
					                        .HasColumnType("char(36)");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<string>("Description")
 | 
				
			||||||
 | 
					                        .IsRequired()
 | 
				
			||||||
 | 
					                        .HasColumnType("longtext");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<bool>("IsSystem")
 | 
				
			||||||
 | 
					                        .HasColumnType("tinyint(1)");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<string>("Name")
 | 
				
			||||||
 | 
					                        .IsRequired()
 | 
				
			||||||
 | 
					                        .HasColumnType("longtext");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<Guid>("TenantId")
 | 
				
			||||||
 | 
					                        .HasColumnType("char(36)");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.HasKey("Id");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.HasIndex("TenantId");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.ToTable("WorkStatusMasters");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.HasData(
 | 
				
			||||||
 | 
					                        new
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            Id = new Guid("030bb085-e230-4370-aec7-9a74d652864e"),
 | 
				
			||||||
 | 
					                            Description = "Confirm the tasks are actually finished as reported",
 | 
				
			||||||
 | 
					                            IsSystem = true,
 | 
				
			||||||
 | 
					                            Name = "Approve",
 | 
				
			||||||
 | 
					                            TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                        new
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            Id = new Guid("2a1a5b96-cf93-4111-b4b1-76c19d6333b4"),
 | 
				
			||||||
 | 
					                            Description = "Not all tasks are actually finished as reported",
 | 
				
			||||||
 | 
					                            IsSystem = true,
 | 
				
			||||||
 | 
					                            Name = "Partially Approve",
 | 
				
			||||||
 | 
					                            TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                        new
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            Id = new Guid("00a062e6-62e6-42c5-b6b1-024328651b72"),
 | 
				
			||||||
 | 
					                            Description = "Tasks are not finished as reported or have any issues in al the tasks",
 | 
				
			||||||
 | 
					                            IsSystem = true,
 | 
				
			||||||
 | 
					                            Name = "NCR",
 | 
				
			||||||
 | 
					                            TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
 | 
				
			||||||
 | 
					                        });
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            modelBuilder.Entity("Marco.Pms.Model.Projects.Building", b =>
 | 
					            modelBuilder.Entity("Marco.Pms.Model.Projects.Building", b =>
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    b.Property<Guid>("Id")
 | 
					                    b.Property<Guid>("Id")
 | 
				
			||||||
@ -2098,6 +2198,12 @@ namespace Marco.Pms.DataAccess.Migrations
 | 
				
			|||||||
                    b.Property<double>("CompletedWork")
 | 
					                    b.Property<double>("CompletedWork")
 | 
				
			||||||
                        .HasColumnType("double");
 | 
					                        .HasColumnType("double");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<string>("Description")
 | 
				
			||||||
 | 
					                        .HasColumnType("longtext");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<Guid?>("ParentTaskId")
 | 
				
			||||||
 | 
					                        .HasColumnType("char(36)");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<double>("PlannedWork")
 | 
					                    b.Property<double>("PlannedWork")
 | 
				
			||||||
                        .HasColumnType("double");
 | 
					                        .HasColumnType("double");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -2428,12 +2534,20 @@ namespace Marco.Pms.DataAccess.Migrations
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            modelBuilder.Entity("Marco.Pms.Model.Activities.TaskAllocation", b =>
 | 
					            modelBuilder.Entity("Marco.Pms.Model.Activities.TaskAllocation", b =>
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
 | 
					                    b.HasOne("Marco.Pms.Model.Employees.Employee", "ApprovedBy")
 | 
				
			||||||
 | 
					                        .WithMany()
 | 
				
			||||||
 | 
					                        .HasForeignKey("ApprovedById");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.HasOne("Marco.Pms.Model.Employees.Employee", "Employee")
 | 
					                    b.HasOne("Marco.Pms.Model.Employees.Employee", "Employee")
 | 
				
			||||||
                        .WithMany()
 | 
					                        .WithMany()
 | 
				
			||||||
                        .HasForeignKey("AssignedBy")
 | 
					                        .HasForeignKey("AssignedBy")
 | 
				
			||||||
                        .OnDelete(DeleteBehavior.Cascade)
 | 
					                        .OnDelete(DeleteBehavior.Cascade)
 | 
				
			||||||
                        .IsRequired();
 | 
					                        .IsRequired();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.HasOne("Marco.Pms.Model.Employees.Employee", "ReportedBy")
 | 
				
			||||||
 | 
					                        .WithMany()
 | 
				
			||||||
 | 
					                        .HasForeignKey("ReportedById");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.HasOne("Marco.Pms.Model.Entitlements.Tenant", "Tenant")
 | 
					                    b.HasOne("Marco.Pms.Model.Entitlements.Tenant", "Tenant")
 | 
				
			||||||
                        .WithMany()
 | 
					                        .WithMany()
 | 
				
			||||||
                        .HasForeignKey("TenantId")
 | 
					                        .HasForeignKey("TenantId")
 | 
				
			||||||
@ -2446,11 +2560,21 @@ namespace Marco.Pms.DataAccess.Migrations
 | 
				
			|||||||
                        .OnDelete(DeleteBehavior.Cascade)
 | 
					                        .OnDelete(DeleteBehavior.Cascade)
 | 
				
			||||||
                        .IsRequired();
 | 
					                        .IsRequired();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.HasOne("Marco.Pms.Model.Master.WorkStatusMaster", "WorkStatus")
 | 
				
			||||||
 | 
					                        .WithMany()
 | 
				
			||||||
 | 
					                        .HasForeignKey("WorkStatusId");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Navigation("ApprovedBy");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Navigation("Employee");
 | 
					                    b.Navigation("Employee");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Navigation("ReportedBy");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Navigation("Tenant");
 | 
					                    b.Navigation("Tenant");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Navigation("WorkItem");
 | 
					                    b.Navigation("WorkItem");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Navigation("WorkStatus");
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            modelBuilder.Entity("Marco.Pms.Model.Activities.TaskComment", b =>
 | 
					            modelBuilder.Entity("Marco.Pms.Model.Activities.TaskComment", b =>
 | 
				
			||||||
@ -3063,6 +3187,17 @@ namespace Marco.Pms.DataAccess.Migrations
 | 
				
			|||||||
                    b.Navigation("Tenant");
 | 
					                    b.Navigation("Tenant");
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            modelBuilder.Entity("Marco.Pms.Model.Master.WorkStatusMaster", b =>
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    b.HasOne("Marco.Pms.Model.Entitlements.Tenant", "Tenant")
 | 
				
			||||||
 | 
					                        .WithMany()
 | 
				
			||||||
 | 
					                        .HasForeignKey("TenantId")
 | 
				
			||||||
 | 
					                        .OnDelete(DeleteBehavior.Cascade)
 | 
				
			||||||
 | 
					                        .IsRequired();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Navigation("Tenant");
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            modelBuilder.Entity("Marco.Pms.Model.Projects.Building", b =>
 | 
					            modelBuilder.Entity("Marco.Pms.Model.Projects.Building", b =>
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    b.HasOne("Marco.Pms.Model.Entitlements.Tenant", "Tenant")
 | 
					                    b.HasOne("Marco.Pms.Model.Entitlements.Tenant", "Tenant")
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,6 @@
 | 
				
			|||||||
using System.ComponentModel.DataAnnotations.Schema;
 | 
					using System.ComponentModel.DataAnnotations.Schema;
 | 
				
			||||||
using Marco.Pms.Model.Employees;
 | 
					using Marco.Pms.Model.Employees;
 | 
				
			||||||
 | 
					using Marco.Pms.Model.Master;
 | 
				
			||||||
using Marco.Pms.Model.Projects;
 | 
					using Marco.Pms.Model.Projects;
 | 
				
			||||||
using Marco.Pms.Model.Utilities;
 | 
					using Marco.Pms.Model.Utilities;
 | 
				
			||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
 | 
					using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
 | 
				
			||||||
@ -10,29 +11,43 @@ namespace Marco.Pms.Model.Activities
 | 
				
			|||||||
    public class TaskAllocation : TenantRelation
 | 
					    public class TaskAllocation : TenantRelation
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        public Guid Id { get; set; }
 | 
					        public Guid Id { get; set; }
 | 
				
			||||||
 | 
					        public Guid? ParentTaskId { get; set; }
 | 
				
			||||||
 | 
					 | 
				
			||||||
        public DateTime AssignmentDate { get; set; }
 | 
					        public DateTime AssignmentDate { get; set; }
 | 
				
			||||||
        public double PlannedTask { get; set; }
 | 
					        public double PlannedTask { get; set; }
 | 
				
			||||||
        public double CompletedTask { get; set; }
 | 
					        public double CompletedTask { get; set; }
 | 
				
			||||||
 | 
					        public double ReportedTask { get; set; }
 | 
				
			||||||
        public DateTime? ReportedDate { get; set; }
 | 
					        public DateTime? ReportedDate { get; set; }
 | 
				
			||||||
 | 
					        public DateTime? ApprovedDate { get; set; }
 | 
				
			||||||
        public string? Description { get; set; }
 | 
					        public string? Description { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        //public int? WorkItemMappingId { get; set; }
 | 
					        public Guid AssignedBy { get; set; }   //Employee Id 
 | 
				
			||||||
        //[ForeignKey("WorkItemMappingId")]
 | 
					 | 
				
			||||||
        //[ValidateNever]
 | 
					 | 
				
			||||||
        //public WorkItemMapping? WorkItemMapping { get; set; }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public Guid AssignedBy { get; set; }   //Employee Id
 | 
					 | 
				
			||||||
        [ForeignKey("AssignedBy")]
 | 
					        [ForeignKey("AssignedBy")]
 | 
				
			||||||
        [ValidateNever]
 | 
					        [ValidateNever]
 | 
				
			||||||
        public Employee? Employee { get; set; }
 | 
					        public Employee? Employee { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public Guid? ReportedById { get; set; }   //Employee Id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [ForeignKey("ReportedById")]
 | 
				
			||||||
 | 
					        [ValidateNever]
 | 
				
			||||||
 | 
					        public Employee? ReportedBy { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public Guid? ApprovedById { get; set; }   //Employee Id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [ForeignKey("ApprovedById")]
 | 
				
			||||||
 | 
					        [ValidateNever]
 | 
				
			||||||
 | 
					        public Employee? ApprovedBy { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public Guid WorkItemId { get; set; }
 | 
					        public Guid WorkItemId { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [ForeignKey("WorkItemId")]
 | 
					        [ForeignKey("WorkItemId")]
 | 
				
			||||||
        [ValidateNever]
 | 
					        [ValidateNever]
 | 
				
			||||||
        public WorkItem? WorkItem { get; set; }
 | 
					        public WorkItem? WorkItem { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public Guid? WorkStatusId { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [ForeignKey("WorkStatusId")]
 | 
				
			||||||
 | 
					        [ValidateNever]
 | 
				
			||||||
 | 
					        public WorkStatusMaster? WorkStatus { get; set; }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										9
									
								
								Marco.Pms.Model/Activities/TaskAttachment.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								Marco.Pms.Model/Activities/TaskAttachment.cs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					namespace Marco.Pms.Model.Activities
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public class TaskAttachment
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        public Guid Id { get; set; }
 | 
				
			||||||
 | 
					        public Guid ReferenceId { get; set; }
 | 
				
			||||||
 | 
					        public Guid DocumentId { get; set; }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,17 +0,0 @@
 | 
				
			|||||||
using System.ComponentModel.DataAnnotations.Schema;
 | 
					 | 
				
			||||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace Marco.Pms.Model.Activities
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    public class TaskImages
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        public Guid Id { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Guid TaskAllocationId { get; set; }
 | 
					 | 
				
			||||||
        [ValidateNever]
 | 
					 | 
				
			||||||
        [ForeignKey(nameof(TaskAllocationId))]
 | 
					 | 
				
			||||||
        public TaskAllocation? TaskAllocation { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public string? ImagePath { get; set; }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										12
									
								
								Marco.Pms.Model/Activities/WorkStatusMaster.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								Marco.Pms.Model/Activities/WorkStatusMaster.cs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					using Marco.Pms.Model.Utilities;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Marco.Pms.Model.Master
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public class WorkStatusMaster : TenantRelation
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        public Guid Id { get; set; }
 | 
				
			||||||
 | 
					        public string Name { get; set; } = string.Empty;
 | 
				
			||||||
 | 
					        public string Description { get; set; } = string.Empty;
 | 
				
			||||||
 | 
					        public bool IsSystem { get; set; } = false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										13
									
								
								Marco.Pms.Model/Dtos/Activities/ApproveTaskDto.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								Marco.Pms.Model/Dtos/Activities/ApproveTaskDto.cs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					using Marco.Pms.Model.Utilities;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Marco.Pms.Model.Dtos.Activities
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public class ApproveTaskDto
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        public Guid Id { get; set; }
 | 
				
			||||||
 | 
					        public Guid WorkStatus { get; set; }
 | 
				
			||||||
 | 
					        public long ApprovedTask { get; set; }
 | 
				
			||||||
 | 
					        public string? Comment { get; set; }
 | 
				
			||||||
 | 
					        public List<FileUploadModel>? Images { get; set; }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -3,6 +3,7 @@
 | 
				
			|||||||
    public class AssignTaskDto
 | 
					    public class AssignTaskDto
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        public DateTime AssignmentDate { get; set; }
 | 
					        public DateTime AssignmentDate { get; set; }
 | 
				
			||||||
 | 
					        public Guid? ParentTaskId { get; set; }
 | 
				
			||||||
        public double PlannedTask { get; set; }
 | 
					        public double PlannedTask { get; set; }
 | 
				
			||||||
        public string? Description { get; set; }
 | 
					        public string? Description { get; set; }
 | 
				
			||||||
        public List<Guid>? TaskTeam { get; set; }   //Employee Ids
 | 
					        public List<Guid>? TaskTeam { get; set; }   //Employee Ids
 | 
				
			||||||
 | 
				
			|||||||
@ -1,11 +1,12 @@
 | 
				
			|||||||
namespace Marco.Pms.Model.Dtos.Activities
 | 
					using Marco.Pms.Model.Utilities;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Marco.Pms.Model.Dtos.Activities
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public class CreateCommentDto
 | 
					    public class CreateCommentDto
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        public Guid TaskAllocationId { get; set; }
 | 
					        public Guid TaskAllocationId { get; set; }
 | 
				
			||||||
 | 
					 | 
				
			||||||
        public DateTime CommentDate { get; set; }
 | 
					        public DateTime CommentDate { get; set; }
 | 
				
			||||||
        public string? Comment { get; set; }
 | 
					        public string? Comment { get; set; }
 | 
				
			||||||
 | 
					        public List<FileUploadModel>? Images { get; set; }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					namespace Marco.Pms.Model.Dtos.Master
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public class CreateWorkStatusMasterDto
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        public string? Name { get; set; }
 | 
				
			||||||
 | 
					        public string? Description { get; set; }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,11 +1,15 @@
 | 
				
			|||||||
namespace Marco.Pms.Model.Dtos.Activities
 | 
					using Marco.Pms.Model.Utilities;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Marco.Pms.Model.Dtos.Activities
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public class ReportTaskDto
 | 
					    public class ReportTaskDto
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        public Guid Id { get; set; }
 | 
					        public Guid Id { get; set; }
 | 
				
			||||||
 | 
					        public Guid? ParentTaskId { get; set; }
 | 
				
			||||||
        public double CompletedTask { get; set; }
 | 
					        public double CompletedTask { get; set; }
 | 
				
			||||||
        public DateTime ReportedDate { get; set; }
 | 
					        public DateTime ReportedDate { get; set; }
 | 
				
			||||||
        public string? Comment { get; set; }
 | 
					        public string? Comment { get; set; }
 | 
				
			||||||
        public List<ReportCheckListDto>? CheckList { get; set; }
 | 
					        public List<ReportCheckListDto>? CheckList { get; set; }
 | 
				
			||||||
 | 
					        public List<FileUploadModel>? Images { get; set; }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					namespace Marco.Pms.Model.Dtos.Master
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public class UpdateWorkStatusMasterDto
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        public Guid Id { get; set; }
 | 
				
			||||||
 | 
					        public string? Name { get; set; }
 | 
				
			||||||
 | 
					        public string? Description { get; set; }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -8,7 +8,7 @@
 | 
				
			|||||||
        public List<UpdateContactPhoneDto>? ContactPhones { get; set; }
 | 
					        public List<UpdateContactPhoneDto>? ContactPhones { get; set; }
 | 
				
			||||||
        public List<UpdateContactEmailDto>? ContactEmails { get; set; }
 | 
					        public List<UpdateContactEmailDto>? ContactEmails { get; set; }
 | 
				
			||||||
        public List<Guid>? BucketIds { get; set; }
 | 
					        public List<Guid>? BucketIds { get; set; }
 | 
				
			||||||
        public Guid ContactCategoryId { get; set; }
 | 
					        public Guid? ContactCategoryId { get; set; }
 | 
				
			||||||
        public string? Description { get; set; }
 | 
					        public string? Description { get; set; }
 | 
				
			||||||
        public string? Organization { get; set; }
 | 
					        public string? Organization { get; set; }
 | 
				
			||||||
        public string? Address { get; set; }
 | 
					        public string? Address { get; set; }
 | 
				
			||||||
 | 
				
			|||||||
@ -11,5 +11,7 @@ namespace Marco.Pms.Model.Dtos.Project
 | 
				
			|||||||
        public Guid ActivityID { get; set; }
 | 
					        public Guid ActivityID { get; set; }
 | 
				
			||||||
        public int PlannedWork { get; set; }
 | 
					        public int PlannedWork { get; set; }
 | 
				
			||||||
        public int CompletedWork { get; set; }
 | 
					        public int CompletedWork { get; set; }
 | 
				
			||||||
 | 
					        public Guid? ParentTaskId { get; set; }
 | 
				
			||||||
 | 
					        public string? Comment { get; set; }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -13,6 +13,7 @@ namespace Marco.Pms.Model.Mapper
 | 
				
			|||||||
            return new TaskAllocation
 | 
					            return new TaskAllocation
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                AssignmentDate = assignTask.AssignmentDate,
 | 
					                AssignmentDate = assignTask.AssignmentDate,
 | 
				
			||||||
 | 
					                ParentTaskId = assignTask.ParentTaskId,
 | 
				
			||||||
                PlannedTask = assignTask.PlannedTask,
 | 
					                PlannedTask = assignTask.PlannedTask,
 | 
				
			||||||
                CompletedTask = 0,
 | 
					                CompletedTask = 0,
 | 
				
			||||||
                Description = assignTask.Description,
 | 
					                Description = assignTask.Description,
 | 
				
			||||||
@ -43,18 +44,23 @@ namespace Marco.Pms.Model.Mapper
 | 
				
			|||||||
                TenantId = tenantId
 | 
					                TenantId = tenantId
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        public static TaskVM TaskAllocationToTaskVM(this TaskAllocation taskAllocation, string employeeName)
 | 
					        public static TaskVM TaskAllocationToTaskVM(this TaskAllocation taskAllocation)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return new TaskVM
 | 
					            return new TaskVM
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                Id = taskAllocation.Id,
 | 
					                Id = taskAllocation.Id,
 | 
				
			||||||
                AssignmentDate = taskAllocation.AssignmentDate,
 | 
					                AssignmentDate = taskAllocation.AssignmentDate,
 | 
				
			||||||
 | 
					                ReportedDate = taskAllocation.ReportedDate,
 | 
				
			||||||
 | 
					                ApprovedDate = taskAllocation.ApprovedDate,
 | 
				
			||||||
                PlannedTask = taskAllocation.PlannedTask,
 | 
					                PlannedTask = taskAllocation.PlannedTask,
 | 
				
			||||||
                CompletedTask = taskAllocation.CompletedTask,
 | 
					                CompletedTask = taskAllocation.CompletedTask,
 | 
				
			||||||
                ReportedDate = taskAllocation.ReportedDate,
 | 
					                NotApprovedTask = taskAllocation.ApprovedById == null ? taskAllocation.CompletedTask : (taskAllocation.CompletedTask - taskAllocation.ReportedTask),
 | 
				
			||||||
                Description = taskAllocation.Description,
 | 
					                Description = taskAllocation.Description,
 | 
				
			||||||
                AssignBy = employeeName,
 | 
					                AssignedBy = taskAllocation.Employee?.ToBasicEmployeeVMFromEmployee(),
 | 
				
			||||||
                WorkItem = taskAllocation.WorkItem
 | 
					                ReportedBy = taskAllocation.ReportedBy?.ToBasicEmployeeVMFromEmployee(),
 | 
				
			||||||
 | 
					                ApprovedBy = taskAllocation.ApprovedBy?.ToBasicEmployeeVMFromEmployee(),
 | 
				
			||||||
 | 
					                WorkItem = taskAllocation.WorkItem,
 | 
				
			||||||
 | 
					                WorkStatus = taskAllocation.WorkStatus
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        public static AssignedTaskVM ToAssignTaskVMFromTaskAllocation(this TaskAllocation taskAllocation)
 | 
					        public static AssignedTaskVM ToAssignTaskVMFromTaskAllocation(this TaskAllocation taskAllocation)
 | 
				
			||||||
@ -100,11 +106,18 @@ namespace Marco.Pms.Model.Mapper
 | 
				
			|||||||
            return new ListTaskVM
 | 
					            return new ListTaskVM
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                Id = taskAllocation.Id,
 | 
					                Id = taskAllocation.Id,
 | 
				
			||||||
 | 
					                ParentTaskId = taskAllocation.ParentTaskId,
 | 
				
			||||||
                AssignmentDate = taskAllocation.AssignmentDate,
 | 
					                AssignmentDate = taskAllocation.AssignmentDate,
 | 
				
			||||||
 | 
					                ApprovedDate = taskAllocation.ApprovedDate,
 | 
				
			||||||
 | 
					                Description = taskAllocation.Description,
 | 
				
			||||||
                PlannedTask = taskAllocation.PlannedTask,
 | 
					                PlannedTask = taskAllocation.PlannedTask,
 | 
				
			||||||
                ReportedDate = taskAllocation.ReportedDate,
 | 
					                ReportedDate = taskAllocation.ReportedDate,
 | 
				
			||||||
 | 
					                WorkStatus = taskAllocation.WorkStatus,
 | 
				
			||||||
                CompletedTask = taskAllocation.CompletedTask,
 | 
					                CompletedTask = taskAllocation.CompletedTask,
 | 
				
			||||||
                AssignedBy = taskAllocation.Employee != null ? taskAllocation.Employee.ToBasicEmployeeVMFromEmployee() : new BasicEmployeeVM(),
 | 
					                NotApprovedTask = taskAllocation.ApprovedById == null ? taskAllocation.CompletedTask : (taskAllocation.CompletedTask - taskAllocation.ReportedTask),
 | 
				
			||||||
 | 
					                AssignedBy = taskAllocation.Employee?.ToBasicEmployeeVMFromEmployee(),
 | 
				
			||||||
 | 
					                ReportedBy = taskAllocation.ReportedBy?.ToBasicEmployeeVMFromEmployee(),
 | 
				
			||||||
 | 
					                ApprovedBy = taskAllocation.ApprovedBy?.ToBasicEmployeeVMFromEmployee(),
 | 
				
			||||||
                WorkItemId = taskAllocation.WorkItemId,
 | 
					                WorkItemId = taskAllocation.WorkItemId,
 | 
				
			||||||
                WorkItem = taskAllocation.WorkItem
 | 
					                WorkItem = taskAllocation.WorkItem
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
 | 
				
			|||||||
@ -59,7 +59,9 @@ namespace Marco.Pms.Model.Mapper
 | 
				
			|||||||
                WorkCategoryId = model.WorkCategoryId,
 | 
					                WorkCategoryId = model.WorkCategoryId,
 | 
				
			||||||
                TaskDate = DateTime.Now,
 | 
					                TaskDate = DateTime.Now,
 | 
				
			||||||
                TenantId = tenantId,
 | 
					                TenantId = tenantId,
 | 
				
			||||||
                WorkAreaId = model.WorkAreaID
 | 
					                WorkAreaId = model.WorkAreaID,
 | 
				
			||||||
 | 
					                ParentTaskId = model.ParentTaskId,
 | 
				
			||||||
 | 
					                Description = model.Comment
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -20,11 +20,17 @@ namespace Marco.Pms.Model.Projects
 | 
				
			|||||||
        [ValidateNever]
 | 
					        [ValidateNever]
 | 
				
			||||||
        public ActivityMaster? ActivityMaster { get; set; }
 | 
					        public ActivityMaster? ActivityMaster { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [ForeignKey("WorkCategoryId")]
 | 
					        [ForeignKey("WorkCategoryId")]
 | 
				
			||||||
        [ValidateNever]
 | 
					        [ValidateNever]
 | 
				
			||||||
        public WorkCategoryMaster? WorkCategoryMaster { get; set; }
 | 
					        public WorkCategoryMaster? WorkCategoryMaster { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public Guid? ParentTaskId { get; set; } 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public double PlannedWork { get; set; }
 | 
					        public double PlannedWork { get; set; }
 | 
				
			||||||
        public double CompletedWork { get; set; }
 | 
					        public double CompletedWork { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public string? Description { get; set; } 
 | 
				
			||||||
        public DateTime TaskDate { get; set; }
 | 
					        public DateTime TaskDate { get; set; }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -8,5 +8,6 @@
 | 
				
			|||||||
        public string? Comment { get; set; }
 | 
					        public string? Comment { get; set; }
 | 
				
			||||||
        public Guid CommentedBy { get; set; }
 | 
					        public Guid CommentedBy { get; set; }
 | 
				
			||||||
        public BasicEmployeeVM? Employee { get; set; }
 | 
					        public BasicEmployeeVM? Employee { get; set; }
 | 
				
			||||||
 | 
					        public List<string>? PreSignedUrls { get; set; }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,16 +1,25 @@
 | 
				
			|||||||
using Marco.Pms.Model.Projects;
 | 
					using Marco.Pms.Model.Master;
 | 
				
			||||||
 | 
					using Marco.Pms.Model.Projects;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Marco.Pms.Model.ViewModels.Activities
 | 
					namespace Marco.Pms.Model.ViewModels.Activities
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public class ListTaskVM
 | 
					    public class ListTaskVM
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        public Guid Id { get; set; }
 | 
					        public Guid Id { get; set; }
 | 
				
			||||||
 | 
					        public Guid? ParentTaskId { get; set; }
 | 
				
			||||||
        public DateTime AssignmentDate { get; set; }
 | 
					        public DateTime AssignmentDate { get; set; }
 | 
				
			||||||
        public DateTime? ReportedDate { get; set; }
 | 
					        public DateTime? ReportedDate { get; set; }
 | 
				
			||||||
 | 
					        public DateTime? ApprovedDate { get; set; }
 | 
				
			||||||
        public double PlannedTask { get; set; }
 | 
					        public double PlannedTask { get; set; }
 | 
				
			||||||
        public double CompletedTask { get; set; }
 | 
					        public double CompletedTask { get; set; }
 | 
				
			||||||
 | 
					        public double NotApprovedTask { get; set; }
 | 
				
			||||||
        public BasicEmployeeVM? AssignedBy { get; set; }
 | 
					        public BasicEmployeeVM? AssignedBy { get; set; }
 | 
				
			||||||
 | 
					        public BasicEmployeeVM? ReportedBy { get; set; }
 | 
				
			||||||
 | 
					        public BasicEmployeeVM? ApprovedBy { get; set; }
 | 
				
			||||||
 | 
					        public WorkStatusMaster? WorkStatus { get; set; }
 | 
				
			||||||
 | 
					        public string? Description { get; set; }
 | 
				
			||||||
        public Guid WorkItemId { get; set; }
 | 
					        public Guid WorkItemId { get; set; }
 | 
				
			||||||
 | 
					        public List<string>? ReportedPreSignedUrls { get; set; }
 | 
				
			||||||
        public WorkItem? WorkItem { get; set; }
 | 
					        public WorkItem? WorkItem { get; set; }
 | 
				
			||||||
        public List<BasicEmployeeVM>? teamMembers { get; set; }
 | 
					        public List<BasicEmployeeVM>? teamMembers { get; set; }
 | 
				
			||||||
        public List<CommentVM>? comments { get; set; }
 | 
					        public List<CommentVM>? comments { get; set; }
 | 
				
			||||||
 | 
				
			|||||||
@ -10,7 +10,6 @@
 | 
				
			|||||||
        public string? Description { get; set; }
 | 
					        public string? Description { get; set; }
 | 
				
			||||||
        public Guid AssignedBy { get; set; }
 | 
					        public Guid AssignedBy { get; set; }
 | 
				
			||||||
        public Guid WorkItemId { get; set; }
 | 
					        public Guid WorkItemId { get; set; }
 | 
				
			||||||
 | 
					 | 
				
			||||||
        public List<CommentVM>? Comments { get; set; }
 | 
					        public List<CommentVM>? Comments { get; set; }
 | 
				
			||||||
        public List<CheckListVM>? checkList { get; set; }
 | 
					        public List<CheckListVM>? checkList { get; set; }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
using Marco.Pms.Model.Projects;
 | 
					using Marco.Pms.Model.Master;
 | 
				
			||||||
using Marco.Pms.Model.ViewModels.Employee;
 | 
					using Marco.Pms.Model.Projects;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Marco.Pms.Model.ViewModels.Activities
 | 
					namespace Marco.Pms.Model.ViewModels.Activities
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@ -7,13 +7,19 @@ namespace Marco.Pms.Model.ViewModels.Activities
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        public Guid Id { get; set; }
 | 
					        public Guid Id { get; set; }
 | 
				
			||||||
        public DateTime AssignmentDate { get; set; }
 | 
					        public DateTime AssignmentDate { get; set; }
 | 
				
			||||||
 | 
					        public DateTime? ReportedDate { get; set; }
 | 
				
			||||||
 | 
					        public DateTime? ApprovedDate { get; set; }
 | 
				
			||||||
        public double PlannedTask { get; set; }
 | 
					        public double PlannedTask { get; set; }
 | 
				
			||||||
        public double CompletedTask { get; set; }
 | 
					        public double CompletedTask { get; set; }
 | 
				
			||||||
        public DateTime? ReportedDate { get; set; }
 | 
					        public double NotApprovedTask { get; set; }
 | 
				
			||||||
        public string? Description { get; set; }
 | 
					        public string? Description { get; set; }
 | 
				
			||||||
        public string? AssignBy { get; set; }
 | 
					        public BasicEmployeeVM? AssignedBy { get; set; }
 | 
				
			||||||
 | 
					        public BasicEmployeeVM? ReportedBy { get; set; }
 | 
				
			||||||
 | 
					        public BasicEmployeeVM? ApprovedBy { get; set; }
 | 
				
			||||||
 | 
					        public WorkStatusMaster? WorkStatus { get; set; }
 | 
				
			||||||
        public WorkItem? WorkItem { get; set; }
 | 
					        public WorkItem? WorkItem { get; set; }
 | 
				
			||||||
 | 
					        public List<string>? PreSignedUrls { get; set; }
 | 
				
			||||||
        public List<CommentVM>? Comments { get; set; }
 | 
					        public List<CommentVM>? Comments { get; set; }
 | 
				
			||||||
        public List<EmployeeVM>? TeamMembers { get; set; }
 | 
					        public List<BasicEmployeeVM>? TeamMembers { get; set; }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					namespace Marco.Pms.Model.ViewModels.DashBoard
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public class AttendanceOverviewVM
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        public string? Role { get; set; }
 | 
				
			||||||
 | 
					        public string? Date { get; set; }
 | 
				
			||||||
 | 
					        public int Present { get; set; }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -7,11 +7,13 @@ using Marco.Pms.Model.Mapper;
 | 
				
			|||||||
using Marco.Pms.Model.Projects;
 | 
					using Marco.Pms.Model.Projects;
 | 
				
			||||||
using Marco.Pms.Model.Utilities;
 | 
					using Marco.Pms.Model.Utilities;
 | 
				
			||||||
using Marco.Pms.Model.ViewModels.AttendanceVM;
 | 
					using Marco.Pms.Model.ViewModels.AttendanceVM;
 | 
				
			||||||
 | 
					using Marco.Pms.Services.Hubs;
 | 
				
			||||||
using Marco.Pms.Services.Service;
 | 
					using Marco.Pms.Services.Service;
 | 
				
			||||||
using MarcoBMS.Services.Helpers;
 | 
					using MarcoBMS.Services.Helpers;
 | 
				
			||||||
using MarcoBMS.Services.Service;
 | 
					using MarcoBMS.Services.Service;
 | 
				
			||||||
using Microsoft.AspNetCore.Authorization;
 | 
					using Microsoft.AspNetCore.Authorization;
 | 
				
			||||||
using Microsoft.AspNetCore.Mvc;
 | 
					using Microsoft.AspNetCore.Mvc;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.SignalR;
 | 
				
			||||||
using Microsoft.CodeAnalysis;
 | 
					using Microsoft.CodeAnalysis;
 | 
				
			||||||
using Microsoft.EntityFrameworkCore;
 | 
					using Microsoft.EntityFrameworkCore;
 | 
				
			||||||
using Document = Marco.Pms.Model.DocumentManager.Document;
 | 
					using Document = Marco.Pms.Model.DocumentManager.Document;
 | 
				
			||||||
@ -30,10 +32,11 @@ namespace MarcoBMS.Services.Controllers
 | 
				
			|||||||
        private readonly S3UploadService _s3Service;
 | 
					        private readonly S3UploadService _s3Service;
 | 
				
			||||||
        private readonly PermissionServices _permission;
 | 
					        private readonly PermissionServices _permission;
 | 
				
			||||||
        private readonly ILoggingService _logger;
 | 
					        private readonly ILoggingService _logger;
 | 
				
			||||||
 | 
					        private readonly IHubContext<MarcoHub> _signalR;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public AttendanceController(
 | 
					        public AttendanceController(
 | 
				
			||||||
             ApplicationDbContext context, EmployeeHelper employeeHelper, ProjectsHelper projectsHelper, UserHelper userHelper, S3UploadService s3Service, ILoggingService logger, PermissionServices permission)
 | 
					             ApplicationDbContext context, EmployeeHelper employeeHelper, ProjectsHelper projectsHelper, UserHelper userHelper, S3UploadService s3Service, ILoggingService logger, PermissionServices permission, IHubContext<MarcoHub> signalR)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            _context = context;
 | 
					            _context = context;
 | 
				
			||||||
            _employeeHelper = employeeHelper;
 | 
					            _employeeHelper = employeeHelper;
 | 
				
			||||||
@ -42,6 +45,7 @@ namespace MarcoBMS.Services.Controllers
 | 
				
			|||||||
            _s3Service = s3Service;
 | 
					            _s3Service = s3Service;
 | 
				
			||||||
            _logger = logger;
 | 
					            _logger = logger;
 | 
				
			||||||
            _permission = permission;
 | 
					            _permission = permission;
 | 
				
			||||||
 | 
					            _signalR = signalR;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private Guid GetTenantId()
 | 
					        private Guid GetTenantId()
 | 
				
			||||||
@ -558,6 +562,13 @@ namespace MarcoBMS.Services.Controllers
 | 
				
			|||||||
                        Activity = attendance.Activity,
 | 
					                        Activity = attendance.Activity,
 | 
				
			||||||
                        JobRoleName = employee.JobRole.Name
 | 
					                        JobRoleName = employee.JobRole.Name
 | 
				
			||||||
                    };
 | 
					                    };
 | 
				
			||||||
 | 
					                    var sendActivity = 0;
 | 
				
			||||||
 | 
					                    if (recordAttendanceDot.Id == Guid.Empty)
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        sendActivity = 1;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    var notification = new { LoggedInUserId = currentEmployee.Id, Keyword = "Attendance", Activity = sendActivity, ProjectId = attendance.ProjectID, Response = vm };
 | 
				
			||||||
 | 
					                    await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification);
 | 
				
			||||||
                    _logger.LogInfo("Attendance for employee {FirstName} {LastName} has been marked", employee.FirstName ?? string.Empty, employee.LastName ?? string.Empty);
 | 
					                    _logger.LogInfo("Attendance for employee {FirstName} {LastName} has been marked", employee.FirstName ?? string.Empty, employee.LastName ?? string.Empty);
 | 
				
			||||||
                    return Ok(ApiResponse<object>.SuccessResponse(vm, "Attendance marked successfully.", 200));
 | 
					                    return Ok(ApiResponse<object>.SuccessResponse(vm, "Attendance marked successfully.", 200));
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@ -586,248 +597,185 @@ namespace MarcoBMS.Services.Controllers
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
            if (!ModelState.IsValid)
 | 
					            if (!ModelState.IsValid)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                var errors = ModelState.Values
 | 
					                var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage).ToList();
 | 
				
			||||||
                    .SelectMany(v => v.Errors)
 | 
					                _logger.LogError("Invalid attendance model received.");
 | 
				
			||||||
                    .Select(e => e.ErrorMessage)
 | 
					 | 
				
			||||||
                    .ToList();
 | 
					 | 
				
			||||||
                _logger.LogError("User sent Invalid Date while marking attendance");
 | 
					 | 
				
			||||||
                return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", errors, 400));
 | 
					                return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", errors, 400));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            Guid TenantId = GetTenantId();
 | 
					            Guid tenantId = GetTenantId();
 | 
				
			||||||
 | 
					            var currentEmployee = await _userHelper.GetCurrentEmployeeAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            using var transaction = await _context.Database.BeginTransactionAsync();
 | 
					            using var transaction = await _context.Database.BeginTransactionAsync();
 | 
				
			||||||
            try
 | 
					            try
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                Attendance? attendance = await _context.Attendes.FirstOrDefaultAsync(a => a.Id == recordAttendanceDot.Id && a.TenantId == TenantId); ;
 | 
					                // Validate mark time
 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (recordAttendanceDot.MarkTime == null)
 | 
					                if (recordAttendanceDot.MarkTime == null)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    _logger.LogError("User sent Invalid Mark Time while marking attendance");
 | 
					                    _logger.LogWarning("Null mark time provided.");
 | 
				
			||||||
                    return BadRequest(ApiResponse<object>.ErrorResponse("Invalid Mark Time", "Invalid Mark Time", 400));
 | 
					                    return BadRequest(ApiResponse<object>.ErrorResponse("Invalid Mark Time", "Mark time is required", 400));
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                DateTime finalDateTime = GetDateFromTimeStamp(recordAttendanceDot.Date, recordAttendanceDot.MarkTime);
 | 
					                if (string.IsNullOrWhiteSpace(recordAttendanceDot.Comment))
 | 
				
			||||||
                if (recordAttendanceDot.Comment == null)
 | 
					 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    _logger.LogError("User sent Invalid comment while marking attendance");
 | 
					                    _logger.LogWarning("Empty comment provided.");
 | 
				
			||||||
                    return BadRequest(ApiResponse<object>.ErrorResponse("Invalid Comment", "Invalid Comment", 400));
 | 
					                    return BadRequest(ApiResponse<object>.ErrorResponse("Invalid Comment", "Comment is required", 400));
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (attendance != null)
 | 
					                var finalDateTime = GetDateFromTimeStamp(recordAttendanceDot.Date, recordAttendanceDot.MarkTime);
 | 
				
			||||||
 | 
					                var attendance = await _context.Attendes
 | 
				
			||||||
 | 
					                    .FirstOrDefaultAsync(a => a.Id == recordAttendanceDot.Id && a.TenantId == tenantId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Create or update attendance
 | 
				
			||||||
 | 
					                if (attendance == null)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    attendance.Comment = recordAttendanceDot.Comment;
 | 
					                    attendance = new Attendance
 | 
				
			||||||
                    if (recordAttendanceDot.Action == ATTENDANCE_MARK_TYPE.CHECK_IN)
 | 
					 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
                        attendance.InTime = finalDateTime;
 | 
					                        TenantId = tenantId,
 | 
				
			||||||
                        attendance.OutTime = null;
 | 
					                        AttendanceDate = recordAttendanceDot.Date,
 | 
				
			||||||
                        attendance.Activity = ATTENDANCE_MARK_TYPE.CHECK_OUT;
 | 
					                        Comment = recordAttendanceDot.Comment,
 | 
				
			||||||
                    }
 | 
					                        EmployeeID = recordAttendanceDot.EmployeeID,
 | 
				
			||||||
                    else if (recordAttendanceDot.Action == ATTENDANCE_MARK_TYPE.CHECK_OUT)
 | 
					                        ProjectID = recordAttendanceDot.ProjectID,
 | 
				
			||||||
                    {
 | 
					                        Date = DateTime.UtcNow,
 | 
				
			||||||
                        attendance.IsApproved = true;
 | 
					                        InTime = finalDateTime,
 | 
				
			||||||
                        attendance.Activity = ATTENDANCE_MARK_TYPE.REGULARIZE;
 | 
					                        Activity = ATTENDANCE_MARK_TYPE.CHECK_OUT
 | 
				
			||||||
 | 
					                    };
 | 
				
			||||||
 | 
					                    _context.Attendes.Add(attendance);
 | 
				
			||||||
                        //string timeString = "10:30 PM"; // Format: "hh:mm tt"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        attendance.OutTime = finalDateTime;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    else if (recordAttendanceDot.Action == ATTENDANCE_MARK_TYPE.REQUEST_REGULARIZE)
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        DateTime date = attendance.AttendanceDate;
 | 
					 | 
				
			||||||
                        finalDateTime = GetDateFromTimeStamp(date.Date, recordAttendanceDot.MarkTime);
 | 
					 | 
				
			||||||
                        if (attendance.InTime < finalDateTime)
 | 
					 | 
				
			||||||
                        {
 | 
					 | 
				
			||||||
                            attendance.OutTime = finalDateTime;
 | 
					 | 
				
			||||||
                            attendance.Activity = ATTENDANCE_MARK_TYPE.REQUEST_REGULARIZE;
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                        else
 | 
					 | 
				
			||||||
                        {
 | 
					 | 
				
			||||||
                            _logger.LogError("Employee {EmployeeId} sent regularization request but it check-out time is earlier than check-out");
 | 
					 | 
				
			||||||
                            return BadRequest(ApiResponse<object>.ErrorResponse("Check-out time must be later than check-in time", "Check-out time must be later than check-in time", 400));
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                        // do nothing
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    else if (recordAttendanceDot.Action == ATTENDANCE_MARK_TYPE.REGULARIZE)
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        attendance.IsApproved = true;
 | 
					 | 
				
			||||||
                        attendance.Activity = ATTENDANCE_MARK_TYPE.REGULARIZE;
 | 
					 | 
				
			||||||
                        // do nothing
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    else if (recordAttendanceDot.Action == ATTENDANCE_MARK_TYPE.REGULARIZE_REJECT)
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        attendance.IsApproved = false;
 | 
					 | 
				
			||||||
                        attendance.Activity = ATTENDANCE_MARK_TYPE.REGULARIZE_REJECT;
 | 
					 | 
				
			||||||
                        // do nothing
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    attendance.Date = DateTime.UtcNow;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    // update code
 | 
					 | 
				
			||||||
                    _context.Attendes.Update(attendance);
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                else
 | 
					                else
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    attendance = new Attendance();
 | 
					 | 
				
			||||||
                    attendance.TenantId = TenantId;
 | 
					 | 
				
			||||||
                    attendance.AttendanceDate = recordAttendanceDot.Date;
 | 
					 | 
				
			||||||
                    // attendance.Activity = recordAttendanceDot.Action;
 | 
					 | 
				
			||||||
                    attendance.Comment = recordAttendanceDot.Comment;
 | 
					                    attendance.Comment = recordAttendanceDot.Comment;
 | 
				
			||||||
                    attendance.EmployeeID = recordAttendanceDot.EmployeeID;
 | 
					 | 
				
			||||||
                    attendance.ProjectID = recordAttendanceDot.ProjectID;
 | 
					 | 
				
			||||||
                    attendance.Date = DateTime.UtcNow;
 | 
					                    attendance.Date = DateTime.UtcNow;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    switch (recordAttendanceDot.Action)
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        case ATTENDANCE_MARK_TYPE.CHECK_IN:
 | 
				
			||||||
 | 
					                            attendance.InTime = finalDateTime;
 | 
				
			||||||
 | 
					                            attendance.OutTime = null;
 | 
				
			||||||
 | 
					                            attendance.Activity = ATTENDANCE_MARK_TYPE.CHECK_OUT;
 | 
				
			||||||
 | 
					                            break;
 | 
				
			||||||
 | 
					                        case ATTENDANCE_MARK_TYPE.CHECK_OUT:
 | 
				
			||||||
 | 
					                            attendance.OutTime = finalDateTime;
 | 
				
			||||||
 | 
					                            attendance.Activity = ATTENDANCE_MARK_TYPE.REGULARIZE;
 | 
				
			||||||
 | 
					                            attendance.IsApproved = true;
 | 
				
			||||||
 | 
					                            break;
 | 
				
			||||||
 | 
					                        case ATTENDANCE_MARK_TYPE.REQUEST_REGULARIZE:
 | 
				
			||||||
 | 
					                            DateTime date = attendance.InTime ?? recordAttendanceDot.Date;
 | 
				
			||||||
 | 
					                            finalDateTime = GetDateFromTimeStamp(date.Date, recordAttendanceDot.MarkTime);
 | 
				
			||||||
 | 
					                            if (attendance.InTime < finalDateTime)
 | 
				
			||||||
 | 
					                            {
 | 
				
			||||||
 | 
					                                attendance.OutTime = finalDateTime;
 | 
				
			||||||
 | 
					                                attendance.Activity = ATTENDANCE_MARK_TYPE.REQUEST_REGULARIZE;
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                            else
 | 
				
			||||||
 | 
					                            {
 | 
				
			||||||
 | 
					                                _logger.LogWarning("Regularization check-out time is before check-in.");
 | 
				
			||||||
 | 
					                                return BadRequest(ApiResponse<object>.ErrorResponse("Check-out time must be later than check-in time", "Invalid regularization", 400));
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                            break;
 | 
				
			||||||
 | 
					                        case ATTENDANCE_MARK_TYPE.REGULARIZE:
 | 
				
			||||||
 | 
					                            attendance.IsApproved = true;
 | 
				
			||||||
 | 
					                            attendance.Activity = ATTENDANCE_MARK_TYPE.REGULARIZE;
 | 
				
			||||||
 | 
					                            break;
 | 
				
			||||||
 | 
					                        case ATTENDANCE_MARK_TYPE.REGULARIZE_REJECT:
 | 
				
			||||||
 | 
					                            attendance.IsApproved = false;
 | 
				
			||||||
 | 
					                            attendance.Activity = ATTENDANCE_MARK_TYPE.REGULARIZE_REJECT;
 | 
				
			||||||
 | 
					                            break;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    _context.Attendes.Update(attendance);
 | 
				
			||||||
                    attendance.InTime = finalDateTime;
 | 
					 | 
				
			||||||
                    attendance.OutTime = null;
 | 
					 | 
				
			||||||
                    attendance.Activity = ATTENDANCE_MARK_TYPE.CHECK_OUT;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    _context.Attendes.Add(attendance);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Upload image if present
 | 
				
			||||||
                Document? document = null;
 | 
					                Document? document = null;
 | 
				
			||||||
                var Image = recordAttendanceDot.Image;
 | 
					                string? preSignedUrl = null;
 | 
				
			||||||
                var objectKey = string.Empty;
 | 
					 | 
				
			||||||
                var preSignedUrl = string.Empty;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (Image != null && Image.ContentType != null)
 | 
					                if (recordAttendanceDot.Image != null && recordAttendanceDot.Image.ContentType != null)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
 | 
					                    string base64 = recordAttendanceDot.Image.Base64Data?.Split(',').LastOrDefault() ?? "";
 | 
				
			||||||
 | 
					                    if (string.IsNullOrWhiteSpace(base64))
 | 
				
			||||||
 | 
					                        return BadRequest(ApiResponse<object>.ErrorResponse("Base64 data is missing", "Image data missing", 400));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    if (string.IsNullOrEmpty(Image.Base64Data))
 | 
					                    var fileType = _s3Service.GetContentTypeFromBase64(base64);
 | 
				
			||||||
                        return BadRequest(ApiResponse<object>.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400));
 | 
					                    var fileName = _s3Service.GenerateFileName(fileType, tenantId, "attendance");
 | 
				
			||||||
 | 
					                    var objectKey = $"tenant-{tenantId}/Employee/{recordAttendanceDot.EmployeeID}/Attendance/{fileName}";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    objectKey = await _s3Service.UploadFileAsync(Image.Base64Data, TenantId, "attendance");
 | 
					                    await _s3Service.UploadFileAsync(base64, fileType, objectKey);
 | 
				
			||||||
                    preSignedUrl = _s3Service.GeneratePreSignedUrlAsync(objectKey);
 | 
					                    preSignedUrl = _s3Service.GeneratePreSignedUrlAsync(objectKey);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    document = new Document
 | 
					                    document = new Document
 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
                        FileName = Image.FileName ?? "",
 | 
					                        FileName = recordAttendanceDot.Image.FileName ?? "",
 | 
				
			||||||
                        ContentType = Image.ContentType,
 | 
					                        ContentType = recordAttendanceDot.Image.ContentType,
 | 
				
			||||||
                        S3Key = objectKey,
 | 
					                        S3Key = objectKey,
 | 
				
			||||||
                        Base64Data = Image.Base64Data,
 | 
					                        Base64Data = recordAttendanceDot.Image.Base64Data,
 | 
				
			||||||
                        FileSize = Image.FileSize,
 | 
					                        FileSize = recordAttendanceDot.Image.FileSize,
 | 
				
			||||||
                        UploadedAt = recordAttendanceDot.Date,
 | 
					                        UploadedAt = recordAttendanceDot.Date,
 | 
				
			||||||
                        TenantId = TenantId
 | 
					                        TenantId = tenantId
 | 
				
			||||||
                    };
 | 
					                    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    _context.Documents.Add(document);
 | 
					                    _context.Documents.Add(document);
 | 
				
			||||||
 | 
					 | 
				
			||||||
                    await _context.SaveChangesAsync();
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Log attendance
 | 
				
			||||||
 | 
					                var attendanceLog = new AttendanceLog
 | 
				
			||||||
 | 
					 | 
				
			||||||
                // Step 3: Always insert a new log entry
 | 
					 | 
				
			||||||
                if (document != null)
 | 
					 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    var attendanceLog = new AttendanceLog
 | 
					                    AttendanceId = attendance.Id,
 | 
				
			||||||
                    {
 | 
					                    Activity = attendance.Activity,
 | 
				
			||||||
                        AttendanceId = attendance.Id, // Use existing or new AttendanceId
 | 
					                    ActivityTime = finalDateTime,
 | 
				
			||||||
                        Activity = attendance.Activity,
 | 
					                    Comment = recordAttendanceDot.Comment,
 | 
				
			||||||
 | 
					                    EmployeeID = recordAttendanceDot.EmployeeID,
 | 
				
			||||||
 | 
					                    Latitude = recordAttendanceDot.Latitude,
 | 
				
			||||||
 | 
					                    Longitude = recordAttendanceDot.Longitude,
 | 
				
			||||||
 | 
					                    DocumentId = document?.Id,
 | 
				
			||||||
 | 
					                    TenantId = tenantId,
 | 
				
			||||||
 | 
					                    UpdatedBy = recordAttendanceDot.EmployeeID,
 | 
				
			||||||
 | 
					                    UpdatedOn = recordAttendanceDot.Date
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					                _context.AttendanceLogs.Add(attendanceLog);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        ActivityTime = finalDateTime,
 | 
					 | 
				
			||||||
                        Comment = recordAttendanceDot.Comment,
 | 
					 | 
				
			||||||
                        EmployeeID = recordAttendanceDot.EmployeeID,
 | 
					 | 
				
			||||||
                        Latitude = recordAttendanceDot.Latitude,
 | 
					 | 
				
			||||||
                        Longitude = recordAttendanceDot.Longitude,
 | 
					 | 
				
			||||||
                        DocumentId = document.Id,
 | 
					 | 
				
			||||||
                        TenantId = TenantId,
 | 
					 | 
				
			||||||
                        UpdatedBy = recordAttendanceDot.EmployeeID,
 | 
					 | 
				
			||||||
                        UpdatedOn = recordAttendanceDot.Date
 | 
					 | 
				
			||||||
                    };
 | 
					 | 
				
			||||||
                    _context.AttendanceLogs.Add(attendanceLog);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                else
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    var attendanceLog = new AttendanceLog
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        AttendanceId = attendance.Id, // Use existing or new AttendanceId
 | 
					 | 
				
			||||||
                        Activity = attendance.Activity,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        ActivityTime = finalDateTime,
 | 
					 | 
				
			||||||
                        Comment = recordAttendanceDot.Comment,
 | 
					 | 
				
			||||||
                        EmployeeID = recordAttendanceDot.EmployeeID,
 | 
					 | 
				
			||||||
                        Latitude = recordAttendanceDot.Latitude,
 | 
					 | 
				
			||||||
                        Longitude = recordAttendanceDot.Longitude,
 | 
					 | 
				
			||||||
                        DocumentId = document != null ? document.Id : null,
 | 
					 | 
				
			||||||
                        TenantId = TenantId,
 | 
					 | 
				
			||||||
                        UpdatedBy = recordAttendanceDot.EmployeeID,
 | 
					 | 
				
			||||||
                        UpdatedOn = recordAttendanceDot.Date
 | 
					 | 
				
			||||||
                    };
 | 
					 | 
				
			||||||
                    _context.AttendanceLogs.Add(attendanceLog);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                //if (recordAttendanceDot.Image != null && recordAttendanceDot.Image.Count > 0)
 | 
					 | 
				
			||||||
                //{
 | 
					 | 
				
			||||||
                //    attendanceLog.Photo = recordAttendanceDot.Image[0].Base64Data;
 | 
					 | 
				
			||||||
                //}
 | 
					 | 
				
			||||||
                await _context.SaveChangesAsync();
 | 
					                await _context.SaveChangesAsync();
 | 
				
			||||||
 | 
					                await transaction.CommitAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                await transaction.CommitAsync(); // Commit transaction
 | 
					                // Construct view model
 | 
				
			||||||
 | 
					                var employee = await _employeeHelper.GetEmployeeByID(recordAttendanceDot.EmployeeID);
 | 
				
			||||||
                Employee employee = await _employeeHelper.GetEmployeeByID(recordAttendanceDot.EmployeeID);
 | 
					                var vm = new EmployeeAttendanceVM
 | 
				
			||||||
                if (employee.JobRole != null)
 | 
					 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    EmployeeAttendanceVM vm = new EmployeeAttendanceVM();
 | 
					                    Id = attendance.Id,
 | 
				
			||||||
                    if (document != null)
 | 
					                    EmployeeId = employee.Id,
 | 
				
			||||||
                    {
 | 
					                    FirstName = employee.FirstName,
 | 
				
			||||||
                        vm = new EmployeeAttendanceVM()
 | 
					                    LastName = employee.LastName,
 | 
				
			||||||
                        {
 | 
					                    CheckInTime = attendance.InTime,
 | 
				
			||||||
                            CheckInTime = attendance.InTime,
 | 
					                    CheckOutTime = attendance.OutTime,
 | 
				
			||||||
                            CheckOutTime = attendance.OutTime,
 | 
					                    Activity = attendance.Activity,
 | 
				
			||||||
                            EmployeeAvatar = null,
 | 
					                    JobRoleName = employee.JobRole?.Name,
 | 
				
			||||||
                            EmployeeId = recordAttendanceDot.EmployeeID,
 | 
					                    DocumentId = document?.Id ?? Guid.Empty,
 | 
				
			||||||
                            FirstName = employee.FirstName,
 | 
					                    ThumbPreSignedUrl = preSignedUrl ?? "",
 | 
				
			||||||
                            LastName = employee.LastName,
 | 
					                    PreSignedUrl = preSignedUrl ?? ""
 | 
				
			||||||
                            Id = attendance.Id,
 | 
					                };
 | 
				
			||||||
                            Activity = attendance.Activity,
 | 
					 | 
				
			||||||
                            JobRoleName = employee.JobRole.Name,
 | 
					 | 
				
			||||||
                            DocumentId = document.Id,
 | 
					 | 
				
			||||||
                            ThumbPreSignedUrl = preSignedUrl,
 | 
					 | 
				
			||||||
                            PreSignedUrl = preSignedUrl
 | 
					 | 
				
			||||||
                        };
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    else
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        vm = new EmployeeAttendanceVM()
 | 
					 | 
				
			||||||
                        {
 | 
					 | 
				
			||||||
                            CheckInTime = attendance.InTime,
 | 
					 | 
				
			||||||
                            CheckOutTime = attendance.OutTime,
 | 
					 | 
				
			||||||
                            EmployeeAvatar = null,
 | 
					 | 
				
			||||||
                            EmployeeId = recordAttendanceDot.EmployeeID,
 | 
					 | 
				
			||||||
                            FirstName = employee.FirstName,
 | 
					 | 
				
			||||||
                            LastName = employee.LastName,
 | 
					 | 
				
			||||||
                            Id = attendance.Id,
 | 
					 | 
				
			||||||
                            Activity = attendance.Activity,
 | 
					 | 
				
			||||||
                            JobRoleName = employee.JobRole.Name,
 | 
					 | 
				
			||||||
                            DocumentId = Guid.Empty,
 | 
					 | 
				
			||||||
                            ThumbPreSignedUrl = string.Empty,
 | 
					 | 
				
			||||||
                            PreSignedUrl = string.Empty
 | 
					 | 
				
			||||||
                        };
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    _logger.LogInfo("Attendance for employee {FirstName} {LastName} has been marked", employee.FirstName ?? string.Empty, employee.LastName ?? string.Empty);
 | 
					                var notification = new
 | 
				
			||||||
                    return Ok(ApiResponse<object>.SuccessResponse(vm, "Attendance marked successfully.", 200));
 | 
					                {
 | 
				
			||||||
                }
 | 
					                    LoggedInUserId = currentEmployee.Id,
 | 
				
			||||||
                _logger.LogInfo("Attendance for employee {FirstName} {LastName} has been marked", employee.FirstName ?? string.Empty, employee.LastName ?? string.Empty);
 | 
					                    Keyword = "Attendance",
 | 
				
			||||||
                return Ok(ApiResponse<object>.SuccessResponse(new EmployeeAttendanceVM(), "Attendance marked successfully.", 200));
 | 
					                    Activity = recordAttendanceDot.Id == Guid.Empty ? 1 : 0,
 | 
				
			||||||
 | 
					                    ProjectId = attendance.ProjectID,
 | 
				
			||||||
 | 
					                    Response = vm
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                _logger.LogInfo("Attendance recorded for employee: {FullName}", $"{employee.FirstName} {employee.LastName}");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return Ok(ApiResponse<object>.SuccessResponse(vm, "Attendance marked successfully.", 200));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            catch (Exception ex)
 | 
					            catch (Exception ex)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                await transaction.RollbackAsync(); // Rollback on failure
 | 
					                await transaction.RollbackAsync();
 | 
				
			||||||
                _logger.LogError("{Error} while marking attendance", ex.Message);
 | 
					                _logger.LogError("Error while recording attendance : {Error}", ex.Message);
 | 
				
			||||||
                var response = new
 | 
					                return BadRequest(ApiResponse<object>.ErrorResponse("Something went wrong", ex.Message, 500));
 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    message = ex.Message,
 | 
					 | 
				
			||||||
                    detail = ex.StackTrace,
 | 
					 | 
				
			||||||
                    statusCode = StatusCodes.Status500InternalServerError
 | 
					 | 
				
			||||||
                };
 | 
					 | 
				
			||||||
                return BadRequest(ApiResponse<object>.ErrorResponse(ex.Message, response, 400));
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private static DateTime GetDateFromTimeStamp(DateTime date, string timeString)
 | 
					        private static DateTime GetDateFromTimeStamp(DateTime date, string timeString)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            //DateTime date = recordAttendanceDot.Date;
 | 
					            //DateTime date = recordAttendanceDot.Date;
 | 
				
			||||||
 | 
				
			|||||||
@ -5,6 +5,7 @@ using Marco.Pms.Model.Employees;
 | 
				
			|||||||
using Marco.Pms.Model.Projects;
 | 
					using Marco.Pms.Model.Projects;
 | 
				
			||||||
using Marco.Pms.Model.Utilities;
 | 
					using Marco.Pms.Model.Utilities;
 | 
				
			||||||
using Marco.Pms.Model.ViewModels.DashBoard;
 | 
					using Marco.Pms.Model.ViewModels.DashBoard;
 | 
				
			||||||
 | 
					using Marco.Pms.Services.Service;
 | 
				
			||||||
using MarcoBMS.Services.Helpers;
 | 
					using MarcoBMS.Services.Helpers;
 | 
				
			||||||
using MarcoBMS.Services.Service;
 | 
					using MarcoBMS.Services.Service;
 | 
				
			||||||
using Microsoft.AspNetCore.Authorization;
 | 
					using Microsoft.AspNetCore.Authorization;
 | 
				
			||||||
@ -21,11 +22,13 @@ namespace Marco.Pms.Services.Controllers
 | 
				
			|||||||
        private readonly ApplicationDbContext _context;
 | 
					        private readonly ApplicationDbContext _context;
 | 
				
			||||||
        private readonly UserHelper _userHelper;
 | 
					        private readonly UserHelper _userHelper;
 | 
				
			||||||
        private readonly ILoggingService _logger;
 | 
					        private readonly ILoggingService _logger;
 | 
				
			||||||
        public DashboardController(ApplicationDbContext context, UserHelper userHelper, ILoggingService logger)
 | 
					        private readonly PermissionServices _permissionServices;
 | 
				
			||||||
 | 
					        public DashboardController(ApplicationDbContext context, UserHelper userHelper, ILoggingService logger, PermissionServices permissionServices)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            _context = context;
 | 
					            _context = context;
 | 
				
			||||||
            _userHelper = userHelper;
 | 
					            _userHelper = userHelper;
 | 
				
			||||||
            _logger = logger;
 | 
					            _logger = logger;
 | 
				
			||||||
 | 
					            _permissionServices = permissionServices;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        [HttpGet("progression")]
 | 
					        [HttpGet("progression")]
 | 
				
			||||||
        public async Task<IActionResult> GetGraph([FromQuery] double days, [FromQuery] string FromDate, [FromQuery] Guid? projectId)
 | 
					        public async Task<IActionResult> GetGraph([FromQuery] double days, [FromQuery] string FromDate, [FromQuery] Guid? projectId)
 | 
				
			||||||
@ -354,5 +357,102 @@ namespace Marco.Pms.Services.Controllers
 | 
				
			|||||||
            _logger.LogInfo($"Record of performed activities for project {projectId} for date {currentDate.Date} by employee {LoggedInEmployee.Id}");
 | 
					            _logger.LogInfo($"Record of performed activities for project {projectId} for date {currentDate.Date} by employee {LoggedInEmployee.Id}");
 | 
				
			||||||
            return Ok(ApiResponse<object>.SuccessResponse(report, $"Record of performed activities for project {project.Name} for date {currentDate.Date}", 200));
 | 
					            return Ok(ApiResponse<object>.SuccessResponse(report, $"Record of performed activities for project {project.Name} for date {currentDate.Date}", 200));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [HttpGet("attendance-overview/{projectId}")]
 | 
				
			||||||
 | 
					        public async Task<IActionResult> GetAttendanceOverView(Guid projectId, [FromQuery] string days)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            _logger.LogInfo("GetAttendanceOverView called for ProjectId: {ProjectId}, Days: {Days}", projectId, days);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Step 1: Validate project existence
 | 
				
			||||||
 | 
					            var project = await _context.Projects.AsNoTracking().FirstOrDefaultAsync(p => p.Id == projectId);
 | 
				
			||||||
 | 
					            if (project == null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogWarning("Project not found for ProjectId: {ProjectId}", projectId);
 | 
				
			||||||
 | 
					                return BadRequest(ApiResponse<object>.ErrorResponse("Project not found", "Project not found", 400));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Step 2: Permission check
 | 
				
			||||||
 | 
					            var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
 | 
				
			||||||
 | 
					            bool hasAssigned = await _permissionServices.HasProjectPermission(loggedInEmployee, projectId.ToString());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!hasAssigned)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogWarning("Unauthorized access attempt. EmployeeId: {EmployeeId}, ProjectId: {ProjectId}", loggedInEmployee.Id, projectId);
 | 
				
			||||||
 | 
					                return StatusCode(403, ApiResponse<object>.ErrorResponse(
 | 
				
			||||||
 | 
					                    "You don't have permission to access this feature",
 | 
				
			||||||
 | 
					                    "You don't have permission to access this feature", 403));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Step 3: Validate and parse days input
 | 
				
			||||||
 | 
					            if (!int.TryParse(days, out int dayCount) || dayCount <= 0)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogWarning("Invalid days input received: {Days}", days);
 | 
				
			||||||
 | 
					                return BadRequest(ApiResponse<object>.ErrorResponse("Invalid number of days", "Days must be a positive integer", 400));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Step 4: Define date range
 | 
				
			||||||
 | 
					            DateTime today = DateTime.UtcNow.Date;
 | 
				
			||||||
 | 
					            DateTime startDate = today.AddDays(-dayCount);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Step 5: Load project allocations and related job roles
 | 
				
			||||||
 | 
					            var allocations = await _context.ProjectAllocations
 | 
				
			||||||
 | 
					                .Where(pa => pa.ProjectId == projectId)
 | 
				
			||||||
 | 
					                .ToListAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!allocations.Any())
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogInfo("No employee allocations found for project: {ProjectId}", projectId);
 | 
				
			||||||
 | 
					                return Ok(ApiResponse<object>.SuccessResponse(new List<AttendanceOverviewVM>(), "No allocations found", 200));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var jobRoleIds = allocations.Select(pa => pa.JobRoleId).Distinct().ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var jobRoles = await _context.JobRoles
 | 
				
			||||||
 | 
					                .Where(jr => jobRoleIds.Contains(jr.Id))
 | 
				
			||||||
 | 
					                .ToListAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Step 6: Load attendance records for given date range
 | 
				
			||||||
 | 
					            var attendances = await _context.Attendes
 | 
				
			||||||
 | 
					                .Where(a =>
 | 
				
			||||||
 | 
					                    a.ProjectID == projectId &&
 | 
				
			||||||
 | 
					                    a.InTime.HasValue &&
 | 
				
			||||||
 | 
					                    a.InTime.Value.Date >= startDate &&
 | 
				
			||||||
 | 
					                    a.InTime.Value.Date <= today)
 | 
				
			||||||
 | 
					                .ToListAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var overviewList = new List<AttendanceOverviewVM>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Step 7: Process attendance per date per role
 | 
				
			||||||
 | 
					            for (DateTime date = today; date > startDate; date = date.AddDays(-1))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                foreach (var jobRole in jobRoles)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    var employeeIds = allocations
 | 
				
			||||||
 | 
					                        .Where(pa => pa.JobRoleId == jobRole.Id)
 | 
				
			||||||
 | 
					                        .Select(pa => pa.EmployeeId)
 | 
				
			||||||
 | 
					                        .ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    int presentCount = attendances
 | 
				
			||||||
 | 
					                        .Count(a => employeeIds.Contains(a.EmployeeID) && a.InTime!.Value.Date == date);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    overviewList.Add(new AttendanceOverviewVM
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        Role = jobRole.Name,
 | 
				
			||||||
 | 
					                        Date = date.ToString("yyyy-MM-dd"),
 | 
				
			||||||
 | 
					                        Present = presentCount
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Step 8: Order result for consistent presentation
 | 
				
			||||||
 | 
					            var sortedResult = overviewList
 | 
				
			||||||
 | 
					                .OrderByDescending(r => r.Date)
 | 
				
			||||||
 | 
					                .ThenByDescending(r => r.Present)
 | 
				
			||||||
 | 
					                .ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            _logger.LogInfo("Attendance overview fetched. ProjectId: {ProjectId}, Records: {Count}", projectId, sortedResult.Count);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return Ok(ApiResponse<object>.SuccessResponse(sortedResult, $"{sortedResult.Count} records fetched for attendance overview", 200));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -9,11 +9,13 @@ using Marco.Pms.Model.Mapper;
 | 
				
			|||||||
using Marco.Pms.Model.Projects;
 | 
					using Marco.Pms.Model.Projects;
 | 
				
			||||||
using Marco.Pms.Model.Utilities;
 | 
					using Marco.Pms.Model.Utilities;
 | 
				
			||||||
using Marco.Pms.Model.ViewModels.Employee;
 | 
					using Marco.Pms.Model.ViewModels.Employee;
 | 
				
			||||||
 | 
					using Marco.Pms.Services.Hubs;
 | 
				
			||||||
using MarcoBMS.Services.Helpers;
 | 
					using MarcoBMS.Services.Helpers;
 | 
				
			||||||
using MarcoBMS.Services.Service;
 | 
					using MarcoBMS.Services.Service;
 | 
				
			||||||
using Microsoft.AspNetCore.Authorization;
 | 
					using Microsoft.AspNetCore.Authorization;
 | 
				
			||||||
using Microsoft.AspNetCore.Identity;
 | 
					using Microsoft.AspNetCore.Identity;
 | 
				
			||||||
using Microsoft.AspNetCore.Mvc;
 | 
					using Microsoft.AspNetCore.Mvc;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.SignalR;
 | 
				
			||||||
using Microsoft.EntityFrameworkCore;
 | 
					using Microsoft.EntityFrameworkCore;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace MarcoBMS.Services.Controllers
 | 
					namespace MarcoBMS.Services.Controllers
 | 
				
			||||||
@ -32,9 +34,11 @@ namespace MarcoBMS.Services.Controllers
 | 
				
			|||||||
        private readonly UserHelper _userHelper;
 | 
					        private readonly UserHelper _userHelper;
 | 
				
			||||||
        private readonly IConfiguration _configuration;
 | 
					        private readonly IConfiguration _configuration;
 | 
				
			||||||
        private readonly ILoggingService _logger;
 | 
					        private readonly ILoggingService _logger;
 | 
				
			||||||
 | 
					        private readonly IHubContext<MarcoHub> _signalR;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public EmployeeController(UserManager<ApplicationUser> userManager, IEmailSender emailSender,
 | 
					        public EmployeeController(UserManager<ApplicationUser> userManager, IEmailSender emailSender,
 | 
				
			||||||
            ApplicationDbContext context, EmployeeHelper employeeHelper, UserHelper userHelper, IConfiguration configuration, ILoggingService logger)
 | 
					            ApplicationDbContext context, EmployeeHelper employeeHelper, UserHelper userHelper, IConfiguration configuration, ILoggingService logger,
 | 
				
			||||||
 | 
					            IHubContext<MarcoHub> signalR)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            _context = context;
 | 
					            _context = context;
 | 
				
			||||||
            _userManager = userManager;
 | 
					            _userManager = userManager;
 | 
				
			||||||
@ -43,6 +47,7 @@ namespace MarcoBMS.Services.Controllers
 | 
				
			|||||||
            _userHelper = userHelper;
 | 
					            _userHelper = userHelper;
 | 
				
			||||||
            _configuration = configuration;
 | 
					            _configuration = configuration;
 | 
				
			||||||
            _logger = logger;
 | 
					            _logger = logger;
 | 
				
			||||||
 | 
					            _signalR = signalR;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [HttpGet]
 | 
					        [HttpGet]
 | 
				
			||||||
@ -154,6 +159,8 @@ namespace MarcoBMS.Services.Controllers
 | 
				
			|||||||
        public async Task<IActionResult> CreateUser([FromBody] CreateUserDto model)
 | 
					        public async Task<IActionResult> CreateUser([FromBody] CreateUserDto model)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            Guid tenantId = _userHelper.GetTenantId();
 | 
					            Guid tenantId = _userHelper.GetTenantId();
 | 
				
			||||||
 | 
					            var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
 | 
				
			||||||
 | 
					            Guid employeeId = Guid.Empty;
 | 
				
			||||||
            if (model == null)
 | 
					            if (model == null)
 | 
				
			||||||
                return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", "Invaild Data", 400));
 | 
					                return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", "Invaild Data", 400));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -180,6 +187,7 @@ namespace MarcoBMS.Services.Controllers
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                        _context.Employees.Update(existingEmployee);
 | 
					                        _context.Employees.Update(existingEmployee);
 | 
				
			||||||
                        await _context.SaveChangesAsync();
 | 
					                        await _context.SaveChangesAsync();
 | 
				
			||||||
 | 
					                        employeeId = existingEmployee.Id;
 | 
				
			||||||
                        responsemessage = "User updated successfully.";
 | 
					                        responsemessage = "User updated successfully.";
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    else
 | 
					                    else
 | 
				
			||||||
@ -214,7 +222,7 @@ namespace MarcoBMS.Services.Controllers
 | 
				
			|||||||
                        _context.Employees.Add(newEmployee);
 | 
					                        _context.Employees.Add(newEmployee);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        await _context.SaveChangesAsync();
 | 
					                        await _context.SaveChangesAsync();
 | 
				
			||||||
 | 
					                        employeeId = newEmployee.Id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        /* SEND USER REGISTRATION MAIL*/
 | 
					                        /* SEND USER REGISTRATION MAIL*/
 | 
				
			||||||
                        var token = await _userManager.GeneratePasswordResetTokenAsync(user);
 | 
					                        var token = await _userManager.GeneratePasswordResetTokenAsync(user);
 | 
				
			||||||
@ -233,6 +241,7 @@ namespace MarcoBMS.Services.Controllers
 | 
				
			|||||||
                        _context.Employees.Update(existingEmployee);
 | 
					                        _context.Employees.Update(existingEmployee);
 | 
				
			||||||
                        await _context.SaveChangesAsync();
 | 
					                        await _context.SaveChangesAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        employeeId = existingEmployee.Id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        /* SEND USER REGISTRATION MAIL*/
 | 
					                        /* SEND USER REGISTRATION MAIL*/
 | 
				
			||||||
                        var token = await _userManager.GeneratePasswordResetTokenAsync(user);
 | 
					                        var token = await _userManager.GeneratePasswordResetTokenAsync(user);
 | 
				
			||||||
@ -256,17 +265,22 @@ namespace MarcoBMS.Services.Controllers
 | 
				
			|||||||
                    existingEmployee = GetUpdateEmployeeModel(model, existingEmployee);
 | 
					                    existingEmployee = GetUpdateEmployeeModel(model, existingEmployee);
 | 
				
			||||||
                    _context.Employees.Update(existingEmployee);
 | 
					                    _context.Employees.Update(existingEmployee);
 | 
				
			||||||
                    responsemessage = "User updated successfully.";
 | 
					                    responsemessage = "User updated successfully.";
 | 
				
			||||||
 | 
					                    employeeId = existingEmployee.Id;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                else
 | 
					                else
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    // Create Employee record if missing
 | 
					                    // Create Employee record if missing
 | 
				
			||||||
                    Employee newEmployee = GetNewEmployeeModel(model, tenantId, string.Empty);
 | 
					                    Employee newEmployee = GetNewEmployeeModel(model, tenantId, string.Empty);
 | 
				
			||||||
                    _context.Employees.Add(newEmployee);
 | 
					                    _context.Employees.Add(newEmployee);
 | 
				
			||||||
 | 
					                    employeeId = newEmployee.Id;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                await _context.SaveChangesAsync();
 | 
					                await _context.SaveChangesAsync();
 | 
				
			||||||
                responsemessage = "User created successfully.";
 | 
					                responsemessage = "User created successfully.";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "Employee", EmployeeId = employeeId };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification);
 | 
				
			||||||
            return Ok(ApiResponse<object>.SuccessResponse("Success.", responsemessage, 200));
 | 
					            return Ok(ApiResponse<object>.SuccessResponse("Success.", responsemessage, 200));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -420,6 +434,9 @@ namespace MarcoBMS.Services.Controllers
 | 
				
			|||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    await _context.SaveChangesAsync();
 | 
					                    await _context.SaveChangesAsync();
 | 
				
			||||||
                    _logger.LogInfo("Employee with ID {EmployeId} Deleted successfully", employee.Id);
 | 
					                    _logger.LogInfo("Employee with ID {EmployeId} Deleted successfully", employee.Id);
 | 
				
			||||||
 | 
					                    var notification = new { LoggedInUserId = LoggedEmployee.Id, Keyword = "Employee", EmployeeId = employee.Id };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            else
 | 
					            else
 | 
				
			||||||
 | 
				
			|||||||
@ -68,7 +68,16 @@ namespace Marco.Pms.Services.Controllers
 | 
				
			|||||||
                        return BadRequest(ApiResponse<object>.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400));
 | 
					                        return BadRequest(ApiResponse<object>.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400));
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    var objectKey = await _s3Service.UploadFileAsync(Image.Base64Data, tenantId, "forum");
 | 
					                    //If base64 has a data URI prefix, strip it
 | 
				
			||||||
 | 
					                    var base64 = Image.Base64Data.Contains(",")
 | 
				
			||||||
 | 
					                        ? Image.Base64Data.Substring(Image.Base64Data.IndexOf(",") + 1)
 | 
				
			||||||
 | 
					                        : Image.Base64Data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    string fileType = _s3Service.GetContentTypeFromBase64(base64);
 | 
				
			||||||
 | 
					                    string fileName = _s3Service.GenerateFileName(fileType, tenantId, "forum");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    string objectKey = $"tenant-{tenantId}/project-{createTicketDto.LinkedProjectId}/froum/{fileName}";
 | 
				
			||||||
 | 
					                    await _s3Service.UploadFileAsync(base64, fileType, objectKey);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    Document document = attachmentDto.ToDocumentFromForumAttachmentDto(objectKey, objectKey, createTicketDto.CreatedAt, tenantId);
 | 
					                    Document document = attachmentDto.ToDocumentFromForumAttachmentDto(objectKey, objectKey, createTicketDto.CreatedAt, tenantId);
 | 
				
			||||||
                    _context.Documents.Add(document);
 | 
					                    _context.Documents.Add(document);
 | 
				
			||||||
@ -182,7 +191,16 @@ namespace Marco.Pms.Services.Controllers
 | 
				
			|||||||
                                return BadRequest(ApiResponse<object>.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400));
 | 
					                                return BadRequest(ApiResponse<object>.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400));
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            var objectKey = await _s3Service.UploadFileAsync(Image.Base64Data, tenantId, "forum");
 | 
					                            //If base64 has a data URI prefix, strip it
 | 
				
			||||||
 | 
					                            var base64 = Image.Base64Data.Contains(",")
 | 
				
			||||||
 | 
					                                ? Image.Base64Data.Substring(Image.Base64Data.IndexOf(",") + 1)
 | 
				
			||||||
 | 
					                                : Image.Base64Data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            string fileType = _s3Service.GetContentTypeFromBase64(base64);
 | 
				
			||||||
 | 
					                            string fileName = _s3Service.GenerateFileName(fileType, tenantId, "forum");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            string objectKey = $"tenant-{tenantId}/project-{updateTicketDto.LinkedProjectId}/froum/{fileName}";
 | 
				
			||||||
 | 
					                            await _s3Service.UploadFileAsync(base64, fileType, objectKey);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            Document document = attachmentDto.ToDocumentFromUpdateAttachmentDto(objectKey, objectKey, updateTicketDto.CreatedAt, tenantId);
 | 
					                            Document document = attachmentDto.ToDocumentFromUpdateAttachmentDto(objectKey, objectKey, updateTicketDto.CreatedAt, tenantId);
 | 
				
			||||||
                            _context.Documents.Add(document);
 | 
					                            _context.Documents.Add(document);
 | 
				
			||||||
@ -329,6 +347,14 @@ namespace Marco.Pms.Services.Controllers
 | 
				
			|||||||
            List<TicketAttachment> attachments = new List<TicketAttachment>();
 | 
					            List<TicketAttachment> attachments = new List<TicketAttachment>();
 | 
				
			||||||
            List<Document> documents = new List<Document>();
 | 
					            List<Document> documents = new List<Document>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            TicketForum? ticket = await _context.Tickets.FirstOrDefaultAsync(t => t.Id == addCommentDto.TicketId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (ticket == null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogError("Ticket {TicketId} not Found in database", addCommentDto.TicketId);
 | 
				
			||||||
 | 
					                return NotFound(ApiResponse<object>.ErrorResponse("Ticket not Found", "Ticket not Found", 404));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            TicketComment comment = addCommentDto.ToTicketCommentFromAddCommentDto(tenantId);
 | 
					            TicketComment comment = addCommentDto.ToTicketCommentFromAddCommentDto(tenantId);
 | 
				
			||||||
            _context.TicketComments.Add(comment);
 | 
					            _context.TicketComments.Add(comment);
 | 
				
			||||||
            await _context.SaveChangesAsync();
 | 
					            await _context.SaveChangesAsync();
 | 
				
			||||||
@ -344,7 +370,16 @@ namespace Marco.Pms.Services.Controllers
 | 
				
			|||||||
                        return BadRequest(ApiResponse<object>.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400));
 | 
					                        return BadRequest(ApiResponse<object>.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400));
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    var objectKey = await _s3Service.UploadFileAsync(Image.Base64Data, tenantId, "forum");
 | 
					                    //If base64 has a data URI prefix, strip it
 | 
				
			||||||
 | 
					                    var base64 = Image.Base64Data.Contains(",")
 | 
				
			||||||
 | 
					                        ? Image.Base64Data.Substring(Image.Base64Data.IndexOf(",") + 1)
 | 
				
			||||||
 | 
					                        : Image.Base64Data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    string fileType = _s3Service.GetContentTypeFromBase64(base64);
 | 
				
			||||||
 | 
					                    string fileName = _s3Service.GenerateFileName(fileType, tenantId, "forum");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    string objectKey = $"tenant-{tenantId}/project-{ticket.LinkedProjectId}/froum/{fileName}";
 | 
				
			||||||
 | 
					                    await _s3Service.UploadFileAsync(base64, fileType, objectKey);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    Document document = attachmentDto.ToDocumentFromForumAttachmentDto(objectKey, objectKey, addCommentDto.SentAt, tenantId);
 | 
					                    Document document = attachmentDto.ToDocumentFromForumAttachmentDto(objectKey, objectKey, addCommentDto.SentAt, tenantId);
 | 
				
			||||||
                    _context.Documents.Add(document);
 | 
					                    _context.Documents.Add(document);
 | 
				
			||||||
@ -396,6 +431,14 @@ namespace Marco.Pms.Services.Controllers
 | 
				
			|||||||
            Guid tenantId = _userHelper.GetTenantId();
 | 
					            Guid tenantId = _userHelper.GetTenantId();
 | 
				
			||||||
            List<TicketAttachment> attachments = new List<TicketAttachment>();
 | 
					            List<TicketAttachment> attachments = new List<TicketAttachment>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            TicketForum? ticket = await _context.Tickets.FirstOrDefaultAsync(t => t.Id == updateCommentDto.TicketId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (ticket == null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogError("Ticket {TicketId} not Found in database", updateCommentDto.TicketId);
 | 
				
			||||||
 | 
					                return NotFound(ApiResponse<object>.ErrorResponse("Ticket not Found", "Ticket not Found", 404));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            TicketComment existingComment = await _context.TicketComments.AsNoTracking().FirstOrDefaultAsync(c => c.Id == updateCommentDto.Id) ?? new TicketComment();
 | 
					            TicketComment existingComment = await _context.TicketComments.AsNoTracking().FirstOrDefaultAsync(c => c.Id == updateCommentDto.Id) ?? new TicketComment();
 | 
				
			||||||
            TicketComment updateComment = updateCommentDto.ToTicketCommentFromUpdateCommentDto(tenantId, existingComment);
 | 
					            TicketComment updateComment = updateCommentDto.ToTicketCommentFromUpdateCommentDto(tenantId, existingComment);
 | 
				
			||||||
            _context.TicketComments.Update(updateComment);
 | 
					            _context.TicketComments.Update(updateComment);
 | 
				
			||||||
@ -419,7 +462,16 @@ namespace Marco.Pms.Services.Controllers
 | 
				
			|||||||
                            return BadRequest(ApiResponse<object>.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400));
 | 
					                            return BadRequest(ApiResponse<object>.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400));
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        var objectKey = await _s3Service.UploadFileAsync(Image.Base64Data, tenantId, "forum");
 | 
					                        //If base64 has a data URI prefix, strip it
 | 
				
			||||||
 | 
					                        var base64 = Image.Base64Data.Contains(",")
 | 
				
			||||||
 | 
					                            ? Image.Base64Data.Substring(Image.Base64Data.IndexOf(",") + 1)
 | 
				
			||||||
 | 
					                            : Image.Base64Data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        string fileType = _s3Service.GetContentTypeFromBase64(base64);
 | 
				
			||||||
 | 
					                        string fileName = _s3Service.GenerateFileName(fileType, tenantId, "forum");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        string objectKey = $"tenant-{tenantId}/project-{ticket.LinkedProjectId}/froum/{fileName}";
 | 
				
			||||||
 | 
					                        await _s3Service.UploadFileAsync(base64, fileType, objectKey);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        Document document = attachmentDto.ToDocumentFromUpdateAttachmentDto(objectKey, objectKey, existingComment.SentAt, tenantId);
 | 
					                        Document document = attachmentDto.ToDocumentFromUpdateAttachmentDto(objectKey, objectKey, existingComment.SentAt, tenantId);
 | 
				
			||||||
                        _context.Documents.Add(document);
 | 
					                        _context.Documents.Add(document);
 | 
				
			||||||
@ -491,6 +543,16 @@ namespace Marco.Pms.Services.Controllers
 | 
				
			|||||||
            Guid tenantId = _userHelper.GetTenantId();
 | 
					            Guid tenantId = _userHelper.GetTenantId();
 | 
				
			||||||
            List<TicketAttachmentVM> ticketAttachmentVMs = new List<TicketAttachmentVM>();
 | 
					            List<TicketAttachmentVM> ticketAttachmentVMs = new List<TicketAttachmentVM>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            List<Guid> ticketIds = forumAttachmentDtos.Select(f => f.TicketId.HasValue ? f.TicketId.Value : Guid.Empty).ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            List<TicketForum> tickets = await _context.Tickets.Where(t => ticketIds.Contains(t.Id)).ToListAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (tickets == null || tickets.Count > 0)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogError("Tickets not Found in database");
 | 
				
			||||||
 | 
					                return NotFound(ApiResponse<object>.ErrorResponse("Ticket not Found", "Ticket not Found", 404));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            TicketAttachment attachment = new TicketAttachment();
 | 
					            TicketAttachment attachment = new TicketAttachment();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            foreach (var forumAttachmentDto in forumAttachmentDtos)
 | 
					            foreach (var forumAttachmentDto in forumAttachmentDtos)
 | 
				
			||||||
@ -505,7 +567,17 @@ namespace Marco.Pms.Services.Controllers
 | 
				
			|||||||
                    _logger.LogError("ticket ID is missing");
 | 
					                    _logger.LogError("ticket ID is missing");
 | 
				
			||||||
                    return BadRequest(ApiResponse<object>.ErrorResponse("ticket ID is missing", "ticket ID is missing", 400));
 | 
					                    return BadRequest(ApiResponse<object>.ErrorResponse("ticket ID is missing", "ticket ID is missing", 400));
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                var objectKey = await _s3Service.UploadFileAsync(forumAttachmentDto.Base64Data, tenantId, "forum");
 | 
					                var ticket = tickets.FirstOrDefault(t => t.Id == forumAttachmentDto.TicketId);
 | 
				
			||||||
 | 
					                //If base64 has a data URI prefix, strip it
 | 
				
			||||||
 | 
					                var base64 = forumAttachmentDto.Base64Data.Contains(",")
 | 
				
			||||||
 | 
					                    ? forumAttachmentDto.Base64Data.Substring(forumAttachmentDto.Base64Data.IndexOf(",") + 1)
 | 
				
			||||||
 | 
					                    : forumAttachmentDto.Base64Data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                string fileType = _s3Service.GetContentTypeFromBase64(base64);
 | 
				
			||||||
 | 
					                string fileName = _s3Service.GenerateFileName(fileType, tenantId, "forum");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                string objectKey = $"tenant-{tenantId}/project-{ticket?.LinkedProjectId}/froum/{fileName}";
 | 
				
			||||||
 | 
					                await _s3Service.UploadFileAsync(base64, fileType, objectKey);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                Document document = forumAttachmentDto.ToDocumentFromForumAttachmentDto(objectKey, objectKey, forumAttachmentDto.SentAt, tenantId);
 | 
					                Document document = forumAttachmentDto.ToDocumentFromForumAttachmentDto(objectKey, objectKey, forumAttachmentDto.SentAt, tenantId);
 | 
				
			||||||
                _context.Documents.Add(document);
 | 
					                _context.Documents.Add(document);
 | 
				
			||||||
 | 
				
			|||||||
@ -671,6 +671,45 @@ namespace Marco.Pms.Services.Controllers
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // -------------------------------- Work Status  --------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [HttpGet("work-status")]
 | 
				
			||||||
 | 
					        public async Task<IActionResult> GetWorkStatusMasterList()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var response = await _masterHelper.GetWorkStatusList();
 | 
				
			||||||
 | 
					            return StatusCode(response.StatusCode, response);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [HttpPost("work-status")]
 | 
				
			||||||
 | 
					        public async Task<IActionResult> CreateWorkStatusMaster([FromBody] CreateWorkStatusMasterDto createWorkStatusDto)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (!ModelState.IsValid)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                var errors = ModelState.Values
 | 
				
			||||||
 | 
					                    .SelectMany(v => v.Errors)
 | 
				
			||||||
 | 
					                    .Select(e => e.ErrorMessage)
 | 
				
			||||||
 | 
					                    .ToList();
 | 
				
			||||||
 | 
					                _logger.LogError("User sent Invalid Date while marking attendance");
 | 
				
			||||||
 | 
					                return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", errors, 400));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            var response = await _masterHelper.CreateWorkStatus(createWorkStatusDto);
 | 
				
			||||||
 | 
					            return StatusCode(response.StatusCode, response);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [HttpPost("work-status/edit/{id}")]
 | 
				
			||||||
 | 
					        public async Task<IActionResult> UpdateWorkStatusMaster(Guid id, [FromBody] UpdateWorkStatusMasterDto updateWorkStatusDto)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var response = await _masterHelper.UpdateWorkStatus(id, updateWorkStatusDto);
 | 
				
			||||||
 | 
					            return StatusCode(response.StatusCode, response);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        [HttpDelete("work-status/{id}")]
 | 
				
			||||||
 | 
					        public async Task<IActionResult> DeleteWorkStatusMaster(Guid id)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var response = await _masterHelper.DeleteWorkStatus(id);
 | 
				
			||||||
 | 
					            return StatusCode(response.StatusCode, response);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // -------------------------------- Contact Category  --------------------------------
 | 
					        // -------------------------------- Contact Category  --------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [HttpGet("contact-categories")]
 | 
					        [HttpGet("contact-categories")]
 | 
				
			||||||
@ -749,11 +788,11 @@ namespace Marco.Pms.Services.Controllers
 | 
				
			|||||||
            return Ok(response);
 | 
					            return Ok(response);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [HttpGet("contact-tag/{id}")]
 | 
					        //[HttpGet("contact-tag/{id}")]
 | 
				
			||||||
        public async Task<IActionResult> GetContactTagMaster(Guid id)
 | 
					        //public async Task<IActionResult> GetContactTagMaster(Guid id)
 | 
				
			||||||
        {
 | 
					        //{
 | 
				
			||||||
            return Ok();
 | 
					        //    return Ok();
 | 
				
			||||||
        }
 | 
					        //}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [HttpPost("contact-tag")]
 | 
					        [HttpPost("contact-tag")]
 | 
				
			||||||
        public async Task<IActionResult> CreateContactTagMaster([FromBody] CreateContactTagDto contactTagDto)
 | 
					        public async Task<IActionResult> CreateContactTagMaster([FromBody] CreateContactTagDto contactTagDto)
 | 
				
			||||||
 | 
				
			|||||||
@ -8,12 +8,13 @@ using Marco.Pms.Model.Projects;
 | 
				
			|||||||
using Marco.Pms.Model.Utilities;
 | 
					using Marco.Pms.Model.Utilities;
 | 
				
			||||||
using Marco.Pms.Model.ViewModels.Employee;
 | 
					using Marco.Pms.Model.ViewModels.Employee;
 | 
				
			||||||
using Marco.Pms.Model.ViewModels.Projects;
 | 
					using Marco.Pms.Model.ViewModels.Projects;
 | 
				
			||||||
 | 
					using Marco.Pms.Services.Hubs;
 | 
				
			||||||
using MarcoBMS.Services.Helpers;
 | 
					using MarcoBMS.Services.Helpers;
 | 
				
			||||||
using MarcoBMS.Services.Service;
 | 
					using MarcoBMS.Services.Service;
 | 
				
			||||||
using Microsoft.AspNetCore.Authorization;
 | 
					using Microsoft.AspNetCore.Authorization;
 | 
				
			||||||
using Microsoft.AspNetCore.Mvc;
 | 
					using Microsoft.AspNetCore.Mvc;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.SignalR;
 | 
				
			||||||
using Microsoft.EntityFrameworkCore;
 | 
					using Microsoft.EntityFrameworkCore;
 | 
				
			||||||
using Microsoft.Extensions.Logging;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace MarcoBMS.Services.Controllers
 | 
					namespace MarcoBMS.Services.Controllers
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@ -27,15 +28,18 @@ namespace MarcoBMS.Services.Controllers
 | 
				
			|||||||
        private readonly ILoggingService _logger;
 | 
					        private readonly ILoggingService _logger;
 | 
				
			||||||
        private readonly RolesHelper _rolesHelper;
 | 
					        private readonly RolesHelper _rolesHelper;
 | 
				
			||||||
        private readonly ProjectsHelper _projectsHelper;
 | 
					        private readonly ProjectsHelper _projectsHelper;
 | 
				
			||||||
 | 
					        private readonly IHubContext<MarcoHub> _signalR;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public ProjectController(ApplicationDbContext context, UserHelper userHelper, ILoggingService logger, RolesHelper rolesHelper, ProjectsHelper projectHelper)
 | 
					        public ProjectController(ApplicationDbContext context, UserHelper userHelper, ILoggingService logger, RolesHelper rolesHelper, ProjectsHelper projectHelper, IHubContext<MarcoHub> signalR)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            _context = context;
 | 
					            _context = context;
 | 
				
			||||||
            _userHelper = userHelper;
 | 
					            _userHelper = userHelper;
 | 
				
			||||||
            _logger = logger;
 | 
					            _logger = logger;
 | 
				
			||||||
            _rolesHelper = rolesHelper;
 | 
					            _rolesHelper = rolesHelper;
 | 
				
			||||||
            _projectsHelper = projectHelper;
 | 
					            _projectsHelper = projectHelper;
 | 
				
			||||||
 | 
					            _signalR = signalR;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [HttpGet("list/basic")]
 | 
					        [HttpGet("list/basic")]
 | 
				
			||||||
@ -59,9 +63,9 @@ namespace MarcoBMS.Services.Controllers
 | 
				
			|||||||
                return Unauthorized(ApiResponse<object>.ErrorResponse("Employee not found.", null, 401));
 | 
					                return Unauthorized(ApiResponse<object>.ErrorResponse("Employee not found.", null, 401));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
           
 | 
					
 | 
				
			||||||
            List<Project> projects =  await _projectsHelper.GetMyProjects(tenantId, LoggedInEmployee);
 | 
					            List<Project> projects = await _projectsHelper.GetMyProjects(tenantId, LoggedInEmployee);
 | 
				
			||||||
            
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // 4. Project projection to ProjectInfoVM
 | 
					            // 4. Project projection to ProjectInfoVM
 | 
				
			||||||
            // This part is already quite efficient.
 | 
					            // This part is already quite efficient.
 | 
				
			||||||
@ -84,8 +88,6 @@ namespace MarcoBMS.Services.Controllers
 | 
				
			|||||||
            return Ok(ApiResponse<object>.SuccessResponse(response, "Success.", 200));
 | 
					            return Ok(ApiResponse<object>.SuccessResponse(response, "Success.", 200));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
     
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [HttpGet("list")]
 | 
					        [HttpGet("list")]
 | 
				
			||||||
        public async Task<IActionResult> GetAll()
 | 
					        public async Task<IActionResult> GetAll()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
@ -314,6 +316,7 @@ namespace MarcoBMS.Services.Controllers
 | 
				
			|||||||
        [HttpPost]
 | 
					        [HttpPost]
 | 
				
			||||||
        public async Task<IActionResult> Create([FromBody] CreateProjectDto projectDto)
 | 
					        public async Task<IActionResult> Create([FromBody] CreateProjectDto projectDto)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
 | 
					            var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
 | 
				
			||||||
            if (!ModelState.IsValid)
 | 
					            if (!ModelState.IsValid)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                var errors = ModelState.Values
 | 
					                var errors = ModelState.Values
 | 
				
			||||||
@ -330,6 +333,9 @@ namespace MarcoBMS.Services.Controllers
 | 
				
			|||||||
            _context.Projects.Add(project);
 | 
					            _context.Projects.Add(project);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            await _context.SaveChangesAsync();
 | 
					            await _context.SaveChangesAsync();
 | 
				
			||||||
 | 
					            var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "Create_Project", Response = project.ToProjectDto() };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return Ok(ApiResponse<object>.SuccessResponse(project.ToProjectDto(), "Success.", 200));
 | 
					            return Ok(ApiResponse<object>.SuccessResponse(project.ToProjectDto(), "Success.", 200));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -338,6 +344,7 @@ namespace MarcoBMS.Services.Controllers
 | 
				
			|||||||
        [Route("update/{id}")]
 | 
					        [Route("update/{id}")]
 | 
				
			||||||
        public async Task<IActionResult> Update([FromRoute] Guid id, [FromBody] UpdateProjectDto updateProjectDto)
 | 
					        public async Task<IActionResult> Update([FromRoute] Guid id, [FromBody] UpdateProjectDto updateProjectDto)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
 | 
					            var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
 | 
				
			||||||
            if (!ModelState.IsValid)
 | 
					            if (!ModelState.IsValid)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                var errors = ModelState.Values
 | 
					                var errors = ModelState.Values
 | 
				
			||||||
@ -356,6 +363,10 @@ namespace MarcoBMS.Services.Controllers
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                await _context.SaveChangesAsync();
 | 
					                await _context.SaveChangesAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "Update_Project", Response = project.ToProjectDto() };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                return Ok(ApiResponse<object>.SuccessResponse(project.ToProjectDto(), "Success.", 200));
 | 
					                return Ok(ApiResponse<object>.SuccessResponse(project.ToProjectDto(), "Success.", 200));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@ -365,7 +376,6 @@ namespace MarcoBMS.Services.Controllers
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
        //[HttpPost("assign-employee")]
 | 
					        //[HttpPost("assign-employee")]
 | 
				
			||||||
        //public async Task<IActionResult> AssignEmployee(int? allocationid, int employeeId, int projectId)
 | 
					        //public async Task<IActionResult> AssignEmployee(int? allocationid, int employeeId, int projectId)
 | 
				
			||||||
        //{
 | 
					        //{
 | 
				
			||||||
@ -506,7 +516,11 @@ namespace MarcoBMS.Services.Controllers
 | 
				
			|||||||
            if (projectAllocationDot != null)
 | 
					            if (projectAllocationDot != null)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                Guid TenentID = GetTenantId();
 | 
					                Guid TenentID = GetTenantId();
 | 
				
			||||||
 | 
					                var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                List<object>? result = new List<object>();
 | 
					                List<object>? result = new List<object>();
 | 
				
			||||||
 | 
					                List<Guid> employeeIds = new List<Guid>();
 | 
				
			||||||
 | 
					                List<Guid> projectIds = new List<Guid>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                foreach (var item in projectAllocationDot)
 | 
					                foreach (var item in projectAllocationDot)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
@ -535,6 +549,9 @@ namespace MarcoBMS.Services.Controllers
 | 
				
			|||||||
                                projectAllocationFromDb.IsActive = false;
 | 
					                                projectAllocationFromDb.IsActive = false;
 | 
				
			||||||
                                _context.Entry(projectAllocationFromDb).Property(e => e.ReAllocationDate).IsModified = true;
 | 
					                                _context.Entry(projectAllocationFromDb).Property(e => e.ReAllocationDate).IsModified = true;
 | 
				
			||||||
                                _context.Entry(projectAllocationFromDb).Property(e => e.IsActive).IsModified = true;
 | 
					                                _context.Entry(projectAllocationFromDb).Property(e => e.IsActive).IsModified = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                employeeIds.Add(projectAllocation.EmployeeId);
 | 
				
			||||||
 | 
					                                projectIds.Add(projectAllocation.ProjectId);
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                            await _context.SaveChangesAsync();
 | 
					                            await _context.SaveChangesAsync();
 | 
				
			||||||
                            var result1 = new
 | 
					                            var result1 = new
 | 
				
			||||||
@ -556,6 +573,9 @@ namespace MarcoBMS.Services.Controllers
 | 
				
			|||||||
                            projectAllocation.IsActive = true;
 | 
					                            projectAllocation.IsActive = true;
 | 
				
			||||||
                            _context.ProjectAllocations.Add(projectAllocation);
 | 
					                            _context.ProjectAllocations.Add(projectAllocation);
 | 
				
			||||||
                            await _context.SaveChangesAsync();
 | 
					                            await _context.SaveChangesAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            employeeIds.Add(projectAllocation.EmployeeId);
 | 
				
			||||||
 | 
					                            projectIds.Add(projectAllocation.ProjectId);
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
@ -564,7 +584,9 @@ namespace MarcoBMS.Services.Controllers
 | 
				
			|||||||
                        return Ok(ApiResponse<object>.ErrorResponse(ex.Message, ex, 400));
 | 
					                        return Ok(ApiResponse<object>.ErrorResponse(ex.Message, ex, 400));
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					                var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "Assign_Project", ProjectIds = projectIds, EmployeeList = employeeIds };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification);
 | 
				
			||||||
                return Ok(ApiResponse<object>.SuccessResponse(result, "Data saved successfully", 200));
 | 
					                return Ok(ApiResponse<object>.SuccessResponse(result, "Data saved successfully", 200));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@ -573,52 +595,91 @@ namespace MarcoBMS.Services.Controllers
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [HttpPost("task")]
 | 
					        [HttpPost("task")]
 | 
				
			||||||
        public async Task<IActionResult> CreateProjectTask(List<WorkItemDot> workItemDot)
 | 
					        public async Task<IActionResult> CreateProjectTask(List<WorkItemDot> workItemDtos)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            Guid tenantId = GetTenantId();
 | 
					            _logger.LogInfo("CreateProjectTask called with {Count} items", workItemDtos?.Count ?? 0);
 | 
				
			||||||
            List<WorkItemVM> workItems = new List<WorkItemVM> { };
 | 
					 | 
				
			||||||
            string responseMessage = "";
 | 
					 | 
				
			||||||
            if (workItemDot != null)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                foreach (var item in workItemDot)
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    WorkItem workItem = item.ToWorkItemFromWorkItemDto(tenantId);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    if (item.Id != null)
 | 
					            // Validate request
 | 
				
			||||||
                    {
 | 
					            if (workItemDtos == null || !workItemDtos.Any())
 | 
				
			||||||
                        //update
 | 
					            {
 | 
				
			||||||
                        _context.WorkItems.Update(workItem);
 | 
					                _logger.LogWarning("No work items provided in the request.");
 | 
				
			||||||
                        await _context.SaveChangesAsync();
 | 
					                return BadRequest(ApiResponse<object>.ErrorResponse("Invalid details.", "Work Item details are not valid.", 400));
 | 
				
			||||||
                        responseMessage = "Task Added Successfully";
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    else
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        //create
 | 
					 | 
				
			||||||
                        _context.WorkItems.Add(workItem);
 | 
					 | 
				
			||||||
                        await _context.SaveChangesAsync();
 | 
					 | 
				
			||||||
                        responseMessage = "Task Updated Successfully";
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    var result = new WorkItemVM
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        WorkItemId = workItem.Id,
 | 
					 | 
				
			||||||
                        WorkItem = workItem
 | 
					 | 
				
			||||||
                    };
 | 
					 | 
				
			||||||
                    workItems.Add(result);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                var activity = await _context.ActivityMasters.ToListAsync();
 | 
					 | 
				
			||||||
                var category = await _context.WorkCategoryMasters.ToListAsync();
 | 
					 | 
				
			||||||
                return Ok(ApiResponse<object>.SuccessResponse(workItems, responseMessage, 200));
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return BadRequest(ApiResponse<object>.ErrorResponse("Invalid details.", "Work Item Details are not valid.", 400));
 | 
					            Guid tenantId = GetTenantId();
 | 
				
			||||||
 | 
					            var workItemsToCreate = new List<WorkItem>();
 | 
				
			||||||
 | 
					            var workItemsToUpdate = new List<WorkItem>();
 | 
				
			||||||
 | 
					            var responseList = new List<WorkItemVM>();
 | 
				
			||||||
 | 
					            var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
 | 
				
			||||||
 | 
					            string message = "";
 | 
				
			||||||
 | 
					            List<Guid> projectIds = new List<Guid>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            foreach (var itemDto in workItemDtos)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                var workItem = itemDto.ToWorkItemFromWorkItemDto(tenantId);
 | 
				
			||||||
 | 
					                var workArea = await _context.WorkAreas.Include(a => a.Floor).FirstOrDefaultAsync(a => a.Id == workItem.WorkAreaId) ?? new WorkArea();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Building building = await _context.Buildings.FirstOrDefaultAsync(b => b.Id == (workArea.Floor != null ? workArea.Floor.BuildingId : Guid.Empty)) ?? new Building();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (itemDto.Id != null && itemDto.Id != Guid.Empty)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    // Update existing
 | 
				
			||||||
 | 
					                    workItemsToUpdate.Add(workItem);
 | 
				
			||||||
 | 
					                    message = $"Task Updated in Building: {building.Name}, on Floor: {workArea.Floor?.FloorName}, in Area: {workArea.AreaName} by {LoggedInEmployee.FirstName} {LoggedInEmployee.LastName}";
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                else
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    // Create new
 | 
				
			||||||
 | 
					                    workItem.Id = Guid.NewGuid();
 | 
				
			||||||
 | 
					                    workItemsToCreate.Add(workItem);
 | 
				
			||||||
 | 
					                    message = $"Task Added in Building: {building.Name}, on Floor: {workArea.Floor?.FloorName}, in Area: {workArea.AreaName} by {LoggedInEmployee.FirstName} {LoggedInEmployee.LastName}";
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                responseList.Add(new WorkItemVM
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    WorkItemId = workItem.Id,
 | 
				
			||||||
 | 
					                    WorkItem = workItem
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					                projectIds.Add(building.ProjectId);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            string responseMessage = "";
 | 
				
			||||||
 | 
					            // Apply DB changes
 | 
				
			||||||
 | 
					            if (workItemsToCreate.Any())
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogInfo("Adding {Count} new work items", workItemsToCreate.Count);
 | 
				
			||||||
 | 
					                await _context.WorkItems.AddRangeAsync(workItemsToCreate);
 | 
				
			||||||
 | 
					                responseMessage = "Task Added Successfully";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (workItemsToUpdate.Any())
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogInfo("Updating {Count} existing work items", workItemsToUpdate.Count);
 | 
				
			||||||
 | 
					                _context.WorkItems.UpdateRange(workItemsToUpdate);
 | 
				
			||||||
 | 
					                responseMessage = "Task Updated Successfully";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await _context.SaveChangesAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            _logger.LogInfo("CreateProjectTask completed successfully. Created: {Created}, Updated: {Updated}", workItemsToCreate.Count, workItemsToUpdate.Count);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "Infra", ProjectIds = projectIds, Message = message };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return Ok(ApiResponse<object>.SuccessResponse(responseList, responseMessage, 200));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [HttpDelete("task/{id}")]
 | 
					        [HttpDelete("task/{id}")]
 | 
				
			||||||
        public async Task<IActionResult> DeleteProjectTask(Guid id)
 | 
					        public async Task<IActionResult> DeleteProjectTask(Guid id)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            Guid tenantId = _userHelper.GetTenantId();
 | 
					            Guid tenantId = _userHelper.GetTenantId();
 | 
				
			||||||
            WorkItem? task = await _context.WorkItems.AsNoTracking().FirstOrDefaultAsync(t => t.Id == id && t.TenantId == tenantId);
 | 
					            var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
 | 
				
			||||||
 | 
					            List<Guid> projectIds = new List<Guid>();
 | 
				
			||||||
 | 
					            WorkItem? task = await _context.WorkItems.AsNoTracking().Include(t => t.WorkArea).FirstOrDefaultAsync(t => t.Id == id && t.TenantId == tenantId);
 | 
				
			||||||
            if (task != null)
 | 
					            if (task != null)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                if (task.CompletedWork == 0)
 | 
					                if (task.CompletedWork == 0)
 | 
				
			||||||
@ -629,6 +690,15 @@ namespace MarcoBMS.Services.Controllers
 | 
				
			|||||||
                        _context.WorkItems.Remove(task);
 | 
					                        _context.WorkItems.Remove(task);
 | 
				
			||||||
                        await _context.SaveChangesAsync();
 | 
					                        await _context.SaveChangesAsync();
 | 
				
			||||||
                        _logger.LogInfo("Task with ID {WorkItemId} has been successfully deleted.", task.Id);
 | 
					                        _logger.LogInfo("Task with ID {WorkItemId} has been successfully deleted.", task.Id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        var floorId = task.WorkArea?.FloorId;
 | 
				
			||||||
 | 
					                        var floor = await _context.Floor.Include(f => f.Building).FirstOrDefaultAsync(f => f.Id == floorId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        projectIds.Add(floor?.Building?.ProjectId ?? Guid.Empty);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "Infra", ProjectIds = projectIds, Message = $"Task Deleted in Building: {floor?.Building?.Name}, on Floor: {floor?.FloorName}, in Area: {task.WorkArea?.AreaName} by {LoggedInEmployee.FirstName} {LoggedInEmployee.LastName}" };
 | 
				
			||||||
 | 
					                        await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    else
 | 
					                    else
 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
@ -656,8 +726,12 @@ namespace MarcoBMS.Services.Controllers
 | 
				
			|||||||
        public async Task<IActionResult> ManageProjectInfra(List<InfraDot> infraDots)
 | 
					        public async Task<IActionResult> ManageProjectInfra(List<InfraDot> infraDots)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            Guid tenantId = GetTenantId();
 | 
					            Guid tenantId = GetTenantId();
 | 
				
			||||||
 | 
					            var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var responseData = new InfraVM { };
 | 
					            var responseData = new InfraVM { };
 | 
				
			||||||
            string responseMessage = "";
 | 
					            string responseMessage = "";
 | 
				
			||||||
 | 
					            string message = "";
 | 
				
			||||||
 | 
					            List<Guid> projectIds = new List<Guid>();
 | 
				
			||||||
            if (infraDots != null)
 | 
					            if (infraDots != null)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                foreach (var item in infraDots)
 | 
					                foreach (var item in infraDots)
 | 
				
			||||||
@ -675,6 +749,7 @@ namespace MarcoBMS.Services.Controllers
 | 
				
			|||||||
                            await _context.SaveChangesAsync();
 | 
					                            await _context.SaveChangesAsync();
 | 
				
			||||||
                            responseData.building = building;
 | 
					                            responseData.building = building;
 | 
				
			||||||
                            responseMessage = "Buliding Added Successfully";
 | 
					                            responseMessage = "Buliding Added Successfully";
 | 
				
			||||||
 | 
					                            message = "Building Added";
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        else
 | 
					                        else
 | 
				
			||||||
                        {
 | 
					                        {
 | 
				
			||||||
@ -683,8 +758,10 @@ namespace MarcoBMS.Services.Controllers
 | 
				
			|||||||
                            await _context.SaveChangesAsync();
 | 
					                            await _context.SaveChangesAsync();
 | 
				
			||||||
                            responseData.building = building;
 | 
					                            responseData.building = building;
 | 
				
			||||||
                            responseMessage = "Buliding Updated Successfully";
 | 
					                            responseMessage = "Buliding Updated Successfully";
 | 
				
			||||||
 | 
					                            message = "Building Updated";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
 | 
					                        projectIds.Add(building.ProjectId);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    if (item.Floor != null)
 | 
					                    if (item.Floor != null)
 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
@ -698,6 +775,7 @@ namespace MarcoBMS.Services.Controllers
 | 
				
			|||||||
                            await _context.SaveChangesAsync();
 | 
					                            await _context.SaveChangesAsync();
 | 
				
			||||||
                            responseData.floor = floor;
 | 
					                            responseData.floor = floor;
 | 
				
			||||||
                            responseMessage = "Floor Added Successfully";
 | 
					                            responseMessage = "Floor Added Successfully";
 | 
				
			||||||
 | 
					                            message = "Floor Added";
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        else
 | 
					                        else
 | 
				
			||||||
                        {
 | 
					                        {
 | 
				
			||||||
@ -706,7 +784,11 @@ namespace MarcoBMS.Services.Controllers
 | 
				
			|||||||
                            await _context.SaveChangesAsync();
 | 
					                            await _context.SaveChangesAsync();
 | 
				
			||||||
                            responseData.floor = floor;
 | 
					                            responseData.floor = floor;
 | 
				
			||||||
                            responseMessage = "Floor Updated Successfully";
 | 
					                            responseMessage = "Floor Updated Successfully";
 | 
				
			||||||
 | 
					                            message = "Floor Updated";
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
 | 
					                        Building? building = await _context.Buildings.FirstOrDefaultAsync(b => b.Id == floor.BuildingId);
 | 
				
			||||||
 | 
					                        projectIds.Add(building?.ProjectId ?? Guid.Empty);
 | 
				
			||||||
 | 
					                        message = $"{message} in Building: {building?.Name}";
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    if (item.WorkArea != null)
 | 
					                    if (item.WorkArea != null)
 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
@ -720,6 +802,7 @@ namespace MarcoBMS.Services.Controllers
 | 
				
			|||||||
                            await _context.SaveChangesAsync();
 | 
					                            await _context.SaveChangesAsync();
 | 
				
			||||||
                            responseData.workArea = workArea;
 | 
					                            responseData.workArea = workArea;
 | 
				
			||||||
                            responseMessage = "Work Area Added Successfully";
 | 
					                            responseMessage = "Work Area Added Successfully";
 | 
				
			||||||
 | 
					                            message = "Work Area Added";
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        else
 | 
					                        else
 | 
				
			||||||
                        {
 | 
					                        {
 | 
				
			||||||
@ -728,9 +811,17 @@ namespace MarcoBMS.Services.Controllers
 | 
				
			|||||||
                            await _context.SaveChangesAsync();
 | 
					                            await _context.SaveChangesAsync();
 | 
				
			||||||
                            responseData.workArea = workArea;
 | 
					                            responseData.workArea = workArea;
 | 
				
			||||||
                            responseMessage = "Work Area Updated Successfully";
 | 
					                            responseMessage = "Work Area Updated Successfully";
 | 
				
			||||||
 | 
					                            message = "Work Area Updated";
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
 | 
					                        Floor? floor = await _context.Floor.Include(f => f.Building).FirstOrDefaultAsync(f => f.Id == workArea.FloorId);
 | 
				
			||||||
 | 
					                        projectIds.Add(floor?.Building?.ProjectId ?? Guid.Empty);
 | 
				
			||||||
 | 
					                        message = $"{message} in Building: {floor?.Building?.Name}, on Floor: {floor?.FloorName}";
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					                message = $"{message} by {LoggedInEmployee.FirstName} {LoggedInEmployee.LastName}";
 | 
				
			||||||
 | 
					                var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "Infra", ProjectIds = projectIds, Message = message };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification);
 | 
				
			||||||
                return Ok(ApiResponse<object>.SuccessResponse(responseData, responseMessage, 200));
 | 
					                return Ok(ApiResponse<object>.SuccessResponse(responseData, responseMessage, 200));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            return BadRequest(ApiResponse<object>.ErrorResponse("Invalid details.", "Infra Details are not valid.", 400));
 | 
					            return BadRequest(ApiResponse<object>.ErrorResponse("Invalid details.", "Infra Details are not valid.", 400));
 | 
				
			||||||
@ -776,16 +867,15 @@ namespace MarcoBMS.Services.Controllers
 | 
				
			|||||||
            return Ok(ApiResponse<object>.SuccessResponse(projects, "Success.", 200));
 | 
					            return Ok(ApiResponse<object>.SuccessResponse(projects, "Success.", 200));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        [HttpPost("assign-projects/{employeeId}")]
 | 
					        [HttpPost("assign-projects/{employeeId}")]
 | 
				
			||||||
        public async Task<ActionResult> AssigneProjectsToEmployee([FromBody] List<ProjectsAllocationDto> projectAllocationDtos, [FromRoute] Guid employeeId)
 | 
					        public async Task<ActionResult> AssigneProjectsToEmployee([FromBody] List<ProjectsAllocationDto> projectAllocationDtos, [FromRoute] Guid employeeId)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (projectAllocationDtos != null && employeeId != Guid.Empty)
 | 
					            if (projectAllocationDtos != null && employeeId != Guid.Empty)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                Guid TenentID = GetTenantId();
 | 
					                Guid TenentID = GetTenantId();
 | 
				
			||||||
 | 
					                var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
 | 
				
			||||||
                List<object>? result = new List<object>();
 | 
					                List<object>? result = new List<object>();
 | 
				
			||||||
 | 
					                List<Guid> projectIds = new List<Guid>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                foreach (var projectAllocationDto in projectAllocationDtos)
 | 
					                foreach (var projectAllocationDto in projectAllocationDtos)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
@ -813,6 +903,8 @@ namespace MarcoBMS.Services.Controllers
 | 
				
			|||||||
                                projectAllocationFromDb.IsActive = false;
 | 
					                                projectAllocationFromDb.IsActive = false;
 | 
				
			||||||
                                _context.Entry(projectAllocationFromDb).Property(e => e.ReAllocationDate).IsModified = true;
 | 
					                                _context.Entry(projectAllocationFromDb).Property(e => e.ReAllocationDate).IsModified = true;
 | 
				
			||||||
                                _context.Entry(projectAllocationFromDb).Property(e => e.IsActive).IsModified = true;
 | 
					                                _context.Entry(projectAllocationFromDb).Property(e => e.IsActive).IsModified = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                projectIds.Add(projectAllocation.ProjectId);
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                            await _context.SaveChangesAsync();
 | 
					                            await _context.SaveChangesAsync();
 | 
				
			||||||
                            var result1 = new
 | 
					                            var result1 = new
 | 
				
			||||||
@ -835,6 +927,8 @@ namespace MarcoBMS.Services.Controllers
 | 
				
			|||||||
                            _context.ProjectAllocations.Add(projectAllocation);
 | 
					                            _context.ProjectAllocations.Add(projectAllocation);
 | 
				
			||||||
                            await _context.SaveChangesAsync();
 | 
					                            await _context.SaveChangesAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            projectIds.Add(projectAllocation.ProjectId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -845,6 +939,9 @@ namespace MarcoBMS.Services.Controllers
 | 
				
			|||||||
                        return Ok(ApiResponse<object>.ErrorResponse(ex.Message, ex, 400));
 | 
					                        return Ok(ApiResponse<object>.ErrorResponse(ex.Message, ex, 400));
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					                var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "Assign_Project", ProjectIds = projectIds, EmployeeId = employeeId };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                return Ok(ApiResponse<object>.SuccessResponse(result, "Data saved successfully", 200));
 | 
					                return Ok(ApiResponse<object>.SuccessResponse(result, "Data saved successfully", 200));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
				
			|||||||
@ -1,17 +1,19 @@
 | 
				
			|||||||
using Marco.Pms.DataAccess.Data;
 | 
					using Marco.Pms.DataAccess.Data;
 | 
				
			||||||
using Marco.Pms.Model.Activities;
 | 
					using Marco.Pms.Model.Activities;
 | 
				
			||||||
using Marco.Pms.Model.Dtos.Activities;
 | 
					using Marco.Pms.Model.Dtos.Activities;
 | 
				
			||||||
using Marco.Pms.Model.Employees;
 | 
					 | 
				
			||||||
using Marco.Pms.Model.Entitlements;
 | 
					using Marco.Pms.Model.Entitlements;
 | 
				
			||||||
using Marco.Pms.Model.Mapper;
 | 
					using Marco.Pms.Model.Mapper;
 | 
				
			||||||
using Marco.Pms.Model.Projects;
 | 
					using Marco.Pms.Model.Projects;
 | 
				
			||||||
using Marco.Pms.Model.Utilities;
 | 
					using Marco.Pms.Model.Utilities;
 | 
				
			||||||
using Marco.Pms.Model.ViewModels.Activities;
 | 
					using Marco.Pms.Model.ViewModels.Activities;
 | 
				
			||||||
using Marco.Pms.Model.ViewModels.Employee;
 | 
					using Marco.Pms.Services.Service;
 | 
				
			||||||
using MarcoBMS.Services.Helpers;
 | 
					using MarcoBMS.Services.Helpers;
 | 
				
			||||||
 | 
					using MarcoBMS.Services.Service;
 | 
				
			||||||
using Microsoft.AspNetCore.Authorization;
 | 
					using Microsoft.AspNetCore.Authorization;
 | 
				
			||||||
using Microsoft.AspNetCore.Mvc;
 | 
					using Microsoft.AspNetCore.Mvc;
 | 
				
			||||||
 | 
					using Microsoft.CodeAnalysis;
 | 
				
			||||||
using Microsoft.EntityFrameworkCore;
 | 
					using Microsoft.EntityFrameworkCore;
 | 
				
			||||||
 | 
					using Document = Marco.Pms.Model.DocumentManager.Document;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace MarcoBMS.Services.Controllers
 | 
					namespace MarcoBMS.Services.Controllers
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@ -23,12 +25,21 @@ namespace MarcoBMS.Services.Controllers
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        private readonly ApplicationDbContext _context;
 | 
					        private readonly ApplicationDbContext _context;
 | 
				
			||||||
        private readonly UserHelper _userHelper;
 | 
					        private readonly UserHelper _userHelper;
 | 
				
			||||||
 | 
					        private readonly S3UploadService _s3Service;
 | 
				
			||||||
 | 
					        private readonly ILoggingService _logger;
 | 
				
			||||||
 | 
					        private readonly PermissionServices _permissionServices;
 | 
				
			||||||
 | 
					        private readonly Guid Approve_Task;
 | 
				
			||||||
 | 
					        private readonly Guid Assign_Report_Task;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public TaskController(ApplicationDbContext context, UserHelper userHelper, S3UploadService s3Service, ILoggingService logger, PermissionServices permissionServices)
 | 
				
			||||||
        public TaskController(ApplicationDbContext context, UserHelper userHelper)
 | 
					 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            _context = context;
 | 
					            _context = context;
 | 
				
			||||||
            _userHelper = userHelper;
 | 
					            _userHelper = userHelper;
 | 
				
			||||||
 | 
					            _s3Service = s3Service;
 | 
				
			||||||
 | 
					            _logger = logger;
 | 
				
			||||||
 | 
					            _permissionServices = permissionServices;
 | 
				
			||||||
 | 
					            Approve_Task = Guid.Parse("db4e40c5-2ba9-4b6d-b8a6-a16a250ff99c");
 | 
				
			||||||
 | 
					            Assign_Report_Task = Guid.Parse("6a32379b-8b3f-49a6-8c48-4b7ac1b55dc2");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private Guid GetTenantId()
 | 
					        private Guid GetTenantId()
 | 
				
			||||||
@ -39,51 +50,69 @@ namespace MarcoBMS.Services.Controllers
 | 
				
			|||||||
        [HttpPost("assign")]
 | 
					        [HttpPost("assign")]
 | 
				
			||||||
        public async Task<IActionResult> AssignTask([FromBody] AssignTaskDto assignTask)
 | 
					        public async Task<IActionResult> AssignTask([FromBody] AssignTaskDto assignTask)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
 | 
					            // Validate the incoming model
 | 
				
			||||||
            if (!ModelState.IsValid)
 | 
					            if (!ModelState.IsValid)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                var errors = ModelState.Values
 | 
					                var errors = ModelState.Values
 | 
				
			||||||
                    .SelectMany(v => v.Errors)
 | 
					                    .SelectMany(v => v.Errors)
 | 
				
			||||||
                    .Select(e => e.ErrorMessage)
 | 
					                    .Select(e => e.ErrorMessage)
 | 
				
			||||||
                    .ToList();
 | 
					                    .ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                _logger.LogWarning("AssignTask failed validation: {@Errors}", errors);
 | 
				
			||||||
                return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", errors, 400));
 | 
					                return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", errors, 400));
 | 
				
			||||||
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            var tenantId = GetTenantId();
 | 
					 | 
				
			||||||
            var Employee = await _userHelper.GetCurrentEmployeeAsync();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var taskAllocation = assignTask.ToTaskAllocationFromAssignTaskDto(Employee.Id, tenantId);
 | 
					            // Retrieve tenant and employee context
 | 
				
			||||||
 | 
					            var tenantId = GetTenantId();
 | 
				
			||||||
 | 
					            var employee = await _userHelper.GetCurrentEmployeeAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Check for permission to approve tasks
 | 
				
			||||||
 | 
					            var hasPermission = await _permissionServices.HasPermission(Assign_Report_Task, employee.Id);
 | 
				
			||||||
 | 
					            if (!hasPermission)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogWarning("Employee {EmployeeId} attempted to assign Task without permission", employee.Id);
 | 
				
			||||||
 | 
					                return StatusCode(403, ApiResponse<object>.ErrorResponse("You don't have access", "User not authorized to approve tasks", 403));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            _logger.LogInfo("Employee {EmployeeId} is assigning a new task", employee.Id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Convert DTO to entity and save TaskAllocation
 | 
				
			||||||
 | 
					            var taskAllocation = assignTask.ToTaskAllocationFromAssignTaskDto(employee.Id, tenantId);
 | 
				
			||||||
            _context.TaskAllocations.Add(taskAllocation);
 | 
					            _context.TaskAllocations.Add(taskAllocation);
 | 
				
			||||||
            await _context.SaveChangesAsync();
 | 
					            await _context.SaveChangesAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            _logger.LogInfo("Task {TaskId} assigned by Employee {EmployeeId}", taskAllocation.Id, employee.Id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var response = taskAllocation.ToAssignTaskVMFromTaskAllocation();
 | 
					            var response = taskAllocation.ToAssignTaskVMFromTaskAllocation();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var teamMembers = new List<TaskMembers> { };
 | 
					            // Map team members
 | 
				
			||||||
            if (assignTask.TaskTeam != null)
 | 
					            var teamMembers = new List<TaskMembers>();
 | 
				
			||||||
 | 
					            if (assignTask.TaskTeam != null && assignTask.TaskTeam.Any())
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                foreach (var teamMember in assignTask.TaskTeam)
 | 
					                teamMembers = assignTask.TaskTeam.Select(memberId => new TaskMembers
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    var result = new TaskMembers
 | 
					                    TaskAllocationId = taskAllocation.Id,
 | 
				
			||||||
                    {
 | 
					                    EmployeeId = memberId,
 | 
				
			||||||
                        TaskAllocationId = taskAllocation.Id,
 | 
					                    TenantId = tenantId
 | 
				
			||||||
                        EmployeeId = teamMember,
 | 
					                }).ToList();
 | 
				
			||||||
                        TenantId = tenantId,
 | 
					 | 
				
			||||||
                    };
 | 
					 | 
				
			||||||
                    teamMembers.Add(result);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            _context.TaskMembers.AddRange(teamMembers);
 | 
					 | 
				
			||||||
            await _context.SaveChangesAsync();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var idList = teamMembers.Select(m => m.EmployeeId);
 | 
					                _context.TaskMembers.AddRange(teamMembers);
 | 
				
			||||||
            List<Employee> employees = await _context.Employees.Where(e => idList.Contains(e.Id)).ToListAsync();
 | 
					                await _context.SaveChangesAsync();
 | 
				
			||||||
            List<BasicEmployeeVM> team = new List<BasicEmployeeVM>();
 | 
					
 | 
				
			||||||
            foreach (var employee in employees)
 | 
					                _logger.LogInfo("Team members added to Task {TaskId}: {@TeamMemberIds}", taskAllocation.Id, assignTask.TaskTeam);
 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                team.Add(employee.ToBasicEmployeeVMFromEmployee());
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Get team member details
 | 
				
			||||||
 | 
					            var employeeIds = teamMembers.Select(m => m.EmployeeId).ToList();
 | 
				
			||||||
 | 
					            var employees = await _context.Employees
 | 
				
			||||||
 | 
					                .Where(e => employeeIds.Contains(e.Id))
 | 
				
			||||||
 | 
					                .ToListAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var team = employees.Select(e => e.ToBasicEmployeeVMFromEmployee()).ToList();
 | 
				
			||||||
            response.teamMembers = team;
 | 
					            response.teamMembers = team;
 | 
				
			||||||
            return Ok(ApiResponse<object>.SuccessResponse(response, "Task assignned successfully", 200));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return Ok(ApiResponse<object>.SuccessResponse(response, "Task assigned successfully", 200));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [HttpPost("report")]
 | 
					        [HttpPost("report")]
 | 
				
			||||||
        public async Task<IActionResult> ReportTaskProgress([FromBody] ReportTaskDto reportTask)
 | 
					        public async Task<IActionResult> ReportTaskProgress([FromBody] ReportTaskDto reportTask)
 | 
				
			||||||
@ -94,211 +123,661 @@ namespace MarcoBMS.Services.Controllers
 | 
				
			|||||||
                    .SelectMany(v => v.Errors)
 | 
					                    .SelectMany(v => v.Errors)
 | 
				
			||||||
                    .Select(e => e.ErrorMessage)
 | 
					                    .Select(e => e.ErrorMessage)
 | 
				
			||||||
                    .ToList();
 | 
					                    .ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                _logger.LogWarning("Task report validation failed: {@Errors}", errors);
 | 
				
			||||||
                return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", errors, 400));
 | 
					                return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", errors, 400));
 | 
				
			||||||
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var tenantId = GetTenantId();
 | 
					            var tenantId = GetTenantId();
 | 
				
			||||||
            var Employee = await _userHelper.GetCurrentEmployeeAsync();
 | 
					            var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var taskAllocation = await _context.TaskAllocations.Include(t => t.WorkItem).FirstOrDefaultAsync(t => t.Id == reportTask.Id);
 | 
					            var hasPermission = await _permissionServices.HasPermission(Assign_Report_Task, loggedInEmployee.Id);
 | 
				
			||||||
 | 
					            if (!hasPermission)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogWarning("Unauthorized task report attempt by Employee {EmployeeId} for Task {TaskId}", loggedInEmployee.Id, reportTask.Id);
 | 
				
			||||||
 | 
					                return StatusCode(403, ApiResponse<object>.ErrorResponse("You don't have access", "User not authorized to report tasks", 403));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var taskAllocation = await _context.TaskAllocations
 | 
				
			||||||
 | 
					                .Include(t => t.WorkItem)
 | 
				
			||||||
 | 
					                .FirstOrDefaultAsync(t => t.Id == reportTask.Id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var checkListIds = reportTask.CheckList != null ? reportTask.CheckList.Select(c => c.Id).ToList() : new List<Guid>();
 | 
					 | 
				
			||||||
            var checkList = await _context.ActivityCheckLists.Where(c => checkListIds.Contains(c.Id)).ToListAsync();
 | 
					 | 
				
			||||||
            if (taskAllocation == null)
 | 
					            if (taskAllocation == null)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogWarning("No task allocation found with ID {TaskId}", reportTask.Id);
 | 
				
			||||||
                return BadRequest(ApiResponse<object>.ErrorResponse("No such task has been allocated.", "No such task has been allocated.", 400));
 | 
					                return BadRequest(ApiResponse<object>.ErrorResponse("No such task has been allocated.", "No such task has been allocated.", 400));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var checkListIds = reportTask.CheckList?.Select(c => c.Id).ToList() ?? new List<Guid>();
 | 
				
			||||||
 | 
					            var checkList = await _context.ActivityCheckLists
 | 
				
			||||||
 | 
					                .Where(c => checkListIds.Contains(c.Id))
 | 
				
			||||||
 | 
					                .ToListAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (taskAllocation.WorkItem != null)
 | 
					            if (taskAllocation.WorkItem != null)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                if (taskAllocation.CompletedTask != 0)
 | 
					                if (taskAllocation.CompletedTask > 0)
 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    taskAllocation.WorkItem.CompletedWork -= taskAllocation.CompletedTask;
 | 
					                    taskAllocation.WorkItem.CompletedWork -= taskAllocation.CompletedTask;
 | 
				
			||||||
                }
 | 
					
 | 
				
			||||||
                taskAllocation.ReportedDate = reportTask.ReportedDate;
 | 
					 | 
				
			||||||
                taskAllocation.CompletedTask = reportTask.CompletedTask;
 | 
					 | 
				
			||||||
                taskAllocation.WorkItem.CompletedWork += reportTask.CompletedTask;
 | 
					                taskAllocation.WorkItem.CompletedWork += reportTask.CompletedTask;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            List<CheckListMappings> checkListMappings = new List<CheckListMappings>();
 | 
					
 | 
				
			||||||
            List<CheckListVM> checkListVMs = new List<CheckListVM>();
 | 
					            taskAllocation.ParentTaskId = reportTask.ParentTaskId;
 | 
				
			||||||
 | 
					            taskAllocation.ReportedDate = reportTask.ReportedDate;
 | 
				
			||||||
 | 
					            taskAllocation.ReportedById = loggedInEmployee.Id;
 | 
				
			||||||
 | 
					            taskAllocation.CompletedTask = reportTask.CompletedTask;
 | 
				
			||||||
 | 
					            //taskAllocation.ReportedTask = reportTask.CompletedTask;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var checkListMappings = new List<CheckListMappings>();
 | 
				
			||||||
 | 
					            var checkListVMs = new List<CheckListVM>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (reportTask.CheckList != null)
 | 
					            if (reportTask.CheckList != null)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
 | 
					                var activityId = taskAllocation.WorkItem?.ActivityId ?? Guid.Empty;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                foreach (var checkDto in reportTask.CheckList)
 | 
					                foreach (var checkDto in reportTask.CheckList)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    checkListVMs.Add(checkDto.ToCheckListVMFromReportCheckListDto(taskAllocation.WorkItem != null ? taskAllocation.WorkItem.ActivityId : Guid.Empty));
 | 
					                    checkListVMs.Add(checkDto.ToCheckListVMFromReportCheckListDto(activityId));
 | 
				
			||||||
                    if (checkDto.IsChecked)
 | 
					
 | 
				
			||||||
 | 
					                    if (checkDto.IsChecked && checkList.Any(c => c.Id == checkDto.Id))
 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
                        var check = checkList.Find(c => c.Id == checkDto.Id);
 | 
					                        checkListMappings.Add(new CheckListMappings
 | 
				
			||||||
                        if (check != null)
 | 
					 | 
				
			||||||
                        {
 | 
					                        {
 | 
				
			||||||
                            CheckListMappings checkListMapping = new CheckListMappings
 | 
					                            CheckListId = checkDto.Id,
 | 
				
			||||||
                            {
 | 
					                            TaskAllocationId = reportTask.Id
 | 
				
			||||||
                                CheckListId = check.Id,
 | 
					                        });
 | 
				
			||||||
                                TaskAllocationId = reportTask.Id
 | 
					 | 
				
			||||||
                            };
 | 
					 | 
				
			||||||
                            checkListMappings.Add(checkListMapping);
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            _context.CheckListMappings.AddRange(checkListMappings);
 | 
					 | 
				
			||||||
            var comment = reportTask.ToCommentFromReportTaskDto(tenantId, Employee.Id);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                _context.CheckListMappings.AddRange(checkListMappings);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var comment = reportTask.ToCommentFromReportTaskDto(tenantId, loggedInEmployee.Id);
 | 
				
			||||||
            _context.TaskComments.Add(comment);
 | 
					            _context.TaskComments.Add(comment);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (reportTask.Images?.Any() == true)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                var workAreaId = taskAllocation.WorkItem?.WorkAreaId;
 | 
				
			||||||
 | 
					                var workArea = await _context.WorkAreas.Include(a => a.Floor)
 | 
				
			||||||
 | 
					                    .FirstOrDefaultAsync(a => a.Id == workAreaId) ?? new WorkArea();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                var buildingId = workArea.Floor?.BuildingId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                var building = await _context.Buildings
 | 
				
			||||||
 | 
					                    .FirstOrDefaultAsync(b => b.Id == buildingId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                foreach (var image in reportTask.Images)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    if (string.IsNullOrEmpty(image.Base64Data))
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        _logger.LogWarning("Image upload failed: Base64 data is missing");
 | 
				
			||||||
 | 
					                        return BadRequest(ApiResponse<object>.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400));
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    var base64 = image.Base64Data.Contains(',')
 | 
				
			||||||
 | 
					                        ? image.Base64Data[(image.Base64Data.IndexOf(",") + 1)..]
 | 
				
			||||||
 | 
					                        : image.Base64Data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    var fileType = _s3Service.GetContentTypeFromBase64(base64);
 | 
				
			||||||
 | 
					                    var fileName = _s3Service.GenerateFileName(fileType, tenantId, "task_report");
 | 
				
			||||||
 | 
					                    var objectKey = $"tenant-{tenantId}/project-{building?.ProjectId}/Actitvity/{fileName}";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    await _s3Service.UploadFileAsync(base64, fileType, objectKey);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    var document = new Document
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        FileName = image.FileName ?? "",
 | 
				
			||||||
 | 
					                        ContentType = image.ContentType ?? "",
 | 
				
			||||||
 | 
					                        S3Key = objectKey,
 | 
				
			||||||
 | 
					                        Base64Data = image.Base64Data,
 | 
				
			||||||
 | 
					                        FileSize = image.FileSize,
 | 
				
			||||||
 | 
					                        UploadedAt = DateTime.UtcNow,
 | 
				
			||||||
 | 
					                        TenantId = tenantId
 | 
				
			||||||
 | 
					                    };
 | 
				
			||||||
 | 
					                    _context.Documents.Add(document);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    var attachment = new TaskAttachment
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        DocumentId = document.Id,
 | 
				
			||||||
 | 
					                        ReferenceId = reportTask.Id
 | 
				
			||||||
 | 
					                    };
 | 
				
			||||||
 | 
					                    _context.TaskAttachments.Add(attachment);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            await _context.SaveChangesAsync();
 | 
					            await _context.SaveChangesAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var response = taskAllocation.ToReportTaskVMFromTaskAllocation();
 | 
					            var response = taskAllocation.ToReportTaskVMFromTaskAllocation();
 | 
				
			||||||
            List<TaskComment> comments = await _context.TaskComments.Where(c => c.TaskAllocationId == taskAllocation.Id).ToListAsync();
 | 
					            var comments = await _context.TaskComments
 | 
				
			||||||
            List<CommentVM> resultComments = new List<CommentVM> { };
 | 
					                .Where(c => c.TaskAllocationId == taskAllocation.Id)
 | 
				
			||||||
            foreach (var result in comments)
 | 
					                .ToListAsync();
 | 
				
			||||||
            {
 | 
					
 | 
				
			||||||
                resultComments.Add(result.ToCommentVMFromTaskComment());
 | 
					            response.Comments = comments.Select(c => c.ToCommentVMFromTaskComment()).ToList();
 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            response.Comments = resultComments;
 | 
					 | 
				
			||||||
            response.checkList = checkListVMs;
 | 
					            response.checkList = checkListVMs;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            _logger.LogInfo("Task {TaskId} reported successfully by Employee {EmployeeId}", taskAllocation.Id, loggedInEmployee.Id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return Ok(ApiResponse<object>.SuccessResponse(response, "Task reported successfully", 200));
 | 
					            return Ok(ApiResponse<object>.SuccessResponse(response, "Task reported successfully", 200));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [HttpPost("comment")]
 | 
					        [HttpPost("comment")]
 | 
				
			||||||
        public async Task<IActionResult> AddCommentForTask([FromBody] CreateCommentDto createComment)
 | 
					        public async Task<IActionResult> AddCommentForTask([FromBody] CreateCommentDto createComment)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var tenantId = GetTenantId();
 | 
					            _logger.LogInfo("AddCommentForTask called for TaskAllocationId: {TaskId}", createComment.TaskAllocationId);
 | 
				
			||||||
            var Employee = await _userHelper.GetCurrentEmployeeAsync();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var comment = createComment.ToCommentFromCommentDto(tenantId, Employee.Id);
 | 
					            var tenantId = GetTenantId();
 | 
				
			||||||
 | 
					            var employee = await _userHelper.GetCurrentEmployeeAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Validate Task Allocation and associated WorkItem
 | 
				
			||||||
 | 
					            var taskAllocation = await _context.TaskAllocations
 | 
				
			||||||
 | 
					                .Include(t => t.WorkItem)
 | 
				
			||||||
 | 
					                .FirstOrDefaultAsync(t => t.Id == createComment.TaskAllocationId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (taskAllocation == null || taskAllocation.WorkItem == null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogWarning("Invalid task allocation or work item not found.");
 | 
				
			||||||
 | 
					                return BadRequest(ApiResponse<object>.ErrorResponse("No such task has been allocated.", "No such task has been allocated.", 400));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Fetch WorkArea and Building (if available)
 | 
				
			||||||
 | 
					            var workArea = await _context.WorkAreas
 | 
				
			||||||
 | 
					                .Include(a => a.Floor)
 | 
				
			||||||
 | 
					                .FirstOrDefaultAsync(a => a.Id == taskAllocation.WorkItem.WorkAreaId) ?? new WorkArea();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var buildingId = workArea.Floor?.BuildingId ?? Guid.Empty;
 | 
				
			||||||
 | 
					            var building = await _context.Buildings.FirstOrDefaultAsync(b => b.Id == buildingId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Save comment
 | 
				
			||||||
 | 
					            var comment = createComment.ToCommentFromCommentDto(tenantId, employee.Id);
 | 
				
			||||||
            _context.TaskComments.Add(comment);
 | 
					            _context.TaskComments.Add(comment);
 | 
				
			||||||
            await _context.SaveChangesAsync();
 | 
					            await _context.SaveChangesAsync();
 | 
				
			||||||
 | 
					            _logger.LogInfo("Comment saved with Id: {CommentId}", comment.Id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Process image uploads
 | 
				
			||||||
 | 
					            var images = createComment.Images;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (images != null && images.Any())
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                foreach (var image in images)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    if (string.IsNullOrWhiteSpace(image.Base64Data))
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        _logger.LogWarning("Missing Base64 data in one of the images.");
 | 
				
			||||||
 | 
					                        return BadRequest(ApiResponse<object>.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400));
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // Clean base64 string
 | 
				
			||||||
 | 
					                    var base64 = image.Base64Data.Contains(",")
 | 
				
			||||||
 | 
					                        ? image.Base64Data.Substring(image.Base64Data.IndexOf(",") + 1)
 | 
				
			||||||
 | 
					                        : image.Base64Data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    var fileType = _s3Service.GetContentTypeFromBase64(base64);
 | 
				
			||||||
 | 
					                    var fileName = _s3Service.GenerateFileName(fileType, tenantId, "task_comment");
 | 
				
			||||||
 | 
					                    var objectKey = $"tenant-{tenantId}/project-{building?.ProjectId}/Activity/{fileName}";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    await _s3Service.UploadFileAsync(base64, fileType, objectKey);
 | 
				
			||||||
 | 
					                    _logger.LogInfo("Image uploaded to S3 with key: {ObjectKey}", objectKey);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    var document = new Document
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        FileName = image.FileName ?? string.Empty,
 | 
				
			||||||
 | 
					                        ContentType = image.ContentType ?? fileType,
 | 
				
			||||||
 | 
					                        S3Key = objectKey,
 | 
				
			||||||
 | 
					                        Base64Data = image.Base64Data,
 | 
				
			||||||
 | 
					                        FileSize = image.FileSize,
 | 
				
			||||||
 | 
					                        UploadedAt = DateTime.UtcNow,
 | 
				
			||||||
 | 
					                        TenantId = tenantId
 | 
				
			||||||
 | 
					                    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    _context.Documents.Add(document);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    var attachment = new TaskAttachment
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        DocumentId = document.Id,
 | 
				
			||||||
 | 
					                        ReferenceId = comment.Id
 | 
				
			||||||
 | 
					                    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    _context.TaskAttachments.Add(attachment);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                await _context.SaveChangesAsync();
 | 
				
			||||||
 | 
					                _logger.LogInfo("Documents and attachments saved for commentId: {CommentId}", comment.Id);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Convert to view model and return response
 | 
				
			||||||
 | 
					            var response = comment.ToCommentVMFromTaskComment();
 | 
				
			||||||
 | 
					            _logger.LogInfo("Returning response for commentId: {CommentId}", comment.Id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            CommentVM response = comment.ToCommentVMFromTaskComment();
 | 
					 | 
				
			||||||
            return Ok(ApiResponse<object>.SuccessResponse(response, "Comment saved successfully", 200));
 | 
					            return Ok(ApiResponse<object>.SuccessResponse(response, "Comment saved successfully", 200));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [HttpGet("list")]
 | 
					        [HttpGet("list")]
 | 
				
			||||||
        public async Task<IActionResult> GetTasksList([FromQuery] Guid projectId, [FromQuery] string? dateFrom = null, [FromQuery] string? dateTo = null)
 | 
					        public async Task<IActionResult> GetTasksList([FromQuery] Guid projectId, [FromQuery] string? dateFrom = null, [FromQuery] string? dateTo = null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
 | 
					            _logger.LogInfo("GetTasksList called for projectId: {ProjectId}, dateFrom: {DateFrom}, dateTo: {DateTo}", projectId, dateFrom ?? "", dateTo ?? "");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            Guid tenantId = GetTenantId();
 | 
					            Guid tenantId = GetTenantId();
 | 
				
			||||||
            DateTime fromDate = new DateTime();
 | 
					            DateTime fromDate = new DateTime();
 | 
				
			||||||
            DateTime toDate = new DateTime();
 | 
					            DateTime toDate = new DateTime();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (dateFrom != null && DateTime.TryParse(dateFrom, out fromDate) == false)
 | 
					            // Parse and validate dateFrom
 | 
				
			||||||
 | 
					            if (dateFrom != null && !DateTime.TryParse(dateFrom, out fromDate))
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogWarning("Invalid starting date provided: {DateFrom}", dateFrom);
 | 
				
			||||||
                return BadRequest(ApiResponse<object>.ErrorResponse("Invalid starting date.", "Invalid starting date.", 400));
 | 
					                return BadRequest(ApiResponse<object>.ErrorResponse("Invalid starting date.", "Invalid starting date.", 400));
 | 
				
			||||||
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            if (dateTo != null && DateTime.TryParse(dateTo, out toDate) == false)
 | 
					
 | 
				
			||||||
 | 
					            // Parse and validate dateTo
 | 
				
			||||||
 | 
					            if (dateTo != null && !DateTime.TryParse(dateTo, out toDate))
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogWarning("Invalid ending date provided: {DateTo}", dateTo);
 | 
				
			||||||
                return BadRequest(ApiResponse<object>.ErrorResponse("Invalid ending date.", "Invalid ending date.", 400));
 | 
					                return BadRequest(ApiResponse<object>.ErrorResponse("Invalid ending date.", "Invalid ending date.", 400));
 | 
				
			||||||
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            if (dateFrom == null) fromDate = DateTime.UtcNow.Date;
 | 
					 | 
				
			||||||
            if (dateTo == null) toDate = fromDate.AddDays(1);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var jobroles = await _context.JobRoles.Where(r => r.TenantId == tenantId).ToListAsync();
 | 
					            // Set default date range if not provided
 | 
				
			||||||
            //var taskAllocations = await _context.TaskAllocations.Where(t => t.WorkItem.WorkArea.Floor.Building.ProjectId == projectId && t.AssignmentDate >= fromDate && t.AssignmentDate <= toDate && t.TenantId == tenantId).Include(t => t.WorkItemId).ToListAsync();
 | 
					            fromDate = dateFrom == null ? DateTime.UtcNow.Date : fromDate;
 | 
				
			||||||
            List<Building> buildings = await _context.Buildings.Where(b => b.ProjectId == projectId && b.TenantId == tenantId).ToListAsync();
 | 
					            toDate = dateTo == null ? fromDate.AddDays(1) : toDate;
 | 
				
			||||||
            List<Guid> idList = buildings.Select(b => b.Id).ToList();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            List<Floor> floors = await _context.Floor.Where(f => idList.Contains(f.BuildingId) && f.TenantId == tenantId).ToListAsync();
 | 
					            // 1. Get all buildings under this project
 | 
				
			||||||
            idList = floors.Select(f => f.Id).ToList();
 | 
					            _logger.LogInfo("Fetching buildings for projectId: {ProjectId}", projectId);
 | 
				
			||||||
 | 
					            var buildings = await _context.Buildings
 | 
				
			||||||
 | 
					                .Where(b => b.ProjectId == projectId && b.TenantId == tenantId)
 | 
				
			||||||
 | 
					                .ToListAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            List<WorkArea> workAreas = await _context.WorkAreas.Where(a => idList.Contains(a.FloorId) && a.TenantId == tenantId).ToListAsync();
 | 
					            var buildingIds = buildings.Select(b => b.Id).ToList();
 | 
				
			||||||
            idList = workAreas.Select(a => a.Id).ToList();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            List<WorkItem> workItems = await _context.WorkItems.Where(i => idList.Contains(i.WorkAreaId) && i.TenantId == tenantId).Include(i => i.ActivityMaster).ToListAsync();
 | 
					            // 2. Get floors under the buildings
 | 
				
			||||||
            idList = workItems.Select(i => i.Id).ToList();
 | 
					            var floors = await _context.Floor
 | 
				
			||||||
            var activityIdList = workItems.Select(i => i.ActivityId).ToList();
 | 
					                .Where(f => buildingIds.Contains(f.BuildingId) && f.TenantId == tenantId)
 | 
				
			||||||
 | 
					                .ToListAsync();
 | 
				
			||||||
 | 
					            var floorIds = floors.Select(f => f.Id).ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            List<TaskAllocation> taskAllocations = await _context.TaskAllocations.Where(t => idList.Contains(t.WorkItemId) && t.AssignmentDate.Date >= fromDate.Date && t.AssignmentDate.Date <= toDate.Date && t.TenantId == tenantId).Include(t => t.WorkItem).Include(t => t.Employee).ToListAsync();
 | 
					            // 3. Get work areas under the floors
 | 
				
			||||||
            var taskIdList = taskAllocations.Select(t => t.Id).ToList();
 | 
					            var workAreas = await _context.WorkAreas
 | 
				
			||||||
 | 
					                .Where(a => floorIds.Contains(a.FloorId) && a.TenantId == tenantId)
 | 
				
			||||||
 | 
					                .ToListAsync();
 | 
				
			||||||
 | 
					            var workAreaIds = workAreas.Select(a => a.Id).ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            List<TaskMembers> teamMembers = await _context.TaskMembers.Where(t => taskIdList.Contains(t.TaskAllocationId)).ToListAsync();
 | 
					            // 4. Get work items under the work areas
 | 
				
			||||||
            var employeeIdList = teamMembers.Select(e => e.EmployeeId).ToList();
 | 
					            var workItems = await _context.WorkItems
 | 
				
			||||||
 | 
					                .Where(i => workAreaIds.Contains(i.WorkAreaId) && i.TenantId == tenantId)
 | 
				
			||||||
 | 
					                .Include(i => i.ActivityMaster)
 | 
				
			||||||
 | 
					                .ToListAsync();
 | 
				
			||||||
 | 
					            var workItemIds = workItems.Select(i => i.Id).ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            _logger.LogInfo("Fetching task allocations between {FromDate} and {ToDate}", fromDate, toDate);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // 5. Get task allocations in the specified date range
 | 
				
			||||||
 | 
					            var taskAllocations = await _context.TaskAllocations
 | 
				
			||||||
 | 
					                .Include(t => t.Employee)
 | 
				
			||||||
 | 
					                .Include(t => t.ReportedBy)
 | 
				
			||||||
 | 
					                .Include(t => t.ApprovedBy)
 | 
				
			||||||
 | 
					                .Include(t => t.WorkStatus)
 | 
				
			||||||
 | 
					                .Include(t => t.WorkItem)
 | 
				
			||||||
 | 
					                .Where(t => workItemIds.Contains(t.WorkItemId) &&
 | 
				
			||||||
 | 
					                            t.AssignmentDate.Date >= fromDate.Date &&
 | 
				
			||||||
 | 
					                            t.AssignmentDate.Date <= toDate.Date &&
 | 
				
			||||||
 | 
					                            t.TenantId == tenantId)
 | 
				
			||||||
 | 
					                .ToListAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var taskIds = taskAllocations.Select(t => t.Id).ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // 6. Load team members
 | 
				
			||||||
 | 
					            _logger.LogInfo("Loading task members and related employee data.");
 | 
				
			||||||
 | 
					            var teamMembers = await _context.TaskMembers
 | 
				
			||||||
 | 
					                .Include(t => t.Employee)
 | 
				
			||||||
 | 
					                .Where(t => taskIds.Contains(t.TaskAllocationId))
 | 
				
			||||||
 | 
					                .ToListAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // 7. Load task comments
 | 
				
			||||||
 | 
					            _logger.LogInfo("Fetching comments and attachments.");
 | 
				
			||||||
 | 
					            var allComments = await _context.TaskComments
 | 
				
			||||||
 | 
					                .Include(c => c.Employee)
 | 
				
			||||||
 | 
					                .Where(c => taskIds.Contains(c.TaskAllocationId))
 | 
				
			||||||
 | 
					                .ToListAsync();
 | 
				
			||||||
 | 
					            var commentIds = allComments.Select(c => c.Id).ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // 8. Load all attachments (task and comment)
 | 
				
			||||||
 | 
					            var attachments = await _context.TaskAttachments
 | 
				
			||||||
 | 
					                .Where(t => taskIds.Contains(t.ReferenceId) || commentIds.Contains(t.ReferenceId))
 | 
				
			||||||
 | 
					                .ToListAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var documentIds = attachments.Select(t => t.DocumentId).ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // 9. Load actual documents from attachment references
 | 
				
			||||||
 | 
					            var documents = await _context.Documents
 | 
				
			||||||
 | 
					                .Where(d => documentIds.Contains(d.Id))
 | 
				
			||||||
 | 
					                .ToListAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var tasks = new List<ListTaskVM>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            _logger.LogInfo("Constructing task response data.");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            List<Employee> employees = await _context.Employees.Where(e => employeeIdList.Contains(e.Id)).Include(e => e.JobRole).ToListAsync();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            List<ListTaskVM> tasks = new List<ListTaskVM>();
 | 
					 | 
				
			||||||
            //foreach (var workItem in workItems)
 | 
					 | 
				
			||||||
            //{
 | 
					 | 
				
			||||||
            foreach (var taskAllocation in taskAllocations)
 | 
					            foreach (var taskAllocation in taskAllocations)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
 | 
					 | 
				
			||||||
                var response = taskAllocation.ToListTaskVMFromTaskAllocation();
 | 
					                var response = taskAllocation.ToListTaskVMFromTaskAllocation();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                List<TaskComment> comments = await _context.TaskComments.Where(c => c.TaskAllocationId == taskAllocation.Id).ToListAsync();
 | 
					                // Attach documents to the main task
 | 
				
			||||||
                List<BasicEmployeeVM> team = new List<BasicEmployeeVM>();
 | 
					                var taskDocIds = attachments
 | 
				
			||||||
                List<TaskMembers> taskMembers = teamMembers.Where(m => m.TaskAllocationId == taskAllocation.Id).ToList();
 | 
					                    .Where(a => a.ReferenceId == taskAllocation.Id)
 | 
				
			||||||
 | 
					                    .Select(a => a.DocumentId)
 | 
				
			||||||
 | 
					                    .ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                foreach (var taskMember in taskMembers)
 | 
					                var taskDocs = documents
 | 
				
			||||||
 | 
					                    .Where(d => taskDocIds.Contains(d.Id))
 | 
				
			||||||
 | 
					                    .ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                response.ReportedPreSignedUrls = taskDocs
 | 
				
			||||||
 | 
					                    .Select(d => _s3Service.GeneratePreSignedUrlAsync(d.S3Key))
 | 
				
			||||||
 | 
					                    .ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Add team members
 | 
				
			||||||
 | 
					                var taskMemberEntries = teamMembers
 | 
				
			||||||
 | 
					                    .Where(m => m.TaskAllocationId == taskAllocation.Id)
 | 
				
			||||||
 | 
					                    .ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                response.teamMembers = taskMemberEntries
 | 
				
			||||||
 | 
					                    .Select(m => m.Employee)
 | 
				
			||||||
 | 
					                    .Where(e => e != null)
 | 
				
			||||||
 | 
					                    .Select(e => e!.ToBasicEmployeeVMFromEmployee())
 | 
				
			||||||
 | 
					                    .ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Add comments with attachments
 | 
				
			||||||
 | 
					                var commentVMs = new List<CommentVM>();
 | 
				
			||||||
 | 
					                var taskComments = allComments
 | 
				
			||||||
 | 
					                    .Where(c => c.TaskAllocationId == taskAllocation.Id)
 | 
				
			||||||
 | 
					                    .ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                foreach (var comment in taskComments)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    var teamMember = employees.Find(e => e.Id == taskMember.EmployeeId);
 | 
					                    var commentDocIds = attachments
 | 
				
			||||||
                    if (teamMember != null)
 | 
					                        .Where(a => a.ReferenceId == comment.Id)
 | 
				
			||||||
                    {
 | 
					                        .Select(a => a.DocumentId)
 | 
				
			||||||
                        team.Add(teamMember.ToBasicEmployeeVMFromEmployee());
 | 
					                        .ToList();
 | 
				
			||||||
                    }
 | 
					
 | 
				
			||||||
 | 
					                    var commentDocs = documents
 | 
				
			||||||
 | 
					                        .Where(d => commentDocIds.Contains(d.Id))
 | 
				
			||||||
 | 
					                        .ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    var commentVm = comment.ToCommentVMFromTaskComment();
 | 
				
			||||||
 | 
					                    commentVm.PreSignedUrls = commentDocs
 | 
				
			||||||
 | 
					                        .Select(d => _s3Service.GeneratePreSignedUrlAsync(d.S3Key))
 | 
				
			||||||
 | 
					                        .ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    commentVMs.Add(commentVm);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                List<CommentVM> commentVM = new List<CommentVM> { };
 | 
					
 | 
				
			||||||
                foreach (var comment in comments)
 | 
					                response.comments = commentVMs;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Checklists
 | 
				
			||||||
 | 
					                var activityId = taskAllocation.WorkItem?.ActivityId ?? Guid.Empty;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                var checkLists = await _context.ActivityCheckLists
 | 
				
			||||||
 | 
					                    .Where(c => c.ActivityId == activityId)
 | 
				
			||||||
 | 
					                    .ToListAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                var checkListMappings = await _context.CheckListMappings
 | 
				
			||||||
 | 
					                    .Where(c => c.TaskAllocationId == taskAllocation.Id)
 | 
				
			||||||
 | 
					                    .ToListAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                response.CheckList = checkLists.Select(check =>
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    commentVM.Add(comment.ToCommentVMFromTaskComment());
 | 
					                    var isChecked = checkListMappings.Any(m => m.CheckListId == check.Id);
 | 
				
			||||||
                }
 | 
					                    return check.ToCheckListVMFromActivityCheckList(check.ActivityId, isChecked);
 | 
				
			||||||
                List<ActivityCheckList> checkLists = await _context.ActivityCheckLists.Where(x => x.ActivityId == (taskAllocation.WorkItem != null ? taskAllocation.WorkItem.ActivityId : Guid.Empty)).ToListAsync();
 | 
					                }).ToList();
 | 
				
			||||||
                List<CheckListMappings> checkListMappings = await _context.CheckListMappings.Where(c => c.TaskAllocationId == taskAllocation.Id).ToListAsync();
 | 
					
 | 
				
			||||||
                List<CheckListVM> checkList = new List<CheckListVM>();
 | 
					 | 
				
			||||||
                foreach (var check in checkLists)
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    var checkListMapping = checkListMappings.Find(c => c.CheckListId == check.Id);
 | 
					 | 
				
			||||||
                    if (checkListMapping != null)
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        checkList.Add(check.ToCheckListVMFromActivityCheckList(check.ActivityId, true));
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    else
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        checkList.Add(check.ToCheckListVMFromActivityCheckList(check.ActivityId, false));
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                response.comments = commentVM;
 | 
					 | 
				
			||||||
                response.teamMembers = team;
 | 
					 | 
				
			||||||
                response.CheckList = checkList;
 | 
					 | 
				
			||||||
                tasks.Add(response);
 | 
					                tasks.Add(response);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            //}
 | 
					            _logger.LogInfo("Task list constructed successfully. Returning {Count} tasks.", tasks.Count);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return Ok(ApiResponse<object>.SuccessResponse(tasks, "Success", 200));
 | 
					            return Ok(ApiResponse<object>.SuccessResponse(tasks, "Success", 200));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [HttpGet("get/{taskId}")]
 | 
					        [HttpGet("get/{taskId}")]
 | 
				
			||||||
        public async Task<IActionResult> GetTask(Guid taskId)
 | 
					        public async Task<IActionResult> GetTask(Guid taskId)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (taskId == Guid.Empty) return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", "Invalid data", 400));
 | 
					            _logger.LogInfo("GetTask called with taskId: {TaskId}", taskId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var taskAllocation = await _context.TaskAllocations.Include(t => t.Tenant).Include(t => t.Employee).Include(t => t.WorkItem).FirstOrDefaultAsync(t => t.Id == taskId);
 | 
					            // Validate input
 | 
				
			||||||
            if (taskAllocation != null && taskAllocation.Employee != null && taskAllocation.Tenant != null)
 | 
					            if (taskId == Guid.Empty)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                //var employee = await _context.Employees.FirstOrDefaultAsync(e => e.Id == taskAllocation.AssignedBy);
 | 
					                _logger.LogWarning("Invalid taskId provided.");
 | 
				
			||||||
                string employeeName = System.String.Format("{0} {1}", taskAllocation.Employee.FirstName, taskAllocation.Employee.LastName);
 | 
					                return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", "Invalid data", 400));
 | 
				
			||||||
                string tenantName = taskAllocation.Tenant.ContactName ?? string.Empty;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (taskAllocation == null) return NotFound(ApiResponse<object>.ErrorResponse("Task Not Found", "Task not found", 404));
 | 
					 | 
				
			||||||
                var taskVM = taskAllocation.TaskAllocationToTaskVM(employeeName);
 | 
					 | 
				
			||||||
                var comments = await _context.TaskComments.Where(c => c.TaskAllocationId == taskAllocation.Id).ToListAsync();
 | 
					 | 
				
			||||||
                var team = await _context.TaskMembers.Where(m => m.TaskAllocationId == taskAllocation.Id).Include(m => m.Employee).ToListAsync();
 | 
					 | 
				
			||||||
                var teamMembers = new List<EmployeeVM> { };
 | 
					 | 
				
			||||||
                foreach (var member in team)
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    var result = member.Employee != null ? member.Employee.ToEmployeeVMFromEmployee() : new EmployeeVM();
 | 
					 | 
				
			||||||
                    teamMembers.Add(result);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                List<CommentVM> Comments = new List<CommentVM> { };
 | 
					 | 
				
			||||||
                foreach (var comment in comments)
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    Comments.Add(comment.ToCommentVMFromTaskComment());
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                taskVM.Comments = Comments;
 | 
					 | 
				
			||||||
                taskVM.TeamMembers = teamMembers;
 | 
					 | 
				
			||||||
                return Ok(ApiResponse<object>.SuccessResponse(taskVM, "Success", 200));
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Fetch Task Allocation with required related data
 | 
				
			||||||
 | 
					            var taskAllocation = await _context.TaskAllocations
 | 
				
			||||||
 | 
					                .Include(t => t.Tenant)
 | 
				
			||||||
 | 
					                .Include(t => t.Employee)
 | 
				
			||||||
 | 
					                .Include(t => t.ReportedBy)
 | 
				
			||||||
 | 
					                .Include(t => t.ApprovedBy)
 | 
				
			||||||
 | 
					                .Include(t => t.WorkItem)
 | 
				
			||||||
 | 
					                .Include(t => t.WorkStatus)
 | 
				
			||||||
 | 
					                .FirstOrDefaultAsync(t => t.Id == taskId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return NotFound(ApiResponse<object>.ErrorResponse("Task Not Found", "Task not Found", 404));
 | 
					            if (taskAllocation == null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogWarning("Task not found for taskId: {TaskId}", taskId);
 | 
				
			||||||
 | 
					                return NotFound(ApiResponse<object>.ErrorResponse("Task Not Found", "Task not found", 404));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (taskAllocation.Employee == null || taskAllocation.Tenant == null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogWarning("Task found but missing Employee or Tenant data for taskId: {TaskId}", taskId);
 | 
				
			||||||
 | 
					                return NotFound(ApiResponse<object>.ErrorResponse("Task Not Found", "Task not found", 404));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            _logger.LogInfo("Task allocation found. Preparing response.");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var taskVM = taskAllocation.TaskAllocationToTaskVM();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Fetch comments and attachments
 | 
				
			||||||
 | 
					            _logger.LogInfo("Fetching comments and attachments for taskId: {TaskId}", taskId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var comments = await _context.TaskComments
 | 
				
			||||||
 | 
					                .Where(c => c.TaskAllocationId == taskId)
 | 
				
			||||||
 | 
					                .Include(c => c.Employee)
 | 
				
			||||||
 | 
					                .ToListAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var commentIds = comments.Select(c => c.Id).ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var taskAttachments = await _context.TaskAttachments
 | 
				
			||||||
 | 
					                .Where(t => t.ReferenceId == taskId || commentIds.Contains(t.ReferenceId))
 | 
				
			||||||
 | 
					                .ToListAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var documentIds = taskAttachments.Select(t => t.DocumentId).Distinct().ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var documents = await _context.Documents
 | 
				
			||||||
 | 
					                .Where(d => documentIds.Contains(d.Id))
 | 
				
			||||||
 | 
					                .ToListAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Fetch team members
 | 
				
			||||||
 | 
					            _logger.LogInfo("Fetching team members for taskId: {TaskId}", taskId);
 | 
				
			||||||
 | 
					            var team = await _context.TaskMembers
 | 
				
			||||||
 | 
					                .Where(m => m.TaskAllocationId == taskId)
 | 
				
			||||||
 | 
					                .Include(m => m.Employee)
 | 
				
			||||||
 | 
					                .ToListAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var teamMembers = team
 | 
				
			||||||
 | 
					                .Where(m => m.Employee != null)
 | 
				
			||||||
 | 
					                .Select(m => m.Employee!.ToBasicEmployeeVMFromEmployee())
 | 
				
			||||||
 | 
					                .ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            taskVM.TeamMembers = teamMembers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Attach documents to the main task
 | 
				
			||||||
 | 
					            _logger.LogInfo("Generating presigned URLs for task documents.");
 | 
				
			||||||
 | 
					            var taskDocumentIds = taskAttachments
 | 
				
			||||||
 | 
					                .Where(t => t.ReferenceId == taskId)
 | 
				
			||||||
 | 
					                .Select(t => t.DocumentId)
 | 
				
			||||||
 | 
					                .ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var taskDocuments = documents
 | 
				
			||||||
 | 
					                .Where(d => taskDocumentIds.Contains(d.Id))
 | 
				
			||||||
 | 
					                .ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            taskVM.PreSignedUrls = taskDocuments
 | 
				
			||||||
 | 
					                .Select(d => _s3Service.GeneratePreSignedUrlAsync(d.S3Key))
 | 
				
			||||||
 | 
					                .ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Construct CommentVM list with document URLs
 | 
				
			||||||
 | 
					            _logger.LogInfo("Preparing comment response data.");
 | 
				
			||||||
 | 
					            var commentVMs = comments.Select(comment =>
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                var commentDocIds = taskAttachments
 | 
				
			||||||
 | 
					                    .Where(t => t.ReferenceId == comment.Id)
 | 
				
			||||||
 | 
					                    .Select(t => t.DocumentId)
 | 
				
			||||||
 | 
					                    .ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                var commentDocs = documents
 | 
				
			||||||
 | 
					                    .Where(d => commentDocIds.Contains(d.Id))
 | 
				
			||||||
 | 
					                    .ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                var commentVM = comment.ToCommentVMFromTaskComment();
 | 
				
			||||||
 | 
					                commentVM.PreSignedUrls = commentDocs
 | 
				
			||||||
 | 
					                    .Select(d => _s3Service.GeneratePreSignedUrlAsync(d.S3Key))
 | 
				
			||||||
 | 
					                    .ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return commentVM;
 | 
				
			||||||
 | 
					            }).ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            taskVM.Comments = commentVMs;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            _logger.LogInfo("Task details prepared successfully for taskId: {TaskId}", taskId);
 | 
				
			||||||
 | 
					            return Ok(ApiResponse<object>.SuccessResponse(taskVM, "Success", 200));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Approves a reported task after validation, updates status, and stores attachments/comments.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="approveTask">DTO containing task approval details.</param>
 | 
				
			||||||
 | 
					        /// <returns>IActionResult indicating success or failure.</returns>
 | 
				
			||||||
 | 
					        [HttpPost("approve")]
 | 
				
			||||||
 | 
					        public async Task<IActionResult> ApproveTask(ApproveTaskDto approveTask)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            Guid tenantId = _userHelper.GetTenantId();
 | 
				
			||||||
 | 
					            var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            _logger.LogInfo("Employee {EmployeeId} is attempting to approve Task {TaskId}", loggedInEmployee.Id, approveTask.Id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Fetch task allocation with work item, only if it's reported
 | 
				
			||||||
 | 
					            var taskAllocation = await _context.TaskAllocations
 | 
				
			||||||
 | 
					                .Include(t => t.WorkItem)
 | 
				
			||||||
 | 
					                .FirstOrDefaultAsync(t => t.Id == approveTask.Id && t.TenantId == tenantId && t.ReportedDate != null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (taskAllocation == null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogWarning("Task {TaskId} not found or not reported yet for Tenant {TenantId} by Employee {EmployeeId}",
 | 
				
			||||||
 | 
					                    approveTask.Id, tenantId, loggedInEmployee.Id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return NotFound(ApiResponse<object>.ErrorResponse("Task not found", "Task not found", 404));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Check for permission to approve tasks
 | 
				
			||||||
 | 
					            var hasPermission = await _permissionServices.HasPermission(Approve_Task, loggedInEmployee.Id);
 | 
				
			||||||
 | 
					            if (!hasPermission)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogWarning("Employee {EmployeeId} attempted to approve Task {TaskId} without permission", loggedInEmployee.Id, approveTask.Id);
 | 
				
			||||||
 | 
					                return StatusCode(403, ApiResponse<object>.ErrorResponse("You don't have access", "User not authorized to approve tasks", 403));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Validation: Approved task count cannot exceed completed task count
 | 
				
			||||||
 | 
					            if (taskAllocation.CompletedTask < approveTask.ApprovedTask)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogWarning("Invalid approval attempt on Task {TaskId}: Approved tasks ({ApprovedTask}) > Completed tasks ({CompletedTask})",
 | 
				
			||||||
 | 
					                    approveTask.Id, approveTask.ApprovedTask, taskAllocation.CompletedTask);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return BadRequest(ApiResponse<object>.ErrorResponse("Approved tasks cannot be greater than completed tasks",
 | 
				
			||||||
 | 
					                    "Approved tasks cannot be greater than completed tasks", 400));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            //// Update completed work in the associated work item, if it exists
 | 
				
			||||||
 | 
					            //if (taskAllocation.WorkItem != null && taskAllocation.CompletedTask != approveTask.ApprovedTask)
 | 
				
			||||||
 | 
					            //{
 | 
				
			||||||
 | 
					            //    if (taskAllocation.CompletedTask > 0)
 | 
				
			||||||
 | 
					            //    {
 | 
				
			||||||
 | 
					            //        taskAllocation.WorkItem.CompletedWork -= taskAllocation.CompletedTask;
 | 
				
			||||||
 | 
					            //    }
 | 
				
			||||||
 | 
					            //    taskAllocation.WorkItem.CompletedWork += approveTask.ApprovedTask;
 | 
				
			||||||
 | 
					            //}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Update task allocation details
 | 
				
			||||||
 | 
					            taskAllocation.ApprovedById = loggedInEmployee.Id;
 | 
				
			||||||
 | 
					            taskAllocation.ApprovedDate = DateTime.UtcNow;
 | 
				
			||||||
 | 
					            taskAllocation.WorkStatusId = approveTask.WorkStatus;
 | 
				
			||||||
 | 
					            taskAllocation.ReportedTask = approveTask.ApprovedTask;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Add a comment (optional)
 | 
				
			||||||
 | 
					            var comment = new TaskComment
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                TaskAllocationId = taskAllocation.Id,
 | 
				
			||||||
 | 
					                CommentDate = DateTime.UtcNow,
 | 
				
			||||||
 | 
					                Comment = approveTask.Comment ?? string.Empty,
 | 
				
			||||||
 | 
					                CommentedBy = loggedInEmployee.Id,
 | 
				
			||||||
 | 
					                TenantId = tenantId
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            _context.TaskComments.Add(comment);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Handle image attachments, if any
 | 
				
			||||||
 | 
					            if (approveTask.Images?.Count > 0)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                var workAreaId = taskAllocation.WorkItem?.WorkAreaId;
 | 
				
			||||||
 | 
					                var workArea = await _context.WorkAreas.Include(a => a.Floor)
 | 
				
			||||||
 | 
					                    .FirstOrDefaultAsync(a => a.Id == workAreaId) ?? new WorkArea();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                var buildingId = workArea.Floor?.BuildingId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                var building = await _context.Buildings
 | 
				
			||||||
 | 
					                    .FirstOrDefaultAsync(b => b.Id == buildingId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                foreach (var image in approveTask.Images)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    if (string.IsNullOrEmpty(image.Base64Data))
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        _logger.LogWarning("Image for Task {TaskId} is missing base64 data", approveTask.Id);
 | 
				
			||||||
 | 
					                        return BadRequest(ApiResponse<object>.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400));
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    var base64 = image.Base64Data.Contains(",") ? image.Base64Data[(image.Base64Data.IndexOf(",") + 1)..] : image.Base64Data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    var fileType = _s3Service.GetContentTypeFromBase64(base64);
 | 
				
			||||||
 | 
					                    var fileName = _s3Service.GenerateFileName(fileType, tenantId, "task_comment");
 | 
				
			||||||
 | 
					                    var objectKey = $"tenant-{tenantId}/project-{building?.ProjectId}/Activity/{fileName}";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    await _s3Service.UploadFileAsync(base64, fileType, objectKey);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    var document = new Document
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        FileName = fileName,
 | 
				
			||||||
 | 
					                        ContentType = image.ContentType ?? string.Empty,
 | 
				
			||||||
 | 
					                        S3Key = objectKey,
 | 
				
			||||||
 | 
					                        Base64Data = image.Base64Data,
 | 
				
			||||||
 | 
					                        FileSize = image.FileSize,
 | 
				
			||||||
 | 
					                        UploadedAt = DateTime.UtcNow,
 | 
				
			||||||
 | 
					                        TenantId = tenantId
 | 
				
			||||||
 | 
					                    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    _context.Documents.Add(document);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    var attachment = new TaskAttachment
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        DocumentId = document.Id,
 | 
				
			||||||
 | 
					                        ReferenceId = comment.Id
 | 
				
			||||||
 | 
					                    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    _context.TaskAttachments.Add(attachment);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    _logger.LogInfo("Attachment uploaded for Task {TaskId}: {FileName}", approveTask.Id, fileName);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Commit all changes to the database
 | 
				
			||||||
 | 
					            await _context.SaveChangesAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            _logger.LogInfo("Task {TaskId} successfully approved by Employee {EmployeeId}", approveTask.Id, loggedInEmployee.Id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return Ok(ApiResponse<object>.SuccessResponse("Task has been approved", "Task has been approved", 200));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -3,8 +3,10 @@ using Marco.Pms.Model.Directory;
 | 
				
			|||||||
using Marco.Pms.Model.Dtos.Master;
 | 
					using Marco.Pms.Model.Dtos.Master;
 | 
				
			||||||
using Marco.Pms.Model.Employees;
 | 
					using Marco.Pms.Model.Employees;
 | 
				
			||||||
using Marco.Pms.Model.Mapper;
 | 
					using Marco.Pms.Model.Mapper;
 | 
				
			||||||
 | 
					using Marco.Pms.Model.Master;
 | 
				
			||||||
using Marco.Pms.Model.Utilities;
 | 
					using Marco.Pms.Model.Utilities;
 | 
				
			||||||
using Marco.Pms.Model.ViewModels.Master;
 | 
					using Marco.Pms.Model.ViewModels.Master;
 | 
				
			||||||
 | 
					using Marco.Pms.Services.Service;
 | 
				
			||||||
using MarcoBMS.Services.Helpers;
 | 
					using MarcoBMS.Services.Helpers;
 | 
				
			||||||
using MarcoBMS.Services.Service;
 | 
					using MarcoBMS.Services.Service;
 | 
				
			||||||
using Microsoft.EntityFrameworkCore;
 | 
					using Microsoft.EntityFrameworkCore;
 | 
				
			||||||
@ -16,13 +18,19 @@ namespace Marco.Pms.Services.Helpers
 | 
				
			|||||||
        private readonly ApplicationDbContext _context;
 | 
					        private readonly ApplicationDbContext _context;
 | 
				
			||||||
        private readonly ILoggingService _logger;
 | 
					        private readonly ILoggingService _logger;
 | 
				
			||||||
        private readonly UserHelper _userHelper;
 | 
					        private readonly UserHelper _userHelper;
 | 
				
			||||||
 | 
					        private readonly PermissionServices _permissionService;
 | 
				
			||||||
 | 
					        private readonly Guid View_Master;
 | 
				
			||||||
 | 
					        private readonly Guid Manage_Master;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public MasterHelper(ApplicationDbContext context, ILoggingService logger, UserHelper userHelper)
 | 
					        public MasterHelper(ApplicationDbContext context, ILoggingService logger, UserHelper userHelper, PermissionServices permissionServices)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            _context = context;
 | 
					            _context = context;
 | 
				
			||||||
            _logger = logger;
 | 
					            _logger = logger;
 | 
				
			||||||
            _userHelper = userHelper;
 | 
					            _userHelper = userHelper;
 | 
				
			||||||
 | 
					            _permissionService = permissionServices;
 | 
				
			||||||
 | 
					            View_Master = Guid.Parse("5ffbafe0-7ab0-48b1-bb50-c1bf76b65f9d");
 | 
				
			||||||
 | 
					            Manage_Master = Guid.Parse("588a8824-f924-4955-82d8-fc51956cf323");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        // -------------------------------- Contact Category  --------------------------------
 | 
					        // -------------------------------- Contact Category  --------------------------------
 | 
				
			||||||
        public async Task<ApiResponse<object>> CreateContactCategory(CreateContactCategoryDto contactCategoryDto)
 | 
					        public async Task<ApiResponse<object>> CreateContactCategory(CreateContactCategoryDto contactCategoryDto)
 | 
				
			||||||
@ -247,5 +255,215 @@ namespace Marco.Pms.Services.Helpers
 | 
				
			|||||||
            return ApiResponse<object>.SuccessResponse(new { }, "Tag deleted successfully", 200);
 | 
					            return ApiResponse<object>.SuccessResponse(new { }, "Tag deleted successfully", 200);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // -------------------------------- Work Status  --------------------------------
 | 
				
			||||||
 | 
					        public async Task<ApiResponse<object>> GetWorkStatusList()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            _logger.LogInfo("GetWorkStatusList called.");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            try
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                // Step 1: Get tenant and logged-in employee info
 | 
				
			||||||
 | 
					                Guid tenantId = _userHelper.GetTenantId();
 | 
				
			||||||
 | 
					                var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Step 2: Check permission to view master data
 | 
				
			||||||
 | 
					                bool hasViewPermission = await _permissionService.HasPermission(View_Master, loggedInEmployee.Id);
 | 
				
			||||||
 | 
					                if (!hasViewPermission)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    _logger.LogWarning("Access denied for employeeId: {EmployeeId}", loggedInEmployee.Id);
 | 
				
			||||||
 | 
					                    return ApiResponse<object>.ErrorResponse("You don't have access", "Don't have access to take action", 403);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Step 3: Fetch work statuses for the tenant
 | 
				
			||||||
 | 
					                var workStatusList = await _context.WorkStatusMasters
 | 
				
			||||||
 | 
					                    .Where(ws => ws.TenantId == tenantId)
 | 
				
			||||||
 | 
					                    .Select(ws => new
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        ws.Id,
 | 
				
			||||||
 | 
					                        ws.Name,
 | 
				
			||||||
 | 
					                        ws.Description,
 | 
				
			||||||
 | 
					                        ws.IsSystem
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                    .ToListAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                _logger.LogInfo("{Count} work statuses fetched for tenantId: {TenantId}", workStatusList.Count, tenantId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Step 4: Return successful response
 | 
				
			||||||
 | 
					                return ApiResponse<object>.SuccessResponse(
 | 
				
			||||||
 | 
					                    workStatusList,
 | 
				
			||||||
 | 
					                    $"{workStatusList.Count} work status records fetched successfully",
 | 
				
			||||||
 | 
					                    200
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            catch (Exception ex)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogError("Error occurred while fetching work status list : {Error}", ex.Message);
 | 
				
			||||||
 | 
					                return ApiResponse<object>.ErrorResponse("An error occurred", "Unable to fetch work status list", 500);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        public async Task<ApiResponse<object>> CreateWorkStatus(CreateWorkStatusMasterDto createWorkStatusDto)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            _logger.LogInfo("CreateWorkStatus called with Name: {Name}", createWorkStatusDto.Name ?? "");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            try
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                // Step 1: Get tenant and logged-in employee
 | 
				
			||||||
 | 
					                Guid tenantId = _userHelper.GetTenantId();
 | 
				
			||||||
 | 
					                var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Step 2: Check if user has permission to manage master data
 | 
				
			||||||
 | 
					                var hasManageMasterPermission = await _permissionService.HasPermission(Manage_Master, loggedInEmployee.Id);
 | 
				
			||||||
 | 
					                if (!hasManageMasterPermission)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    _logger.LogWarning("Access denied for employeeId: {EmployeeId}", loggedInEmployee.Id);
 | 
				
			||||||
 | 
					                    return ApiResponse<object>.ErrorResponse("You don't have access", "Don't have access to take action", 403);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Step 3: Check if work status with the same name already exists
 | 
				
			||||||
 | 
					                var existingWorkStatus = await _context.WorkStatusMasters
 | 
				
			||||||
 | 
					                    .FirstOrDefaultAsync(ws => ws.Name == createWorkStatusDto.Name && ws.TenantId == tenantId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (existingWorkStatus != null)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    _logger.LogWarning("Work status already exists: {Name}", createWorkStatusDto.Name ?? "");
 | 
				
			||||||
 | 
					                    return ApiResponse<object>.ErrorResponse("Work status already exists", "Work status already exists", 400);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Step 4: Create new WorkStatusMaster entry
 | 
				
			||||||
 | 
					                var workStatus = new WorkStatusMaster
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    Name = createWorkStatusDto.Name?.Trim() ?? "",
 | 
				
			||||||
 | 
					                    Description = createWorkStatusDto.Description?.Trim() ?? "",
 | 
				
			||||||
 | 
					                    IsSystem = false,
 | 
				
			||||||
 | 
					                    TenantId = tenantId
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                _context.WorkStatusMasters.Add(workStatus);
 | 
				
			||||||
 | 
					                await _context.SaveChangesAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                _logger.LogInfo("Work status created successfully: {Id}, Name: {Name}", workStatus.Id, workStatus.Name);
 | 
				
			||||||
 | 
					                return ApiResponse<object>.SuccessResponse(workStatus, "Work status created successfully", 200);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            catch (Exception ex)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogError("Error occurred while creating work status : {Error}", ex.Message);
 | 
				
			||||||
 | 
					                return ApiResponse<object>.ErrorResponse("An error occurred", "Unable to create work status", 500);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        public async Task<ApiResponse<object>> UpdateWorkStatus(Guid id, UpdateWorkStatusMasterDto updateWorkStatusDto)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            _logger.LogInfo("UpdateWorkStatus called for WorkStatus ID: {Id}, New Name: {Name}", id, updateWorkStatusDto.Name ?? "");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            try
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                // Step 1: Validate input
 | 
				
			||||||
 | 
					                if (id == Guid.Empty || id != updateWorkStatusDto.Id)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    _logger.LogWarning("Invalid ID provided for update. Route ID: {RouteId}, DTO ID: {DtoId}", id, updateWorkStatusDto.Id);
 | 
				
			||||||
 | 
					                    return ApiResponse<object>.ErrorResponse("Invalid data provided", "The provided work status ID is invalid", 400);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Step 2: Get tenant and logged-in employee
 | 
				
			||||||
 | 
					                Guid tenantId = _userHelper.GetTenantId();
 | 
				
			||||||
 | 
					                var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Step 3: Check permissions
 | 
				
			||||||
 | 
					                var hasManageMasterPermission = await _permissionService.HasPermission(Manage_Master, loggedInEmployee.Id);
 | 
				
			||||||
 | 
					                if (!hasManageMasterPermission)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    _logger.LogWarning("Access denied. EmployeeId: {EmployeeId} does not have Manage Master permission.", loggedInEmployee.Id);
 | 
				
			||||||
 | 
					                    return ApiResponse<object>.ErrorResponse("Access denied", "You do not have permission to update this work status", 403);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Step 4: Retrieve the work status record
 | 
				
			||||||
 | 
					                var workStatus = await _context.WorkStatusMasters
 | 
				
			||||||
 | 
					                    .FirstOrDefaultAsync(ws => ws.Id == id && ws.TenantId == tenantId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (workStatus == null)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    _logger.LogWarning("Work status not found for ID: {Id}", id);
 | 
				
			||||||
 | 
					                    return ApiResponse<object>.ErrorResponse("Work status not found", "No work status found with the provided ID", 404);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Step 5: Check for duplicate name (optional)
 | 
				
			||||||
 | 
					                var isDuplicate = await _context.WorkStatusMasters
 | 
				
			||||||
 | 
					                    .AnyAsync(ws => ws.Name == updateWorkStatusDto.Name && ws.Id != id && ws.TenantId == tenantId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (isDuplicate)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    _logger.LogWarning("Duplicate work status name '{Name}' detected during update. ID: {Id}", updateWorkStatusDto.Name ?? "", id);
 | 
				
			||||||
 | 
					                    return ApiResponse<object>.ErrorResponse("Work status with the same name already exists", "Duplicate name", 400);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Step 6: Update fields
 | 
				
			||||||
 | 
					                workStatus.Name = updateWorkStatusDto.Name?.Trim() ?? "";
 | 
				
			||||||
 | 
					                workStatus.Description = updateWorkStatusDto.Description?.Trim() ?? "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                await _context.SaveChangesAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                _logger.LogInfo("Work status updated successfully. ID: {Id}", id);
 | 
				
			||||||
 | 
					                return ApiResponse<object>.SuccessResponse(workStatus, "Work status updated successfully", 200);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            catch (Exception ex)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogError("Error occurred while updating work status ID: {Id} : {Error}", id, ex.Message);
 | 
				
			||||||
 | 
					                return ApiResponse<object>.ErrorResponse("An error occurred", "Unable to update the work status at this time", 500);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        public async Task<ApiResponse<object>> DeleteWorkStatus(Guid id)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            _logger.LogInfo("DeleteWorkStatus called for Id: {Id}", id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            try
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                // Step 1: Get current tenant and logged-in employee
 | 
				
			||||||
 | 
					                Guid tenantId = _userHelper.GetTenantId();
 | 
				
			||||||
 | 
					                var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Step 2: Check permission to manage master data
 | 
				
			||||||
 | 
					                var hasManageMasterPermission = await _permissionService.HasPermission(Manage_Master, loggedInEmployee.Id);
 | 
				
			||||||
 | 
					                if (!hasManageMasterPermission)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    _logger.LogWarning("Delete denied. EmployeeId: {EmployeeId} lacks Manage_Master permission.", loggedInEmployee.Id);
 | 
				
			||||||
 | 
					                    return ApiResponse<object>.ErrorResponse("You don't have access", "Access denied for deleting work status", 403);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Step 3: Find the work status
 | 
				
			||||||
 | 
					                var workStatus = await _context.WorkStatusMasters
 | 
				
			||||||
 | 
					                    .FirstOrDefaultAsync(ws => ws.Id == id && ws.TenantId == tenantId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (workStatus == null)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    _logger.LogWarning("Work status not found for Id: {Id}", id);
 | 
				
			||||||
 | 
					                    return ApiResponse<object>.ErrorResponse("Work status not found", "Work status not found", 404);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Step 4: Check for dependencies in TaskAllocations
 | 
				
			||||||
 | 
					                bool hasDependency = await _context.TaskAllocations
 | 
				
			||||||
 | 
					                    .AnyAsync(ta => ta.TenantId == tenantId && ta.WorkStatusId == id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (hasDependency)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    _logger.LogWarning("Cannot delete WorkStatus Id: {Id} due to existing task dependency", id);
 | 
				
			||||||
 | 
					                    return ApiResponse<object>.ErrorResponse(
 | 
				
			||||||
 | 
					                        "Work status has a dependency in assigned tasks and cannot be deleted",
 | 
				
			||||||
 | 
					                        "Deletion failed due to associated tasks",
 | 
				
			||||||
 | 
					                        400
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Step 5: Delete and persist
 | 
				
			||||||
 | 
					                _context.WorkStatusMasters.Remove(workStatus);
 | 
				
			||||||
 | 
					                await _context.SaveChangesAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                _logger.LogInfo("Work status deleted successfully. Id: {Id}", id);
 | 
				
			||||||
 | 
					                return ApiResponse<object>.SuccessResponse(new { }, "Work status deleted successfully", 200);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            catch (Exception ex)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogError("Error occurred while deleting WorkStatus Id: {Id} : {Error}", id, ex.Message);
 | 
				
			||||||
 | 
					                return ApiResponse<object>.ErrorResponse("An error occurred", "Unable to delete work status", 500);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										35
									
								
								Marco.Pms.Services/Hub/MarcoHub.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								Marco.Pms.Services/Hub/MarcoHub.cs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					using MarcoBMS.Services.Service;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Authorization;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.SignalR;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Marco.Pms.Services.Hubs
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    [Authorize]
 | 
				
			||||||
 | 
					    public class MarcoHub : Hub
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        private readonly ILoggingService _logger;
 | 
				
			||||||
 | 
					        public MarcoHub(ILoggingService logger)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            _logger = logger;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        public async Task SendMessage(string user, string message)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            _logger.LogInfo($"User: {user} Message : {message}");
 | 
				
			||||||
 | 
					            await Clients.All.SendAsync("ReceiveMessage", user, message);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        public override async Task OnConnectedAsync()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            await base.OnConnectedAsync();
 | 
				
			||||||
 | 
					            _logger.LogInfo($"Connected successfully");
 | 
				
			||||||
 | 
					            await Clients.All.SendAsync("Connected successfully");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Optional: override OnDisconnectedAsync
 | 
				
			||||||
 | 
					        public override async Task OnDisconnectedAsync(Exception? exception)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            await base.OnDisconnectedAsync(exception);
 | 
				
			||||||
 | 
					            _logger.LogInfo($"DIsonnected successfully");
 | 
				
			||||||
 | 
					            await Clients.All.SendAsync("Disonnected successfully");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -17,6 +17,7 @@
 | 
				
			|||||||
    <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.12" />
 | 
					    <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.12" />
 | 
				
			||||||
    <PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="8.0.12" />
 | 
					    <PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="8.0.12" />
 | 
				
			||||||
    <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.7" />
 | 
					    <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.7" />
 | 
				
			||||||
 | 
					    <PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.2.0" />
 | 
				
			||||||
    <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.12">
 | 
					    <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.12">
 | 
				
			||||||
      <PrivateAssets>all</PrivateAssets>
 | 
					      <PrivateAssets>all</PrivateAssets>
 | 
				
			||||||
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
 | 
					      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
 | 
				
			||||||
 | 
				
			|||||||
@ -4,6 +4,7 @@ using Marco.Pms.Model.Authentication;
 | 
				
			|||||||
using Marco.Pms.Model.Entitlements;
 | 
					using Marco.Pms.Model.Entitlements;
 | 
				
			||||||
using Marco.Pms.Model.Utilities;
 | 
					using Marco.Pms.Model.Utilities;
 | 
				
			||||||
using Marco.Pms.Services.Helpers;
 | 
					using Marco.Pms.Services.Helpers;
 | 
				
			||||||
 | 
					using Marco.Pms.Services.Hubs;
 | 
				
			||||||
using Marco.Pms.Services.Service;
 | 
					using Marco.Pms.Services.Service;
 | 
				
			||||||
using MarcoBMS.Services.Helpers;
 | 
					using MarcoBMS.Services.Helpers;
 | 
				
			||||||
using MarcoBMS.Services.Middleware;
 | 
					using MarcoBMS.Services.Middleware;
 | 
				
			||||||
@ -59,7 +60,8 @@ builder.Services.AddCors(options =>
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        policy.AllowAnyOrigin()
 | 
					        policy.AllowAnyOrigin()
 | 
				
			||||||
              .AllowAnyMethod()
 | 
					              .AllowAnyMethod()
 | 
				
			||||||
              .AllowAnyHeader();
 | 
					              .AllowAnyHeader()
 | 
				
			||||||
 | 
					              .WithExposedHeaders("Authorization");
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -161,10 +163,28 @@ if (jwtSettings != null && jwtSettings.Key != null)
 | 
				
			|||||||
            ValidAudience = jwtSettings.Audience,
 | 
					            ValidAudience = jwtSettings.Audience,
 | 
				
			||||||
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.Key))
 | 
					            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.Key))
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        options.Events = new JwtBearerEvents
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            OnMessageReceived = context =>
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                var accessToken = context.Request.Query["access_token"];
 | 
				
			||||||
 | 
					                var path = context.HttpContext.Request.Path;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Match your hub route here
 | 
				
			||||||
 | 
					                if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/hubs/marco"))
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    context.Token = accessToken;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return Task.CompletedTask;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    builder.Services.AddSingleton(jwtSettings);
 | 
					    builder.Services.AddSingleton(jwtSettings);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					builder.Services.AddSignalR();
 | 
				
			||||||
builder.WebHost.ConfigureKestrel(options =>
 | 
					builder.WebHost.ConfigureKestrel(options =>
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    options.AddServerHeader = false; // Disable the "Server" header
 | 
					    options.AddServerHeader = false; // Disable the "Server" header
 | 
				
			||||||
@ -207,7 +227,7 @@ app.UseHttpsRedirection();
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
app.UseAuthorization();
 | 
					app.UseAuthorization();
 | 
				
			||||||
 | 
					app.MapHub<MarcoHub>("/hubs/marco");
 | 
				
			||||||
app.MapControllers();
 | 
					app.MapControllers();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
app.Run();
 | 
					app.Run();
 | 
				
			||||||
 | 
				
			|||||||
@ -28,53 +28,46 @@ namespace Marco.Pms.Services.Service
 | 
				
			|||||||
            _s3Client = new AmazonS3Client(settings.AccessKey, settings.SecretKey, region);
 | 
					            _s3Client = new AmazonS3Client(settings.AccessKey, settings.SecretKey, region);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        //public async Task<string> UploadFileAsync(string fileName, string contentType)
 | 
					        //public async Task<string> UploadFileAsync(string fileName, string contentType)
 | 
				
			||||||
        public async Task<string> UploadFileAsync(string base64Data, Guid tenantId, string tag)
 | 
					        public async Task UploadFileAsync(string base64, string fileType, string objectKey)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            byte[] fileBytes;
 | 
					            byte[] fileBytes;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            //If base64 has a data URI prefix, strip it
 | 
					
 | 
				
			||||||
            var base64 = base64Data.Contains(",")
 | 
					 | 
				
			||||||
                ? base64Data.Substring(base64Data.IndexOf(",") + 1)
 | 
					 | 
				
			||||||
                : base64Data;
 | 
					 | 
				
			||||||
            var allowedFilesType = _configuration.GetSection("WhiteList:ContentType")
 | 
					            var allowedFilesType = _configuration.GetSection("WhiteList:ContentType")
 | 
				
			||||||
                                                .GetChildren()
 | 
					                                                .GetChildren()
 | 
				
			||||||
                                                .Select(x => x.Value)
 | 
					                                                .Select(x => x.Value)
 | 
				
			||||||
                                                .ToList();
 | 
					                                                .ToList();
 | 
				
			||||||
            string fileType = GetContentTypeFromBase64(base64);
 | 
					
 | 
				
			||||||
            if (allowedFilesType != null && allowedFilesType.Contains(fileType))
 | 
					            if (allowedFilesType == null || !allowedFilesType.Contains(fileType))
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                string fileName = GenerateFileName(fileType, tenantId, tag);
 | 
					                throw new InvalidOperationException("Unsupported file type.");
 | 
				
			||||||
 | 
					 | 
				
			||||||
                fileBytes = Convert.FromBase64String(base64);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                using var fileStream = new MemoryStream(fileBytes);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // Generate a unique object key (you can customize this)
 | 
					 | 
				
			||||||
                var objectKey = $"{fileName}";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                var uploadRequest = new TransferUtilityUploadRequest
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    InputStream = fileStream,
 | 
					 | 
				
			||||||
                    Key = objectKey,
 | 
					 | 
				
			||||||
                    BucketName = _bucketName,
 | 
					 | 
				
			||||||
                    ContentType = fileType,
 | 
					 | 
				
			||||||
                    AutoCloseStream = true
 | 
					 | 
				
			||||||
                };
 | 
					 | 
				
			||||||
                try
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    var transferUtility = new TransferUtility(_s3Client);
 | 
					 | 
				
			||||||
                    await transferUtility.UploadAsync(uploadRequest);
 | 
					 | 
				
			||||||
                    _logger.LogInfo("File uploaded to Amazon S3");
 | 
					 | 
				
			||||||
                    return objectKey;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                catch (Exception ex)
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    _logger.LogError("{error} while uploading file to S3", ex.Message);
 | 
					 | 
				
			||||||
                    return string.Empty;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            throw new InvalidOperationException("Unsupported file type.");
 | 
					
 | 
				
			||||||
 | 
					            fileBytes = Convert.FromBase64String(base64);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            using var fileStream = new MemoryStream(fileBytes);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var uploadRequest = new TransferUtilityUploadRequest
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                InputStream = fileStream,
 | 
				
			||||||
 | 
					                Key = objectKey,
 | 
				
			||||||
 | 
					                BucketName = _bucketName,
 | 
				
			||||||
 | 
					                ContentType = fileType,
 | 
				
			||||||
 | 
					                AutoCloseStream = true
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            try
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                var transferUtility = new TransferUtility(_s3Client);
 | 
				
			||||||
 | 
					                await transferUtility.UploadAsync(uploadRequest);
 | 
				
			||||||
 | 
					                _logger.LogInfo("File uploaded to Amazon S3");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            catch (Exception ex)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogError("{error} while uploading file to S3", ex.Message);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        public string GeneratePreSignedUrlAsync(string objectKey)
 | 
					        public string GeneratePreSignedUrlAsync(string objectKey)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
 | 
				
			|||||||
@ -9,7 +9,7 @@
 | 
				
			|||||||
        "Title": "Dev"
 | 
					        "Title": "Dev"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "ConnectionStrings": {
 | 
					    "ConnectionStrings": {
 | 
				
			||||||
        "DefaultConnectionString": "Server=147.93.98.152;User ID=devuser;Password=AppUser@123$;Database=MarcoBMSGuid"
 | 
					        "DefaultConnectionString": "Server=147.93.98.152;User ID=devuser;Password=AppUser@123$;Database=MarcoBMS1"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "SmtpSettings": {
 | 
					    "SmtpSettings": {
 | 
				
			||||||
        "SmtpServer": "smtp.gmail.com",
 | 
					        "SmtpServer": "smtp.gmail.com",
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user