Optimized the manage organization hierarchy API
This commit is contained in:
parent
b023883233
commit
128417858e
@ -780,79 +780,168 @@ namespace Marco.Pms.Services.Service
|
||||
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)
|
||||
{
|
||||
var reportToIDs = model.Select(oh => oh.ReportToId).ToList();
|
||||
var existingOrganizationHieraechies = await _context.OrganizationHierarchies
|
||||
.AsNoTracking()
|
||||
.Where(oh => oh.EmployeeId == employeeId && reportToIDs.Contains(oh.ReportToId) && oh.IsActive && oh.TenantId == tenantId)
|
||||
.ToListAsync();
|
||||
var newOrganizationHieraechies = new List<OrganizationHierarchy>();
|
||||
var removeOrganizationHieraechies = new List<OrganizationHierarchy>();
|
||||
var orgHierarchyLogs = new List<OrgHierarchyLog>();
|
||||
// Validate required parameters early to avoid wasted DB calls
|
||||
if (tenantId == Guid.Empty || loggedOrganizationId == Guid.Empty)
|
||||
{
|
||||
_logger.LogWarning("Unauthorized attempt: Invalid tenant or organization IDs. TenantId: {TenantId}, OrgId: {OrgId}", tenantId, loggedOrganizationId);
|
||||
|
||||
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);
|
||||
if (hierarchy.IsActive && existingOrganizationHieraechy == null)
|
||||
_logger.LogInfo("No data provided for employee {EmployeeId} hierarchy update.", employeeId);
|
||||
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(),
|
||||
EmployeeId = employeeId,
|
||||
ReportToId = hierarchy.ReportToId,
|
||||
IsPrimary = hierarchy.IsPrimary,
|
||||
ReportToId = dto.ReportToId,
|
||||
IsPrimary = dto.IsPrimary,
|
||||
IsActive = true,
|
||||
AssignedAt = DateTime.UtcNow,
|
||||
AssignedById = loggedInEmployee.Id,
|
||||
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;
|
||||
removeOrganizationHieraechies.Add(existingOrganizationHieraechy);
|
||||
// Deactivate existing entry if found and allowed
|
||||
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(),
|
||||
OrganizationHierarchyId = existingOrganizationHieraechy.Id,
|
||||
OrganizationHierarchyId = matchingEntry.Id,
|
||||
ReAssignedAt = DateTime.UtcNow,
|
||||
ReAssignedById = loggedInEmployee.Id,
|
||||
TenantId = tenantId
|
||||
});
|
||||
|
||||
_logger.LogInfo("Marked hierarchy for deactivation: EmployeeId {EmployeeId}, ReportsTo {ReportToId}", employeeId, dto.ReportToId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (newOrganizationHieraechies.Any())
|
||||
{
|
||||
_context.OrganizationHierarchies.AddRange(newOrganizationHieraechies);
|
||||
}
|
||||
if (removeOrganizationHieraechies.Any())
|
||||
{
|
||||
_context.OrganizationHierarchies.UpdateRange(removeOrganizationHieraechies);
|
||||
}
|
||||
if (orgHierarchyLogs.Any())
|
||||
{
|
||||
_context.OrgHierarchyLogs.AddRange(orgHierarchyLogs);
|
||||
}
|
||||
// Batch database operations for insertions and updates
|
||||
if (newEntries.Any()) _context.OrganizationHierarchies.AddRange(newEntries);
|
||||
if (deactivateEntries.Any()) _context.OrganizationHierarchies.UpdateRange(deactivateEntries);
|
||||
if (auditLogs.Any()) _context.OrgHierarchyLogs.AddRange(auditLogs);
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var organizationHieraechies = await _context.OrganizationHierarchies
|
||||
.Include(oh => oh.Employee).ThenInclude(e => e!.JobRole)
|
||||
.Include(oh => oh.ReportTo).ThenInclude(e => e!.JobRole)
|
||||
.Include(oh => oh.AssignedBy).ThenInclude(e => e!.JobRole)
|
||||
// Reload updated active hierarchy with related entities to respond with fresh data
|
||||
var updatedHierarchy = await _context.OrganizationHierarchies
|
||||
.Include(o => o.Employee).ThenInclude(e => e!.JobRole)
|
||||
.Include(o => o.ReportTo).ThenInclude(e => e!.JobRole)
|
||||
.Include(o => o.AssignedBy).ThenInclude(e => e!.JobRole)
|
||||
.AsNoTracking()
|
||||
.Where(oh => oh.EmployeeId == employeeId && oh.IsActive && oh.TenantId == tenantId)
|
||||
.OrderByDescending(oh => oh.AssignedAt)
|
||||
.ToListAsync();
|
||||
|
||||
var response = _mapper.Map<List<OrganizationHierarchyVM>>(organizationHieraechies);
|
||||
return ApiResponse<object>.SuccessResponse(response, $"{response.Count} superior fetched successfully", 200);
|
||||
var response = _mapper.Map<List<OrganizationHierarchyVM>>(updatedHierarchy);
|
||||
|
||||
_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
|
||||
|
||||
#region =================================================================== Put Functions ===================================================================
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user