Added the service project details API and pagination in list of infra projects

This commit is contained in:
ashutosh.nehete 2025-11-12 11:35:47 +05:30
parent bfe9c03147
commit a32515fc84
12 changed files with 228 additions and 61 deletions

View File

@ -209,8 +209,12 @@ namespace Marco.Pms.DataAccess.Data
#region ======================================================= Service Project =======================================================
public DbSet<ServiceProject> ServiceProjects { get; set; }
public DbSet<ServiceProjectServiceMapping> ServiceProjectServiceMapping { get; set; }
#region ======================================================= Job =======================================================
#endregion
//#region ======================================================= Job =======================================================
//public DbSet<JobTicket> JobTickets { get; set; }
//public DbSet<JobStatus> JobStatus { get; set; }
//#endregion
#endregion
@ -1266,6 +1270,26 @@ namespace Marco.Pms.DataAccess.Data
}
);
//modelBuilder.Entity<JobStatus>().HasData(
// new JobStatus
// {
// Id = Guid.Parse(""),
// Name = "",
// DisplayName = ""
// },
// new JobStatus
// {
// Id = Guid.Parse(""),
// Name = "",
// DisplayName = ""
// },
// new JobStatus
// {
// Id = Guid.Parse(""),
// Name = "",
// DisplayName = ""
// });
modelBuilder.Entity<Module>().HasData(
new Module
{

View File

@ -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;
}
}

View File

@ -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; }
}
}

View File

@ -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<ServiceMasterVM>? 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; }
}
}

View File

@ -57,7 +57,7 @@ namespace MarcoBMS.Services.Controllers
/// <returns>An ApiResponse containing a list of projects or an error.</returns>
[HttpGet("list")]
public async Task<IActionResult> GetAllProjects()
public async Task<IActionResult> 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<object>.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);
}

View File

@ -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<IActionResult> GetServiceProjectDetails(Guid id)
{
if (!ModelState.IsValid)
{
var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage).ToList();
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", errors, 400));
}
Employee loggedInEmploee = await _userHelper.GetCurrentEmployeeAsync();
var response = await _serviceProject.GetServiceProjectDetailsAsync(id, loggedInEmploee, tenantId);
return StatusCode(response.StatusCode, response);
}

View File

@ -195,6 +195,7 @@ namespace Marco.Pms.Services.MappingProfiles
CreateMap<ServiceProjectDto, ServiceProject>();
CreateMap<ServiceProject, ServiceProjectVM>();
CreateMap<ServiceProject, ServiceProjectDetailsVM>();
#endregion
#region ======================================================= Employee =======================================================

View File

@ -97,7 +97,7 @@ namespace Marco.Pms.Services.Service
return ApiResponse<object>.ErrorResponse("An internal server error occurred. Please try again later.", null, 500);
}
}
public async Task<ApiResponse<object>> GetAllProjectsAsync(Guid tenantId, Employee loggedInEmployee)
public async Task<ApiResponse<object>> 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<object>.SuccessResponse(responseVms, "Projects retrieved successfully.", 200);
return ApiResponse<object>.SuccessResponse(response, "Projects retrieved successfully.", 200);
}
catch (Exception ex)
{

View File

@ -11,7 +11,7 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces
public interface IProjectServices
{
Task<ApiResponse<object>> GetAllProjectsBasicAsync(bool provideAll, Guid tenantId, Employee loggedInEmployee);
Task<ApiResponse<object>> GetAllProjectsAsync(Guid tenantId, Employee loggedInEmployee);
Task<ApiResponse<object>> GetAllProjectsAsync(int pageNumber, int pageSize, Guid tenantId, Employee loggedInEmployee);
Task<ApiResponse<object>> GetProjectAsync(Guid id, Guid tenantId, Employee loggedInEmployee);
Task<ApiResponse<object>> GetProjectDetailsAsync(Guid id, Guid tenantId, Employee loggedInEmployee);
Task<ApiResponse<object>> GetProjectDetailsOldAsync(Guid id, Guid tenantId, Employee loggedInEmployee);

View File

@ -6,9 +6,9 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces
{
public interface IServiceProject
{
Task<ApiResponse<object>> CreateServiceProjectAsync(ServiceProjectDto serviceProject, Employee loggedInEmployee, Guid TenantId);
Task<ApiResponse<object>> GetServiceProjectListAsync(int pageNumber, int pageSize, Employee loggedInEmployee, Guid tenantId);
Task<ApiResponse<object>> GetServiceProjectDetailsAsync(Guid id, Employee loggedInEmployee, Guid tenantId);
Task<ApiResponse<object>> CreateServiceProjectAsync(ServiceProjectDto serviceProject, Employee loggedInEmployee, Guid TenantId);
Task<ApiResponse<object>> UpdateServiceProjectAsync(Guid id, ServiceProjectDto serviceProject, Employee loggedInEmployee, Guid tenantId);
Task<ApiResponse<object>> DeActivateServiceProjectAsync(Guid id, bool isActive, Employee loggedInEmployee, Guid tenantId);
}

View File

@ -40,7 +40,90 @@ namespace Marco.Pms.Services.Service
_dbContextFactory = dbContextFactory;
}
#region =================================================================== Service Project Functions ===================================================================
public async Task<ApiResponse<object>> 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<ServiceProjectVM>(sp);
result.Services = _mapper.Map<List<ServiceMasterVM>>(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<object>.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<object>.ErrorResponse("An internal server error occurred. Please try again later.", null, 500);
}
}
public async Task<ApiResponse<object>> 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<object>.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<ServiceProjectDetailsVM>(serviceProject);
response.Services = _mapper.Map<List<ServiceMasterVM>>(services);
//response.NumberOfJobs = numberOfJobs;
response.NumberOfJobs = 0;
return ApiResponse<object>.SuccessResponse(response, "Service Project Details fetched successfully", 200);
}
public async Task<ApiResponse<object>> 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<ApiResponse<object>> 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<ServiceProjectVM>(sp);
result.Services = _mapper.Map<List<ServiceMasterVM>>(services);
return result;
}).ToList();
_logger.LogInfo("Successfully retrieved a total of {ProjectCount} projects.", serviceProjectVMs.Count);
return ApiResponse<object>.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<object>.ErrorResponse("An internal server error occurred. Please try again later.", null, 500);
}
}
public async Task<ApiResponse<object>> 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
}
}

View File

@ -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"