Added the API to get list organization hierarchy for certain employee

This commit is contained in:
ashutosh.nehete 2025-11-11 13:06:33 +05:30
parent 7928c6ca36
commit 7c80e49809
11 changed files with 7977 additions and 11 deletions

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,137 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Marco.Pms.DataAccess.Migrations
{
/// <inheritdoc />
public partial class Added_OrganiazationHierarchy_Related_Table : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "OrganizationHierarchies",
columns: table => new
{
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
EmployeeId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
ReportToId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
IsPrimary = table.Column<bool>(type: "tinyint(1)", nullable: false),
IsActive = table.Column<bool>(type: "tinyint(1)", nullable: false),
AssignedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
AssignedById = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
TenantId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci")
},
constraints: table =>
{
table.PrimaryKey("PK_OrganizationHierarchies", x => x.Id);
table.ForeignKey(
name: "FK_OrganizationHierarchies_Employees_AssignedById",
column: x => x.AssignedById,
principalTable: "Employees",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_OrganizationHierarchies_Employees_EmployeeId",
column: x => x.EmployeeId,
principalTable: "Employees",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_OrganizationHierarchies_Employees_ReportToId",
column: x => x.ReportToId,
principalTable: "Employees",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_OrganizationHierarchies_Tenants_TenantId",
column: x => x.TenantId,
principalTable: "Tenants",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "OrgHierarchyLogs",
columns: table => new
{
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
OrganizationHierarchyId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
ReAssignedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
ReAssignedById = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
TenantId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci")
},
constraints: table =>
{
table.PrimaryKey("PK_OrgHierarchyLogs", x => x.Id);
table.ForeignKey(
name: "FK_OrgHierarchyLogs_Employees_ReAssignedById",
column: x => x.ReAssignedById,
principalTable: "Employees",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_OrgHierarchyLogs_OrganizationHierarchies_OrganizationHierarc~",
column: x => x.OrganizationHierarchyId,
principalTable: "OrganizationHierarchies",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_OrgHierarchyLogs_Tenants_TenantId",
column: x => x.TenantId,
principalTable: "Tenants",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "IX_OrganizationHierarchies_AssignedById",
table: "OrganizationHierarchies",
column: "AssignedById");
migrationBuilder.CreateIndex(
name: "IX_OrganizationHierarchies_EmployeeId",
table: "OrganizationHierarchies",
column: "EmployeeId");
migrationBuilder.CreateIndex(
name: "IX_OrganizationHierarchies_ReportToId",
table: "OrganizationHierarchies",
column: "ReportToId");
migrationBuilder.CreateIndex(
name: "IX_OrganizationHierarchies_TenantId",
table: "OrganizationHierarchies",
column: "TenantId");
migrationBuilder.CreateIndex(
name: "IX_OrgHierarchyLogs_OrganizationHierarchyId",
table: "OrgHierarchyLogs",
column: "OrganizationHierarchyId");
migrationBuilder.CreateIndex(
name: "IX_OrgHierarchyLogs_ReAssignedById",
table: "OrgHierarchyLogs",
column: "ReAssignedById");
migrationBuilder.CreateIndex(
name: "IX_OrgHierarchyLogs_TenantId",
table: "OrgHierarchyLogs",
column: "TenantId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "OrgHierarchyLogs");
migrationBuilder.DropTable(
name: "OrganizationHierarchies");
}
}
}

View File

@ -4387,6 +4387,35 @@ namespace Marco.Pms.DataAccess.Migrations
});
});
modelBuilder.Entity("Marco.Pms.Model.OrganizationModel.OrgHierarchyLog", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<Guid>("OrganizationHierarchyId")
.HasColumnType("char(36)");
b.Property<DateTime>("ReAssignedAt")
.HasColumnType("datetime(6)");
b.Property<Guid>("ReAssignedById")
.HasColumnType("char(36)");
b.Property<Guid>("TenantId")
.HasColumnType("char(36)");
b.HasKey("Id");
b.HasIndex("OrganizationHierarchyId");
b.HasIndex("ReAssignedById");
b.HasIndex("TenantId");
b.ToTable("OrgHierarchyLogs");
});
modelBuilder.Entity("Marco.Pms.Model.OrganizationModel.OrgServiceMapping", b =>
{
b.Property<Guid>("Id")
@ -4501,6 +4530,46 @@ namespace Marco.Pms.DataAccess.Migrations
});
});
modelBuilder.Entity("Marco.Pms.Model.OrganizationModel.OrganizationHierarchy", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<DateTime>("AssignedAt")
.HasColumnType("datetime(6)");
b.Property<Guid>("AssignedById")
.HasColumnType("char(36)");
b.Property<Guid>("EmployeeId")
.HasColumnType("char(36)");
b.Property<bool>("IsActive")
.HasColumnType("tinyint(1)");
b.Property<bool>("IsPrimary")
.HasColumnType("tinyint(1)");
b.Property<Guid>("ReportToId")
.HasColumnType("char(36)");
b.Property<Guid>("TenantId")
.HasColumnType("char(36)");
b.HasKey("Id");
b.HasIndex("AssignedById");
b.HasIndex("EmployeeId");
b.HasIndex("ReportToId");
b.HasIndex("TenantId");
b.ToTable("OrganizationHierarchies");
});
modelBuilder.Entity("Marco.Pms.Model.OrganizationModel.ProjectOrgMapping", b =>
{
b.Property<Guid>("Id")
@ -6999,6 +7068,33 @@ namespace Marco.Pms.DataAccess.Migrations
b.Navigation("Tenant");
});
modelBuilder.Entity("Marco.Pms.Model.OrganizationModel.OrgHierarchyLog", b =>
{
b.HasOne("Marco.Pms.Model.OrganizationModel.OrganizationHierarchy", "OrganizationHierarchy")
.WithMany()
.HasForeignKey("OrganizationHierarchyId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Marco.Pms.Model.Employees.Employee", "ReAssignedBy")
.WithMany()
.HasForeignKey("ReAssignedById")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Marco.Pms.Model.TenantModels.Tenant", "Tenant")
.WithMany()
.HasForeignKey("TenantId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("OrganizationHierarchy");
b.Navigation("ReAssignedBy");
b.Navigation("Tenant");
});
modelBuilder.Entity("Marco.Pms.Model.OrganizationModel.OrgServiceMapping", b =>
{
b.HasOne("Marco.Pms.Model.OrganizationModel.Organization", "Organization")
@ -7018,6 +7114,41 @@ namespace Marco.Pms.DataAccess.Migrations
b.Navigation("Service");
});
modelBuilder.Entity("Marco.Pms.Model.OrganizationModel.OrganizationHierarchy", b =>
{
b.HasOne("Marco.Pms.Model.Employees.Employee", "AssignedBy")
.WithMany()
.HasForeignKey("AssignedById")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Marco.Pms.Model.Employees.Employee", "Employee")
.WithMany()
.HasForeignKey("EmployeeId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Marco.Pms.Model.Employees.Employee", "ReportTo")
.WithMany()
.HasForeignKey("ReportToId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Marco.Pms.Model.TenantModels.Tenant", "Tenant")
.WithMany()
.HasForeignKey("TenantId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("AssignedBy");
b.Navigation("Employee");
b.Navigation("ReportTo");
b.Navigation("Tenant");
});
modelBuilder.Entity("Marco.Pms.Model.OrganizationModel.ProjectOrgMapping", b =>
{
b.HasOne("Marco.Pms.Model.Employees.Employee", "AssignedBy")

View File

@ -0,0 +1,9 @@
namespace Marco.Pms.Model.Dtos.Organization
{
public class OrganizationHierarchyDto
{
public Guid ReportToId { get; set; }
public bool IsPrimary { get; set; }
public bool IsActive { get; set; }
}
}

View File

@ -13,14 +13,8 @@ namespace Marco.Pms.Model.OrganizationModel
[ValidateNever]
[ForeignKey("OrganizationHierarchyId")]
public OrganizationHierarchy? OrganizationHierarchy { get; set; }
public DateTime AssignedAt { get; set; }
public Guid AssignedById { get; set; }
[ValidateNever]
[ForeignKey("AssignedById")]
public Employee? AssignedBy { get; set; }
public DateTime? ReAssignedAt { get; set; }
public Guid? ReAssignedById { get; set; }
public DateTime ReAssignedAt { get; set; }
public Guid ReAssignedById { get; set; }
[ValidateNever]
[ForeignKey("ReAssignedById")]

View File

@ -0,0 +1,15 @@
using Marco.Pms.Model.ViewModels.Activities;
namespace Marco.Pms.Model.ViewModels.Organization
{
public class OrganizationHierarchyVM
{
public Guid Id { get; set; }
public BasicEmployeeVM? Employee { get; set; }
public BasicEmployeeVM? ReportTo { get; set; }
public bool IsPrimary { get; set; }
public bool IsActive { get; set; }
public DateTime AssignedAt { get; set; }
public BasicEmployeeVM? AssignedBy { get; set; }
}
}

View File

@ -104,6 +104,8 @@ namespace Marco.Pms.Services.Controllers
return StatusCode(response.StatusCode, response);
}
#endregion
#region =================================================================== Put Functions ===================================================================

View File

@ -59,7 +59,8 @@ namespace Marco.Pms.Services.MappingProfiles
dest => dest.Id,
// Explicitly and safely convert string Id to Guid Id
opt => opt.MapFrom(src => new Guid(src.Id))
);
);
CreateMap<OrganizationHierarchy, OrganizationHierarchyVM>();
#endregion

View File

@ -268,11 +268,58 @@ namespace Marco.Pms.Services.Service
return ApiResponse<object>.ErrorResponse("Internal error", "An internal exception occurred", 500);
}
}
/// <summary>
/// Retrieves the active organization hierarchy list for a specified employee within a given tenant.
/// </summary>
/// <param name="employeeId">ID of the employee whose hierarchy is requested.</param>
/// <param name="loggedInEmployee">Logged-in employee making the request (for audit/logging).</param>
/// <param name="tenantId">Tenant ID for multi-tenant filtering.</param>
/// <param name="loggedOrganizationId">Organization ID of the logged-in employee (for access validation).</param>
/// <returns>ApiResponse containing the list of organization hierarchy view models or error details.</returns>
public async Task<ApiResponse<object>> GetOrganizationHierarchyListAsync(Guid employeeId, Employee loggedInEmployee, Guid tenantId, Guid loggedOrganizationId)
{
return ApiResponse<object>.SuccessResponse(new { });
// Validate input IDs
if (tenantId == Guid.Empty || loggedOrganizationId == Guid.Empty)
{
_logger.LogWarning("Access denied: Invalid tenantId or loggedOrganizationId. TenantId: {TenantId}, OrganizationId: {OrganizationId}", tenantId, loggedOrganizationId);
return ApiResponse<object>.ErrorResponse("Access Denied", "Invalid tenant or organization identifier.", 403);
}
try
{
// Query to get active organization hierarchies, including related navigation properties for richer data
var organizationHierarchies = await _context.OrganizationHierarchies
.Include(oh => oh.Employee).ThenInclude(e => e!.JobRole)
.Include(oh => oh.AssignedBy).ThenInclude(e => e!.JobRole)
.Include(oh => oh.ReportTo).ThenInclude(e => e!.JobRole)
.AsNoTracking()
.Where(oh => oh.EmployeeId == employeeId && oh.IsActive && oh.TenantId == tenantId)
.OrderByDescending(oh => oh.AssignedAt)
.ToListAsync();
// Check if any records found
if (!organizationHierarchies.Any())
{
_logger.LogWarning("No active organization hierarchy found for EmployeeId: {EmployeeId} in TenantId: {TenantId}.", employeeId, tenantId);
return ApiResponse<object>.SuccessResponse(new List<OrganizationHierarchyVM>(), "No active superiors found.", 200);
}
// Map entities to view models
var response = _mapper.Map<List<OrganizationHierarchyVM>>(organizationHierarchies);
_logger.LogInfo("Fetched {Count} active superiors for EmployeeId: {EmployeeId} in TenantId: {TenantId}.", response.Count, employeeId, tenantId);
return ApiResponse<object>.SuccessResponse(response, $"{response.Count} superior(s) fetched successfully.", 200);
}
catch (Exception ex)
{
_logger.LogError(ex, "An error occurred while fetching organization hierarchy list for EmployeeId: {EmployeeId} in TenantId: {TenantId}.", employeeId, tenantId);
return ApiResponse<object>.ErrorResponse("Internal Server Error", "An error occurred while processing your request.", 500);
}
}
#endregion
#region =================================================================== Post Functions ===================================================================
@ -734,6 +781,7 @@ namespace Marco.Pms.Services.Service
}
}
#endregion
#region =================================================================== Put Functions ===================================================================

View File

@ -16,6 +16,7 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces
Task<ApiResponse<object>> CreateOrganizationAsync(CreateOrganizationDto model, Employee loggedInEmployee, Guid tenantId, Guid loggedOrganizationId);
Task<ApiResponse<object>> AssignOrganizationToProjectAsync(AssignOrganizationDto model, Employee loggedInEmployee, Guid tenantId, Guid loggedOrganizationId);
Task<ApiResponse<object>> AssignOrganizationToTenantAsync(Guid organizationId, Employee loggedInEmployee, Guid tenantId, Guid loggedOrganizationId);
#endregion
#region =================================================================== Put Functions ===================================================================

View File

@ -9,7 +9,7 @@
"Title": "Dev"
},
"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=MarcoBMSOrg"
},
"SmtpSettings": {
"SmtpServer": "smtp.gmail.com",