Compare commits

..

No commits in common. "8c691c5d3e63cca3b8ce333c49a483dee76175fe" and "d12f8ed0fbffad4f11ff1b7db3efb4206c25e3eb" have entirely different histories.

43 changed files with 483 additions and 15394 deletions

View File

@ -44,7 +44,6 @@ 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; }
@ -69,7 +68,6 @@ 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; }
@ -161,20 +159,15 @@ namespace Marco.Pms.DataAccess.Data
TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26") TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
}, },
new StatusMaster new StatusMaster
{
Id = new Guid("cdad86aa-8a56-4ff4-b633-9c629057dfef"),
Status = "In Progress",
TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
}, new StatusMaster
{ {
Id = new Guid("603e994b-a27f-4e5d-a251-f3d69b0498ba"), Id = new Guid("603e994b-a27f-4e5d-a251-f3d69b0498ba"),
Status = "On Hold", Status = "In Progress",
TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26") 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 = "In Active", Status = "On Hold",
TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26") TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
}, },
new StatusMaster new StatusMaster
@ -435,32 +428,6 @@ 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")
}
);
} }

View File

@ -1,36 +0,0 @@
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");
}
}
}

View File

@ -1,188 +0,0 @@
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");
}
}
}

View File

@ -1,41 +0,0 @@
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");
}
}
}

View File

@ -1,57 +0,0 @@
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");
}
}
}

View File

@ -28,12 +28,6 @@ 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)");
@ -46,64 +40,29 @@ 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")
@ -1699,20 +1658,14 @@ namespace Marco.Pms.DataAccess.Migrations
}, },
new new
{ {
Id = new Guid("cdad86aa-8a56-4ff4-b633-9c629057dfef"), Id = new Guid("603e994b-a27f-4e5d-a251-f3d69b0498ba"),
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("603e994b-a27f-4e5d-a251-f3d69b0498ba"),
Status = "On Hold",
TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
},
new
{ {
Id = new Guid("ef1c356e-0fe0-42df-a5d3-8daee355492d"), Id = new Guid("ef1c356e-0fe0-42df-a5d3-8daee355492d"),
Status = "In Active", Status = "On Hold",
TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26") TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
}, },
new new
@ -1966,59 +1919,6 @@ 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")
@ -2198,12 +2098,6 @@ 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");
@ -2534,20 +2428,12 @@ 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")
@ -2560,21 +2446,11 @@ 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 =>
@ -3187,17 +3063,6 @@ 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")

View File

@ -1,6 +1,5 @@
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;
@ -11,43 +10,29 @@ 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 Guid AssignedBy { get; set; } //Employee Id //public int? WorkItemMappingId { get; set; }
//[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; }
} }
} }

View File

@ -1,9 +0,0 @@
namespace Marco.Pms.Model.Activities
{
public class TaskAttachment
{
public Guid Id { get; set; }
public Guid ReferenceId { get; set; }
public Guid DocumentId { get; set; }
}
}

View File

@ -0,0 +1,17 @@
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; }
}
}

View File

@ -1,12 +0,0 @@
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;
}
}

View File

@ -1,13 +0,0 @@
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; }
}
}

View File

@ -3,7 +3,6 @@
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

View File

@ -1,12 +1,11 @@
using Marco.Pms.Model.Utilities; namespace Marco.Pms.Model.Dtos.Activities
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; }
} }
} }

View File

@ -1,8 +0,0 @@
namespace Marco.Pms.Model.Dtos.Master
{
public class CreateWorkStatusMasterDto
{
public string? Name { get; set; }
public string? Description { get; set; }
}
}

View File

@ -1,15 +1,11 @@
using Marco.Pms.Model.Utilities; namespace Marco.Pms.Model.Dtos.Activities
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; }
} }
} }

View File

@ -1,9 +0,0 @@
namespace Marco.Pms.Model.Dtos.Master
{
public class UpdateWorkStatusMasterDto
{
public Guid Id { get; set; }
public string? Name { get; set; }
public string? Description { get; set; }
}
}

View File

@ -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; }

View File

@ -11,7 +11,5 @@ 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; }
} }
} }

View File

