diff --git a/Marco.Pms.DataAccess/Data/ApplicationDbContext.cs b/Marco.Pms.DataAccess/Data/ApplicationDbContext.cs index f39fd61..e2f579a 100644 --- a/Marco.Pms.DataAccess/Data/ApplicationDbContext.cs +++ b/Marco.Pms.DataAccess/Data/ApplicationDbContext.cs @@ -209,8 +209,12 @@ namespace Marco.Pms.DataAccess.Data #region ======================================================= Service Project ======================================================= public DbSet ServiceProjects { get; set; } public DbSet ServiceProjectServiceMapping { get; set; } - #region ======================================================= Job ======================================================= - #endregion + + //#region ======================================================= Job ======================================================= + //public DbSet JobTickets { get; set; } + //public DbSet JobStatus { get; set; } + + //#endregion #endregion @@ -1266,6 +1270,26 @@ namespace Marco.Pms.DataAccess.Data } ); + //modelBuilder.Entity().HasData( + // new JobStatus + // { + // Id = Guid.Parse(""), + // Name = "", + // DisplayName = "" + // }, + // new JobStatus + // { + // Id = Guid.Parse(""), + // Name = "", + // DisplayName = "" + // }, + // new JobStatus + // { + // Id = Guid.Parse(""), + // Name = "", + // DisplayName = "" + // }); + modelBuilder.Entity().HasData( new Module { diff --git a/Marco.Pms.Model/ServiceProject/JobStatus.cs b/Marco.Pms.Model/ServiceProject/JobStatus.cs new file mode 100644 index 0000000..1efa950 --- /dev/null +++ b/Marco.Pms.Model/ServiceProject/JobStatus.cs @@ -0,0 +1,9 @@ +namespace Marco.Pms.Model.ServiceProject +{ + public class JobStatus + { + public Guid Id { get; set; } + public string Name { get; set; } = string.Empty; + public string DisplayName { get; set; } = string.Empty; + } +} diff --git a/Marco.Pms.Model/ServiceProject/JobTicket.cs b/Marco.Pms.Model/ServiceProject/JobTicket.cs new file mode 100644 index 0000000..589e6c6 --- /dev/null +++ b/Marco.Pms.Model/ServiceProject/JobTicket.cs @@ -0,0 +1,39 @@ +using Marco.Pms.Model.Employees; +using Marco.Pms.Model.Utilities; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Marco.Pms.Model.ServiceProject +{ + public class JobTicket : TenantRelation + { + public Guid Id { get; set; } + public string Title { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + public Guid ProjectId { get; set; } + + [ValidateNever] + [ForeignKey("ProjectId")] + public ServiceProject? Project { get; set; } + public Guid StatusId { get; set; } + + [ValidateNever] + [ForeignKey("StatusId")] + public JobStatus? Status { get; set; } + public DateTime StartDate { get; set; } + public DateTime DueDate { get; set; } + public bool IsActive { get; set; } + public DateTime CreatedAt { get; set; } + public Guid CreatedById { get; set; } + + [ValidateNever] + [ForeignKey("CreatedById")] + public Employee? CreatedBy { get; set; } + public DateTime? UpdatedAt { get; set; } + public Guid? UpdatedById { get; set; } + + [ValidateNever] + [ForeignKey("UpdatedById")] + public Employee? UpdatedBy { get; set; } + } +} diff --git a/Marco.Pms.Model/ViewModels/ServiceProject/ServiceProjectDetailsVM.cs b/Marco.Pms.Model/ViewModels/ServiceProject/ServiceProjectDetailsVM.cs new file mode 100644 index 0000000..4a31839 --- /dev/null +++ b/Marco.Pms.Model/ViewModels/ServiceProject/ServiceProjectDetailsVM.cs @@ -0,0 +1,27 @@ +using Marco.Pms.Model.Master; +using Marco.Pms.Model.ViewModels.Activities; +using Marco.Pms.Model.ViewModels.Master; +using Marco.Pms.Model.ViewModels.Organization; + +namespace Marco.Pms.Model.ViewModels.ServiceProject +{ + public class ServiceProjectDetailsVM + { + public Guid Id { get; set; } + public string? Name { get; set; } + public string? ShortName { get; set; } + public string? Address { get; set; } + public DateTime AssignedDate { get; set; } + public StatusMaster? Status { get; set; } + public BasicOrganizationVm? Client { get; set; } + public List? Services { get; set; } + public int NumberOfJobs { get; set; } + public string? ContactName { get; set; } + public string? ContactPhone { get; set; } + public string? ContactEmail { get; set; } + public DateTime CreatedAt { get; set; } + public BasicEmployeeVM? CreatedBy { get; set; } + public DateTime? UpdatedAt { get; set; } + public BasicEmployeeVM? UpdatedBy { get; set; } + } +} diff --git a/Marco.Pms.Services/Controllers/ProjectController.cs b/Marco.Pms.Services/Controllers/ProjectController.cs index 6244ce3..4b76165 100644 --- a/Marco.Pms.Services/Controllers/ProjectController.cs +++ b/Marco.Pms.Services/Controllers/ProjectController.cs @@ -57,7 +57,7 @@ namespace MarcoBMS.Services.Controllers /// An ApiResponse containing a list of projects or an error. [HttpGet("list")] - public async Task GetAllProjects() + public async Task GetAllProjects([FromQuery] int pageNumber = 1, [FromQuery] int pageSize = 20) { // --- Input Validation and Initial Setup --- if (!ModelState.IsValid) @@ -70,7 +70,7 @@ namespace MarcoBMS.Services.Controllers return BadRequest(ApiResponse.ErrorResponse("Invalid request data provided.", errors, 400)); } var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); - var response = await _projectServices.GetAllProjectsAsync(tenantId, loggedInEmployee); + var response = await _projectServices.GetAllProjectsAsync(pageNumber, pageSize, tenantId, loggedInEmployee); return StatusCode(response.StatusCode, response); } diff --git a/Marco.Pms.Services/Controllers/ServiceProjectController.cs b/Marco.Pms.Services/Controllers/ServiceProjectController.cs index 96feef3..7ea7bdf 100644 --- a/Marco.Pms.Services/Controllers/ServiceProjectController.cs +++ b/Marco.Pms.Services/Controllers/ServiceProjectController.cs @@ -62,6 +62,19 @@ namespace Marco.Pms.Services.Controllers var response = await _serviceProject.GetServiceProjectListAsync(pageNumber, pageSize, loggedInEmploee, tenantId); return StatusCode(response.StatusCode, response); + } + + [HttpGet("details/{id}")] + public async Task GetServiceProjectDetails(Guid id) + { + if (!ModelState.IsValid) + { + var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage).ToList(); + return BadRequest(ApiResponse.ErrorResponse("Invalid data", errors, 400)); + } + Employee loggedInEmploee = await _userHelper.GetCurrentEmployeeAsync(); + var response = await _serviceProject.GetServiceProjectDetailsAsync(id, loggedInEmploee, tenantId); + return StatusCode(response.StatusCode, response); } diff --git a/Marco.Pms.Services/MappingProfiles/MappingProfile.cs b/Marco.Pms.Services/MappingProfiles/MappingProfile.cs index ef95d37..49dfb32 100644 --- a/Marco.Pms.Services/MappingProfiles/MappingProfile.cs +++ b/Marco.Pms.Services/MappingProfiles/MappingProfile.cs @@ -195,6 +195,7 @@ namespace Marco.Pms.Services.MappingProfiles CreateMap(); CreateMap(); + CreateMap(); #endregion #region ======================================================= Employee ======================================================= diff --git a/Marco.Pms.Services/Service/ProjectServices.cs b/Marco.Pms.Services/Service/ProjectServices.cs index 1af9177..f02a456 100644 --- a/Marco.Pms.Services/Service/ProjectServices.cs +++ b/Marco.Pms.Services/Service/ProjectServices.cs @@ -97,7 +97,7 @@ namespace Marco.Pms.Services.Service return ApiResponse.ErrorResponse("An internal server error occurred. Please try again later.", null, 500); } } - public async Task> GetAllProjectsAsync(Guid tenantId, Employee loggedInEmployee) + public async Task> GetAllProjectsAsync(int pageNumber, int pageSize, Guid tenantId, Employee loggedInEmployee) { try { @@ -144,9 +144,25 @@ namespace Marco.Pms.Services.Service _logger.LogInfo("Cache HIT. All {ProjectCount} projects found in cache.", projectIds.Count); } + var totalEntites = responseVms.Count; + var totalPages = (int)Math.Ceiling((double)totalEntites / pageSize); + + responseVms = responseVms + .OrderBy(p => p.Name) + .Skip((pageNumber - 1) * pageSize) + .Take(pageSize).ToList(); + // --- Step 4: Return the combined result --- + + var response = new + { + CurrentPage = pageNumber, + TotalPages = totalPages, + TotalEntites = totalEntites, + Data = responseVms, + }; _logger.LogInfo("Successfully retrieved a total of {ProjectCount} projects.", responseVms.Count); - return ApiResponse.SuccessResponse(responseVms, "Projects retrieved successfully.", 200); + return ApiResponse.SuccessResponse(response, "Projects retrieved successfully.", 200); } catch (Exception ex) { diff --git a/Marco.Pms.Services/Service/ServiceInterfaces/IProjectServices.cs b/Marco.Pms.Services/Service/ServiceInterfaces/IProjectServices.cs index 4dfce8c..078c66e 100644 --- a/Marco.Pms.Services/Service/ServiceInterfaces/IProjectServices.cs +++ b/Marco.Pms.Services/Service/ServiceInterfaces/IProjectServices.cs @@ -11,7 +11,7 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces public interface IProjectServices { Task> GetAllProjectsBasicAsync(bool provideAll, Guid tenantId, Employee loggedInEmployee); - Task> GetAllProjectsAsync(Guid tenantId, Employee loggedInEmployee); + Task> GetAllProjectsAsync(int pageNumber, int pageSize, Guid tenantId, Employee loggedInEmployee); Task> GetProjectAsync(Guid id, Guid tenantId, Employee loggedInEmployee); Task> GetProjectDetailsAsync(Guid id, Guid tenantId, Employee loggedInEmployee); Task> GetProjectDetailsOldAsync(Guid id, Guid tenantId, Employee loggedInEmployee); diff --git a/Marco.Pms.Services/Service/ServiceInterfaces/IServiceProject.cs b/Marco.Pms.Services/Service/ServiceInterfaces/IServiceProject.cs index 27433bf..edc6135 100644 --- a/Marco.Pms.Services/Service/ServiceInterfaces/IServiceProject.cs +++ b/Marco.Pms.Services/Service/ServiceInterfaces/IServiceProject.cs @@ -6,9 +6,9 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces { public interface IServiceProject { - - Task> CreateServiceProjectAsync(ServiceProjectDto serviceProject, Employee loggedInEmployee, Guid TenantId); Task> GetServiceProjectListAsync(int pageNumber, int pageSize, Employee loggedInEmployee, Guid tenantId); + Task> GetServiceProjectDetailsAsync(Guid id, Employee loggedInEmployee, Guid tenantId); + Task> CreateServiceProjectAsync(ServiceProjectDto serviceProject, Employee loggedInEmployee, Guid TenantId); Task> UpdateServiceProjectAsync(Guid id, ServiceProjectDto serviceProject, Employee loggedInEmployee, Guid tenantId); Task> DeActivateServiceProjectAsync(Guid id, bool isActive, Employee loggedInEmployee, Guid tenantId); } diff --git a/Marco.Pms.Services/Service/ServiceProjectService.cs b/Marco.Pms.Services/Service/ServiceProjectService.cs index 0b442bb..596737f 100644 --- a/Marco.Pms.Services/Service/ServiceProjectService.cs +++ b/Marco.Pms.Services/Service/ServiceProjectService.cs @@ -40,7 +40,90 @@ namespace Marco.Pms.Services.Service _dbContextFactory = dbContextFactory; } + #region =================================================================== Service Project Functions =================================================================== + public async Task> GetServiceProjectListAsync(int pageNumber, int pageSize, Employee loggedInEmployee, Guid tenantId) + { + try + { + var serviceProjectQuery = _context.ServiceProjects + .Include(sp => sp.Client) + .Include(sp => sp.Status) + .Include(sp => sp.CreatedBy).ThenInclude(e => e!.JobRole) + .Where(sp => sp.TenantId == tenantId && sp.IsActive); + + var totalEntites = await serviceProjectQuery.CountAsync(); + var totalPages = (int)Math.Ceiling((double)totalEntites / pageSize); + + var serviceProjects = await serviceProjectQuery + .OrderByDescending(e => e.CreatedAt) + .Skip((pageNumber - 1) * pageSize) + .Take(pageSize) + .ToListAsync(); + + var serviceProjectIds = serviceProjects.Select(sp => sp.Id).ToList(); + var serviceProjectServiceMappings = await _context.ServiceProjectServiceMapping + .Include(sps => sps.Service) + .Where(sps => serviceProjectIds.Contains(sps.ProjectId) && + sps.Service != null && + sps.TenantId == tenantId) + .ToListAsync(); + + var serviceProjectVMs = serviceProjects.Select(sp => + { + var services = serviceProjectServiceMappings.Where(sps => sps.ProjectId == sp.Id).Select(sps => sps.Service!).ToList(); + var result = _mapper.Map(sp); + result.Services = _mapper.Map>(services); + return result; + }).ToList(); + + var response = new + { + CurrentPage = pageNumber, + TotalPages = totalPages, + TotalEntites = totalEntites, + Data = serviceProjectVMs, + }; + + _logger.LogInfo("Successfully retrieved a total of {ProjectCount} projects.", serviceProjectVMs.Count); + return ApiResponse.SuccessResponse(serviceProjectVMs, "Projects retrieved successfully.", 200); + + } + catch (Exception ex) + { + // --- Step 5: Graceful Error Handling --- + _logger.LogError(ex, "An unexpected error occurred in GetAllProjects for tenant {TenantId}.", tenantId); + return ApiResponse.ErrorResponse("An internal server error occurred. Please try again later.", null, 500); + } + + } + public async Task> GetServiceProjectDetailsAsync(Guid id, Employee loggedInEmployee, Guid tenantId) + { + var serviceProject = await _context.ServiceProjects + .Include(sp => sp.Client) + .Include(sp => sp.Status) + .Include(sp => sp.CreatedBy).ThenInclude(e => e!.JobRole) + .Include(sp => sp.UpdatedBy).ThenInclude(e => e!.JobRole) + .FirstOrDefaultAsync(sp => sp.Id == id && sp.TenantId == tenantId); + if (serviceProject == null) + { + return ApiResponse.ErrorResponse("Service Project not found", "Service Project not found", 404); + } + var services = await _context.ServiceProjectServiceMapping + .Include(sps => sps.Service) + .Where(sps => sps.ProjectId == serviceProject.Id && + sps.Service != null && + sps.TenantId == tenantId) + .Select(sps => sps.Service!) + .ToListAsync(); + //var numberOfJobs = await _context.JobTickets.Where(jt => jt.ProjectId == serviceProject.Id && jt.TenantId == tenantId).CountAsync(); + + var response = _mapper.Map(serviceProject); + response.Services = _mapper.Map>(services); + //response.NumberOfJobs = numberOfJobs; + response.NumberOfJobs = 0; + return ApiResponse.SuccessResponse(response, "Service Project Details fetched successfully", 200); + } public async Task> CreateServiceProjectAsync(ServiceProjectDto model, Employee loggedInEmployee, Guid tenantId) { var serviceIds = model.Services.Where(s => s.IsActive).Select(s => s.ServiceId).ToList(); @@ -116,51 +199,6 @@ namespace Marco.Pms.Services.Service } - } - public async Task> GetServiceProjectListAsync(int pageNumber, int pageSize, Employee loggedInEmployee, Guid tenantId) - { - try - { - - var serviceProjectQuery = _context.ServiceProjects - .Include(sp => sp.Client) - .Include(sp => sp.Status) - .Include(sp => sp.CreatedBy).ThenInclude(e => e!.JobRole) - .Where(sp => sp.TenantId == tenantId && sp.IsActive); - - var serviceProjects = await serviceProjectQuery - .OrderByDescending(e => e.CreatedAt) - .Skip((pageNumber - 1) * pageSize) - .Take(pageSize) - .ToListAsync(); - - var serviceProjectIds = serviceProjects.Select(sp => sp.Id).ToList(); - var serviceProjectServiceMappings = await _context.ServiceProjectServiceMapping - .Include(sps => sps.Service) - .Where(sps => serviceProjectIds.Contains(sps.ProjectId) && - sps.Service != null && - sps.TenantId == tenantId) - .ToListAsync(); - - var serviceProjectVMs = serviceProjects.Select(sp => - { - var services = serviceProjectServiceMappings.Where(sps => sps.ProjectId == sp.Id).Select(sps => sps.Service!).ToList(); - var result = _mapper.Map(sp); - result.Services = _mapper.Map>(services); - return result; - }).ToList(); - - _logger.LogInfo("Successfully retrieved a total of {ProjectCount} projects.", serviceProjectVMs.Count); - return ApiResponse.SuccessResponse(serviceProjectVMs, "Projects retrieved successfully.", 200); - - } - catch (Exception ex) - { - // --- Step 5: Graceful Error Handling --- - _logger.LogError(ex, "An unexpected error occurred in GetAllProjects for tenant {TenantId}.", tenantId); - return ApiResponse.ErrorResponse("An internal server error occurred. Please try again later.", null, 500); - } - } public async Task> UpdateServiceProjectAsync(Guid id, ServiceProjectDto model, Employee loggedInEmployee, Guid tenantId) { @@ -314,5 +352,12 @@ namespace Marco.Pms.Services.Service } } + #endregion + + #region =================================================================== Expense Functions =================================================================== + #endregion + + #region =================================================================== Expense Functions =================================================================== + #endregion } } diff --git a/Marco.Pms.Services/appsettings.Development.json b/Marco.Pms.Services/appsettings.Development.json index 978accc..316474a 100644 --- a/Marco.Pms.Services/appsettings.Development.json +++ b/Marco.Pms.Services/appsettings.Development.json @@ -18,13 +18,6 @@ "SenderEmail": "marcoioitsoft@gmail.com", "Password": "qrtq wfuj hwpp fhqr" }, - //"SmtpSettings": { - // "SmtpServer": "mail.marcoaiot.com", - // "Port": 587, - // "SenderName": "MarcoAIOT", - // "SenderEmail": "ashutosh.nehete@marcoaiot.com", - // "Password": "Reset@123" - //}, "AppSettings": { "WebFrontendUrl": "http://localhost:5173", "ImagesBaseUrl": "http://localhost:5173"