From fef3db297cc779370b380d99475b8a451e6d5c34 Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Wed, 24 Sep 2025 19:43:45 +0530 Subject: [PATCH] Mapping the tenant with organization when creating the organization --- .../Controllers/OrganizationController.cs | 110 +++++++++++------- 1 file changed, 65 insertions(+), 45 deletions(-) diff --git a/Marco.Pms.Services/Controllers/OrganizationController.cs b/Marco.Pms.Services/Controllers/OrganizationController.cs index 7daba1d..cc32b48 100644 --- a/Marco.Pms.Services/Controllers/OrganizationController.cs +++ b/Marco.Pms.Services/Controllers/OrganizationController.cs @@ -125,38 +125,43 @@ namespace Marco.Pms.Services.Controllers public async Task 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(); 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.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.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(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(); - var _emailSender = scope.ServiceProvider.GetRequiredService(); - var _userManager = scope.ServiceProvider.GetRequiredService>(); + var configuration = scope.ServiceProvider.GetRequiredService(); + var emailSender = scope.ServiceProvider.GetRequiredService(); + var userManager = scope.ServiceProvider.GetRequiredService>(); - // 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.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(); 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(organization); response.CreatedBy = _mapper.Map(loggedInEmployee); - return Ok(ApiResponse.SuccessResponse(response, "Successfully created the service provider", 200)); + return Ok(ApiResponse.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.ErrorResponse("Internal error", "An database exception has been occured", 500)); + _logger.LogError(dbEx, "Database exception occurred while creating organization"); + return StatusCode(500, ApiResponse.ErrorResponse("Internal error", "A database exception occurred", 500)); } catch (Exception ex) { - _logger.LogError(ex, "Exception has been occured"); - return StatusCode(500, ApiResponse.ErrorResponse("Internal error", "An internal exception has been occured", 500)); + _logger.LogError(ex, "Unexpected exception occurred while creating organization"); + return StatusCode(500, ApiResponse.ErrorResponse("Internal error", "An unexpected error occurred", 500)); } } + [HttpPost("assign/project")] public async Task 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.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(); }