@ -13,7 +13,6 @@ 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,
@ -44,23 +43,18 @@ namespace Marco.Pms.Model.Mapper
TenantId = tenantId TenantId = tenantId
}; };
} }
public static TaskVM TaskAllocationToTaskVM(this TaskAllocation taskAllocation) public static TaskVM TaskAllocationToTaskVM(this TaskAllocation taskAllocation, string employeeName)
{ {
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,
NotApprovedTask = taskAllocation.ApprovedById == null ? taskAllocation.CompletedTask : (taskAllocation.CompletedTask - taskAllocation.ReportedTask), ReportedDate = taskAllocation.ReportedDate,
Description = taskAllocation.Description, Description = taskAllocation.Description,
AssignedBy = taskAllocation.Employee?.ToBasicEmployeeVMFromEmployee(), AssignBy = employeeName,
ReportedBy = taskAllocation.ReportedBy?.ToBasicEmployeeVMFromEmployee(), WorkItem = taskAllocation.WorkItem
ApprovedBy = taskAllocation.ApprovedBy?.ToBasicEmployeeVMFromEmployee(),
WorkItem = taskAllocation.WorkItem,
WorkStatus = taskAllocation.WorkStatus
}; };
} }
public static AssignedTaskVM ToAssignTaskVMFromTaskAllocation(this TaskAllocation taskAllocation) public static AssignedTaskVM ToAssignTaskVMFromTaskAllocation(this TaskAllocation taskAllocation)
@ -106,18 +100,11 @@ 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,
NotApprovedTask = taskAllocation.ApprovedById == null ? taskAllocation.CompletedTask : (taskAllocation.CompletedTask - taskAllocation.ReportedTask), AssignedBy = taskAllocation.Employee != null ? taskAllocation.Employee.ToBasicEmployeeVMFromEmployee() : new BasicEmployeeVM(),
AssignedBy = taskAllocation.Employee?.ToBasicEmployeeVMFromEmployee(),
ReportedBy = taskAllocation.ReportedBy?.ToBasicEmployeeVMFromEmployee(),
ApprovedBy = taskAllocation.ApprovedBy?.ToBasicEmployeeVMFromEmployee(),
WorkItemId = taskAllocation.WorkItemId, WorkItemId = taskAllocation.WorkItemId,
WorkItem = taskAllocation.WorkItem WorkItem = taskAllocation.WorkItem
}; };

View File

@ -59,9 +59,7 @@ 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
}; };
} }

View File

@ -20,17 +20,11 @@ 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; }
} }
} }

View File

@ -8,6 +8,5 @@
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; }
} }
} }

View File

@ -1,25 +1,16 @@
using Marco.Pms.Model.Master; using Marco.Pms.Model.Projects;
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; }

View File

@ -10,6 +10,7 @@
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; }
} }

View File

@ -1,5 +1,5 @@
using Marco.Pms.Model.Master; using Marco.Pms.Model.Projects;
using Marco.Pms.Model.Projects; using Marco.Pms.Model.ViewModels.Employee;
namespace Marco.Pms.Model.ViewModels.Activities namespace Marco.Pms.Model.ViewModels.Activities
{ {
@ -7,19 +7,13 @@ 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 double NotApprovedTask { get; set; } public DateTime? ReportedDate { get; set; }
public string? Description { get; set; } public string? Description { get; set; }
public BasicEmployeeVM? AssignedBy { get; set; } public string? AssignBy { 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<BasicEmployeeVM>? TeamMembers { get; set; } public List<EmployeeVM>? TeamMembers { get; set; }
} }
} }

View File

@ -1,9 +0,0 @@
namespace Marco.Pms.Model.ViewModels.DashBoard
{
public class AttendanceOverviewVM
{
public string? Role { get; set; }
public string? Date { get; set; }
public int Present { get; set; }
}
}

View File

