using AutoMapper; using Marco.Pms.DataAccess.Data; using Marco.Pms.Model.Dtos.Organization; using Marco.Pms.Model.Employees; using Marco.Pms.Model.Entitlements; using Marco.Pms.Model.OrganizationModel; using Marco.Pms.Model.Utilities; using Marco.Pms.Model.ViewModels.Activities; using Marco.Pms.Model.ViewModels.Master; using Marco.Pms.Model.ViewModels.Organization; using Marco.Pms.Model.ViewModels.Projects; using Marco.Pms.Services.Service; using MarcoBMS.Services.Helpers; using MarcoBMS.Services.Service; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using System.Net; namespace Marco.Pms.Services.Controllers { [Route("api/[controller]")] [ApiController] [Authorize] public class OrganizationController : ControllerBase { private readonly IDbContextFactory _dbContextFactory; private readonly IServiceScopeFactory _serviceScope; private readonly UserHelper _userHelper; private readonly Guid tenantId; private readonly IMapper _mapper; private readonly ILoggingService _logger; private static readonly Guid PMCProvider = Guid.Parse("b1877a3b-8832-47b1-bbe3-dc7e98672f49"); private static readonly Guid ServiceProvider = Guid.Parse("5ee49bcd-b6d3-482f-9aaf-484afe04abec"); private static readonly Guid SubContractorProvider = Guid.Parse("a283356a-9b02-4029-afb7-e65c703efdd4"); private static readonly Guid superTenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26"); public OrganizationController(IDbContextFactory dbContextFactory, IServiceScopeFactory serviceScope, UserHelper userHelper, ILoggingService logger, IMapper mapper) { _dbContextFactory = dbContextFactory ?? throw new ArgumentNullException(nameof(dbContextFactory)); _serviceScope = serviceScope ?? throw new ArgumentNullException(nameof(serviceScope)); _userHelper = userHelper ?? throw new ArgumentNullException(nameof(userHelper)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); tenantId = userHelper.GetTenantId(); } #region =================================================================== Get Functions =================================================================== [HttpGet("list")] public async Task GetOrganizarionListAsync([FromQuery] string? searchString, [FromQuery] double? sprid, [FromQuery] bool active = true, [FromQuery] int pageNumber = 1, [FromQuery] int pageSize = 20) { await using var _context = await _dbContextFactory.CreateDbContextAsync(); using var scope = _serviceScope.CreateScope(); var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); var organizationQuery = _context.Organizations .Where(o => o.IsActive == active); if (sprid.HasValue) { organizationQuery = organizationQuery.Where(o => o.SPRID == sprid.Value); } else { var organizationIds = await _context.TenantOrgMappings.Where(to => to.TenantId == tenantId && to.IsActive).Select(to => to.OrganizationId).ToListAsync(); organizationQuery = organizationQuery.Where(o => organizationIds.Contains(o.Id)); if (!string.IsNullOrWhiteSpace(searchString)) { organizationQuery = organizationQuery.Where(sp => sp.Name.Contains(searchString) //|| sp.ContactPerson.Contains(searchString) //|| sp.Address.Contains(searchString) //|| sp.Email.Contains(searchString) //|| sp.ContactNumber.Contains(searchString) ); } } var organizations = await organizationQuery .OrderBy(e => e.Name) .Skip((pageNumber - 1) * pageSize) .Take(pageSize) .ToListAsync(); var createdByIds = organizations.Where(o => o.CreatedById != null).Select(o => o.CreatedById).ToList(); var updatedByIds = organizations.Where(o => o.UpdatedById != null).Select(o => o.UpdatedById).ToList(); var employees = await _context.Employees.Where(e => createdByIds.Contains(e.Id) || updatedByIds.Contains(e.Id)).ToListAsync(); var response = organizations.Select(o => { var result = _mapper.Map(o); result.CreatedBy = employees.Where(e => e.Id == o.CreatedById).Select(e => _mapper.Map(e)).FirstOrDefault(); result.UpdatedBy = employees.Where(e => e.Id == o.UpdatedById).Select(e => _mapper.Map(e)).FirstOrDefault(); return result; }); return Ok(ApiResponse.SuccessResponse(response, "Successfully fetched the Service Provider list", 200)); } #endregion #region =================================================================== Post Functions =================================================================== [HttpPost("create")] public async Task CreateOrganizationAsync([FromBody] CreateOrganizationDto model) { await using var _context = await _dbContextFactory.CreateDbContextAsync(); using var scope = _serviceScope.CreateScope(); await using var transaction = await _context.Database.BeginTransactionAsync(); try { var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); var hasPermissionTask = Task.Run(async () => { var permissionService = scope.ServiceProvider.GetRequiredService(); return await permissionService.HasPermission(PermissionsMaster.AddOrganization, loggedInEmployee.Id); }); 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); var hasPermission = hasPermissionTask.Result; var IsPrimaryOrganization = IsPrimaryOrganizationTask.Result; if (!hasPermission && !IsPrimaryOrganization) { return StatusCode(403, ApiResponse.ErrorResponse("Access Denied", "You do not have permission to create new service provider.", 403)); } var lastOrganization = await _context.Organizations.OrderByDescending(sp => sp.SPRID).FirstOrDefaultAsync(); var lastSPRID = lastOrganization != null ? lastOrganization.SPRID : 5400; Organization organization = _mapper.Map(model); organization.SPRID = lastSPRID + 1; organization.CreatedAt = DateTime.UtcNow; organization.CreatedById = loggedInEmployee.Id; organization.IsActive = true; _context.Organizations.Add(organization); await _context.SaveChangesAsync(); var user = new ApplicationUser { UserName = model.Email, Email = model.Email, EmailConfirmed = true }; 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"); if (!result.Succeeded) 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); var fullName = model.ContactPerson.Split(" "); Employee newEmployee = new Employee { FirstName = fullName[0], LastName = fullName[fullName.Length - 1], Email = model.Email, PermanentAddress = model.Address, CurrentAddress = model.Address, PhoneNumber = model.ContactNumber, ApplicationUserId = user.Id, JobRoleId = jobRole?.Id ?? Guid.Empty, IsActive = true, IsSystem = false, IsPrimary = true, OrganizationId = organization.Id }; if (newEmployee.ApplicationUserId != null) { newEmployee.HasApplicationAccess = true; } _context.Employees.Add(newEmployee); var serviceOrgMapping = model.ServiceIds.Select(s => new OrgServiceMapping { ServiceId = s, OrganizationId = organization.Id }).ToList(); _context.OrgServiceMappings.AddRange(serviceOrgMapping); 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) { await _emailSender.SendResetPasswordEmailOnRegister(user.Email, newEmployee.FirstName, resetLink); } var response = _mapper.Map(organization); response.CreatedBy = _mapper.Map(loggedInEmployee); return Ok(ApiResponse.SuccessResponse(response, "Successfully created the service provider", 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)); } catch (Exception ex) { _logger.LogError(ex, "Exception has been occured"); return StatusCode(500, ApiResponse.ErrorResponse("Internal error", "An internal exception has been occured", 500)); } } [HttpPost("assign/project")] public async Task AssignOrganizationToProjectAsync([FromBody] AssignOrganizationDto model) { await using var _context = await _dbContextFactory.CreateDbContextAsync(); using var scope = _serviceScope.CreateScope(); await using var transaction = await _context.Database.BeginTransactionAsync(); try { var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); var todaysDate = DateTime.UtcNow.Date; var projectServicesTask = Task.Run(async () => { await using var context = await _dbContextFactory.CreateDbContextAsync(); return await context.ProjectServiceMappings .Where(sp => model.ServiceIds.Contains(sp.ServiceId) && sp.ProjectId == model.ProjectId && sp.IsActive).ToListAsync(); }); var projectOrganizationsTask = Task.Run(async () => { await using var context = await _dbContextFactory.CreateDbContextAsync(); return await context.ProjectOrgMappings .Include(po => po.ProjectService) .Where(po => po.ProjectService != null && model.ServiceIds.Contains(po.ProjectService.ServiceId) && po.ProjectService.ProjectId == model.ProjectId).ToListAsync(); }); var serviceTask = Task.Run(async () => { await using var context = await _dbContextFactory.CreateDbContextAsync(); return await context.ServiceMasters.Where(s => model.ServiceIds.Contains(s.Id) && s.TenantId == tenantId).ToListAsync(); }); var organizationTypeTask = Task.Run(async () => { await using var context = await _dbContextFactory.CreateDbContextAsync(); return await context.OrgTypeMasters.FirstOrDefaultAsync(o => o.Id == model.OrganizationId); }); var organizationTask = Task.Run(async () => { await using var context = await _dbContextFactory.CreateDbContextAsync(); return await context.Organizations.FirstOrDefaultAsync(o => o.Id == model.OrganizationId); }); var parentorganizationTask = Task.Run(async () => { await using var context = await _dbContextFactory.CreateDbContextAsync(); return await context.Organizations.FirstOrDefaultAsync(o => o.Id == model.ParentOrganizationId); }); var projectTask = Task.Run(async () => { await using var context = await _dbContextFactory.CreateDbContextAsync(); return await context.Projects.FirstOrDefaultAsync(p => p.Id == model.ProjectId); }); var isPMCTask = Task.Run(async () => { await using var context = await _dbContextFactory.CreateDbContextAsync(); return await context.Projects.AnyAsync(p => p.Id == model.ProjectId && p.PMCId == loggedInEmployee.OrganizationId); }); var isServiceProviderTask = Task.Run(async () => { await using var context = await _dbContextFactory.CreateDbContextAsync(); return await context.ProjectOrgMappings.AnyAsync(p => p.Id == model.ProjectId && p.OrganizationId == loggedInEmployee.OrganizationId && p.OrganizationTypeId == ServiceProvider); }); await Task.WhenAll(organizationTask, parentorganizationTask, projectTask, projectServicesTask, 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 isPMC = isPMCTask.Result; var isServiceProvider = isServiceProviderTask.Result; if (organization == null) { return NotFound(ApiResponse.ErrorResponse("Organization not found", "Organization not found in database", 404)); } if (project == null) { return NotFound(ApiResponse.ErrorResponse("Project not found", "Project not found in database", 404)); } if (projectServices == null) { return NotFound(ApiResponse.ErrorResponse("Project Service not found", "Project Service not found in database", 404)); } var serviceProviderTenantMapping = await _context.TenantOrgMappings .FirstOrDefaultAsync(spt => spt.OrganizationId == model.OrganizationId && spt.TenantId == project.TenantId && spt.IsActive); if (serviceProviderTenantMapping == null) { var newServiceProviderTenantMapping = new TenantOrgMapping { OrganizationId = organization.Id, SPRID = organization.SPRID, AssignedDate = DateTime.UtcNow, IsActive = true, TenantId = project.TenantId }; _context.TenantOrgMappings.Add(newServiceProviderTenantMapping); } List projectOrgMappings = new List(); foreach (var serviceId in model.ServiceIds) { if (isPMC && model.OrganizationTypeId != ServiceProvider && model.OrganizationTypeId != SubContractorProvider) { continue; } if (isServiceProvider && model.OrganizationTypeId == ServiceProvider) { continue; } var projectService = projectServices.FirstOrDefault(ps => ps.ServiceId == serviceId); if (projectService == null) { projectService = new ProjectServiceMapping { ProjectId = project.Id, ServiceId = serviceId, TenantId = project.TenantId, PlannedStartDate = project.StartDate ?? DateTime.UtcNow, PlannedEndDate = project.EndDate ?? DateTime.UtcNow, ActualStartDate = DateTime.UtcNow, IsActive = true }; _context.ProjectServiceMappings.Add(projectService); } var projectOrgMapping = new ProjectOrgMapping { ProjectServiceId = projectService.Id, OrganizationId = model.OrganizationId, OrganizationTypeId = model.OrganizationTypeId, ParentOrganizationId = model.ParentOrganizationId ?? loggedInEmployee.OrganizationId, AssignedDate = DateTime.UtcNow, TenantId = project.TenantId }; var projectOrganization = projectOrganizations .FirstOrDefault(po => po.ProjectService != null && po.ProjectService.ProjectId == project.Id && po.ProjectService.ServiceId == serviceId); if (projectOrganization == null) { projectOrgMappings.Add(projectOrgMapping); } } _context.ProjectOrgMappings.AddRange(projectOrgMappings); await _context.SaveChangesAsync(); await transaction.CommitAsync(); var organizationVm = _mapper.Map(organization); var parentorganizationVm = _mapper.Map(parentorganization); var projectvm = _mapper.Map(project); var response = services.Select(s => new AssignOrganizationVm { Project = projectvm, OrganizationType = organizationType, Organization = organizationVm, ParentOrganization = parentorganizationVm, Service = _mapper.Map(s) }).ToList(); return Ok(ApiResponse.SuccessResponse(response, "Organization successfully assigned to the project", 200)); } catch (DbUpdateException dbEx) { await transaction.RollbackAsync(); _logger.LogError(dbEx, "Database Exception has been occured, While assigning the organization to project"); return StatusCode(500, ApiResponse.ErrorResponse("Internal error", "An database exception has been occured", 500)); } catch (Exception ex) { _logger.LogError(ex, "Exception has been occured, While assigned the organizatio to project"); return StatusCode(500, ApiResponse.ErrorResponse("Internal error", "An internal exception has been occured", 500)); } } [HttpPost("assign/tenant/{organizationId}")] public async Task AssignOrganizationToTenantAsync(Guid organizationId) { await using var _context = await _dbContextFactory.CreateDbContextAsync(); using var scope = _serviceScope.CreateScope(); await using var transaction = await _context.Database.BeginTransactionAsync(); try { var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); var serviceProviderTenantMappingTask = Task.Run(async () => { await using var context = await _dbContextFactory.CreateDbContextAsync(); return await context.TenantOrgMappings .FirstOrDefaultAsync(spt => spt.OrganizationId == organizationId && spt.TenantId == tenantId && spt.IsActive); }); var organizationTask = Task.Run(async () => { await using var context = await _dbContextFactory.CreateDbContextAsync(); return await context.Organizations .FirstOrDefaultAsync(o => o.Id == organizationId); }); await Task.WhenAll(serviceProviderTenantMappingTask, organizationTask); var serviceProviderTenantMapping = serviceProviderTenantMappingTask.Result; var organization = organizationTask.Result; if (organization == null) { return NotFound(ApiResponse.ErrorResponse("Organization not found", "Organization not found in database", 404)); } if (serviceProviderTenantMapping == null) { var newServiceProviderTenantMapping = new TenantOrgMapping { OrganizationId = organization.Id, SPRID = organization.SPRID, AssignedDate = DateTime.UtcNow, IsActive = true, TenantId = tenantId }; _context.TenantOrgMappings.Add(newServiceProviderTenantMapping); await _context.SaveChangesAsync(); } var response = _mapper.Map(organization); return Ok(ApiResponse.SuccessResponse(response, "Organization has been assigned to tenant", 200)); } catch (DbUpdateException dbEx) { await transaction.RollbackAsync(); _logger.LogError(dbEx, "Database Exception has been occured, While assigning the organization to project"); return StatusCode(500, ApiResponse.ErrorResponse("Internal error", "An database exception has been occured", 500)); } catch (Exception ex) { _logger.LogError(ex, "Exception has been occured, While assigned the organizatio to project"); return StatusCode(500, ApiResponse.ErrorResponse("Internal error", "An internal exception has been occured", 500)); } } #endregion #region =================================================================== Put Functions =================================================================== //[HttpPut("edit/{id}")] //public async Task UpdateServiceProviderAsync(Guid id, [FromBody] UpdateOrganizationDto model) //{ // await using var _context = await _dbContextFactory.CreateDbContextAsync(); // using var scope = _serviceScope.CreateScope(); // var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); // var clientTask = Task.Run(async () => // { // await using var context = await _dbContextFactory.CreateDbContextAsync(); // return await context.Clients.FirstOrDefaultAsync(c => c.PrimaryEmployeeId == loggedInEmployee.Id && c.TenantId == tenantId); // }); // var employeeTask = Task.Run(async () => // { // await using var context = await _dbContextFactory.CreateDbContextAsync(); // return await context.Employees.AsNoTracking().FirstOrDefaultAsync(e => e.OrganizationId == id && e.IsPrimary); // }); // await Task.WhenAll(clientTask, employeeTask); // var client = clientTask.Result; // var employee = employeeTask.Result; // if (employee == null) // { // return NotFound(ApiResponse.ErrorResponse("Primary employee for service provider not found", "Primary employee for service provider not found in database", 400)); // } // if (!(loggedInEmployee.ApplicationUser?.IsRootUser ?? false) && !loggedInEmployee.IsPrimary && client == null && employee.Id == loggedInEmployee.Id) // { // return StatusCode(403, ApiResponse.ErrorResponse("Access Denied", "You do not have permission to create new service provider.", 403)); // } // if (model.Id == id) // { // return BadRequest(ApiResponse.ErrorResponse("Invalid Input", "Id from path parameters do not matches with id from model", 400)); // } // var serviceProvider = await _context.ServiceProviders.FirstOrDefaultAsync(sp => sp.Id == model.Id); // if (serviceProvider == null) // { // return NotFound(ApiResponse.ErrorResponse("Service Provider not found", "Service Provider not found in database", 404)); // } // _mapper.Map(model, serviceProvider); // _context.ServiceProviders.Update(serviceProvider); // var fullName = model.ContactPerson.Split(" "); // employee.FirstName = fullName[0]; // employee.LastName = fullName[fullName.Length - 1]; // employee.CurrentAddress = model.Address; // employee.PermanentAddress = model.Address; // employee.PhoneNumber = model.PhoneNumber; // _context.Employees.Update(employee); // await _context.SaveChangesAsync(); // var response = _mapper.Map(serviceProvider); // return Ok(ApiResponse.SuccessResponse(response, "Successfully updated the service provider", 200)); //} #endregion #region =================================================================== Delete Functions =================================================================== //[HttpDelete("delete/{id}")] //public async Task DeleteServiceProviderAsync(Guid id, [FromQuery] bool active) //{ // await using var _context = await _dbContextFactory.CreateDbContextAsync(); // using var scope = _serviceScope.CreateScope(); // var message = active ? "Restore" : "Delete"; // var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); // var client = await _context.Clients.FirstOrDefaultAsync(c => c.PrimaryEmployeeId == loggedInEmployee.Id && c.TenantId == tenantId); // if (!(loggedInEmployee.ApplicationUser?.IsRootUser ?? false) && !loggedInEmployee.IsPrimary && client == null) // { // return StatusCode(403, ApiResponse.ErrorResponse("Access Denied", $"You do not have permission to {message}d service provider.", 403)); // } // var serviceProvider = await _context.ServiceProviders.FirstOrDefaultAsync(sp => sp.Id == id); // if (serviceProvider == null) // { // return NotFound(ApiResponse.ErrorResponse("Service Provider not Found", "Service Provider not Found in database", 404)); // } // if (serviceProvider.IsActive == active) // { // return BadRequest(ApiResponse.ErrorResponse($"Service Provider is already {message}d", $"Service Provider is already {message}d", 400)); // } // var employeeIds = await _context.Employees.Where(e => e.ServiceProviderId == id).Select(e => e.Id).ToListAsync(); // var isPendingTask = await _context.TaskMembers.AnyAsync(tm => employeeIds.Contains(tm.EmployeeId)); // if (isPendingTask && !active) // { // return BadRequest(ApiResponse.ErrorResponse("There is an unfinshed task, Service provider cannot be deleted", "There is an unfinshed task, Service provider cannot be deleted", 400)); // } // serviceProvider.IsActive = active; // if (!active) // { // var servicePeroviderTenant = await _context.ServiceProviderTenantMappings.AsNoTracking().Where(spt => spt.ServiceProviderId == id && spt.IsActive).ToListAsync(); // var newServiceProviderTenant = servicePeroviderTenant.Select(spt => // { // spt.IsActive = false; // return spt; // }).ToList(); // _context.ServiceProviderTenantMappings.UpdateRange(newServiceProviderTenant); // } // await _context.SaveChangesAsync(); // return Ok(ApiResponse.SuccessResponse(new { }, $"Service Provider is {message}d", 200)); //} #endregion #region =================================================================== Helper Functions =================================================================== //private ServicesProviderFilter? TryDeserializeServicesProviderFilter(string? filter) //{ // if (string.IsNullOrWhiteSpace(filter)) // { // return null; // } // var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; // ServicesProviderFilter? documentFilter = null; // try // { // // First, try to deserialize directly. This is the expected case (e.g., from a web client). // documentFilter = JsonSerializer.Deserialize(filter, options); // } // catch (JsonException ex) // { // _logger.LogError(ex, "[{MethodName}] Failed to directly deserialize filter. Attempting to unescape and re-parse. Filter: {Filter}", nameof(TryDeserializeServicesProviderFilter), filter); // // If direct deserialization fails, it might be an escaped string (common with tools like Postman or some mobile clients). // try // { // // Unescape the string first, then deserialize the result. // string unescapedJsonString = JsonSerializer.Deserialize(filter, options) ?? ""; // if (!string.IsNullOrWhiteSpace(unescapedJsonString)) // { // documentFilter = JsonSerializer.Deserialize(unescapedJsonString, options); // } // } // catch (JsonException ex1) // { // // If both attempts fail, log the final error and return null. // _logger.LogError(ex1, "[{MethodName}] All attempts to deserialize the filter failed. Filter will be ignored. Filter: {Filter}", nameof(TryDeserializeServicesProviderFilter), filter); // return null; // } // } // return documentFilter; //} #endregion } }