diff --git a/Marco.Pms.Model/Dtos/Tenant/UpdateTenantDto.cs b/Marco.Pms.Model/Dtos/Tenant/UpdateTenantDto.cs new file mode 100644 index 0000000..7578ecb --- /dev/null +++ b/Marco.Pms.Model/Dtos/Tenant/UpdateTenantDto.cs @@ -0,0 +1,21 @@ +namespace Marco.Pms.Model.Dtos.Tenant +{ + public class UpdateTenantDto + { + public Guid Id { get; set; } + public required string FirstName { get; set; } + public required string LastName { get; set; } + public string? Description { get; set; } + public string? DomainName { get; set; } + public required string BillingAddress { get; set; } + public string? TaxId { get; set; } + public string? logoImage { get; set; } + public required string OrganizationName { get; set; } + public string? OfficeNumber { get; set; } + public required string ContactNumber { get; set; } + //public required DateTime OnBoardingDate { get; set; } + public required string OrganizationSize { get; set; } + public required Guid IndustryId { get; set; } + public required string Reference { get; set; } + } +} diff --git a/Marco.Pms.Services/Controllers/TenantController.cs b/Marco.Pms.Services/Controllers/TenantController.cs index 8df2fc9..ad1b874 100644 --- a/Marco.Pms.Services/Controllers/TenantController.cs +++ b/Marco.Pms.Services/Controllers/TenantController.cs @@ -294,7 +294,7 @@ namespace Marco.Pms.Services.Controllers .Include(sp => sp.UpdatedBy) .ThenInclude(e => e!.JobRole) .Include(sp => sp.Currency) - .Include(ts => ts.Plan).ThenInclude(sp => sp.Plan) + .Include(ts => ts.Plan).ThenInclude(sp => sp!.Plan) .Where(ts => ts.TenantId == tenant.Id && ts.Plan != null) .AsNoTracking() .OrderBy(ts => ts.CreatedBy) @@ -587,12 +587,103 @@ namespace Marco.Pms.Services.Controllers } } - // PUT api//5 - [HttpPut("{id}")] - public void Put(int id, [FromBody] string value) + /// + /// Updates tenant and root employee details for a specified tenant ID. + /// + /// ID of the tenant to update + /// Details to update + /// Result of the operation + + [HttpPut("edit/{id}")] + public async Task UpdateTenant(Guid id, [FromBody] UpdateTenantDto model) { + _logger.LogInfo("UpdateTenant called for TenantId: {TenantId} by user.", id); + + // 1. Retrieve the logged-in employee information + var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + if (loggedInEmployee == null) + { + _logger.LogWarning("Unauthorized access - User not logged in."); + return StatusCode(403, ApiResponse.ErrorResponse("Unauthorized", "User must be logged in.", 403)); + } + + // 2. Check permissions using a single service scope to avoid overhead + bool hasManagePermission, hasModifyPermission; + using (var scope = _serviceScopeFactory.CreateScope()) + { + var permissionService = scope.ServiceProvider.GetRequiredService(); + + var manageTask = permissionService.HasPermission(PermissionsMaster.ManageTenants, loggedInEmployee.Id); + var modifyTask = permissionService.HasPermission(PermissionsMaster.ModifyTenant, loggedInEmployee.Id); + + await Task.WhenAll(manageTask, modifyTask); + + hasManagePermission = manageTask.Result; + hasModifyPermission = modifyTask.Result; + } + + if (!hasManagePermission && !hasModifyPermission) + { + _logger.LogWarning("Access denied: User {EmployeeId} lacks required permissions for UpdateTenant on TenantId: {TenantId}.", loggedInEmployee.Id, id); + return StatusCode(403, ApiResponse.ErrorResponse("Access denied", "User does not have the required permissions for this action.", 403)); + } + + // 3. Use a single DbContext instance for data access + await using var context = await _dbContextFactory.CreateDbContextAsync(); + + // 4. Fetch tenant (with related Industry, TenantStatus), tracking enabled for updates + var tenant = await context.Tenants + .Include(t => t.Industry) + .Include(t => t.TenantStatus) + .FirstOrDefaultAsync(t => t.Id == id); + + if (tenant == null) + { + _logger.LogWarning("Tenant not found: ID {TenantId}", id); + return NotFound(ApiResponse.ErrorResponse("Tenant not found", "Tenant not found", 404)); + } + + _logger.LogInfo("Tenant {TenantId} fetched for update.", tenant.Id); + + // 5. Map update DTO properties to the tenant entity + _mapper.Map(model, tenant); + + // 6. Fetch root employee for the tenant (includes ApplicationUser) + var rootEmployee = await context.Employees + .Include(e => e.ApplicationUser) + .FirstOrDefaultAsync(e => e.TenantId == tenant.Id && e.ApplicationUser != null && (e.ApplicationUser.IsRootUser ?? false)); + + if (rootEmployee == null) + { + _logger.LogWarning("Root employee not found for tenant {TenantId}", id); + return NotFound(ApiResponse.ErrorResponse("Root employee not found", "Root employee not found", 404)); + } + + // 7. Update root employee details + rootEmployee.FirstName = model.FirstName; + rootEmployee.LastName = model.LastName; + rootEmployee.PhoneNumber = model.ContactNumber; + rootEmployee.CurrentAddress = model.BillingAddress; + + // 8. Save changes to DB + try + { + await context.SaveChangesAsync(); + _logger.LogInfo("Tenant {TenantId} and root employee updated successfully.", tenant.Id); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error updating Tenant {TenantId} or root employee.", tenant.Id); + return StatusCode(500, ApiResponse.ErrorResponse("Error updating tenant", "Unexpected error occurred while updating tenant.", 500)); + } + + // 9. Map updated tenant to ViewModel for response + var response = _mapper.Map(tenant); + + return Ok(ApiResponse.SuccessResponse(response, "Tenant updated successfully", 200)); } + // DELETE api//5 [HttpDelete("{id}")] public void Delete(int id) diff --git a/Marco.Pms.Services/MappingProfiles/MappingProfile.cs b/Marco.Pms.Services/MappingProfiles/MappingProfile.cs index 47552a4..c390c3c 100644 --- a/Marco.Pms.Services/MappingProfiles/MappingProfile.cs +++ b/Marco.Pms.Services/MappingProfiles/MappingProfile.cs @@ -33,6 +33,15 @@ namespace Marco.Pms.Services.MappingProfiles dest => dest.Name, opt => opt.MapFrom(src => src.OrganizationName) ); + CreateMap() + .ForMember( + dest => dest.ContactName, + opt => opt.MapFrom(src => $"{src.FirstName} {src.LastName}") + ) + .ForMember( + dest => dest.Name, + opt => opt.MapFrom(src => src.OrganizationName) + ); CreateMap() .ForMember(