@ -7,13 +7,11 @@ 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;
@ -32,11 +30,10 @@ 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, IHubContext<MarcoHub> signalR) ApplicationDbContext context, EmployeeHelper employeeHelper, ProjectsHelper projectsHelper, UserHelper userHelper, S3UploadService s3Service, ILoggingService logger, PermissionServices permission)
{ {
_context = context; _context = context;
_employeeHelper = employeeHelper; _employeeHelper = employeeHelper;
@ -45,7 +42,6 @@ namespace MarcoBMS.Services.Controllers
_s3Service = s3Service; _s3Service = s3Service;
_logger = logger; _logger = logger;
_permission = permission; _permission = permission;
_signalR = signalR;
} }
private Guid GetTenantId() private Guid GetTenantId()
@ -562,13 +558,6 @@ 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));
} }
@ -597,185 +586,248 @@ namespace MarcoBMS.Services.Controllers
{ {
if (!ModelState.IsValid) if (!ModelState.IsValid)
{ {
var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage).ToList(); var errors = ModelState.Values
_logger.LogError("Invalid attendance model received."); .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)); 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
{ {
// Validate mark time Attendance? attendance = await _context.Attendes.FirstOrDefaultAsync(a => a.Id == recordAttendanceDot.Id && a.TenantId == TenantId); ;
if (recordAttendanceDot.MarkTime == null) if (recordAttendanceDot.MarkTime == null)
{ {
_logger.LogWarning("Null mark time provided."); _logger.LogError("User sent Invalid Mark Time while marking attendance");
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid Mark Time", "Mark time is required", 400)); return BadRequest(ApiResponse<object>.ErrorResponse("Invalid Mark Time", "Invalid Mark Time", 400));
} }
if (string.IsNullOrWhiteSpace(recordAttendanceDot.Comment)) DateTime finalDateTime = GetDateFromTimeStamp(recordAttendanceDot.Date, recordAttendanceDot.MarkTime);
if (recordAttendanceDot.Comment == null)
{ {
_logger.LogWarning("Empty comment provided."); _logger.LogError("User sent Invalid comment while marking attendance");
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid Comment", "Comment is required", 400)); return BadRequest(ApiResponse<object>.ErrorResponse("Invalid Comment", "Invalid Comment", 400));
} }
var finalDateTime = GetDateFromTimeStamp(recordAttendanceDot.Date, recordAttendanceDot.MarkTime); if (attendance != null)
var attendance = await _context.Attendes
.FirstOrDefaultAsync(a => a.Id == recordAttendanceDot.Id && a.TenantId == tenantId);
// Create or update attendance
if (attendance == null)
{ {
attendance = new Attendance attendance.Comment = recordAttendanceDot.Comment;
if (recordAttendanceDot.Action == ATTENDANCE_MARK_TYPE.CHECK_IN)
{ {
TenantId = tenantId, attendance.InTime = finalDateTime;
AttendanceDate = recordAttendanceDot.Date, attendance.OutTime = null;
Comment = recordAttendanceDot.Comment, attendance.Activity = ATTENDANCE_MARK_TYPE.CHECK_OUT;
EmployeeID = recordAttendanceDot.EmployeeID, }
ProjectID = recordAttendanceDot.ProjectID, else if (recordAttendanceDot.Action == ATTENDANCE_MARK_TYPE.CHECK_OUT)
Date = DateTime.UtcNow, {
InTime = finalDateTime, attendance.IsApproved = true;
Activity = ATTENDANCE_MARK_TYPE.CHECK_OUT attendance.Activity = ATTENDANCE_MARK_TYPE.REGULARIZE;
};
_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;
string? preSignedUrl = null; var Image = recordAttendanceDot.Image;
var objectKey = string.Empty;
var preSignedUrl = string.Empty;
if (recordAttendanceDot.Image != null && recordAttendanceDot.Image.ContentType != null) if (Image != null && 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));
var fileType = _s3Service.GetContentTypeFromBase64(base64); if (string.IsNullOrEmpty(Image.Base64Data))
var fileName = _s3Service.GenerateFileName(fileType, tenantId, "attendance"); return BadRequest(ApiResponse<object>.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400));
var objectKey = $"tenant-{tenantId}/Employee/{recordAttendanceDot.EmployeeID}/Attendance/{fileName}";
await _s3Service.UploadFileAsync(base64, fileType, objectKey); objectKey = await _s3Service.UploadFileAsync(Image.Base64Data, TenantId, "attendance");
preSignedUrl = _s3Service.GeneratePreSignedUrlAsync(objectKey); preSignedUrl = _s3Service.GeneratePreSignedUrlAsync(objectKey);
document = new Document document = new Document
{ {
FileName = recordAttendanceDot.Image.FileName ?? "", FileName = Image.FileName ?? "",
ContentType = recordAttendanceDot.Image.ContentType, ContentType = Image.ContentType,
S3Key = objectKey, S3Key = objectKey,
Base64Data = recordAttendanceDot.Image.Base64Data, Base64Data = Image.Base64Data,
FileSize = recordAttendanceDot.Image.FileSize, FileSize = 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
{
AttendanceId = attendance.Id,
Activity = attendance.Activity,
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);
// Step 3: Always insert a new log entry
if (document != null)
{
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.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();
// Construct view model await transaction.CommitAsync(); // Commit transaction
var employee = await _employeeHelper.GetEmployeeByID(recordAttendanceDot.EmployeeID);
var vm = new EmployeeAttendanceVM Employee employee = await _employeeHelper.GetEmployeeByID(recordAttendanceDot.EmployeeID);
if (employee.JobRole != null)
{ {
Id = attendance.Id, EmployeeAttendanceVM vm = new EmployeeAttendanceVM();
EmployeeId = employee.Id, if (document != null)
FirstName = employee.FirstName, {
LastName = employee.LastName, vm = new EmployeeAttendanceVM()
CheckInTime = attendance.InTime, {
CheckOutTime = attendance.OutTime, CheckInTime = attendance.InTime,
Activity = attendance.Activity, CheckOutTime = attendance.OutTime,
JobRoleName = employee.JobRole?.Name, EmployeeAvatar = null,
DocumentId = document?.Id ?? Guid.Empty, EmployeeId = recordAttendanceDot.EmployeeID,
ThumbPreSignedUrl = preSignedUrl ?? "", FirstName = employee.FirstName,
PreSignedUrl = preSignedUrl ?? "" LastName = employee.LastName,
}; 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
};
}
var notification = new _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));
LoggedInUserId = currentEmployee.Id, }
Keyword = "Attendance", _logger.LogInfo("Attendance for employee {FirstName} {LastName} has been marked", employee.FirstName ?? string.Empty, employee.LastName ?? string.Empty);
Activity = recordAttendanceDot.Id == Guid.Empty ? 1 : 0, return Ok(ApiResponse<object>.SuccessResponse(new EmployeeAttendanceVM(), "Attendance marked successfully.", 200));
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(); await transaction.RollbackAsync(); // Rollback on failure
_logger.LogError("Error while recording attendance : {Error}", ex.Message); _logger.LogError("{Error} while marking attendance", ex.Message);
return BadRequest(ApiResponse<object>.ErrorResponse("Something went wrong", ex.Message, 500)); var response = new
{
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;

View File

@ -5,7 +5,6 @@ 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;
@ -22,13 +21,11 @@ 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;
private readonly PermissionServices _permissionServices; public DashboardController(ApplicationDbContext context, UserHelper userHelper, ILoggingService logger)
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)
@ -357,102 +354,5 @@ 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));
}
} }
} }

