diff --git a/Marco.Pms.Services/Controllers/ServiceProjectController.cs b/Marco.Pms.Services/Controllers/ServiceProjectController.cs index 7ec2216..e6f96f8 100644 --- a/Marco.Pms.Services/Controllers/ServiceProjectController.cs +++ b/Marco.Pms.Services/Controllers/ServiceProjectController.cs @@ -98,11 +98,11 @@ namespace Marco.Pms.Services.Controllers #region =================================================================== Service Project Allocation Functions =================================================================== - [HttpPost("get/allocation/list")] - public async Task GetServiceProjectAllocationList([FromQuery] Guid? projectId, [FromQuery] Guid? employeeId) + [HttpGet("get/allocation/list")] + public async Task GetServiceProjectAllocationList([FromQuery] Guid? projectId, [FromQuery] Guid? employeeId, [FromQuery] bool isActive = true) { Employee loggedInEmploee = await _userHelper.GetCurrentEmployeeAsync(); - var response = await _serviceProject.GetServiceProjectAllocationListAsync(projectId, employeeId, loggedInEmploee, tenantId); + var response = await _serviceProject.GetServiceProjectAllocationListAsync(projectId, employeeId, true, loggedInEmploee, tenantId); return StatusCode(response.StatusCode, response); } diff --git a/Marco.Pms.Services/Service/ServiceInterfaces/IServiceProject.cs b/Marco.Pms.Services/Service/ServiceInterfaces/IServiceProject.cs index bd40b70..d48b558 100644 --- a/Marco.Pms.Services/Service/ServiceInterfaces/IServiceProject.cs +++ b/Marco.Pms.Services/Service/ServiceInterfaces/IServiceProject.cs @@ -16,7 +16,7 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces #endregion #region =================================================================== Service Project Allocation Functions =================================================================== - Task> GetServiceProjectAllocationListAsync(Guid? projectId, Guid? employeeId, Employee loggedInEmployee, Guid tenantId); + Task> GetServiceProjectAllocationListAsync(Guid? projectId, Guid? employeeId, bool isActive, Employee loggedInEmployee, Guid tenantId); Task> ManageServiceProjectAllocationAsync(List model, Employee loggedInEmployee, Guid tenantId); #endregion #region =================================================================== Job Tickets Functions =================================================================== diff --git a/Marco.Pms.Services/Service/ServiceProjectService.cs b/Marco.Pms.Services/Service/ServiceProjectService.cs index 77365b5..045f564 100644 --- a/Marco.Pms.Services/Service/ServiceProjectService.cs +++ b/Marco.Pms.Services/Service/ServiceProjectService.cs @@ -363,10 +363,74 @@ namespace Marco.Pms.Services.Service #endregion #region =================================================================== Service Project Allocation Functions =================================================================== - public async Task> GetServiceProjectAllocationListAsync(Guid? projectId, Guid? employeeId, Employee loggedInEmployee, Guid tenantId) + + /// + /// Retrieves a list of service project allocations filtered by project ID or employee ID and active status within the tenant context. + /// + /// Optional project ID filter. + /// Optional employee ID filter. + /// Filter for active/inactive allocations. + /// Employee making the request (for audit/logging). + /// Tenant identifier for multi-tenant data isolation. + /// ApiResponse with the list of matching service project allocations or error details. + public async Task> GetServiceProjectAllocationListAsync(Guid? projectId, Guid? employeeId, bool isActive, Employee loggedInEmployee, Guid tenantId) { - return ApiResponse.SuccessResponse(projectId, "Service project allocation fetched successfully", 200); + if (tenantId == Guid.Empty) + { + _logger.LogWarning("GetServiceProjectAllocationListAsync called with invalid tenant context by employee {EmployeeId}", loggedInEmployee.Id); + return ApiResponse.ErrorResponse("Access Denied", "Invalid tenant context.", 403); + } + + if (!projectId.HasValue && !employeeId.HasValue) + { + _logger.LogInfo("GetServiceProjectAllocationListAsync missing required filters by employee {EmployeeId}", loggedInEmployee.Id); + return ApiResponse.ErrorResponse("Bad Request", "Provide at least one of (ProjectId or EmployeeId).", 400); + } + + try + { + _logger.LogInfo("Fetching service project allocations for tenant {TenantId} by employee {EmployeeId} " + + "with filters - ProjectId: {ProjectId}, EmployeeId: {EmployeeId}, IsActive: {IsActive}", + tenantId, loggedInEmployee.Id, projectId ?? Guid.Empty, employeeId ?? Guid.Empty, isActive); + + // Base query with includes for related navigation properties + var allocationQuery = _context.ServiceProjectAllocations + .Include(spa => spa.Project) + .Include(spa => spa.TeamRole) + .Include(spa => spa.Employee).ThenInclude(e => e!.JobRole) + .Include(spa => spa.AssignedBy).ThenInclude(e => e!.JobRole) + .Include(spa => spa.ReAssignedBy).ThenInclude(e => e!.JobRole) + .Where(spa => spa.IsActive == isActive && spa.TenantId == tenantId); + + // Apply filtering by either project or employee (mutually exclusive) + if (projectId.HasValue) + { + allocationQuery = allocationQuery.Where(spa => spa.ProjectId == projectId.Value); + } + else if (employeeId.HasValue) + { + allocationQuery = allocationQuery.Where(spa => spa.EmployeeId == employeeId.Value); + } + + // Execute query and sort results by most recent assignment + var allocations = await allocationQuery + .OrderByDescending(spa => spa.AssignedAt) + .ToListAsync(); + + var response = _mapper.Map>(allocations); + + _logger.LogInfo("{Count} service project allocations fetched successfully for tenant {TenantId}", response.Count, tenantId); + + return ApiResponse.SuccessResponse(response, "Service project allocations fetched successfully", 200); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error fetching service project allocations for tenant {TenantId} by employee {EmployeeId}", tenantId, loggedInEmployee.Id); + return ApiResponse.ErrorResponse("Internal Server Error", "Failed to fetch service project allocations. Please try again later.", 500); + } } + + /// /// Manages service project allocations by adding new active allocations and removing inactive ones. /// Validates projects, employees, and team roles exist before applying changes. @@ -400,22 +464,22 @@ namespace Marco.Pms.Services.Service var projectTask = Task.Run(async () => { await using var context = await _dbContextFactory.CreateDbContextAsync(); - return await context.ServiceProjects.Where(sp => projectIds.Contains(sp.Id) && sp.TenantId == tenantId && sp.IsActive).ToListAsync(); + return await context.ServiceProjects.AsNoTracking().Where(sp => projectIds.Contains(sp.Id) && sp.TenantId == tenantId && sp.IsActive).ToListAsync(); }); var employeeTask = Task.Run(async () => { await using var context = await _dbContextFactory.CreateDbContextAsync(); - return await context.Employees.Where(e => employeeIds.Contains(e.Id) && e.IsActive).ToListAsync(); + return await context.Employees.AsNoTracking().Where(e => employeeIds.Contains(e.Id) && e.IsActive).ToListAsync(); }); var teamRoleTask = Task.Run(async () => { await using var context = await _dbContextFactory.CreateDbContextAsync(); - return await context.TeamRoleMasters.Where(trm => teamRoleIds.Contains(trm.Id)).ToListAsync(); + return await context.TeamRoleMasters.AsNoTracking().Where(trm => teamRoleIds.Contains(trm.Id)).ToListAsync(); }); var allocationTask = Task.Run(async () => { await using var context = await _dbContextFactory.CreateDbContextAsync(); - return await context.ServiceProjectAllocations.Where(spa => projectIds.Contains(spa.ProjectId) && spa.TenantId == tenantId && spa.IsActive).ToListAsync(); + return await context.ServiceProjectAllocations.AsNoTracking().Where(spa => projectIds.Contains(spa.ProjectId) && spa.TenantId == tenantId && spa.IsActive).ToListAsync(); }); await Task.WhenAll(projectTask, employeeTask, teamRoleTask, allocationTask); @@ -463,13 +527,16 @@ namespace Marco.Pms.Services.Service } else if (!dto.IsActive && existingAllocation != null) { + existingAllocation.IsActive = false; + existingAllocation.ReAssignedAt = DateTime.UtcNow; + existingAllocation.ReAssignedById = loggedInEmployee.Id; allocationsToRemove.Add(existingAllocation); } } // Batch changes for efficiency if (newAllocations.Any()) _context.ServiceProjectAllocations.AddRange(newAllocations); - if (allocationsToRemove.Any()) _context.ServiceProjectAllocations.RemoveRange(allocationsToRemove); + if (allocationsToRemove.Any()) _context.ServiceProjectAllocations.UpdateRange(allocationsToRemove); await _context.SaveChangesAsync();