319 lines
15 KiB
C#
319 lines
15 KiB
C#
using AutoMapper;
|
|
using Marco.Pms.DataAccess.Data;
|
|
using Marco.Pms.Model.Dtos.ServiceProject;
|
|
using Marco.Pms.Model.Employees;
|
|
using Marco.Pms.Model.ServiceProject;
|
|
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.ServiceProject;
|
|
using Marco.Pms.Services.Helpers;
|
|
using Marco.Pms.Services.Service.ServiceInterfaces;
|
|
using MarcoBMS.Services.Service;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace Marco.Pms.Services.Service
|
|
{
|
|
public class ServiceProjectService : IServiceProject
|
|
{
|
|
|
|
private readonly IDbContextFactory<ApplicationDbContext> _dbContextFactory;
|
|
private readonly IServiceScopeFactory _serviceScopeFactory;
|
|
private readonly ApplicationDbContext _context; // Keeping this for direct scoped context use where appropriate
|
|
private readonly ILoggingService _logger;
|
|
private readonly CacheUpdateHelper _cache;
|
|
private readonly IMapper _mapper;
|
|
|
|
public ServiceProjectService(IDbContextFactory<ApplicationDbContext> dbContextFactory,
|
|
IServiceScopeFactory serviceScopeFactory,
|
|
ApplicationDbContext context,
|
|
ILoggingService logger,
|
|
CacheUpdateHelper cache,
|
|
IMapper mapper)
|
|
{
|
|
_serviceScopeFactory = serviceScopeFactory;
|
|
_context = context;
|
|
_logger = logger;
|
|
_cache = cache;
|
|
_mapper = mapper;
|
|
_dbContextFactory = dbContextFactory;
|
|
}
|
|
|
|
|
|
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();
|
|
var clientTask = Task.Run(async () =>
|
|
{
|
|
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
|
return await context.Organizations.FirstOrDefaultAsync(o => o.Id == model.ClientId && o.IsActive);
|
|
});
|
|
var serviceTask = Task.Run(async () =>
|
|
{
|
|
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
|
return await context.ServiceMasters.Where(s => serviceIds.Contains(s.Id) && s.TenantId == tenantId && s.IsActive).ToListAsync();
|
|
});
|
|
var statusTask = Task.Run(async () =>
|
|
{
|
|
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
|
return await context.StatusMasters.FirstOrDefaultAsync(s => s.Id == model.StatusId);
|
|
});
|
|
|
|
await Task.WhenAll(clientTask, serviceTask, statusTask);
|
|
|
|
var client = clientTask.Result;
|
|
var services = serviceTask.Result;
|
|
var status = statusTask.Result;
|
|
|
|
if (client == null)
|
|
{
|
|
return ApiResponse<object>.ErrorResponse("Client not found", "Client not found", 404);
|
|
}
|
|
if (status == null)
|
|
{
|
|
return ApiResponse<object>.ErrorResponse("Project Status not found", "Project Status not found", 404);
|
|
}
|
|
|
|
var serviceProject = _mapper.Map<ServiceProject>(model);
|
|
serviceProject.Id = Guid.NewGuid();
|
|
serviceProject.CreatedById = loggedInEmployee.Id;
|
|
serviceProject.CreatedAt = DateTime.UtcNow;
|
|
serviceProject.IsActive = true;
|
|
serviceProject.TenantId = tenantId;
|
|
|
|
var projectServiceMapping = model.Services.Where(sdto => services.Any(s => s.Id == sdto.ServiceId)).Select(sdto => new ServiceProjectServiceMapping
|
|
{
|
|
ServiceId = sdto.ServiceId,
|
|
ProjectId = serviceProject.Id,
|
|
TenantId = tenantId
|
|
}).ToList();
|
|
try
|
|
{
|
|
_context.ServiceProjects.Add(serviceProject);
|
|
_context.ServiceProjectServiceMapping.AddRange(projectServiceMapping);
|
|
|
|
await _context.SaveChangesAsync();
|
|
|
|
_logger.LogInfo("Service Project {ProjectId} created successfully for TenantId={TenantId}, by Employee {EmployeeId}.",
|
|
serviceProject.Id, tenantId, loggedInEmployee);
|
|
|
|
var serviceProjectVM = _mapper.Map<ServiceProjectVM>(serviceProject);
|
|
|
|
serviceProjectVM.Client = _mapper.Map<BasicOrganizationVm>(client);
|
|
serviceProjectVM.Status = status;
|
|
|
|
serviceProjectVM.Services = services.Where(s => serviceIds.Contains(s.Id)).Select(s => _mapper.Map<ServiceMasterVM>(s)).ToList();
|
|
|
|
serviceProjectVM.CreatedBy = _mapper.Map<BasicEmployeeVM>(loggedInEmployee);
|
|
return ApiResponse<object>.SuccessResponse(serviceProjectVM, "An Successfullly occurred while saving the project.", 201);
|
|
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "DB Failure: Service Project creation failed for TenantId={TenantId}. Rolling back.", tenantId);
|
|
return ApiResponse<object>.ErrorResponse("An error occurred while saving the project.", ex.Message, 500);
|
|
}
|
|
|
|
|
|
}
|
|
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)
|
|
{
|
|
try
|
|
{
|
|
var serviceIds = model.Services.Select(s => s.ServiceId).ToList();
|
|
var clientTask = Task.Run(async () =>
|
|
{
|
|
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
|
return await context.Organizations.FirstOrDefaultAsync(o => o.Id == model.ClientId && o.IsActive);
|
|
});
|
|
var serviceTask = Task.Run(async () =>
|
|
{
|
|
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
|
return await context.ServiceMasters.Where(s => serviceIds.Contains(s.Id) && s.TenantId == tenantId && s.IsActive).ToListAsync();
|
|
});
|
|
var statusTask = Task.Run(async () =>
|
|
{
|
|
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
|
return await context.StatusMasters.FirstOrDefaultAsync(s => s.Id == model.StatusId);
|
|
});
|
|
|
|
await Task.WhenAll(clientTask, serviceTask, statusTask);
|
|
|
|
var client = clientTask.Result;
|
|
var services = serviceTask.Result;
|
|
var status = statusTask.Result;
|
|
|
|
if (client == null)
|
|
{
|
|
return ApiResponse<object>.ErrorResponse("Client not found", "Client not found", 404);
|
|
}
|
|
if (status == null)
|
|
{
|
|
return ApiResponse<object>.ErrorResponse("Project Status not found", "Project Status not found", 404);
|
|
}
|
|
|
|
var serviceProject = await _context.ServiceProjects.Where(sp => sp.Id == id && sp.TenantId == tenantId && sp.IsActive).FirstOrDefaultAsync();
|
|
|
|
if (serviceProject == null)
|
|
{
|
|
_logger.LogWarning("Attempt to update non-existent Service project with ID {ProjectId} by user {UserId}.", id, loggedInEmployee.Id);
|
|
return ApiResponse<object>.ErrorResponse("Project not found.", $"No project found with ID {id}.", 404);
|
|
}
|
|
|
|
_mapper.Map(model, serviceProject);
|
|
|
|
serviceProject.UpdatedAt = DateTime.UtcNow;
|
|
serviceProject.UpdatedById = loggedInEmployee.Id;
|
|
|
|
var serviceProjectServiceMappings = await _context.ServiceProjectServiceMapping
|
|
.AsNoTracking()
|
|
.Where(sps => sps.ProjectId == serviceProject.Id && sps.TenantId == tenantId)
|
|
.ToListAsync();
|
|
|
|
var newMapping = new List<ServiceProjectServiceMapping>();
|
|
var removedMapping = new List<ServiceProjectServiceMapping>();
|
|
|
|
foreach (var dto in model.Services)
|
|
{
|
|
var serviceProjectServiceMapping = serviceProjectServiceMappings
|
|
.FirstOrDefault(sps => sps.ServiceId == dto.ServiceId);
|
|
|
|
if (dto.IsActive && serviceProjectServiceMapping == null)
|
|
{
|
|
newMapping.Add(new ServiceProjectServiceMapping
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
ServiceId = dto.ServiceId,
|
|
ProjectId = serviceProject.Id,
|
|
TenantId = tenantId,
|
|
});
|
|
}
|
|
else if (!dto.IsActive && serviceProjectServiceMapping != null)
|
|
{
|
|
removedMapping.Add(serviceProjectServiceMapping);
|
|
}
|
|
}
|
|
|
|
_context.ServiceProjectServiceMapping.AddRange(newMapping);
|
|
_context.ServiceProjectServiceMapping.RemoveRange(removedMapping);
|
|
|
|
await _context.SaveChangesAsync();
|
|
|
|
var serviceProjectTask = Task.Run(async () =>
|
|
{
|
|
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
|
return await 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).FirstOrDefaultAsync();
|
|
});
|
|
|
|
var servicesTask = Task.Run(async () =>
|
|
{
|
|
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
|
return await context.ServiceProjectServiceMapping
|
|
.Include(sps => sps.Service)
|
|
.Where(sps => sps.ProjectId == serviceProject.Id && sps.Service != null && sps.TenantId == tenantId)
|
|
.Select(sps => sps.Service!)
|
|
.ToListAsync();
|
|
});
|
|
|
|
await Task.WhenAll(serviceProjectTask, servicesTask);
|
|
|
|
|
|
serviceProject = serviceProjectTask.Result;
|
|
services = servicesTask.Result;
|
|
|
|
ServiceProjectVM serviceProjectVm = _mapper.Map<ServiceProjectVM>(serviceProject);
|
|
serviceProjectVm.Services = _mapper.Map<List<ServiceMasterVM>>(services);
|
|
|
|
return ApiResponse<object>.SuccessResponse(serviceProjectVm, "Service Project updated successfully.", 200);
|
|
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "An unexpected error occurred in Updating Service Project for tenant {TenantId}.", tenantId);
|
|
return ApiResponse<object>.ErrorResponse("An internal server error occurred. Please try again later.", null, 500);
|
|
}
|
|
|
|
}
|
|
public async Task<ApiResponse<object>> DeActivateServiceProjectAsync(Guid id, bool isActive, Employee loggedInEmployee, Guid tenantId)
|
|
{
|
|
try
|
|
{
|
|
|
|
var serviceProject = await _context.ServiceProjects
|
|
.Where(sp => sp.Id == id && sp.TenantId == tenantId).FirstOrDefaultAsync();
|
|
|
|
if (serviceProject == null)
|
|
{
|
|
_logger.LogWarning("Attempt to de-activate non-existent Service project with ID {ProjectId} by user {UserId}.", id, loggedInEmployee.Id);
|
|
return ApiResponse<object>.ErrorResponse("Project not found.", $"No project found with ID {id}.", 404);
|
|
}
|
|
|
|
serviceProject.IsActive = isActive;
|
|
|
|
await _context.SaveChangesAsync();
|
|
|
|
_logger.LogInfo("Successfully de-activated service project {ProjectId}", id);
|
|
return ApiResponse<object>.SuccessResponse(new { }, "Projects de-activated successfully.", 200);
|
|
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// --- Step 5: Graceful Error Handling ---
|
|
_logger.LogError(ex, "An unexpected error occurred in DeActivateServiceProject for tenant {TenantId}.", tenantId);
|
|
return ApiResponse<object>.ErrorResponse("An internal server error occurred. Please try again later.", null, 500);
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|