View File

@ -9,13 +9,11 @@ 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
@ -34,11 +32,9 @@ 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;
@ -47,7 +43,6 @@ namespace MarcoBMS.Services.Controllers
_userHelper = userHelper; _userHelper = userHelper;
_configuration = configuration; _configuration = configuration;
_logger = logger; _logger = logger;
_signalR = signalR;
} }
[HttpGet] [HttpGet]
@ -159,8 +154,6 @@ 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));
@ -187,7 +180,6 @@ 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
@ -222,7 +214,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);
@ -241,7 +233,6 @@ 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);
@ -265,22 +256,17 @@ 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));
} }
@ -434,9 +420,6 @@ 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

View File

@ -68,16 +68,7 @@ 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));
} }
//If base64 has a data URI prefix, strip it var objectKey = await _s3Service.UploadFileAsync(Image.Base64Data, tenantId, "forum");
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);
@ -191,16 +182,7 @@ 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));
} }
//If base64 has a data URI prefix, strip it var objectKey = await _s3Service.UploadFileAsync(Image.Base64Data, tenantId, "forum");
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);
@ -347,14 +329,6 @@ 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();
@ -370,16 +344,7 @@ 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));
} }
//If base64 has a data URI prefix, strip it var objectKey = await _s3Service.UploadFileAsync(Image.Base64Data, tenantId, "forum");
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);
@ -431,14 +396,6 @@ 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);
@ -462,16 +419,7 @@ 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));
} }
//If base64 has a data URI prefix, strip it var objectKey = await _s3Service.UploadFileAsync(Image.Base64Data, tenantId, "forum");
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);
@ -543,16 +491,6 @@ 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)
@ -567,17 +505,7 @@ 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 ticket = tickets.FirstOrDefault(t => t.Id == forumAttachmentDto.TicketId); var objectKey = await _s3Service.UploadFileAsync(forumAttachmentDto.Base64Data, tenantId, "forum");
//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);

