Merge pull request 'Ashutosh_UserTask#1727' (#152) from Ashutosh_UserTask#1727 into Organization_Hierarchy
Reviewed-on: #152
This commit is contained in:
commit
b9c8fb539f
@ -104,7 +104,18 @@ namespace Marco.Pms.Services.Controllers
|
|||||||
return StatusCode(response.StatusCode, response);
|
return StatusCode(response.StatusCode, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("hierarchy/manage/{employeeId}")]
|
||||||
|
public async Task<IActionResult> ManageOrganizationHierarchy(Guid employeeId, [FromBody] List<OrganizationHierarchyDto> model)
|
||||||
|
{
|
||||||
|
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||||
|
var response = await _organizationService.ManageOrganizationHierarchyAsync(employeeId, model, loggedInEmployee, tenantId, loggedOrganizationId);
|
||||||
|
if (response.Success)
|
||||||
|
{
|
||||||
|
var notification = new { LoggedInUserId = loggedInEmployee.Id, Keyword = "Organization_Hierarchy", Response = response.Data };
|
||||||
|
await _signalR.SendNotificationAsync(notification);
|
||||||
|
}
|
||||||
|
return StatusCode(response.StatusCode, response);
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|||||||
@ -781,6 +781,166 @@ namespace Marco.Pms.Services.Service
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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)
|
||||||
|
{
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
return ApiResponse<object>.ErrorResponse("Access Denied", "Invalid tenant or organization context.", 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (model == null || model.Count == 0)
|
||||||
|
{
|
||||||
|
_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)
|
||||||
|
{
|
||||||
|
// 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 = dto.ReportToId,
|
||||||
|
IsPrimary = dto.IsPrimary,
|
||||||
|
IsActive = true,
|
||||||
|
AssignedAt = DateTime.UtcNow,
|
||||||
|
AssignedById = loggedInEmployee.Id,
|
||||||
|
TenantId = tenantId
|
||||||
|
});
|
||||||
|
|
||||||
|
_logger.LogInfo("Prepared new active hierarchy link: EmployeeId {EmployeeId}, ReportsTo {ReportToId}, Primary {Primary}",
|
||||||
|
employeeId, dto.ReportToId, dto.IsPrimary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
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 = matchingEntry.Id,
|
||||||
|
ReAssignedAt = DateTime.UtcNow,
|
||||||
|
ReAssignedById = loggedInEmployee.Id,
|
||||||
|
TenantId = tenantId
|
||||||
|
});
|
||||||
|
|
||||||
|
_logger.LogInfo("Marked hierarchy for deactivation: EmployeeId {EmployeeId}, ReportsTo {ReportToId}", employeeId, dto.ReportToId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
// 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>>(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
|
#endregion
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,7 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces
|
|||||||
Task<ApiResponse<object>> CreateOrganizationAsync(CreateOrganizationDto model, Employee loggedInEmployee, Guid tenantId, Guid loggedOrganizationId);
|
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>> AssignOrganizationToProjectAsync(AssignOrganizationDto model, Employee loggedInEmployee, Guid tenantId, Guid loggedOrganizationId);
|
||||||
Task<ApiResponse<object>> AssignOrganizationToTenantAsync(Guid organizationId, Employee loggedInEmployee, Guid tenantId, Guid loggedOrganizationId);
|
Task<ApiResponse<object>> AssignOrganizationToTenantAsync(Guid organizationId, Employee loggedInEmployee, Guid tenantId, Guid loggedOrganizationId);
|
||||||
|
Task<ApiResponse<object>> ManageOrganizationHierarchyAsync(Guid employeeId, List<OrganizationHierarchyDto> model, Employee loggedInEmployee, Guid tenantId, Guid loggedOrganizationId);
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region =================================================================== Put Functions ===================================================================
|
#region =================================================================== Put Functions ===================================================================
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user