Organization_Management #142

Merged
ashutosh.nehete merged 92 commits from Organization_Management into main 2025-09-30 09:05:14 +00:00
Showing only changes of commit fef3db297c - Show all commits

View File

@ -125,38 +125,43 @@ namespace Marco.Pms.Services.Controllers
public async Task<IActionResult> CreateOrganizationAsync([FromBody] CreateOrganizationDto model)
{
await using var _context = await _dbContextFactory.CreateDbContextAsync();
using var scope = _serviceScope.CreateScope();
using var scope = _serviceScope.CreateScope(); // Create scope for scoped services
await using var transaction = await _context.Database.BeginTransactionAsync();
try
{
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
// Concurrent permission check and organization existence check
var hasPermissionTask = Task.Run(async () =>
{
var permissionService = scope.ServiceProvider.GetRequiredService<PermissionServices>();
return await permissionService.HasPermission(PermissionsMaster.AddOrganization, loggedInEmployee.Id);
});
var IsPrimaryOrganizationTask = Task.Run(async () =>
var isPrimaryOrganizationTask = Task.Run(async () =>
{
await using var context = await _dbContextFactory.CreateDbContextAsync();
return await context.Tenants.AnyAsync(t => t.OrganizationId == loggedInEmployee.OrganizationId);
});
await Task.WhenAll(hasPermissionTask, IsPrimaryOrganizationTask);
await Task.WhenAll(hasPermissionTask, isPrimaryOrganizationTask);
var hasPermission = hasPermissionTask.Result;
var IsPrimaryOrganization = IsPrimaryOrganizationTask.Result;
bool hasPermission = hasPermissionTask.Result;
bool isPrimaryOrganization = isPrimaryOrganizationTask.Result;
if (!hasPermission && !IsPrimaryOrganization)
// Check user access permission
if (!hasPermission && !isPrimaryOrganization)
{
return StatusCode(403, ApiResponse<object>.ErrorResponse("Access Denied", "You do not have permission to create new service provider.", 403));
_logger.LogWarning("User {EmployeeId} attempted to create a new organization without permission", loggedInEmployee.Id);
return StatusCode(403, ApiResponse<object>.ErrorResponse("Access Denied", "You do not have permission to create new organization.", 403));
}
// Get last SPRID and increment for new organization
var lastOrganization = await _context.Organizations.OrderByDescending(sp => sp.SPRID).FirstOrDefaultAsync();
double lastSPRID = lastOrganization?.SPRID ?? 5400;
var lastSPRID = lastOrganization != null ? lastOrganization.SPRID : 5400;
// Map DTO to entity and set defaults
Organization organization = _mapper.Map<Organization>(model);
organization.SPRID = lastSPRID + 1;
organization.CreatedAt = DateTime.UtcNow;
@ -165,8 +170,19 @@ namespace Marco.Pms.Services.Controllers
_context.Organizations.Add(organization);
await _context.SaveChangesAsync();
// Create mapping for organization tenant
var newOrganizationTenantMapping = new TenantOrgMapping
{
OrganizationId = organization.Id,
SPRID = organization.SPRID,
AssignedDate = DateTime.UtcNow,
IsActive = true,
AssignedById = loggedInEmployee.Id,
TenantId = tenantId
};
_context.TenantOrgMappings.Add(newOrganizationTenantMapping);
// Prepare user creation for identity
var user = new ApplicationUser
{
UserName = model.Email,
@ -174,25 +190,29 @@ namespace Marco.Pms.Services.Controllers
EmailConfirmed = true
};
var _configuration = scope.ServiceProvider.GetRequiredService<IConfiguration>();
var _emailSender = scope.ServiceProvider.GetRequiredService<IEmailSender>();
var _userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
var configuration = scope.ServiceProvider.GetRequiredService<IConfiguration>();
var emailSender = scope.ServiceProvider.GetRequiredService<IEmailSender>();
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
// Create Identity User
var result = await _userManager.CreateAsync(user, "User@123");
// Create Identity user with a default password (recommend to improve password handling)
var result = await userManager.CreateAsync(user, "User@123");
if (!result.Succeeded)
{
_logger.LogWarning("Failed to create identity user for email {Email}: {Errors}", model.Email, result.Errors);
return BadRequest(ApiResponse<object>.ErrorResponse("Failed to create user", result.Errors, 400));
}
var jobRole = await _context.JobRoles.FirstOrDefaultAsync(jr => jr.Name == "Admin" && jr.TenantId == tenantId);
if (jobRole == null)
jobRole = await _context.JobRoles.FirstOrDefaultAsync(jr => jr.TenantId == tenantId);
// Get admin job role or fallback role of the tenant
var jobRole = await _context.JobRoles.FirstOrDefaultAsync(jr => jr.Name == "Admin" && jr.TenantId == tenantId)
?? await _context.JobRoles.FirstOrDefaultAsync(jr => jr.TenantId == tenantId);
var fullName = model.ContactPerson.Split(" ");
// Parse full name safely (consider improving split logic for multi-part names)
var fullName = model.ContactPerson?.Split(' ', StringSplitOptions.RemoveEmptyEntries) ?? Array.Empty<string>();
Employee newEmployee = new Employee
{
FirstName = fullName[0],
LastName = fullName[fullName.Length - 1],
FirstName = fullName.Length > 0 ? fullName[0] : string.Empty,
LastName = fullName.Length > 1 ? fullName[^1] : string.Empty,
Email = model.Email,
PermanentAddress = model.Address,
CurrentAddress = model.Address,
@ -202,54 +222,54 @@ namespace Marco.Pms.Services.Controllers
IsActive = true,
IsSystem = false,
IsPrimary = true,
OrganizationId = organization.Id
OrganizationId = organization.Id,
HasApplicationAccess = user.Id != null
};
if (newEmployee.ApplicationUserId != null)
{
newEmployee.HasApplicationAccess = true;
}
_context.Employees.Add(newEmployee);
var serviceOrgMapping = model.ServiceIds.Select(s => new OrgServiceMapping
// Map organization services
var serviceOrgMappings = model.ServiceIds.Select(s => new OrgServiceMapping
{
ServiceId = s,
OrganizationId = organization.Id
}).ToList();
_context.OrgServiceMappings.AddRange(serviceOrgMapping);
_context.OrgServiceMappings.AddRange(serviceOrgMappings);
// Persist all changes
await _context.SaveChangesAsync();
await transaction.CommitAsync();
/* SEND USER REGISTRATION MAIL*/
var token = await _userManager.GeneratePasswordResetTokenAsync(user);
var resetLink = $"{_configuration["AppSettings:WebFrontendUrl"]}/reset-password?token={WebUtility.UrlEncode(token)}";
if (newEmployee.FirstName != null)
// Send user registration email with password reset link
var token = await userManager.GeneratePasswordResetTokenAsync(user);
var resetLink = $"{configuration["AppSettings:WebFrontendUrl"]}/reset-password?token={WebUtility.UrlEncode(token)}";
if (!string.IsNullOrEmpty(newEmployee.FirstName))
{
await _emailSender.SendResetPasswordEmailOnRegister(user.Email, newEmployee.FirstName, resetLink);
await emailSender.SendResetPasswordEmailOnRegister(user.Email, newEmployee.FirstName, resetLink);
}
// Prepare response DTO
var response = _mapper.Map<OrganizationVM>(organization);
response.CreatedBy = _mapper.Map<BasicEmployeeVM>(loggedInEmployee);
return Ok(ApiResponse<object>.SuccessResponse(response, "Successfully created the service provider", 200));
return Ok(ApiResponse<object>.SuccessResponse(response, "Successfully created the organization", 200));
}
catch (DbUpdateException dbEx)
{
await transaction.RollbackAsync();
_logger.LogError(dbEx, "Database Exception has been occured");
return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal error", "An database exception has been occured", 500));
_logger.LogError(dbEx, "Database exception occurred while creating organization");
return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal error", "A database exception occurred", 500));
}
catch (Exception ex)
{
_logger.LogError(ex, "Exception has been occured");
return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal error", "An internal exception has been occured", 500));
_logger.LogError(ex, "Unexpected exception occurred while creating organization");
return StatusCode(500, ApiResponse<object>.ErrorResponse("Internal error", "An unexpected error occurred", 500));
}
}
[HttpPost("assign/project")]
public async Task<IActionResult> AssignOrganizationToProjectAsync([FromBody] AssignOrganizationDto model)
{
@ -468,7 +488,7 @@ namespace Marco.Pms.Services.Controllers
{
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
var serviceProviderTenantMappingTask = Task.Run(async () =>
var organizationTenantMappingTask = Task.Run(async () =>
{
await using var context = await _dbContextFactory.CreateDbContextAsync();
return await context.TenantOrgMappings
@ -482,9 +502,9 @@ namespace Marco.Pms.Services.Controllers
.FirstOrDefaultAsync(o => o.Id == organizationId);
});
await Task.WhenAll(serviceProviderTenantMappingTask, organizationTask);
await Task.WhenAll(organizationTenantMappingTask, organizationTask);
var serviceProviderTenantMapping = serviceProviderTenantMappingTask.Result;
var organizationTenantMapping = organizationTenantMappingTask.Result;
var organization = organizationTask.Result;
if (organization == null)
@ -492,9 +512,9 @@ namespace Marco.Pms.Services.Controllers
return NotFound(ApiResponse<object>.ErrorResponse("Organization not found", "Organization not found in database", 404));
}
if (serviceProviderTenantMapping == null)
if (organizationTenantMapping == null)
{
var newServiceProviderTenantMapping = new TenantOrgMapping
var newOrganizationTenantMapping = new TenantOrgMapping
{
OrganizationId = organization.Id,
SPRID = organization.SPRID,
@ -503,7 +523,7 @@ namespace Marco.Pms.Services.Controllers
AssignedById = loggedInEmployee.Id,
TenantId = tenantId
};
_context.TenantOrgMappings.Add(newServiceProviderTenantMapping);
_context.TenantOrgMappings.Add(newOrganizationTenantMapping);
await _context.SaveChangesAsync();
await transaction.CommitAsync();
}