View File

@ -671,45 +671,6 @@ 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")]
@ -788,11 +749,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)

View File

@ -8,13 +8,12 @@ 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
{ {
@ -28,18 +27,15 @@ 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, IHubContext<MarcoHub> signalR) public ProjectController(ApplicationDbContext context, UserHelper userHelper, ILoggingService logger, RolesHelper rolesHelper, ProjectsHelper projectHelper)
{ {
_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")]
@ -64,7 +60,7 @@ namespace MarcoBMS.Services.Controllers
} }
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
@ -88,6 +84,8 @@ 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()
{ {
@ -316,7 +314,6 @@ 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
@ -333,9 +330,6 @@ 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));
} }
@ -344,7 +338,6 @@ 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
@ -363,10 +356,6 @@ 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));
} }
@ -376,6 +365,7 @@ 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)
//{ //{
@ -516,11 +506,7 @@ 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)
{ {
@ -549,9 +535,6 @@ 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
@ -573,9 +556,6 @@ 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);
} }
} }
@ -584,9 +564,7 @@ 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));
} }
@ -595,91 +573,52 @@ namespace MarcoBMS.Services.Controllers
} }
[HttpPost("task")] [HttpPost("task")]
public async Task<IActionResult> CreateProjectTask(List<WorkItemDot> workItemDtos) public async Task<IActionResult> CreateProjectTask(List<WorkItemDot> workItemDot)
{ {
_logger.LogInfo("CreateProjectTask called with {Count} items", workItemDtos?.Count ?? 0);
// Validate request
if (workItemDtos == null || !workItemDtos.Any())
{
_logger.LogWarning("No work items provided in the request.");
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid details.", "Work Item details are not valid.", 400));
}
Guid tenantId = GetTenantId(); Guid tenantId = GetTenantId();
var workItemsToCreate = new List<WorkItem>(); List<WorkItemVM> workItems = new List<WorkItemVM> { };
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 = ""; string responseMessage = "";
// Apply DB changes if (workItemDot != null)
if (workItemsToCreate.Any())
{ {
_logger.LogInfo("Adding {Count} new work items", workItemsToCreate.Count); foreach (var item in workItemDot)
await _context.WorkItems.AddRangeAsync(workItemsToCreate); {
responseMessage = "Task Added Successfully"; WorkItem workItem = item.ToWorkItemFromWorkItemDto(tenantId);
if (item.Id != null)
{
//update
_context.WorkItems.Update(workItem);
await _context.SaveChangesAsync();
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));
} }
if (workItemsToUpdate.Any()) return BadRequest(ApiResponse<object>.ErrorResponse("Invalid details.", "Work Item Details are not valid.", 400));
{
_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();
var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); WorkItem? task = await _context.WorkItems.AsNoTracking().FirstOrDefaultAsync(t => t.Id == id && t.TenantId == tenantId);
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)
@ -690,15 +629,6 @@ 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
{ {
@ -726,12 +656,8 @@ 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)
@ -749,7 +675,6 @@ 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
{ {
@ -758,10 +683,8 @@ 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)
{ {
@ -775,7 +698,6 @@ 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
{ {
@ -784,11 +706,7 @@ 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)
{ {
@ -802,7 +720,6 @@ 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
{ {
@ -811,17 +728,9 @@ 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));
@ -867,15 +776,16 @@ 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)
{ {
@ -903,8 +813,6 @@ 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
@ -927,8 +835,6 @@ namespace MarcoBMS.Services.Controllers
_context.ProjectAllocations.Add(projectAllocation); _context.ProjectAllocations.Add(projectAllocation);
await _context.SaveChangesAsync(); await _context.SaveChangesAsync();
projectIds.Add(projectAllocation.ProjectId);
} }
@ -939,9 +845,6 @@ 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));
} }

View File

@ -1,19 +1,17 @@
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.Services.Service; using Marco.Pms.Model.ViewModels.Employee;
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
{ {
@ -25,21 +23,12 @@ 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()
@ -50,70 +39,52 @@ 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));
}
// Retrieve tenant and employee context }
var tenantId = GetTenantId(); var tenantId = GetTenantId();
var employee = await _userHelper.GetCurrentEmployeeAsync(); var Employee = await _userHelper.GetCurrentEmployeeAsync();
// Check for permission to approve tasks var taskAllocation = assignTask.ToTaskAllocationFromAssignTaskDto(Employee.Id, tenantId);
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();
// Map team members var teamMembers = new List<TaskMembers> { };
var teamMembers = new List<TaskMembers>(); if (assignTask.TaskTeam != null)
if (assignTask.TaskTeam != null && assignTask.TaskTeam.Any())
{ {
teamMembers = assignTask.TaskTeam.Select(memberId => new TaskMembers foreach (var teamMember in assignTask.TaskTeam)
{ {
TaskAllocationId = taskAllocation.Id, var result = new TaskMembers
EmployeeId = memberId, {
TenantId = tenantId TaskAllocationId = taskAllocation.Id,
}).ToList(); EmployeeId = teamMember,
TenantId = tenantId,
_context.TaskMembers.AddRange(teamMembers); };
await _context.SaveChangesAsync(); teamMembers.Add(result);
}
_logger.LogInfo("Team members added to Task {TaskId}: {@TeamMemberIds}", taskAllocation.Id, assignTask.TaskTeam);
} }
_context.TaskMembers.AddRange(teamMembers);
await _context.SaveChangesAsync();
// Get team member details var idList = teamMembers.Select(m => m.EmployeeId);
var employeeIds = teamMembers.Select(m => m.EmployeeId).ToList(); List<Employee> employees = await _context.Employees.Where(e => idList.Contains(e.Id)).ToListAsync();
var employees = await _context.Employees List<BasicEmployeeVM> team = new List<BasicEmployeeVM>();
.Where(e => employeeIds.Contains(e.Id)) foreach (var employee in employees)
.ToListAsync(); {
team.Add(employee.ToBasicEmployeeVMFromEmployee());
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)
{ {
@ -123,661 +94,211 @@ 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 loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); var Employee = await _userHelper.GetCurrentEmployeeAsync();
var hasPermission = await _permissionServices.HasPermission(Assign_Report_Task, loggedInEmployee.Id); var taskAllocation = await _context.TaskAllocations.Include(t => t.WorkItem).FirstOrDefaultAsync(t => t.Id == reportTask.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>();
taskAllocation.ParentTaskId = reportTask.ParentTaskId; List<CheckListVM> checkListVMs = new List<CheckListVM>();
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(activityId)); checkListVMs.Add(checkDto.ToCheckListVMFromReportCheckListDto(taskAllocation.WorkItem != null ? taskAllocation.WorkItem.ActivityId : Guid.Empty));
if (checkDto.IsChecked)
if (checkDto.IsChecked && checkList.Any(c => c.Id == checkDto.Id))
{ {
checkListMappings.Add(new CheckListMappings var check = checkList.Find(c => c.Id == checkDto.Id);
if (check != null)
{ {
CheckListId = checkDto.Id, CheckListMappings checkListMapping = new CheckListMappings
TaskAllocationId = reportTask.Id {
}); CheckListId = check.Id,
TaskAllocationId = reportTask.Id
};
checkListMappings.Add(checkListMapping);
}
} }
} }
_context.CheckListMappings.AddRange(checkListMappings);
} }
_context.CheckListMappings.AddRange(checkListMappings);
var comment = reportTask.ToCommentFromReportTaskDto(tenantId, Employee.Id);
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();
var comments = await _context.TaskComments List<TaskComment> comments = await _context.TaskComments.Where(c => c.TaskAllocationId == taskAllocation.Id).ToListAsync();
.Where(c => c.TaskAllocationId == taskAllocation.Id) List<CommentVM> resultComments = new List<CommentVM> { };
.ToListAsync(); foreach (var result in comments)
{
response.Comments = comments.Select(c => c.ToCommentVMFromTaskComment()).ToList(); resultComments.Add(result.ToCommentVMFromTaskComment());
}
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)
{ {
_logger.LogInfo("AddCommentForTask called for TaskAllocationId: {TaskId}", createComment.TaskAllocationId);
var tenantId = GetTenantId(); var tenantId = GetTenantId();
var employee = await _userHelper.GetCurrentEmployeeAsync(); var Employee = await _userHelper.GetCurrentEmployeeAsync();
// Validate Task Allocation and associated WorkItem var comment = createComment.ToCommentFromCommentDto(tenantId, Employee.Id);
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();
// Parse and validate dateFrom if (dateFrom != null && DateTime.TryParse(dateFrom, out fromDate) == false)
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));
}
// Parse and validate dateTo }
if (dateTo != null && !DateTime.TryParse(dateTo, out toDate)) if (dateTo != null && DateTime.TryParse(dateTo, out toDate) == false)
{ {
_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);
// Set default date range if not provided var jobroles = await _context.JobRoles.Where(r => r.TenantId == tenantId).ToListAsync();
fromDate = dateFrom == null ? DateTime.UtcNow.Date : fromDate; //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();
toDate = dateTo == null ? fromDate.AddDays(1) : toDate; List<Building> buildings = await _context.Buildings.Where(b => b.ProjectId == projectId && b.TenantId == tenantId).ToListAsync();
List<Guid> idList = buildings.Select(b => b.Id).ToList();
// 1. Get all buildings under this project List<Floor> floors = await _context.Floor.Where(f => idList.Contains(f.BuildingId) && f.TenantId == tenantId).ToListAsync();
_logger.LogInfo("Fetching buildings for projectId: {ProjectId}", projectId); idList = floors.Select(f => f.Id).ToList();
var buildings = await _context.Buildings
.Where(b => b.ProjectId == projectId && b.TenantId == tenantId)
.ToListAsync();
var buildingIds = buildings.Select(b => b.Id).ToList(); List<WorkArea> workAreas = await _context.WorkAreas.Where(a => idList.Contains(a.FloorId) && a.TenantId == tenantId).ToListAsync();
idList = workAreas.Select(a => a.Id).ToList();
// 2. Get floors under the buildings List<WorkItem> workItems = await _context.WorkItems.Where(i => idList.Contains(i.WorkAreaId) && i.TenantId == tenantId).Include(i => i.ActivityMaster).ToListAsync();
var floors = await _context.Floor idList = workItems.Select(i => i.Id).ToList();
.Where(f => buildingIds.Contains(f.BuildingId) && f.TenantId == tenantId) var activityIdList = workItems.Select(i => i.ActivityId).ToList();
.ToListAsync();
var floorIds = floors.Select(f => f.Id).ToList();
// 3. Get work areas under the floors 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();
var workAreas = await _context.WorkAreas var taskIdList = taskAllocations.Select(t => t.Id).ToList();
.Where(a => floorIds.Contains(a.FloorId) && a.TenantId == tenantId)
.ToListAsync();
var workAreaIds = workAreas.Select(a => a.Id).ToList();
// 4. Get work items under the work areas List<TaskMembers> teamMembers = await _context.TaskMembers.Where(t => taskIdList.Contains(t.TaskAllocationId)).ToListAsync();
var workItems = await _context.WorkItems var employeeIdList = teamMembers.Select(e => e.EmployeeId).ToList();
.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();
// Attach documents to the main task List<TaskComment> comments = await _context.TaskComments.Where(c => c.TaskAllocationId == taskAllocation.Id).ToListAsync();
var taskDocIds = attachments List<BasicEmployeeVM> team = new List<BasicEmployeeVM>();
.Where(a => a.ReferenceId == taskAllocation.Id) List<TaskMembers> taskMembers = teamMembers.Where(m => m.TaskAllocationId == taskAllocation.Id).ToList();
.Select(a => a.DocumentId)
.ToList();
var taskDocs = documents foreach (var taskMember in taskMembers)
.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 commentDocIds = attachments var teamMember = employees.Find(e => e.Id == taskMember.EmployeeId);
.Where(a => a.ReferenceId == comment.Id) if (teamMember != null)
.Select(a => a.DocumentId) {
.ToList(); team.Add(teamMember.ToBasicEmployeeVMFromEmployee());
}
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> { };
response.comments = commentVMs; foreach (var comment in comments)
// 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 =>
{ {
var isChecked = checkListMappings.Any(m => m.CheckListId == check.Id); commentVM.Add(comment.ToCommentVMFromTaskComment());
return check.ToCheckListVMFromActivityCheckList(check.ActivityId, isChecked); }
}).ToList(); List<ActivityCheckList> checkLists = await _context.ActivityCheckLists.Where(x => x.ActivityId == (taskAllocation.WorkItem != null ? taskAllocation.WorkItem.ActivityId : Guid.Empty)).ToListAsync();
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)
{ {
_logger.LogInfo("GetTask called with taskId: {TaskId}", taskId); if (taskId == Guid.Empty) return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", "Invalid data", 400));
// Validate input var taskAllocation = await _context.TaskAllocations.Include(t => t.Tenant).Include(t => t.Employee).Include(t => t.WorkItem).FirstOrDefaultAsync(t => t.Id == taskId);
if (taskId == Guid.Empty) if (taskAllocation != null && taskAllocation.Employee != null && taskAllocation.Tenant != null)
{ {
_logger.LogWarning("Invalid taskId provided."); //var employee = await _context.Employees.FirstOrDefaultAsync(e => e.Id == taskAllocation.AssignedBy);
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", "Invalid data", 400)); string employeeName = System.String.Format("{0} {1}", taskAllocation.Employee.FirstName, taskAllocation.Employee.LastName);
} string tenantName = taskAllocation.Tenant.ContactName ?? string.Empty;
// Fetch Task Allocation with required related data if (taskAllocation == null) return NotFound(ApiResponse<object>.ErrorResponse("Task Not Found", "Task not found", 404));
var taskAllocation = await _context.TaskAllocations var taskVM = taskAllocation.TaskAllocationToTaskVM(employeeName);
.Include(t => t.Tenant) var comments = await _context.TaskComments.Where(c => c.TaskAllocationId == taskAllocation.Id).ToListAsync();
.Include(t => t.Employee) var team = await _context.TaskMembers.Where(m => m.TaskAllocationId == taskAllocation.Id).Include(m => m.Employee).ToListAsync();
.Include(t => t.ReportedBy) var teamMembers = new List<EmployeeVM> { };
.Include(t => t.ApprovedBy) foreach (var member in team)
.Include(t => t.WorkItem)
.Include(t => t.WorkStatus)
.FirstOrDefaultAsync(t => t.Id == taskId);
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)) var result = member.Employee != null ? member.Employee.ToEmployeeVMFromEmployee() : new EmployeeVM();
{ teamMembers.Add(result);
_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);
} }
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));
} }
// Commit all changes to the database
await _context.SaveChangesAsync();
_logger.LogInfo("Task {TaskId} successfully approved by Employee {EmployeeId}", approveTask.Id, loggedInEmployee.Id); return NotFound(ApiResponse<object>.ErrorResponse("Task Not Found", "Task not Found", 404));
return Ok(ApiResponse<object>.SuccessResponse("Task has been approved", "Task has been approved", 200));
} }
} }
} }

