From 30623d6bbc47570b3e18d1d576f16746e7d394bf Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Wed, 17 Sep 2025 12:01:22 +0530 Subject: [PATCH] Added the create organization API --- .../Dtos/Employees/CreateUserDto.cs | 4 +- .../Organization/CreateOrganizationDto.cs | 12 + .../Entitlements/PermissionsMaster.cs | 4 + .../ViewModels/Organization/OrganizationVM.cs | 20 + .../Controllers/OrganizationController.cs | 436 ++++++++++++++++++ 5 files changed, 474 insertions(+), 2 deletions(-) create mode 100644 Marco.Pms.Model/Dtos/Organization/CreateOrganizationDto.cs create mode 100644 Marco.Pms.Model/ViewModels/Organization/OrganizationVM.cs create mode 100644 Marco.Pms.Services/Controllers/OrganizationController.cs diff --git a/Marco.Pms.Model/Dtos/Employees/CreateUserDto.cs b/Marco.Pms.Model/Dtos/Employees/CreateUserDto.cs index f9d31d3..597861b 100644 --- a/Marco.Pms.Model/Dtos/Employees/CreateUserDto.cs +++ b/Marco.Pms.Model/Dtos/Employees/CreateUserDto.cs @@ -3,7 +3,7 @@ public class CreateUserDto { public Guid? Id { get; set; } - public string? FirstName { get; set; } + public required string FirstName { get; set; } public string? LastName { get; set; } public string? MiddleName { get; set; } public string? Email { get; set; } @@ -26,7 +26,7 @@ //public IFormFile? Photo { get; set; } // To store the captured photo //public List? Documents { get; set; } - public Guid? JobRoleId { get; set; } + public Guid JobRoleId { get; set; } // public int TenantId { get; set; } } diff --git a/Marco.Pms.Model/Dtos/Organization/CreateOrganizationDto.cs b/Marco.Pms.Model/Dtos/Organization/CreateOrganizationDto.cs new file mode 100644 index 0000000..b8db5e7 --- /dev/null +++ b/Marco.Pms.Model/Dtos/Organization/CreateOrganizationDto.cs @@ -0,0 +1,12 @@ +namespace Marco.Pms.Model.Dtos.Organization +{ + public class CreateOrganizationDto + { + public required string Name { get; set; } + public required string Email { get; set; } + public required string ContactPerson { get; set; } + public required string Address { get; set; } + public required string ContactNumber { get; set; } + public required List ServiceIds { get; set; } + } +} diff --git a/Marco.Pms.Model/Entitlements/PermissionsMaster.cs b/Marco.Pms.Model/Entitlements/PermissionsMaster.cs index b8ab3f7..0f1d3bf 100644 --- a/Marco.Pms.Model/Entitlements/PermissionsMaster.cs +++ b/Marco.Pms.Model/Entitlements/PermissionsMaster.cs @@ -46,6 +46,10 @@ public static readonly Guid DeleteDocument = Guid.Parse("40863a13-5a66-469d-9b48-135bc5dbf486"); public static readonly Guid DownloadDocument = Guid.Parse("404373d0-860f-490e-a575-1c086ffbce1d"); public static readonly Guid VerifyDocument = Guid.Parse("13a1f30f-38d1-41bf-8e7a-b75189aab8e0"); + + public static readonly Guid AddOrganization = Guid.Parse("068cb3c1-49c5-4746-9f29-1fce16e820ac"); + public static readonly Guid EditOrganization = Guid.Parse("c1ae1363-ab8a-4bd9-a9d1-8c2c6083873a"); + public static readonly Guid ViewOrganization = Guid.Parse("7a6cf830-0008-4e03-b31d-0d050cb634f4"); } } diff --git a/Marco.Pms.Model/ViewModels/Organization/OrganizationVM.cs b/Marco.Pms.Model/ViewModels/Organization/OrganizationVM.cs new file mode 100644 index 0000000..c00001e --- /dev/null +++ b/Marco.Pms.Model/ViewModels/Organization/OrganizationVM.cs @@ -0,0 +1,20 @@ +using Marco.Pms.Model.ViewModels.Activities; + +namespace Marco.Pms.Model.ViewModels.Organization +{ + public class OrganizationVM + { + public Guid Id { get; set; } + public string? Name { get; set; } + public string? Email { get; set; } + public string? ContactPerson { get; set; } + public string? Address { get; set; } + public string? ContactNumber { get; set; } + public double SPRID { get; set; } + public DateTime CreatedAt { get; set; } + public BasicEmployeeVM? CreatedBy { get; set; } + public BasicEmployeeVM? UpdatedBy { get; set; } + public DateTime? UpdatedAt { get; set; } + public bool IsActive { get; set; } + } +} diff --git a/Marco.Pms.Services/Controllers/OrganizationController.cs b/Marco.Pms.Services/Controllers/OrganizationController.cs new file mode 100644 index 0000000..e8e906c --- /dev/null +++ b/Marco.Pms.Services/Controllers/OrganizationController.cs @@ -0,0 +1,436 @@ +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.Organization; +using Marco.Pms.Services.Service; +using MarcoBMS.Services.Helpers; +using MarcoBMS.Services.Service; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System.Net; + +namespace Marco.Pms.Services.Controllers +{ + [Route("api/[controller]")] + [ApiController] + 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"); + 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 GetListServiceProviderAsync([FromQuery] string? searchString, [FromQuery] string? filter, [FromQuery] bool active = true, + // [FromQuery] int pageNumber = 1, [FromQuery] int pageSize = 1) + //{ + // await using var _context = await _dbContextFactory.CreateDbContextAsync(); + // using var scope = _serviceScope.CreateScope(); + + // var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + + // var serviceProviderQuery = _context.ServiceProviders + // .Include(sp => sp.ProviderType) + // .Where(sp => sp.TenantId == tenantId && sp.IsActive == active); + + // var filterObject = TryDeserializeServicesProviderFilter(filter); + + // if (filterObject != null) + // { + // if (filterObject.ProviderTypeIds?.Any() ?? false) + // { + // serviceProviderQuery = serviceProviderQuery.Where(sp => filterObject.ProviderTypeIds.Contains(sp.ProviderTypeId)); + // } + // } + + // if (!string.IsNullOrWhiteSpace(searchString)) + // { + // if (!(Int32.TryParse(searchString, out int num))) + // { + // num = 0; + // } + // serviceProviderQuery = serviceProviderQuery.Where(sp => + // sp.ProviderName.Contains(searchString) || + // sp.ContactPerson.Contains(searchString) || + // sp.Address.Contains(searchString) || + // sp.Email.Contains(searchString) || + // sp.PhoneNumber.Contains(searchString) || + // sp.SPRID == num); + // } + + // var serviceProviders = await serviceProviderQuery + // .OrderBy(e => e.ProviderName) + // .Skip((pageNumber - 1) * pageSize) + // .Take(pageSize) + // .ToListAsync(); + + // var response = _mapper.Map>(serviceProviders); + + // return Ok(ApiResponse.SuccessResponse(response, "Successfully fetched the Service Provider list", 200)); + //} + + #endregion + + #region =================================================================== Post Functions =================================================================== + + [HttpPost("create")] + public async Task CreateServiceProviderAsync([FromBody] CreateOrganizationDto model) + { + await using var _context = await _dbContextFactory.CreateDbContextAsync(); + using var scope = _serviceScope.CreateScope(); + + var _permission = scope.ServiceProvider.GetRequiredService(); + + var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + + var hasPermission = await _permission.HasPermission(PermissionsMaster.AddOrganization, loggedInEmployee.Id); + + if (!hasPermission) + { + 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.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.ApplicationRoles.FirstOrDefaultAsync(ar => ar.Role == "Admin" && ar.TenantId == tenantId); + if (jobRole == null) + jobRole = await _context.ApplicationRoles.FirstOrDefaultAsync(ar => ar.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, + TenantId = tenantId + }; + _context.Employees.Add(newEmployee); + + await _context.SaveChangesAsync(); + + /* 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); + + return Ok(ApiResponse.SuccessResponse(response, "Successfully created the service provider", 200)); + } + + //[HttpPost("assign/project")] + //public async Task AssignServiceProviderToProjectAsync([FromBody] AssignOrganizationDto model) + //{ + // await using var _context = await _dbContextFactory.CreateDbContextAsync(); + // using var scope = _serviceScope.CreateScope(); + + // var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + + // var todaysDate = DateTime.UtcNow.Date; + // var serviceTask = Task.Run(async () => + // { + // await using var context = await _dbContextFactory.CreateDbContextAsync(); + // //return await context.ServiceMasters.FirstOrDefaultAsync(sp => sp.Id == model.ServiceId); + // }); + // var projectServiceTask = Task.Run(async () => + // { + // await using var context = await _dbContextFactory.CreateDbContextAsync(); + // return await context.ProjectServiceMappings + // .FirstOrDefaultAsync(sp => sp.ServiceId == model.ServiceId && sp.ProjectId == model.ProjectId && sp.PlannedEndDate.Date <= todaysDate + // && sp.PlannedStartDate.Date >= todaysDate && sp.ActualStartDate.Date >= todaysDate); + // }); + // var serviceProviderTask = Task.Run(async () => + // { + // await using var context = await _dbContextFactory.CreateDbContextAsync(); + // return await context.ServiceProviders.FirstOrDefaultAsync(sp => sp.Id == model.ServiceProviderId); + // }); + // var projectTask = Task.Run(async () => + // { + // await using var context = await _dbContextFactory.CreateDbContextAsync(); + // return await context.Projects.FirstOrDefaultAsync(sp => sp.Id == model.ProjectId); + // }); + + // await Task.WhenAll(serviceProviderTask, projectTask, serviceTask, projectServiceTask); + + // var serviceProvider = serviceProviderTask.Result; + // var projectService = projectServiceTask.Result; + // //var service = serviceTask.Result; + // var project = projectTask.Result; + + // //if (service == null) + // //{ + // // return NotFound(ApiResponse.ErrorResponse("Service not found", "Service not found in database", 404)); + // //} + // if (serviceProvider == null) + // { + // return NotFound(ApiResponse.ErrorResponse("Service Provider not found", "Service Provider not found in database", 404)); + // } + // if (project == null) + // { + // return NotFound(ApiResponse.ErrorResponse("Project not found", "Project not found in database", 404)); + // } + // if (projectService == null) + // { + // return NotFound(ApiResponse.ErrorResponse("Project Service not found", "Project Service not found in database", 404)); + // } + // var serviceProviderTenantMapping = await _context.ServiceProviderTenantMappings + // .FirstOrDefaultAsync(spt => spt.ServiceProviderId == model.ServiceProviderId && spt.TenantId == project.TenantId); + + // if (serviceProviderTenantMapping == null) + // { + // var newServiceProviderTenantMapping = new OrganizationTenantMapping + // { + // ServiceProviderId = serviceProvider.Id, + // SPRID = serviceProvider.SPRID, + // IsActive = true, + // TenantId = project.TenantId + // }; + // _context.ServiceProviderTenantMappings.Add(newServiceProviderTenantMapping); + // } + + // var projectServiceProviderMapping = new ProjectOrganizationMapping + // { + // ProjectServiceId = projectService.Id, + // ServiceProviderId = model.ServiceProviderId, + // AssignedDate = model.AssignedDate, + // CompletionDate = model.CompletionDate, + // TenantId = project.TenantId + // }; + + // _context.ProjectServiceProviderMappings.Add(projectServiceProviderMapping); + + // await _context.SaveChangesAsync(); + + // var response = _mapper.Map(serviceProvider); + + // return Ok(ApiResponse.SuccessResponse(response, "Service Provider successfully assigned to the project", 200)); + //} + #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.ServiceProviderId == 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 + } +}