Optimized the manage organization hierarchy API

This commit is contained in:
ashutosh.nehete 2025-11-11 16:53:51 +05:30
parent b023883233
commit 128417858e

View File

@ -780,79 +780,168 @@ namespace Marco.Pms.Services.Service
return ApiResponse<object>.ErrorResponse("Internal error", "An internal exception has occurred", 500); return ApiResponse<object>.ErrorResponse("Internal error", "An internal exception has occurred", 500);
} }
} }
/// <summary>
/// Atomically manage the organization hierarchy for an employee: add, deactivate, and audit changes.
/// </summary>
/// <param name="employeeId">Employee GUID to manage hierarchies for.</param>
/// <param name="model">List of hierarchy changes (DTOs).</param>
/// <param name="loggedInEmployee">Current user performing the operation.</param>
/// <param name="tenantId">Tenant context for multi-tenancy support.</param>
/// <param name="loggedOrganizationId">Current logged-in organization context.</param>
/// <returns>Standardized ApiResponse with updated hierarchy or error details.</returns>
public async Task<ApiResponse<object>> ManageOrganizationHierarchyAsync(Guid employeeId, List<OrganizationHierarchyDto> model, Employee loggedInEmployee, Guid tenantId, Guid loggedOrganizationId) public async Task<ApiResponse<object>> ManageOrganizationHierarchyAsync(Guid employeeId, List<OrganizationHierarchyDto> model, Employee loggedInEmployee, Guid tenantId, Guid loggedOrganizationId)
{ {
var reportToIDs = model.Select(oh => oh.ReportToId).ToList(); // Validate required parameters early to avoid wasted DB calls
var existingOrganizationHieraechies = await _context.OrganizationHierarchies if (tenantId == Guid.Empty || loggedOrganizationId == Guid.Empty)
.AsNoTracking() {
.Where(oh => oh.EmployeeId == employeeId && reportToIDs.Contains(oh.ReportToId) && oh.IsActive && oh.TenantId == tenantId) _logger.LogWarning("Unauthorized attempt: Invalid tenant or organization IDs. TenantId: {TenantId}, OrgId: {OrgId}", tenantId, loggedOrganizationId);
.ToListAsync();
var newOrganizationHieraechies = new List<OrganizationHierarchy>();
var removeOrganizationHieraechies = new List<OrganizationHierarchy>();
var orgHierarchyLogs = new List<OrgHierarchyLog>();
foreach (var hierarchy in model) return ApiResponse<object>.ErrorResponse("Access Denied", "Invalid tenant or organization context.", 403);
}
if (model == null || model.Count == 0)
{ {
var existingOrganizationHieraechy = existingOrganizationHieraechies.FirstOrDefault(oh => oh.EmployeeId == employeeId && oh.ReportToId == hierarchy.ReportToId); _logger.LogInfo("No data provided for employee {EmployeeId} hierarchy update.", employeeId);
if (hierarchy.IsActive && existingOrganizationHieraechy == null) return ApiResponse<object>.ErrorResponse("No hierarchy data provided.", "No hierarchy data provided.", 400);
}
var primaryHierarchies = model.Where(oh => oh.IsPrimary && oh.IsActive).ToList();
// Check if multiple primary hierarchies are provided for the employee
if (primaryHierarchies.Count > 1)
{ {
var newOrganizationHieraechy = new OrganizationHierarchy // Log a warning indicating multiple primary hierarchies are not allowed
_logger.LogWarning("Multiple primary hierarchy entries detected for employee {EmployeeId}. Only one primary hierarchy is allowed.", employeeId);
// Return a bad request response with a clear, user-friendly message and an error code
return ApiResponse<object>.ErrorResponse(
"Multiple primary hierarchies detected. Only one primary hierarchy is permitted per employee.",
"Multiple primary hierarchies detected. Only one primary hierarchy is permitted per employee.",
400);
}
try
{
// Fetch current active hierarchies for employee and tenant status, no tracking needed since we will update selectively
var existingHierarchies = await _context.OrganizationHierarchies
.Where(oh => oh.EmployeeId == employeeId && oh.IsActive && oh.TenantId == tenantId)
.ToListAsync();
var newEntries = new List<OrganizationHierarchy>();
var deactivateEntries = new List<OrganizationHierarchy>();
var auditLogs = new List<OrgHierarchyLog>();
// Cache primary hierarchy for quick reference to enforce business rules about one primary per employee
var existingPrimary = existingHierarchies.FirstOrDefault(oh => oh.IsPrimary);
// Process each input model item intelligently
foreach (var dto in model)
{
var matchingEntry = existingHierarchies
.FirstOrDefault(oh => oh.ReportToId == dto.ReportToId && oh.IsPrimary == dto.IsPrimary);
if (dto.IsActive)
{
// Add new entry if none exists
if (matchingEntry == null)
{
// Enforce primary uniqueness by checking if a primary exists and whether client intends to deactivate the old one
if (dto.IsPrimary && existingPrimary != null)
{
var intendedPrimaryDeactivation = model.Any(m =>
m.IsPrimary && !m.IsActive && m.ReportToId == existingPrimary.ReportToId);
if (!intendedPrimaryDeactivation)
{
_logger.LogWarning("Attempt to assign a second primary hierarchy for employee {EmployeeId} without deactivating current one.",
employeeId);
continue; // Skip this to maintain data integrity
}
}
newEntries.Add(new OrganizationHierarchy
{ {
Id = Guid.NewGuid(), Id = Guid.NewGuid(),
EmployeeId = employeeId, EmployeeId = employeeId,
ReportToId = hierarchy.ReportToId, ReportToId = dto.ReportToId,
IsPrimary = hierarchy.IsPrimary, IsPrimary = dto.IsPrimary,
IsActive = true, IsActive = true,
AssignedAt = DateTime.UtcNow, AssignedAt = DateTime.UtcNow,
AssignedById = loggedInEmployee.Id, AssignedById = loggedInEmployee.Id,
TenantId = tenantId TenantId = tenantId
}; });
newOrganizationHieraechies.Add(newOrganizationHieraechy);
_logger.LogInfo("Prepared new active hierarchy link: EmployeeId {EmployeeId}, ReportsTo {ReportToId}, Primary {Primary}",
employeeId, dto.ReportToId, dto.IsPrimary);
} }
else if (!hierarchy.IsActive && existingOrganizationHieraechy != null) }
else
{ {
existingOrganizationHieraechy.IsActive = false; // Deactivate existing entry if found and allowed
removeOrganizationHieraechies.Add(existingOrganizationHieraechy); if (matchingEntry != null)
{
if (dto.IsPrimary)
{
// Confirm alternative primary exists on active state to avoid orphan primary state
var alternativePrimaryExists = model.Any(m =>
m.IsPrimary && m.IsActive && m.ReportToId != dto.ReportToId);
orgHierarchyLogs.Add(new OrgHierarchyLog if (!alternativePrimaryExists)
{
_logger.LogWarning("Attempt to deactivate sole primary hierarchy for employee {EmployeeId} prevented.", employeeId);
continue; // Skip deactivation to avoid orphan primary
}
}
matchingEntry.IsActive = false;
deactivateEntries.Add(matchingEntry);
auditLogs.Add(new OrgHierarchyLog
{ {
Id = Guid.NewGuid(), Id = Guid.NewGuid(),
OrganizationHierarchyId = existingOrganizationHieraechy.Id, OrganizationHierarchyId = matchingEntry.Id,
ReAssignedAt = DateTime.UtcNow, ReAssignedAt = DateTime.UtcNow,
ReAssignedById = loggedInEmployee.Id, ReAssignedById = loggedInEmployee.Id,
TenantId = tenantId TenantId = tenantId
}); });
_logger.LogInfo("Marked hierarchy for deactivation: EmployeeId {EmployeeId}, ReportsTo {ReportToId}", employeeId, dto.ReportToId);
}
} }
} }
if (newOrganizationHieraechies.Any()) // Batch database operations for insertions and updates
{ if (newEntries.Any()) _context.OrganizationHierarchies.AddRange(newEntries);
_context.OrganizationHierarchies.AddRange(newOrganizationHieraechies); if (deactivateEntries.Any()) _context.OrganizationHierarchies.UpdateRange(deactivateEntries);
} if (auditLogs.Any()) _context.OrgHierarchyLogs.AddRange(auditLogs);
if (removeOrganizationHieraechies.Any())
{
_context.OrganizationHierarchies.UpdateRange(removeOrganizationHieraechies);
}
if (orgHierarchyLogs.Any())
{
_context.OrgHierarchyLogs.AddRange(orgHierarchyLogs);
}
await _context.SaveChangesAsync(); await _context.SaveChangesAsync();
var organizationHieraechies = await _context.OrganizationHierarchies // Reload updated active hierarchy with related entities to respond with fresh data
.Include(oh => oh.Employee).ThenInclude(e => e!.JobRole) var updatedHierarchy = await _context.OrganizationHierarchies
.Include(oh => oh.ReportTo).ThenInclude(e => e!.JobRole) .Include(o => o.Employee).ThenInclude(e => e!.JobRole)
.Include(oh => oh.AssignedBy).ThenInclude(e => e!.JobRole) .Include(o => o.ReportTo).ThenInclude(e => e!.JobRole)
.Include(o => o.AssignedBy).ThenInclude(e => e!.JobRole)
.AsNoTracking() .AsNoTracking()
.Where(oh => oh.EmployeeId == employeeId && oh.IsActive && oh.TenantId == tenantId) .Where(oh => oh.EmployeeId == employeeId && oh.IsActive && oh.TenantId == tenantId)
.OrderByDescending(oh => oh.AssignedAt) .OrderByDescending(oh => oh.AssignedAt)
.ToListAsync(); .ToListAsync();
var response = _mapper.Map<List<OrganizationHierarchyVM>>(organizationHieraechies); var response = _mapper.Map<List<OrganizationHierarchyVM>>(updatedHierarchy);
return ApiResponse<object>.SuccessResponse(response, $"{response.Count} superior fetched successfully", 200);
_logger.LogInfo("Organization hierarchy update completed for employee {EmployeeId}. NewEntries: {NewCount}, DeactivatedEntries: {DeactivatedCount}, LogsCreated: {LogCount}.",
employeeId, newEntries.Count, deactivateEntries.Count, auditLogs.Count);
return ApiResponse<object>.SuccessResponse(response, $"{response.Count} active superior(s) retrieved and updated successfully.", 200);
} }
catch (Exception ex)
{
_logger.LogError(ex, "Exception while managing organization hierarchy for employee {EmployeeId} in tenant {TenantId}.", employeeId, tenantId);
return ApiResponse<object>.ErrorResponse("Internal Server Error", "An unexpected error occurred while processing the request.", 500);
}
}
#endregion #endregion
#region =================================================================== Put Functions =================================================================== #region =================================================================== Put Functions ===================================================================