View File

@ -3,10 +3,8 @@ 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;
@ -18,19 +16,13 @@ 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, PermissionServices permissionServices) public MasterHelper(ApplicationDbContext context, ILoggingService logger, UserHelper userHelper)
{ {
_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)
@ -255,215 +247,5 @@ 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);
}
}
} }
} }

View File

@ -1,35 +0,0 @@
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");
}
}
}

View File

@ -17,7 +17,6 @@
<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>

View File

@ -4,7 +4,6 @@ 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;
@ -60,8 +59,7 @@ builder.Services.AddCors(options =>
{ {
policy.AllowAnyOrigin() policy.AllowAnyOrigin()
.AllowAnyMethod() .AllowAnyMethod()
.AllowAnyHeader() .AllowAnyHeader();
.WithExposedHeaders("Authorization");
}); });
}); });
@ -163,28 +161,10 @@ 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
@ -227,7 +207,7 @@ app.UseHttpsRedirection();
app.UseAuthorization(); app.UseAuthorization();
app.MapHub<MarcoHub>("/hubs/marco");
app.MapControllers(); app.MapControllers();
app.Run(); app.Run();

View File

@ -28,46 +28,53 @@ 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 UploadFileAsync(string base64, string fileType, string objectKey) public async Task<string> UploadFileAsync(string base64Data, Guid tenantId, string tag)
{ {
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))
{ {
throw new InvalidOperationException("Unsupported file type."); string fileName = GenerateFileName(fileType, tenantId, tag);
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)
{ {

View File

@ -9,7 +9,7 @@
"Title": "Dev" "Title": "Dev"
}, },
"ConnectionStrings": { "ConnectionStrings": {
"DefaultConnectionString": "Server=147.93.98.152;User ID=devuser;Password=AppUser@123$;Database=MarcoBMS1" "DefaultConnectionString": "Server=147.93.98.152;User ID=devuser;Password=AppUser@123$;Database=MarcoBMSGuid"
}, },
"SmtpSettings": { "SmtpSettings": {
"SmtpServer": "smtp.gmail.com", "SmtpServer": "smtp.gmail.com",