Added Google map url in service project table
This commit is contained in:
parent
c61ef92f6e
commit
2806dceab2
8628
Marco.Pms.DataAccess/Migrations/20251114120630_Added_GoogleMapUrl_In_ServiceProject_Table.Designer.cs
generated
Normal file
8628
Marco.Pms.DataAccess/Migrations/20251114120630_Added_GoogleMapUrl_In_ServiceProject_Table.Designer.cs
generated
Normal file
File diff suppressed because one or more lines are too long
@ -0,0 +1,29 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Marco.Pms.DataAccess.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class Added_GoogleMapUrl_In_ServiceProject_Table : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "GoogleMapUrl",
|
||||||
|
table: "ServiceProjects",
|
||||||
|
type: "longtext",
|
||||||
|
nullable: true)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "GoogleMapUrl",
|
||||||
|
table: "ServiceProjects");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5463,6 +5463,9 @@ namespace Marco.Pms.DataAccess.Migrations
|
|||||||
b.Property<Guid>("CreatedById")
|
b.Property<Guid>("CreatedById")
|
||||||
.HasColumnType("char(36)");
|
.HasColumnType("char(36)");
|
||||||
|
|
||||||
|
b.Property<string>("GoogleMapUrl")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
b.Property<bool>("IsActive")
|
b.Property<bool>("IsActive")
|
||||||
.HasColumnType("tinyint(1)");
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
|||||||
@ -17,6 +17,6 @@ namespace Marco.Pms.Model.Dtos.ServiceProject
|
|||||||
public required string ContactName { get; set; }
|
public required string ContactName { get; set; }
|
||||||
public required string ContactPhone { get; set; }
|
public required string ContactPhone { get; set; }
|
||||||
public required string ContactEmail { get; set; }
|
public required string ContactEmail { get; set; }
|
||||||
|
public string? GoogleMapUrl { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,6 +27,7 @@ namespace Marco.Pms.Model.ServiceProject
|
|||||||
public string ContactName { get; set; } = string.Empty;
|
public string ContactName { get; set; } = string.Empty;
|
||||||
public string ContactPhone { get; set; } = string.Empty;
|
public string ContactPhone { get; set; } = string.Empty;
|
||||||
public string ContactEmail { get; set; } = string.Empty;
|
public string ContactEmail { get; set; } = string.Empty;
|
||||||
|
public string? GoogleMapUrl { get; set; }
|
||||||
public bool IsActive { get; set; }
|
public bool IsActive { get; set; }
|
||||||
public DateTime CreatedAt { get; set; }
|
public DateTime CreatedAt { get; set; }
|
||||||
public Guid CreatedById { get; set; }
|
public Guid CreatedById { get; set; }
|
||||||
|
|||||||
@ -18,6 +18,7 @@ namespace Marco.Pms.Model.ViewModels.ServiceProject
|
|||||||
public string? ContactName { get; set; }
|
public string? ContactName { get; set; }
|
||||||
public string? ContactPhone { get; set; }
|
public string? ContactPhone { get; set; }
|
||||||
public string? ContactEmail { get; set; }
|
public string? ContactEmail { get; set; }
|
||||||
|
public string? GoogleMapUrl { get; set; }
|
||||||
public DateTime CreatedAt { get; set; }
|
public DateTime CreatedAt { get; set; }
|
||||||
public BasicEmployeeVM? CreatedBy { get; set; }
|
public BasicEmployeeVM? CreatedBy { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -119,6 +119,7 @@ namespace Marco.Pms.Services.Controllers
|
|||||||
return StatusCode(response.StatusCode, response);
|
return StatusCode(response.StatusCode, response);
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region =================================================================== Job Tickets Functions ===================================================================
|
#region =================================================================== Job Tickets Functions ===================================================================
|
||||||
|
|
||||||
[HttpGet("job/list")]
|
[HttpGet("job/list")]
|
||||||
|
|||||||
@ -19,22 +19,23 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces
|
|||||||
Task<ApiResponse<object>> GetServiceProjectAllocationListAsync(Guid? projectId, Guid? employeeId, bool isActive, Employee loggedInEmployee, Guid tenantId);
|
Task<ApiResponse<object>> GetServiceProjectAllocationListAsync(Guid? projectId, Guid? employeeId, bool isActive, Employee loggedInEmployee, Guid tenantId);
|
||||||
Task<ApiResponse<object>> ManageServiceProjectAllocationAsync(List<ServiceProjectAllocationDto> model, Employee loggedInEmployee, Guid tenantId);
|
Task<ApiResponse<object>> ManageServiceProjectAllocationAsync(List<ServiceProjectAllocationDto> model, Employee loggedInEmployee, Guid tenantId);
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region =================================================================== Job Tickets Functions ===================================================================
|
#region =================================================================== Job Tickets Functions ===================================================================
|
||||||
Task<ApiResponse<object>> GetJobTicketsListAsync(Guid? projectId, int pageNumber, int pageSize, bool isActive, Employee loggedInEmployee, Guid tenantId);
|
Task<ApiResponse<object>> GetJobTicketsListAsync(Guid? projectId, int pageNumber, int pageSize, bool isActive, Employee loggedInEmployee, Guid tenantId);
|
||||||
Task<ApiResponse<object>> GetJobTicketDetailsAsync(Guid id, Employee loggedInEmployee, Guid tenantId);
|
Task<ApiResponse<object>> GetJobTicketDetailsAsync(Guid id, Employee loggedInEmployee, Guid tenantId);
|
||||||
Task<ApiResponse<object>> GetCommentListByJobTicketAsync(Guid? jobTicketId, int pageNumber, int pageSize, Employee loggedInEmployee, Guid tenantId);
|
|
||||||
Task<ApiResponse<object>> GetJobTagListAsync(Employee loggedInEmployee, Guid tenantId);
|
Task<ApiResponse<object>> GetJobTagListAsync(Employee loggedInEmployee, Guid tenantId);
|
||||||
Task<ApiResponse<object>> CreateJobTicketAsync(CreateJobTicketDto model, Employee loggedInEmployee, Guid tenantId);
|
Task<ApiResponse<object>> CreateJobTicketAsync(CreateJobTicketDto model, Employee loggedInEmployee, Guid tenantId);
|
||||||
Task<ApiResponse<object>> ChangeJobsStatusAsync(ChangeJobStatusDto model, Employee loggedInEmployee, Guid tenantId);
|
Task<ApiResponse<object>> ChangeJobsStatusAsync(ChangeJobStatusDto model, Employee loggedInEmployee, Guid tenantId);
|
||||||
Task<ApiResponse<object>> UpdateJobTicketAsync(Guid id, JobTicket jobTicket, UpdateJobTicketDto model, Employee loggedInEmployee, Guid tenantId);
|
Task<ApiResponse<object>> UpdateJobTicketAsync(Guid id, JobTicket jobTicket, UpdateJobTicketDto model, Employee loggedInEmployee, Guid tenantId);
|
||||||
Task<ApiResponse<object>> AddCommentToJobTicketAsync(JobCommentDto model, Employee loggedInEmployee, Guid tenantId);
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region =================================================================== Job Comments Functions ===================================================================
|
#region =================================================================== Job Comments Functions ===================================================================
|
||||||
|
Task<ApiResponse<object>> GetCommentListByJobTicketAsync(Guid? jobTicketId, int pageNumber, int pageSize, Employee loggedInEmployee, Guid tenantId);
|
||||||
|
Task<ApiResponse<object>> AddCommentToJobTicketAsync(JobCommentDto model, Employee loggedInEmployee, Guid tenantId);
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region =================================================================== Pubic Helper Functions ===================================================================
|
#region =================================================================== Helper Functions ===================================================================
|
||||||
Task<JobTicket?> GetJobTicketByIdAsync(Guid id, Guid tenantId);
|
Task<JobTicket?> GetJobTicketByIdAsync(Guid id, Guid tenantId);
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@ -49,180 +49,192 @@ namespace Marco.Pms.Services.Service
|
|||||||
}
|
}
|
||||||
|
|
||||||
#region =================================================================== Service Project Functions ===================================================================
|
#region =================================================================== Service Project Functions ===================================================================
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves a paginated list of active service projects including their clients, status, creators, and related services for a given tenant.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pageNumber">Page number (1-based) for pagination.</param>
|
||||||
|
/// <param name="pageSize">Number of records per page.</param>
|
||||||
|
/// <param name="loggedInEmployee">Employee making the request (for logging).</param>
|
||||||
|
/// <param name="tenantId">Tenant identifier for multi-tenant isolation.</param>
|
||||||
|
/// <returns>ApiResponse containing paged service projects with related data or error information.</returns>
|
||||||
public async Task<ApiResponse<object>> GetServiceProjectListAsync(int pageNumber, int pageSize, Employee loggedInEmployee, Guid tenantId)
|
public async Task<ApiResponse<object>> GetServiceProjectListAsync(int pageNumber, int pageSize, Employee loggedInEmployee, Guid tenantId)
|
||||||
{
|
{
|
||||||
|
if (tenantId == Guid.Empty)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("GetServiceProjectListAsync called with invalid tenant context by employee {EmployeeId}", loggedInEmployee.Id);
|
||||||
|
return ApiResponse<object>.ErrorResponse("Access Denied", "Invalid tenant context.", 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pageNumber < 1 || pageSize < 1)
|
||||||
|
{
|
||||||
|
_logger.LogInfo("Invalid pagination parameters: PageNumber={PageNumber}, PageSize={PageSize}", pageNumber, pageSize);
|
||||||
|
return ApiResponse<object>.ErrorResponse("Bad Request", "Page number and size must be greater than zero.", 400);
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// Base query for active projects scoped by tenant including necessary related entities
|
||||||
var serviceProjectQuery = _context.ServiceProjects
|
var serviceProjectQuery = _context.ServiceProjects
|
||||||
.Include(sp => sp.Client)
|
.Include(sp => sp.Client)
|
||||||
.Include(sp => sp.Status)
|
.Include(sp => sp.Status)
|
||||||
.Include(sp => sp.CreatedBy).ThenInclude(e => e!.JobRole)
|
.Include(sp => sp.CreatedBy).ThenInclude(e => e!.JobRole)
|
||||||
.Where(sp => sp.TenantId == tenantId && sp.IsActive);
|
.Where(sp => sp.TenantId == tenantId && sp.IsActive);
|
||||||
|
|
||||||
var totalEntites = await serviceProjectQuery.CountAsync();
|
var totalEntities = await serviceProjectQuery.CountAsync();
|
||||||
var totalPages = (int)Math.Ceiling((double)totalEntites / pageSize);
|
var totalPages = (int)Math.Ceiling((double)totalEntities / pageSize);
|
||||||
|
|
||||||
|
// Fetch paged projects ordered by creation date descending
|
||||||
var serviceProjects = await serviceProjectQuery
|
var serviceProjects = await serviceProjectQuery
|
||||||
.OrderByDescending(e => e.CreatedAt)
|
.OrderByDescending(sp => sp.CreatedAt)
|
||||||
.Skip((pageNumber - 1) * pageSize)
|
.Skip((pageNumber - 1) * pageSize)
|
||||||
.Take(pageSize)
|
.Take(pageSize)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
var serviceProjectIds = serviceProjects.Select(sp => sp.Id).ToList();
|
var serviceProjectIds = serviceProjects.Select(sp => sp.Id).ToList();
|
||||||
|
|
||||||
|
// Load related service mappings with services for current page projects (avoid N+1)
|
||||||
var serviceProjectServiceMappings = await _context.ServiceProjectServiceMapping
|
var serviceProjectServiceMappings = await _context.ServiceProjectServiceMapping
|
||||||
.Include(sps => sps.Service)
|
.Include(sps => sps.Service)
|
||||||
.Where(sps => serviceProjectIds.Contains(sps.ProjectId) &&
|
.Where(sps => serviceProjectIds.Contains(sps.ProjectId) &&
|
||||||
sps.Service != null &&
|
sps.Service != null &&
|
||||||
sps.TenantId == tenantId)
|
sps.TenantId == tenantId)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
|
// Map each project with its related services into the view models
|
||||||
var serviceProjectVMs = serviceProjects.Select(sp =>
|
var serviceProjectVMs = serviceProjects.Select(sp =>
|
||||||
{
|
{
|
||||||
var services = serviceProjectServiceMappings.Where(sps => sps.ProjectId == sp.Id).Select(sps => sps.Service!).ToList();
|
var relatedServices = serviceProjectServiceMappings
|
||||||
var result = _mapper.Map<ServiceProjectVM>(sp);
|
.Where(sps => sps.ProjectId == sp.Id)
|
||||||
result.Services = _mapper.Map<List<ServiceMasterVM>>(services);
|
.Select(sps => sps.Service!)
|
||||||
return result;
|
.ToList();
|
||||||
|
|
||||||
|
var projectVm = _mapper.Map<ServiceProjectVM>(sp);
|
||||||
|
projectVm.Services = _mapper.Map<List<ServiceMasterVM>>(relatedServices);
|
||||||
|
return projectVm;
|
||||||
}).ToList();
|
}).ToList();
|
||||||
|
|
||||||
var response = new
|
var response = new
|
||||||
{
|
{
|
||||||
CurrentPage = pageNumber,
|
CurrentPage = pageNumber,
|
||||||
TotalPages = totalPages,
|
TotalPages = totalPages,
|
||||||
TotalEntites = totalEntites,
|
TotalEntities = totalEntities,
|
||||||
Data = serviceProjectVMs,
|
Data = serviceProjectVMs,
|
||||||
};
|
};
|
||||||
|
|
||||||
_logger.LogInfo("Successfully retrieved a total of {ProjectCount} projects.", serviceProjectVMs.Count);
|
_logger.LogInfo("Retrieved {Count} service projects for tenant {TenantId} by employee {EmployeeId}. Page {PageNumber}/{TotalPages}",
|
||||||
return ApiResponse<object>.SuccessResponse(response, "Projects retrieved successfully.", 200);
|
serviceProjectVMs.Count, tenantId, loggedInEmployee.Id, pageNumber, totalPages);
|
||||||
|
|
||||||
|
return ApiResponse<object>.SuccessResponse(response, "Projects retrieved successfully.", 200);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// --- Step 5: Graceful Error Handling ---
|
_logger.LogError(ex, "An unexpected error occurred in GetServiceProjectListAsync for tenant {TenantId} by employee {EmployeeId}",
|
||||||
_logger.LogError(ex, "An unexpected error occurred in GetAllProjects for tenant {TenantId}.", tenantId);
|
tenantId, loggedInEmployee.Id);
|
||||||
return ApiResponse<object>.ErrorResponse("An internal server error occurred. Please try again later.", null, 500);
|
return ApiResponse<object>.ErrorResponse("An internal server error occurred. Please try again later.", null, 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves detailed information for a specific service project, including related client, status, services, and audit information.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">The unique identifier of the service project.</param>
|
||||||
|
/// <param name="loggedInEmployee">The employee requesting the details (for audit logging).</param>
|
||||||
|
/// <param name="tenantId">Tenant identifier ensuring multi-tenant data isolation.</param>
|
||||||
|
/// <returns>ApiResponse containing detailed service project information or error details.</returns>
|
||||||
public async Task<ApiResponse<object>> GetServiceProjectDetailsAsync(Guid id, Employee loggedInEmployee, Guid tenantId)
|
public async Task<ApiResponse<object>> GetServiceProjectDetailsAsync(Guid id, Employee loggedInEmployee, Guid tenantId)
|
||||||
{
|
{
|
||||||
var serviceProject = await _context.ServiceProjects
|
if (tenantId == Guid.Empty)
|
||||||
.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);
|
_logger.LogWarning("GetServiceProjectDetailsAsync called with missing tenant context by employee {EmployeeId}", loggedInEmployee.Id);
|
||||||
}
|
return ApiResponse<object>.ErrorResponse("Access Denied", "Invalid tenant context.", 403);
|
||||||
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();
|
|
||||||
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
|
try
|
||||||
{
|
{
|
||||||
_context.ServiceProjects.Add(serviceProject);
|
// Load service project with related client, status, and creator/updater roles
|
||||||
_context.ServiceProjectServiceMapping.AddRange(projectServiceMapping);
|
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)
|
||||||
|
.AsNoTracking()
|
||||||
|
.FirstOrDefaultAsync(sp => sp.Id == id && sp.TenantId == tenantId);
|
||||||
|
|
||||||
await _context.SaveChangesAsync();
|
if (serviceProject == null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Service project {ServiceProjectId} not found for tenant {TenantId}", id, tenantId);
|
||||||
|
return ApiResponse<object>.ErrorResponse("Service Project Not Found", "Service project not found for the specified tenant.", 404);
|
||||||
|
}
|
||||||
|
|
||||||
_logger.LogInfo("Service Project {ProjectId} created successfully for TenantId={TenantId}, by Employee {EmployeeId}.",
|
// Retrieve related services for the project
|
||||||
serviceProject.Id, tenantId, loggedInEmployee);
|
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 serviceProjectVM = _mapper.Map<ServiceProjectVM>(serviceProject);
|
// Optional: Count number of job tickets associated with the project (commented out)
|
||||||
|
// var numberOfJobs = await _context.JobTickets.CountAsync(jt => jt.ProjectId == serviceProject.Id && jt.TenantId == tenantId);
|
||||||
|
|
||||||
serviceProjectVM.Client = _mapper.Map<BasicOrganizationVm>(client);
|
var response = _mapper.Map<ServiceProjectDetailsVM>(serviceProject);
|
||||||
serviceProjectVM.Status = status;
|
response.Services = _mapper.Map<List<ServiceMasterVM>>(services);
|
||||||
|
response.NumberOfJobs = await _context.JobTickets.CountAsync(jt => jt.ProjectId == id && jt.IsActive && jt.TenantId == tenantId);
|
||||||
|
|
||||||
serviceProjectVM.Services = services.Where(s => serviceIds.Contains(s.Id)).Select(s => _mapper.Map<ServiceMasterVM>(s)).ToList();
|
_logger.LogInfo("Fetched details for service project {ServiceProjectId} for tenant {TenantId} requested by employee {EmployeeId}",
|
||||||
|
id, tenantId, loggedInEmployee.Id);
|
||||||
serviceProjectVM.CreatedBy = _mapper.Map<BasicEmployeeVM>(loggedInEmployee);
|
|
||||||
return ApiResponse<object>.SuccessResponse(serviceProjectVM, "An Successfullly occurred while saving the project.", 201);
|
|
||||||
|
|
||||||
|
return ApiResponse<object>.SuccessResponse(response, "Service Project details fetched successfully.", 200);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "DB Failure: Service Project creation failed for TenantId={TenantId}. Rolling back.", tenantId);
|
_logger.LogError(ex, "Failed to fetch service project details for project {ServiceProjectId} by employee {EmployeeId} in tenant {TenantId}",
|
||||||
return ApiResponse<object>.ErrorResponse("An error occurred while saving the project.", ex.Message, 500);
|
id, loggedInEmployee.Id, tenantId);
|
||||||
|
return ApiResponse<object>.ErrorResponse("Internal Server Error", "Unable to fetch service project details. Please try again later.", 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new service project along with its associated active services, and returns the created project details.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="model">DTO containing service project information and list of active services.</param>
|
||||||
|
/// <param name="loggedInEmployee">Employee creating the service project (for auditing).</param>
|
||||||
|
/// <param name="tenantId">Tenant context for multi-tenancy.</param>
|
||||||
|
/// <returns>ApiResponse containing created service project details or error info.</returns>
|
||||||
|
public async Task<ApiResponse<object>> CreateServiceProjectAsync(ServiceProjectDto model, Employee loggedInEmployee, Guid tenantId)
|
||||||
|
{
|
||||||
|
if (tenantId == Guid.Empty)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("CreateServiceProjectAsync called with invalid tenant context by employee {EmployeeId}", loggedInEmployee.Id);
|
||||||
|
return ApiResponse<object>.ErrorResponse("Access Denied", "Invalid tenant context.", 403);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (model == null)
|
||||||
|
{
|
||||||
|
_logger.LogInfo("CreateServiceProjectAsync called with null model by employee {EmployeeId}", loggedInEmployee.Id);
|
||||||
|
return ApiResponse<object>.ErrorResponse("Bad Request", "Input model cannot be null.", 400);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
|
||||||
public async Task<ApiResponse<object>> UpdateServiceProjectAsync(Guid id, ServiceProjectDto model, Employee loggedInEmployee, Guid tenantId)
|
|
||||||
{
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var serviceIds = model.Services.Select(s => s.ServiceId).ToList();
|
// Extract active service IDs for validation and association
|
||||||
|
var serviceIds = model.Services?.Where(s => s.IsActive).Select(s => s.ServiceId).ToList() ?? new List<Guid>();
|
||||||
|
|
||||||
|
// Concurrently load dependent entities: client, services, status
|
||||||
var clientTask = Task.Run(async () =>
|
var clientTask = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
||||||
return await context.Organizations.FirstOrDefaultAsync(o => o.Id == model.ClientId && o.IsActive);
|
return await context.Organizations.FirstOrDefaultAsync(o => o.Id == model.ClientId && o.IsActive);
|
||||||
});
|
});
|
||||||
|
|
||||||
var serviceTask = Task.Run(async () =>
|
var serviceTask = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
||||||
return await context.ServiceMasters.Where(s => serviceIds.Contains(s.Id) && s.TenantId == tenantId && s.IsActive).ToListAsync();
|
return await context.ServiceMasters.Where(s => serviceIds.Contains(s.Id) && s.TenantId == tenantId && s.IsActive).ToListAsync();
|
||||||
});
|
});
|
||||||
|
|
||||||
var statusTask = Task.Run(async () =>
|
var statusTask = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
||||||
@ -235,62 +247,181 @@ namespace Marco.Pms.Services.Service
|
|||||||
var services = serviceTask.Result;
|
var services = serviceTask.Result;
|
||||||
var status = statusTask.Result;
|
var status = statusTask.Result;
|
||||||
|
|
||||||
|
// Validate existence of critical foreign entities
|
||||||
if (client == null)
|
if (client == null)
|
||||||
{
|
{
|
||||||
return ApiResponse<object>.ErrorResponse("Client not found", "Client not found", 404);
|
_logger.LogWarning("Client with ID {ClientId} not found or inactive in tenant {TenantId}", model.ClientId, tenantId);
|
||||||
|
return ApiResponse<object>.ErrorResponse("Client Not Found", "Client not found or inactive.", 404);
|
||||||
}
|
}
|
||||||
if (status == null)
|
if (status == null)
|
||||||
{
|
{
|
||||||
return ApiResponse<object>.ErrorResponse("Project Status not found", "Project Status not found", 404);
|
_logger.LogWarning("Status with ID {StatusId} not found in tenant {TenantId}", model.StatusId, tenantId);
|
||||||
|
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();
|
// Map DTO to entity and enhance with auditing and tenant info
|
||||||
|
var serviceProject = _mapper.Map<ServiceProject>(model);
|
||||||
|
serviceProject.Id = Guid.NewGuid();
|
||||||
|
serviceProject.CreatedById = loggedInEmployee.Id;
|
||||||
|
serviceProject.CreatedAt = DateTime.UtcNow;
|
||||||
|
serviceProject.IsActive = true;
|
||||||
|
serviceProject.TenantId = tenantId;
|
||||||
|
|
||||||
|
// Create mappings only for active services validated by database
|
||||||
|
var projectServiceMappings = model.Services?
|
||||||
|
.Where(sdto => services.Any(s => s.Id == sdto.ServiceId) && sdto.IsActive)
|
||||||
|
.Select(sdto => new ServiceProjectServiceMapping
|
||||||
|
{
|
||||||
|
ServiceId = sdto.ServiceId,
|
||||||
|
ProjectId = serviceProject.Id,
|
||||||
|
TenantId = tenantId
|
||||||
|
}).ToList() ?? new List<ServiceProjectServiceMapping>();
|
||||||
|
|
||||||
|
// Add new project and its service mappings to context
|
||||||
|
_context.ServiceProjects.Add(serviceProject);
|
||||||
|
_context.ServiceProjectServiceMapping.AddRange(projectServiceMappings);
|
||||||
|
|
||||||
|
// Persist data
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
_logger.LogInfo("Service Project {ProjectId} created successfully for Tenant {TenantId} by Employee {EmployeeId}", serviceProject.Id, tenantId, loggedInEmployee.Id);
|
||||||
|
|
||||||
|
// Prepare view model to return, mapping related created entities
|
||||||
|
var serviceProjectVM = _mapper.Map<ServiceProjectVM>(serviceProject);
|
||||||
|
serviceProjectVM.Client = _mapper.Map<BasicOrganizationVm>(client);
|
||||||
|
serviceProjectVM.Status = status;
|
||||||
|
serviceProjectVM.Services = services.Select(s => _mapper.Map<ServiceMasterVM>(s)).ToList();
|
||||||
|
serviceProjectVM.CreatedBy = _mapper.Map<BasicEmployeeVM>(loggedInEmployee);
|
||||||
|
|
||||||
|
return ApiResponse<object>.SuccessResponse(serviceProjectVM, "Service project created successfully.", 201);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to create service project for tenant {TenantId} by employee {EmployeeId}", tenantId, loggedInEmployee.Id);
|
||||||
|
return ApiResponse<object>.ErrorResponse("Internal Server Error", "An error occurred while creating the service project. Please try again later.", 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates an existing active service project including its related services, and returns updated project details.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">The ID of the service project to update.</param>
|
||||||
|
/// <param name="model">DTO containing updated service project data and service list.</param>
|
||||||
|
/// <param name="loggedInEmployee">Employee performing the update (for audit/logging).</param>
|
||||||
|
/// <param name="tenantId">Tenant identifier for multi-tenant isolation.</param>
|
||||||
|
/// <returns>ApiResponse containing updated service project details or error info.</returns>
|
||||||
|
public async Task<ApiResponse<object>> UpdateServiceProjectAsync(Guid id, ServiceProjectDto model, Employee loggedInEmployee, Guid tenantId)
|
||||||
|
{
|
||||||
|
if (tenantId == Guid.Empty)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("UpdateServiceProjectAsync called with invalid tenant context by employee {EmployeeId}", loggedInEmployee.Id);
|
||||||
|
return ApiResponse<object>.ErrorResponse("Access Denied", "Invalid tenant context.", 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (model == null)
|
||||||
|
{
|
||||||
|
_logger.LogInfo("UpdateServiceProjectAsync called with null model by employee {EmployeeId}", loggedInEmployee.Id);
|
||||||
|
return ApiResponse<object>.ErrorResponse("Bad Request", "Input model cannot be null.", 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Extract all service IDs from input for validation
|
||||||
|
var serviceIds = model.Services?.Select(s => s.ServiceId).ToList() ?? new List<Guid>();
|
||||||
|
|
||||||
|
// Concurrently fetch related entities for validation
|
||||||
|
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;
|
||||||
|
|
||||||
|
// Validate client and status existence
|
||||||
|
if (client == null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Client with ID {ClientId} not found or inactive in tenant {TenantId}", model.ClientId, tenantId);
|
||||||
|
return ApiResponse<object>.ErrorResponse("Client Not Found", "Client not found or inactive.", 404);
|
||||||
|
}
|
||||||
|
if (status == null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Project status with ID {StatusId} not found in tenant {TenantId}", model.StatusId, tenantId);
|
||||||
|
return ApiResponse<object>.ErrorResponse("Project Status Not Found", "Project status not found.", 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch existing service project for update, ensuring active and tenant scope
|
||||||
|
var serviceProject = await _context.ServiceProjects
|
||||||
|
.Where(sp => sp.Id == id && sp.TenantId == tenantId && sp.IsActive)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
if (serviceProject == null)
|
if (serviceProject == null)
|
||||||
{
|
{
|
||||||
_logger.LogWarning("Attempt to update non-existent Service project with ID {ProjectId} by user {UserId}.", id, loggedInEmployee.Id);
|
_logger.LogWarning("Attempt to update non-existent service project with ID {ProjectId} by employee {EmployeeId}", id, loggedInEmployee.Id);
|
||||||
return ApiResponse<object>.ErrorResponse("Project not found.", $"No project found with ID {id}.", 404);
|
return ApiResponse<object>.ErrorResponse("Project Not Found", $"No active project found with ID {id}.", 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Map incoming DTO to the tracked entity
|
||||||
_mapper.Map(model, serviceProject);
|
_mapper.Map(model, serviceProject);
|
||||||
|
|
||||||
serviceProject.UpdatedAt = DateTime.UtcNow;
|
serviceProject.UpdatedAt = DateTime.UtcNow;
|
||||||
serviceProject.UpdatedById = loggedInEmployee.Id;
|
serviceProject.UpdatedById = loggedInEmployee.Id;
|
||||||
|
|
||||||
var serviceProjectServiceMappings = await _context.ServiceProjectServiceMapping
|
// Get existing service mappings for the project (no tracking as we perform add/remove explicitly)
|
||||||
|
var existingMappings = await _context.ServiceProjectServiceMapping
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Where(sps => sps.ProjectId == serviceProject.Id && sps.TenantId == tenantId)
|
.Where(sps => sps.ProjectId == serviceProject.Id && sps.TenantId == tenantId)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
var newMapping = new List<ServiceProjectServiceMapping>();
|
// Determine service mappings to add or remove based on input DTO state
|
||||||
var removedMapping = new List<ServiceProjectServiceMapping>();
|
var newMappings = new List<ServiceProjectServiceMapping>();
|
||||||
|
var removedMappings = new List<ServiceProjectServiceMapping>();
|
||||||
foreach (var dto in model.Services)
|
if (model.Services != null)
|
||||||
{
|
{
|
||||||
var serviceProjectServiceMapping = serviceProjectServiceMappings
|
foreach (var dto in model.Services)
|
||||||
.FirstOrDefault(sps => sps.ServiceId == dto.ServiceId);
|
{
|
||||||
|
var existingMapping = existingMappings.FirstOrDefault(sps => sps.ServiceId == dto.ServiceId);
|
||||||
|
|
||||||
if (dto.IsActive && serviceProjectServiceMapping == null)
|
if (dto.IsActive && existingMapping == null)
|
||||||
{
|
|
||||||
newMapping.Add(new ServiceProjectServiceMapping
|
|
||||||
{
|
{
|
||||||
Id = Guid.NewGuid(),
|
newMappings.Add(new ServiceProjectServiceMapping
|
||||||
ServiceId = dto.ServiceId,
|
{
|
||||||
ProjectId = serviceProject.Id,
|
Id = Guid.NewGuid(),
|
||||||
TenantId = tenantId,
|
ServiceId = dto.ServiceId,
|
||||||
});
|
ProjectId = serviceProject.Id,
|
||||||
}
|
TenantId = tenantId,
|
||||||
else if (!dto.IsActive && serviceProjectServiceMapping != null)
|
});
|
||||||
{
|
}
|
||||||
removedMapping.Add(serviceProjectServiceMapping);
|
else if (!dto.IsActive && existingMapping != null)
|
||||||
|
{
|
||||||
|
removedMappings.Add(existingMapping);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_context.ServiceProjectServiceMapping.AddRange(newMapping);
|
// Apply added and removed mappings
|
||||||
_context.ServiceProjectServiceMapping.RemoveRange(removedMapping);
|
if (newMappings.Any()) _context.ServiceProjectServiceMapping.AddRange(newMappings);
|
||||||
|
if (removedMappings.Any()) _context.ServiceProjectServiceMapping.RemoveRange(removedMappings);
|
||||||
|
|
||||||
|
// Persist all changes within a single transaction flow
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
// Reload updated project and related entities for comprehensive response
|
||||||
var serviceProjectTask = Task.Run(async () =>
|
var serviceProjectTask = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
||||||
@ -298,9 +429,9 @@ namespace Marco.Pms.Services.Service
|
|||||||
.Include(sp => sp.Client)
|
.Include(sp => sp.Client)
|
||||||
.Include(sp => sp.Status)
|
.Include(sp => sp.Status)
|
||||||
.Include(sp => sp.CreatedBy).ThenInclude(e => e!.JobRole)
|
.Include(sp => sp.CreatedBy).ThenInclude(e => e!.JobRole)
|
||||||
.Where(sp => sp.TenantId == tenantId && sp.IsActive).FirstOrDefaultAsync();
|
.Where(sp => sp.Id == id && sp.TenantId == tenantId && sp.IsActive)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
});
|
});
|
||||||
|
|
||||||
var servicesTask = Task.Run(async () =>
|
var servicesTask = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
||||||
@ -313,50 +444,67 @@ namespace Marco.Pms.Services.Service
|
|||||||
|
|
||||||
await Task.WhenAll(serviceProjectTask, servicesTask);
|
await Task.WhenAll(serviceProjectTask, servicesTask);
|
||||||
|
|
||||||
|
|
||||||
serviceProject = serviceProjectTask.Result;
|
serviceProject = serviceProjectTask.Result;
|
||||||
services = servicesTask.Result;
|
services = servicesTask.Result;
|
||||||
|
|
||||||
ServiceProjectVM serviceProjectVm = _mapper.Map<ServiceProjectVM>(serviceProject);
|
var projectVm = _mapper.Map<ServiceProjectVM>(serviceProject);
|
||||||
serviceProjectVm.Services = _mapper.Map<List<ServiceMasterVM>>(services);
|
projectVm.Services = _mapper.Map<List<ServiceMasterVM>>(services);
|
||||||
|
|
||||||
return ApiResponse<object>.SuccessResponse(serviceProjectVm, "Service Project updated successfully.", 200);
|
_logger.LogInfo("Service project {ProjectId} updated successfully by employee {EmployeeId}", id, loggedInEmployee.Id);
|
||||||
|
|
||||||
|
return ApiResponse<object>.SuccessResponse(projectVm, "Service project updated successfully.", 200);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "An unexpected error occurred in Updating Service Project for tenant {TenantId}.", tenantId);
|
_logger.LogError(ex, "Unexpected error updating service project {ProjectId} for tenant {TenantId} by employee {EmployeeId}", id, tenantId, loggedInEmployee.Id);
|
||||||
return ApiResponse<object>.ErrorResponse("An internal server error occurred. Please try again later.", null, 500);
|
return ApiResponse<object>.ErrorResponse("Internal Server Error", "An error occurred while updating the service project. Please try again later.", 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Activates or deactivates a service project based on the specified 'isActive' flag, scoped by tenant.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">The unique identifier of the service project to update.</param>
|
||||||
|
/// <param name="isActive">True to activate, false to deactivate the service project.</param>
|
||||||
|
/// <param name="loggedInEmployee">The employee performing the operation (for audit/logging).</param>
|
||||||
|
/// <param name="tenantId">Tenant identifier to ensure multi-tenant data isolation.</param>
|
||||||
|
/// <returns>ApiResponse indicating success or detail of failure.</returns>
|
||||||
public async Task<ApiResponse<object>> DeActivateServiceProjectAsync(Guid id, bool isActive, Employee loggedInEmployee, Guid tenantId)
|
public async Task<ApiResponse<object>> DeActivateServiceProjectAsync(Guid id, bool isActive, Employee loggedInEmployee, Guid tenantId)
|
||||||
{
|
{
|
||||||
|
if (tenantId == Guid.Empty)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("DeActivateServiceProjectAsync called with invalid tenant context by employee {EmployeeId}", loggedInEmployee.Id);
|
||||||
|
return ApiResponse<object>.ErrorResponse("Access Denied", "Invalid tenant context.", 403);
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// Load the service project scoped by tenant and ID
|
||||||
var serviceProject = await _context.ServiceProjects
|
var serviceProject = await _context.ServiceProjects
|
||||||
.Where(sp => sp.Id == id && sp.TenantId == tenantId).FirstOrDefaultAsync();
|
.FirstOrDefaultAsync(sp => sp.Id == id && sp.TenantId == tenantId);
|
||||||
|
|
||||||
if (serviceProject == null)
|
if (serviceProject == null)
|
||||||
{
|
{
|
||||||
_logger.LogWarning("Attempt to de-activate non-existent Service project with ID {ProjectId} by user {UserId}.", id, loggedInEmployee.Id);
|
_logger.LogWarning("Attempt to {(Action)} non-existent service project {ProjectId} by employee {EmployeeId} in tenant {TenantId}",
|
||||||
return ApiResponse<object>.ErrorResponse("Project not found.", $"No project found with ID {id}.", 404);
|
isActive ? "activate" : "deactivate", id, loggedInEmployee.Id, tenantId);
|
||||||
|
return ApiResponse<object>.ErrorResponse("Project Not Found", $"No project found with ID {id}.", 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update active status as requested by the client
|
||||||
serviceProject.IsActive = isActive;
|
serviceProject.IsActive = isActive;
|
||||||
|
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
_logger.LogInfo("Successfully de-activated service project {ProjectId}", id);
|
_logger.LogInfo("Service project {ProjectId} {(Action)}d successfully by employee {EmployeeId} in tenant {TenantId}",
|
||||||
return ApiResponse<object>.SuccessResponse(new { }, "Projects de-activated successfully.", 200);
|
id, isActive ? "activate" : "deactivate", loggedInEmployee.Id, tenantId);
|
||||||
|
|
||||||
|
return ApiResponse<object>.SuccessResponse(new { }, $"Project {(isActive ? "activated" : "deactivated")} successfully.", 200);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// --- Step 5: Graceful Error Handling ---
|
_logger.LogError(ex, "Error occurred while {(Action)} service project {ProjectId} by employee {EmployeeId} in tenant {TenantId}",
|
||||||
_logger.LogError(ex, "An unexpected error occurred in DeActivateServiceProject for tenant {TenantId}.", tenantId);
|
isActive ? "activating" : "deactivating", id, loggedInEmployee.Id, tenantId);
|
||||||
return ApiResponse<object>.ErrorResponse("An internal server error occurred. Please try again later.", null, 500);
|
return ApiResponse<object>.ErrorResponse("Internal Server Error", "An internal error occurred, please try again later.", 500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -430,7 +578,6 @@ namespace Marco.Pms.Services.Service
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Manages service project allocations by adding new active allocations and removing inactive ones.
|
/// Manages service project allocations by adding new active allocations and removing inactive ones.
|
||||||
/// Validates projects, employees, and team roles exist before applying changes.
|
/// Validates projects, employees, and team roles exist before applying changes.
|
||||||
@ -1367,6 +1514,7 @@ namespace Marco.Pms.Services.Service
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region =================================================================== Job Comments Functions ===================================================================
|
#region =================================================================== Job Comments Functions ===================================================================
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -1615,7 +1763,8 @@ namespace Marco.Pms.Services.Service
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
#region =================================================================== Pubic Helper Functions ===================================================================
|
|
||||||
|
#region =================================================================== Helper Functions ===================================================================
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves a job ticket by its unique identifier and associated tenant ID.
|
/// Retrieves a job ticket by its unique identifier and associated tenant ID.
|
||||||
@ -1652,7 +1801,6 @@ namespace Marco.Pms.Services.Service
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<JobStatusMapping?> GetJobStatusMappingAsync(Guid statusId, Guid nextStatusId, Guid projectId, Guid loggedInEmployeeId, Guid tenantId)
|
private async Task<JobStatusMapping?> GetJobStatusMappingAsync(Guid statusId, Guid nextStatusId, Guid projectId, Guid loggedInEmployeeId, Guid tenantId)
|
||||||
{
|
{
|
||||||
// Find transition mappings for the current status and desired status, considering team role if allocation exists
|
// Find transition mappings for the current status and desired status, considering team role if allocation exists
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user