Optimized the Organization APIs

This commit is contained in:
ashutosh.nehete 2025-09-25 17:17:21 +05:30
parent 18d590ccbe
commit f248557704

View File

@ -269,19 +269,25 @@ namespace Marco.Pms.Services.Controllers
} }
} }
[HttpPost("assign/project")] [HttpPost("assign/project")]
public async Task<IActionResult> AssignOrganizationToProjectAsync([FromBody] AssignOrganizationDto model) public async Task<IActionResult> AssignOrganizationToProjectAsync([FromBody] AssignOrganizationDto model)
{ {
_logger.LogDebug("Started assigning organization {OrganizationId} to project {ProjectId} with service IDs {@ServiceIds}",
model.OrganizationId, model.ProjectId, model.ServiceIds);
// Create DbContext for the method scope
await using var _context = await _dbContextFactory.CreateDbContextAsync(); await using var _context = await _dbContextFactory.CreateDbContextAsync();
using var scope = _serviceScope.CreateScope();
// Begin a database transaction
await using var transaction = await _context.Database.BeginTransactionAsync(); await using var transaction = await _context.Database.BeginTransactionAsync();
try try
{ {
// Get currently logged in employee
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
var todaysDate = DateTime.UtcNow.Date; var today = DateTime.UtcNow.Date;
// Fetch all needed entities concurrently using the single context
var projectServicesTask = Task.Run(async () => var projectServicesTask = Task.Run(async () =>
{ {
await using var context = await _dbContextFactory.CreateDbContextAsync(); await using var context = await _dbContextFactory.CreateDbContextAsync();
@ -289,7 +295,7 @@ namespace Marco.Pms.Services.Controllers
.Where(sp => model.ServiceIds.Contains(sp.ServiceId) && sp.ProjectId == model.ProjectId && sp.IsActive).ToListAsync(); .Where(sp => model.ServiceIds.Contains(sp.ServiceId) && sp.ProjectId == model.ProjectId && sp.IsActive).ToListAsync();
}); });
var projectOrganizationsTask = Task.Run(async () => var projectOrgMappingsTask = Task.Run(async () =>
{ {
await using var context = await _dbContextFactory.CreateDbContextAsync(); await using var context = await _dbContextFactory.CreateDbContextAsync();
return await context.ProjectOrgMappings return await context.ProjectOrgMappings
@ -303,7 +309,7 @@ namespace Marco.Pms.Services.Controllers
return await context.ServiceMasters.Where(s => model.ServiceIds.Contains(s.Id) && s.TenantId == tenantId).ToListAsync(); return await context.ServiceMasters.Where(s => model.ServiceIds.Contains(s.Id) && s.TenantId == tenantId).ToListAsync();
}); });
var organizationTypeTask = Task.Run(async () => var orgTypeTask = Task.Run(async () =>
{ {
await using var context = await _dbContextFactory.CreateDbContextAsync(); await using var context = await _dbContextFactory.CreateDbContextAsync();
return await context.OrgTypeMasters.FirstOrDefaultAsync(o => o.Id == model.OrganizationId); return await context.OrgTypeMasters.FirstOrDefaultAsync(o => o.Id == model.OrganizationId);
@ -315,7 +321,7 @@ namespace Marco.Pms.Services.Controllers
return await context.Organizations.FirstOrDefaultAsync(o => o.Id == model.OrganizationId); return await context.Organizations.FirstOrDefaultAsync(o => o.Id == model.OrganizationId);
}); });
var parentorganizationTask = Task.Run(async () => var parentOrgTask = Task.Run(async () =>
{ {
await using var context = await _dbContextFactory.CreateDbContextAsync(); await using var context = await _dbContextFactory.CreateDbContextAsync();
return await context.Organizations.FirstOrDefaultAsync(o => o.Id == model.ParentOrganizationId); return await context.Organizations.FirstOrDefaultAsync(o => o.Id == model.ParentOrganizationId);
@ -340,68 +346,82 @@ namespace Marco.Pms.Services.Controllers
&& p.OrganizationTypeId == ServiceProvider); && p.OrganizationTypeId == ServiceProvider);
}); });
await Task.WhenAll(organizationTask, parentorganizationTask, projectTask, projectServicesTask, isPMCTask, isServiceProviderTask, await Task.WhenAll(projectTask, organizationTask, parentOrgTask, serviceTask, orgTypeTask, projectServicesTask, projectOrgMappingsTask, isPMCTask, isServiceProviderTask);
serviceTask, organizationTypeTask, projectOrganizationsTask);
var services = serviceTask.Result;
var organizationType = organizationTypeTask.Result;
var organization = organizationTask.Result;
var projectServices = projectServicesTask.Result;
var parentorganization = parentorganizationTask.Result;
var projectOrganizations = projectOrganizationsTask.Result;
var project = projectTask.Result; var project = projectTask.Result;
var organization = organizationTask.Result;
var parentOrganization = parentOrgTask.Result;
var services = serviceTask.Result;
var organizationType = orgTypeTask.Result;
var projectServices = projectServicesTask.Result;
var projectOrganizations = projectOrgMappingsTask.Result;
var isPMC = isPMCTask.Result; var isPMC = isPMCTask.Result;
var isServiceProvider = isServiceProviderTask.Result; var isServiceProvider = isServiceProviderTask.Result;
// Validation checks
if (organization == null) if (organization == null)
{ {
_logger.LogWarning("Organization with ID {OrganizationId} not found.", model.OrganizationId);
return NotFound(ApiResponse<object>.ErrorResponse("Organization not found", "Organization not found in database", 404)); return NotFound(ApiResponse<object>.ErrorResponse("Organization not found", "Organization not found in database", 404));
} }
if (project == null) if (project == null)
{ {
_logger.LogWarning("Project with ID {ProjectId} not found.", model.ProjectId);
return NotFound(ApiResponse<object>.ErrorResponse("Project not found", "Project not found in database", 404)); return NotFound(ApiResponse<object>.ErrorResponse("Project not found", "Project not found in database", 404));
} }
if (projectServices == null) if (services == null || !services.Any())
{ {
_logger.LogWarning("No services found for Service IDs {@ServiceIds}.", model.ServiceIds);
return NotFound(ApiResponse<object>.ErrorResponse("Project Service not found", "Project Service not found in database", 404)); return NotFound(ApiResponse<object>.ErrorResponse("Project Service not found", "Project Service not found in database", 404));
} }
// Check whether mapping exists between service provider organization and tenant
var serviceProviderTenantMapping = await _context.TenantOrgMappings var serviceProviderTenantMapping = await _context.TenantOrgMappings
.FirstOrDefaultAsync(spt => spt.OrganizationId == model.OrganizationId && spt.TenantId == project.TenantId && spt.IsActive); .FirstOrDefaultAsync(spt => spt.OrganizationId == model.OrganizationId && spt.TenantId == project.TenantId && spt.IsActive);
if (serviceProviderTenantMapping == null) if (serviceProviderTenantMapping == null)
{ {
var newServiceProviderTenantMapping = new TenantOrgMapping var newMapping = new TenantOrgMapping
{ {
OrganizationId = organization.Id, OrganizationId = organization.Id,
SPRID = organization.SPRID, SPRID = organization.SPRID,
AssignedDate = DateTime.UtcNow, AssignedDate = today,
IsActive = true, IsActive = true,
AssignedById = loggedInEmployee.Id, AssignedById = loggedInEmployee.Id,
TenantId = project.TenantId TenantId = project.TenantId
}; };
_context.TenantOrgMappings.Add(newServiceProviderTenantMapping); _context.TenantOrgMappings.Add(newMapping);
_logger.LogInfo("Created new TenantOrgMapping for OrganizationId {OrganizationId} and TenantId {TenantId}",
organization.Id, project.TenantId);
} }
List<ProjectOrgMapping> projectOrgMappings = new List<ProjectOrgMapping>(); // Access control validations
List<ProjectServiceMapping> projectServiceMappings = new List<ProjectServiceMapping>();
if (isPMC && model.OrganizationTypeId != ServiceProvider && model.OrganizationTypeId != SubContractorProvider) if (isPMC && model.OrganizationTypeId != ServiceProvider && model.OrganizationTypeId != SubContractorProvider)
{ {
_logger.LogWarning("PMCs cannot assign organization type {OrganizationTypeId}. UserId: {UserId}",
model.OrganizationTypeId, loggedInEmployee.Id);
return StatusCode(403, ApiResponse<object>.ErrorResponse("Access Denied", "You don't have access to assign this type of organization", 403)); return StatusCode(403, ApiResponse<object>.ErrorResponse("Access Denied", "You don't have access to assign this type of organization", 403));
} }
if (isServiceProvider && model.OrganizationTypeId == ServiceProvider) if (isServiceProvider && model.OrganizationTypeId == ServiceProvider)
{ {
_logger.LogWarning("Service providers cannot assign organization type {OrganizationTypeId}. UserId: {UserId}",
model.OrganizationTypeId, loggedInEmployee.Id);
return StatusCode(403, ApiResponse<object>.ErrorResponse("Access Denied", "You don't have access to assign this type of organization", 403)); return StatusCode(403, ApiResponse<object>.ErrorResponse("Access Denied", "You don't have access to assign this type of organization", 403));
} }
var newProjectOrgMappings = new List<ProjectOrgMapping>();
var newProjectServiceMappings = new List<ProjectServiceMapping>();
// Loop through each service to create mappings
foreach (var serviceId in model.ServiceIds) foreach (var serviceId in model.ServiceIds)
{ {
var service = await _context.ServiceMasters.FirstOrDefaultAsync(s => s.Id == serviceId); var service = services.FirstOrDefault(s => s.Id == serviceId);
if (service == null) if (service == null)
{ {
_logger.LogWarning("Service with ID {ServiceId} not found.", serviceId);
return NotFound(ApiResponse<object>.ErrorResponse("Service not found", "Service not found in database", 404)); return NotFound(ApiResponse<object>.ErrorResponse("Service not found", "Service not found in database", 404));
} }
var projectService = projectServices.FirstOrDefault(ps => ps.ServiceId == serviceId); var projectService = projectServices.FirstOrDefault(ps => ps.ServiceId == serviceId);
if (projectService == null) if (projectService == null)
{ {
@ -411,54 +431,69 @@ namespace Marco.Pms.Services.Controllers
ProjectId = project.Id, ProjectId = project.Id,
ServiceId = serviceId, ServiceId = serviceId,
TenantId = project.TenantId, TenantId = project.TenantId,
PlannedStartDate = project.StartDate ?? DateTime.UtcNow, PlannedStartDate = project.StartDate ?? today,
PlannedEndDate = project.EndDate ?? DateTime.UtcNow, PlannedEndDate = project.EndDate ?? today,
ActualStartDate = DateTime.UtcNow, ActualStartDate = today,
IsActive = true IsActive = true
}; };
projectServiceMappings.Add(projectService); newProjectServiceMappings.Add(projectService);
} }
// Check if the organization is already assigned for this service
var existingAssignment = projectOrganizations.FirstOrDefault(po => po.ProjectService != null
&& po.ProjectService.ProjectId == project.Id
&& po.ProjectService.ServiceId == serviceId
&& po.OrganizationId == model.OrganizationId);
if (existingAssignment != null)
{
_logger.LogWarning("Organization {OrganizationId} is already assigned to project {ProjectId} for service {ServiceId}.",
model.OrganizationId, project.Id, serviceId);
return Conflict(ApiResponse<object>.ErrorResponse("Organization already assigned", "Organization is already assigned to this project and service", 409));
}
// Prepare new project-org mapping
var projectOrgMapping = new ProjectOrgMapping var projectOrgMapping = new ProjectOrgMapping
{ {
ProjectServiceId = projectService.Id, ProjectServiceId = projectService.Id,
OrganizationId = model.OrganizationId, OrganizationId = model.OrganizationId,
OrganizationTypeId = model.OrganizationTypeId, OrganizationTypeId = model.OrganizationTypeId,
ParentOrganizationId = model.ParentOrganizationId ?? loggedInEmployee.OrganizationId, ParentOrganizationId = model.ParentOrganizationId ?? loggedInEmployee.OrganizationId,
AssignedDate = DateTime.UtcNow, AssignedDate = today,
AssignedById = loggedInEmployee.Id, AssignedById = loggedInEmployee.Id,
TenantId = project.TenantId TenantId = project.TenantId
}; };
var projectOrganization = projectOrganizations newProjectOrgMappings.Add(projectOrgMapping);
.FirstOrDefault(po => po.ProjectService != null && po.ProjectService.ProjectId == project.Id && po.ProjectService.ServiceId == serviceId
&& po.OrganizationId == model.OrganizationId);
if (projectOrganization != null)
{
return StatusCode(409, ApiResponse<object>.ErrorResponse("Organization is already assigned to this project", "Organization is already assigned to this project", 409));
}
projectOrgMappings.Add(projectOrgMapping);
} }
if (projectServiceMappings.Any()) // Save new project service mappings if any
if (newProjectServiceMappings.Any())
{ {
_context.ProjectServiceMappings.AddRange(projectServiceMappings); _context.ProjectServiceMappings.AddRange(newProjectServiceMappings);
await _context.SaveChangesAsync(); await _context.SaveChangesAsync();
_logger.LogInfo("Added {Count} new ProjectServiceMappings for ProjectId {ProjectId}.", newProjectServiceMappings.Count, project.Id);
} }
_context.ProjectOrgMappings.AddRange(projectOrgMappings); // Save new project organization mappings
_context.ProjectOrgMappings.AddRange(newProjectOrgMappings);
await _context.SaveChangesAsync(); await _context.SaveChangesAsync();
// Commit transaction
await transaction.CommitAsync(); await transaction.CommitAsync();
_logger.LogInfo("Assigned organization {OrganizationId} to project {ProjectId} successfully.", model.OrganizationId, model.ProjectId);
// Prepare response view models
var organizationVm = _mapper.Map<BasicOrganizationVm>(organization); var organizationVm = _mapper.Map<BasicOrganizationVm>(organization);
var parentorganizationVm = _mapper.Map<BasicOrganizationVm>(parentorganization); var parentOrganizationVm = _mapper.Map<BasicOrganizationVm>(parentOrganization);
var projectvm = _mapper.Map<BasicProjectVM>(project); var projectVm = _mapper.Map<BasicProjectVM>(project);
var response = services.Select(s => new AssignOrganizationVm var response = services.Select(s => new AssignOrganizationVm
{ {
Project = projectvm, Project = projectVm,
OrganizationType = organizationType, OrganizationType = organizationType,
Organization = organizationVm, Organization = organizationVm,
ParentOrganization = parentorganizationVm, ParentOrganization = parentOrganizationVm,
Service = _mapper.Map<ServiceMasterVM>(s) Service = _mapper.Map<ServiceMasterVM>(s)
}).ToList(); }).ToList();
@ -467,54 +502,53 @@ namespace Marco.Pms.Services.Controllers
catch (DbUpdateException dbEx) catch (DbUpdateException dbEx)
{ {
await transaction.RollbackAsync(); await transaction.RollbackAsync();
_logger.LogError(dbEx, "Database exception occurred while assigning organization {OrganizationId} to project {ProjectId}",
_logger.LogError(dbEx, "Database Exception has been occured, While assigning the organization to project"); model.OrganizationId, model.ProjectId);
return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal error", "An database exception has been occured", 500)); return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal error", "A database exception occurred", 500));
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, "Exception has been occured, While assigned the organizatio to project"); await transaction.RollbackAsync();
return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal error", "An internal exception has been occured", 500)); _logger.LogError(ex, "Unhandled exception occurred while assigning organization {OrganizationId} to project {ProjectId}",
model.OrganizationId, model.ProjectId);
return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal error", "An internal exception occurred", 500));
} }
} }
[HttpPost("assign/tenant/{organizationId}")] [HttpPost("assign/tenant/{organizationId}")]
public async Task<IActionResult> AssignOrganizationToTenantAsync(Guid organizationId) public async Task<IActionResult> AssignOrganizationToTenantAsync(Guid organizationId)
{ {
_logger.LogInfo("Started assigning organization {OrganizationId} to tenant {TenantId}", organizationId, tenantId);
// Create a DbContext instance for this method scope
await using var _context = await _dbContextFactory.CreateDbContextAsync(); await using var _context = await _dbContextFactory.CreateDbContextAsync();
using var scope = _serviceScope.CreateScope();
// Begin a database transaction
await using var transaction = await _context.Database.BeginTransactionAsync(); await using var transaction = await _context.Database.BeginTransactionAsync();
try try
{ {
// Get currently logged in employee for auditing purposes
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
var organizationTenantMappingTask = Task.Run(async () => // Fetch existing tenant-organization mapping if any
{ var organizationTenantMapping = await _context.TenantOrgMappings
await using var context = await _dbContextFactory.CreateDbContextAsync(); .FirstOrDefaultAsync(spt => spt.OrganizationId == organizationId && spt.TenantId == tenantId && spt.IsActive);
return await context.TenantOrgMappings
.FirstOrDefaultAsync(spt => spt.OrganizationId == organizationId && spt.TenantId == tenantId && spt.IsActive);
});
var organizationTask = Task.Run(async () => // Fetch the organization details
{ var organization = await _context.Organizations.FirstOrDefaultAsync(o => o.Id == organizationId);
await using var context = await _dbContextFactory.CreateDbContextAsync();
return await context.Organizations
.FirstOrDefaultAsync(o => o.Id == organizationId);
});
await Task.WhenAll(organizationTenantMappingTask, organizationTask);
var organizationTenantMapping = organizationTenantMappingTask.Result;
var organization = organizationTask.Result;
// Validate organization existence
if (organization == null) if (organization == null)
{ {
_logger.LogWarning("Organization with ID {OrganizationId} not found.", organizationId);
return NotFound(ApiResponse<object>.ErrorResponse("Organization not found", "Organization not found in database", 404)); return NotFound(ApiResponse<object>.ErrorResponse("Organization not found", "Organization not found in database", 404));
} }
if (organizationTenantMapping == null) if (organizationTenantMapping == null)
{ {
var newOrganizationTenantMapping = new TenantOrgMapping // Create new tenant-organization mapping if none exists
var newMapping = new TenantOrgMapping
{ {
OrganizationId = organization.Id, OrganizationId = organization.Id,
SPRID = organization.SPRID, SPRID = organization.SPRID,
@ -523,26 +557,38 @@ namespace Marco.Pms.Services.Controllers
AssignedById = loggedInEmployee.Id, AssignedById = loggedInEmployee.Id,
TenantId = tenantId TenantId = tenantId
}; };
_context.TenantOrgMappings.Add(newOrganizationTenantMapping); _context.TenantOrgMappings.Add(newMapping);
await _context.SaveChangesAsync(); await _context.SaveChangesAsync();
await transaction.CommitAsync(); await transaction.CommitAsync();
_logger.LogInfo("Assigned organization {OrganizationId} to tenant {TenantId} successfully.", organizationId, tenantId);
} }
else
{
_logger.LogInfo("Organization {OrganizationId} is already assigned to tenant {TenantId}. No action taken.", organizationId, tenantId);
// Commit transaction anyway to complete scope cleanly (optional)
await transaction.CommitAsync();
}
// Prepare response view model
var response = _mapper.Map<BasicOrganizationVm>(organization); var response = _mapper.Map<BasicOrganizationVm>(organization);
return Ok(ApiResponse<object>.SuccessResponse(response, "Organization has been assigned to tenant", 200)); return Ok(ApiResponse<object>.SuccessResponse(response, "Organization has been assigned to tenant", 200));
} }
catch (DbUpdateException dbEx) catch (DbUpdateException dbEx)
{ {
await transaction.RollbackAsync(); await transaction.RollbackAsync();
_logger.LogError(dbEx, "Database exception occurred while assigning organization {OrganizationId} to tenant {TenantId}.", organizationId, tenantId);
_logger.LogError(dbEx, "Database Exception has been occured, While assigning the organization to project"); return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal error", "A database exception has occurred", 500));
return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal error", "An database exception has been occured", 500));
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, "Exception has been occured, While assigned the organizatio to project"); await transaction.RollbackAsync();
return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal error", "An internal exception has been occured", 500)); _logger.LogError(ex, "Unhandled exception occurred while assigning organization {OrganizationId} to tenant {TenantId}.", organizationId, tenantId);
return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal error", "An internal exception has occurred", 500));
} }
} }
#endregion #endregion
#region =================================================================== Put Functions =================================================================== #region =================================================================== Put Functions ===================================================================