Optimized the Project Allocation API
This commit is contained in:
parent
72dccc0c6a
commit
168922c278
@ -97,7 +97,7 @@ namespace Marco.Pms.CacheHelper
|
|||||||
|
|
||||||
var result = await _collection.UpdateOneAsync(filter, update);
|
var result = await _collection.UpdateOneAsync(filter, update);
|
||||||
|
|
||||||
if (result.MatchedCount == 0)
|
if (result.ModifiedCount == 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
13
Marco.Pms.Model/ViewModels/Projects/ProjectAllocationVM.cs
Normal file
13
Marco.Pms.Model/ViewModels/Projects/ProjectAllocationVM.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
namespace Marco.Pms.Model.ViewModels.Projects
|
||||||
|
{
|
||||||
|
public class ProjectAllocationVM
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public Guid EmployeeId { get; set; }
|
||||||
|
public Guid? JobRoleId { get; set; }
|
||||||
|
public bool IsActive { get; set; } = true;
|
||||||
|
public Guid ProjectId { get; set; }
|
||||||
|
public DateTime AllocationDate { get; set; }
|
||||||
|
public DateTime? ReAllocationDate { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -252,28 +252,31 @@ namespace MarcoBMS.Services.Controllers
|
|||||||
return StatusCode(response.StatusCode, response);
|
return StatusCode(response.StatusCode, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
//[HttpPost("allocation")]
|
[HttpPost("allocation")]
|
||||||
//public async Task<IActionResult> ManageAllocation(List<ProjectAllocationDot> projectAllocationDot)
|
public async Task<IActionResult> ManageAllocation([FromBody] List<ProjectAllocationDot> projectAllocationDot)
|
||||||
//{
|
{
|
||||||
// // --- Step 1: Input Validation ---
|
// --- Step 1: Input Validation ---
|
||||||
// if (!ModelState.IsValid)
|
if (!ModelState.IsValid)
|
||||||
// {
|
{
|
||||||
// var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage).ToList();
|
var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage).ToList();
|
||||||
// _logger.LogWarning("Update project called with invalid model state for ID {ProjectId}. Errors: {Errors}", id, string.Join(", ", errors));
|
_logger.LogWarning("project Alocation called with invalid model state for list of projects. Errors: {Errors}", string.Join(", ", errors));
|
||||||
// return BadRequest(ApiResponse<object>.ErrorResponse("Invalid request data provided.", errors, 400));
|
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid request data provided.", errors, 400));
|
||||||
// }
|
}
|
||||||
|
|
||||||
// // --- Step 2: Prepare data without I/O ---
|
// --- Step 2: Prepare data without I/O ---
|
||||||
// Employee loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
Employee loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||||
// var response = await _projectServices.UpdateProjectAsync(id, updateProjectDto, tenantId, loggedInEmployee);
|
var response = await _projectServices.ManageAllocationAsync(projectAllocationDot, tenantId, loggedInEmployee);
|
||||||
// if (response.Success)
|
if (response.Success)
|
||||||
// {
|
{
|
||||||
// var notification = new { LoggedInUserId = loggedInEmployee.Id, Keyword = "Assign_Project", ProjectIds = projectIds, EmployeeList = employeeIds };
|
List<Guid> employeeIds = response.Data.Select(pa => pa.EmployeeId).ToList();
|
||||||
// await _signalR.SendNotificationAsync(notification);
|
List<Guid> projectIds = response.Data.Select(pa => pa.ProjectId).ToList();
|
||||||
// }
|
|
||||||
// return StatusCode(response.StatusCode, response);
|
|
||||||
|
|
||||||
//}
|
var notification = new { LoggedInUserId = loggedInEmployee.Id, Keyword = "Assign_Project", ProjectIds = projectIds, EmployeeList = employeeIds };
|
||||||
|
await _signalR.SendNotificationAsync(notification);
|
||||||
|
}
|
||||||
|
return StatusCode(response.StatusCode, response);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
[HttpGet("assigned-projects/{employeeId}")]
|
[HttpGet("assigned-projects/{employeeId}")]
|
||||||
public async Task<IActionResult> GetProjectsByEmployee([FromRoute] Guid employeeId)
|
public async Task<IActionResult> GetProjectsByEmployee([FromRoute] Guid employeeId)
|
||||||
|
@ -43,6 +43,12 @@ namespace Marco.Pms.Services.MappingProfiles
|
|||||||
CreateMap<StatusMasterMongoDB, StatusMaster>();
|
CreateMap<StatusMasterMongoDB, StatusMaster>();
|
||||||
CreateMap<ProjectVM, Project>();
|
CreateMap<ProjectVM, Project>();
|
||||||
CreateMap<CreateProjectDto, Project>();
|
CreateMap<CreateProjectDto, Project>();
|
||||||
|
CreateMap<ProjectAllocationDot, ProjectAllocation>()
|
||||||
|
.ForMember(
|
||||||
|
dest => dest.EmployeeId,
|
||||||
|
// Explicitly and safely convert string ProjectStatusId to Guid ProjectStatusId
|
||||||
|
opt => opt.MapFrom(src => src.EmpID));
|
||||||
|
CreateMap<ProjectAllocation, ProjectAllocationVM>();
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region ======================================================= Projects =======================================================
|
#region ======================================================= Projects =======================================================
|
||||||
|
@ -609,85 +609,112 @@ namespace Marco.Pms.Services.Service
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//public async Task<ApiResponse<object>> ManageAllocation(List<ProjectAllocationDot> projectAllocationDot, Guid tenantId, Employee loggedInEmployee)
|
/// <summary>
|
||||||
//{
|
/// Manages project allocations for a list of employees, either adding new allocations or deactivating existing ones.
|
||||||
// if (projectAllocationDot != null)
|
/// This method is optimized to perform all database operations in a single transaction.
|
||||||
// {
|
/// </summary>
|
||||||
// List<object>? result = new List<object>();
|
/// <param name="allocationsDto">The list of allocation changes to process.</param>
|
||||||
// List<Guid> employeeIds = new List<Guid>();
|
/// <param name="tenantId">The ID of the current tenant.</param>
|
||||||
// List<Guid> projectIds = new List<Guid>();
|
/// <param name="loggedInEmployee">The current authenticated employee for permission checks.</param>
|
||||||
|
/// <returns>An ApiResponse containing the list of processed allocations.</returns>
|
||||||
|
public async Task<ApiResponse<List<ProjectAllocationVM>>> ManageAllocationAsync(List<ProjectAllocationDot> allocationsDto, Guid tenantId, Employee loggedInEmployee)
|
||||||
|
{
|
||||||
|
// --- Step 1: Input Validation ---
|
||||||
|
if (allocationsDto == null || !allocationsDto.Any())
|
||||||
|
{
|
||||||
|
return ApiResponse<List<ProjectAllocationVM>>.ErrorResponse("Invalid details.", "Allocation details list cannot be null or empty.", 400);
|
||||||
|
}
|
||||||
|
|
||||||
// foreach (var item in projectAllocationDot)
|
_logger.LogInfo("Starting to manage {AllocationCount} allocations for user {UserId}.", allocationsDto.Count, loggedInEmployee.Id);
|
||||||
// {
|
|
||||||
// try
|
|
||||||
// {
|
|
||||||
// //ProjectAllocation projectAllocation = item.ToProjectAllocationFromProjectAllocationDto(tenantId);
|
|
||||||
// ProjectAllocation projectAllocation = item.ToProjectAllocationFromProjectAllocationDto(tenantId);
|
|
||||||
// ProjectAllocation? projectAllocationFromDb = await _context.ProjectAllocations.Where(c => c.EmployeeId == projectAllocation.EmployeeId
|
|
||||||
// && c.ProjectId == projectAllocation.ProjectId
|
|
||||||
// && c.ReAllocationDate == null
|
|
||||||
// && c.TenantId == tenantId).SingleOrDefaultAsync();
|
|
||||||
|
|
||||||
// if (projectAllocationFromDb != null)
|
// --- (Placeholder) Security Check ---
|
||||||
// {
|
// In a real application, you would check if the loggedInEmployee has permission
|
||||||
// _context.ProjectAllocations.Attach(projectAllocationFromDb);
|
// to manage allocations for ALL projects involved in this batch.
|
||||||
|
var projectIdsInBatch = allocationsDto.Select(a => a.ProjectId).Distinct().ToList();
|
||||||
|
var hasPermission = await _permission.HasPermission(PermissionsMaster.ManageTeam, loggedInEmployee.Id);
|
||||||
|
if (!hasPermission)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Access DENIED for user {UserId} trying to manage allocations for projects.", loggedInEmployee.Id);
|
||||||
|
return ApiResponse<List<ProjectAllocationVM>>.ErrorResponse("Access Denied.", "You do not have permission to manage one or more projects in this request.", 403);
|
||||||
|
}
|
||||||
|
|
||||||
// if (item.Status)
|
// --- Step 2: Fetch all relevant existing data in ONE database call ---
|
||||||
// {
|
var employeeProjectPairs = allocationsDto.Select(a => new { a.EmpID, a.ProjectId }).ToList();
|
||||||
// projectAllocationFromDb.JobRoleId = projectAllocation.JobRoleId; ;
|
List<Guid> employeeIds = allocationsDto.Select(a => a.EmpID).Distinct().ToList();
|
||||||
// projectAllocationFromDb.IsActive = true;
|
|
||||||
// _context.Entry(projectAllocationFromDb).Property(e => e.JobRoleId).IsModified = true;
|
|
||||||
// _context.Entry(projectAllocationFromDb).Property(e => e.IsActive).IsModified = true;
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// projectAllocationFromDb.ReAllocationDate = DateTime.Now;
|
|
||||||
// projectAllocationFromDb.IsActive = false;
|
|
||||||
// _context.Entry(projectAllocationFromDb).Property(e => e.ReAllocationDate).IsModified = true;
|
|
||||||
// _context.Entry(projectAllocationFromDb).Property(e => e.IsActive).IsModified = true;
|
|
||||||
|
|
||||||
// employeeIds.Add(projectAllocation.EmployeeId);
|
// Fetch all currently active allocations for the employees and projects in this batch.
|
||||||
// projectIds.Add(projectAllocation.ProjectId);
|
// We use a dictionary for fast O(1) lookups inside the loop.
|
||||||
// }
|
var existingAllocations = await _context.ProjectAllocations
|
||||||
// await _context.SaveChangesAsync();
|
.Where(pa => pa.TenantId == tenantId &&
|
||||||
// var result1 = new
|
employeeIds.Contains(pa.EmployeeId) &&
|
||||||
// {
|
pa.ReAllocationDate == null)
|
||||||
// Id = projectAllocationFromDb.Id,
|
.ToDictionaryAsync(pa => (pa.EmployeeId, pa.ProjectId));
|
||||||
// EmployeeId = projectAllocation.EmployeeId,
|
|
||||||
// JobRoleId = projectAllocation.JobRoleId,
|
|
||||||
// IsActive = projectAllocation.IsActive,
|
|
||||||
// ProjectId = projectAllocation.ProjectId,
|
|
||||||
// AllocationDate = projectAllocation.AllocationDate,
|
|
||||||
// ReAllocationDate = projectAllocation.ReAllocationDate,
|
|
||||||
// TenantId = projectAllocation.TenantId
|
|
||||||
// };
|
|
||||||
// result.Add(result1);
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// projectAllocation.AllocationDate = DateTime.Now;
|
|
||||||
// projectAllocation.IsActive = true;
|
|
||||||
// _context.ProjectAllocations.Add(projectAllocation);
|
|
||||||
// await _context.SaveChangesAsync();
|
|
||||||
|
|
||||||
// employeeIds.Add(projectAllocation.EmployeeId);
|
var processedAllocations = new List<ProjectAllocation>();
|
||||||
// projectIds.Add(projectAllocation.ProjectId);
|
|
||||||
// }
|
|
||||||
// await _cache.ClearAllProjectIds(item.EmpID);
|
|
||||||
|
|
||||||
// }
|
// --- Step 3: Process logic IN MEMORY ---
|
||||||
// catch (Exception ex)
|
foreach (var dto in allocationsDto)
|
||||||
// {
|
{
|
||||||
// return ApiResponse<object>.ErrorResponse(ex.Message, ex, 400);
|
var key = (dto.EmpID, dto.ProjectId);
|
||||||
// }
|
existingAllocations.TryGetValue(key, out var existingAllocation);
|
||||||
// }
|
|
||||||
|
|
||||||
// return ApiResponse<object>.SuccessResponse(result, "Data saved successfully", 200);
|
if (dto.Status == false) // User wants to DEACTIVATE the allocation
|
||||||
|
{
|
||||||
|
if (existingAllocation != null)
|
||||||
|
{
|
||||||
|
// Mark the existing allocation for deactivation
|
||||||
|
existingAllocation.ReAllocationDate = DateTime.UtcNow; // Use UtcNow for servers
|
||||||
|
existingAllocation.IsActive = false;
|
||||||
|
_context.ProjectAllocations.Update(existingAllocation);
|
||||||
|
processedAllocations.Add(existingAllocation);
|
||||||
|
}
|
||||||
|
// If it doesn't exist, we do nothing. The desired state is "not allocated".
|
||||||
|
}
|
||||||
|
else // User wants to ACTIVATE the allocation
|
||||||
|
{
|
||||||
|
if (existingAllocation == null)
|
||||||
|
{
|
||||||
|
// Create a new allocation because one doesn't exist
|
||||||
|
var newAllocation = _mapper.Map<ProjectAllocation>(dto);
|
||||||
|
newAllocation.TenantId = tenantId;
|
||||||
|
newAllocation.AllocationDate = DateTime.UtcNow;
|
||||||
|
newAllocation.IsActive = true;
|
||||||
|
_context.ProjectAllocations.Add(newAllocation);
|
||||||
|
processedAllocations.Add(newAllocation);
|
||||||
|
}
|
||||||
|
// If it already exists and is active, we do nothing. The state is already correct.
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _cache.ClearAllProjectIds(dto.EmpID);
|
||||||
|
_logger.LogInfo("Successfully completed cache invalidation for employee {EmployeeId}.", dto.EmpID);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Log the error but don't fail the entire request, as the primary DB operation succeeded.
|
||||||
|
_logger.LogError(ex, "Cache invalidation failed for employees after a successful database update.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// }
|
try
|
||||||
// return ApiResponse<object>.ErrorResponse("Invalid details.", "Work Item Details are not valid.", 400);
|
{
|
||||||
|
// --- Step 4: Save all changes in a SINGLE TRANSACTION ---
|
||||||
|
// All Adds and Updates are sent to the database in one batch.
|
||||||
|
// If any part fails, the entire transaction is rolled back.
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
_logger.LogInfo("Successfully saved {ChangeCount} allocation changes to the database.", processedAllocations.Count);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to save allocation changes to the database.");
|
||||||
|
return ApiResponse<List<ProjectAllocationVM>>.ErrorResponse("Database Error.", "An error occurred while saving the changes.", 500);
|
||||||
|
}
|
||||||
|
|
||||||
//}
|
|
||||||
|
// --- Step 5: Map results and return success ---
|
||||||
|
var resultVm = _mapper.Map<List<ProjectAllocationVM>>(processedAllocations);
|
||||||
|
return ApiResponse<List<ProjectAllocationVM>>.SuccessResponse(resultVm, "Allocations managed successfully.", 200);
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using Marco.Pms.Model.Dtos.Project;
|
using Marco.Pms.Model.Dtos.Project;
|
||||||
using Marco.Pms.Model.Employees;
|
using Marco.Pms.Model.Employees;
|
||||||
using Marco.Pms.Model.Utilities;
|
using Marco.Pms.Model.Utilities;
|
||||||
|
using Marco.Pms.Model.ViewModels.Projects;
|
||||||
|
|
||||||
namespace Marco.Pms.Services.Service.ServiceInterfaces
|
namespace Marco.Pms.Services.Service.ServiceInterfaces
|
||||||
{
|
{
|
||||||
@ -15,5 +16,6 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces
|
|||||||
Task<ApiResponse<object>> UpdateProjectAsync(Guid id, UpdateProjectDto updateProjectDto, Guid tenantId, Employee loggedInEmployee);
|
Task<ApiResponse<object>> UpdateProjectAsync(Guid id, UpdateProjectDto updateProjectDto, Guid tenantId, Employee loggedInEmployee);
|
||||||
Task<ApiResponse<object>> GetEmployeeByProjectIdAsync(Guid? projectId, bool includeInactive, Guid tenantId, Employee loggedInEmployee);
|
Task<ApiResponse<object>> GetEmployeeByProjectIdAsync(Guid? projectId, bool includeInactive, Guid tenantId, Employee loggedInEmployee);
|
||||||
Task<ApiResponse<object>> GetProjectAllocationAsync(Guid? projectId, Guid tenantId, Employee loggedInEmployee);
|
Task<ApiResponse<object>> GetProjectAllocationAsync(Guid? projectId, Guid tenantId, Employee loggedInEmployee);
|
||||||
|
Task<ApiResponse<List<ProjectAllocationVM>>> ManageAllocationAsync(List<ProjectAllocationDot> projectAllocationDots, Guid tenantId, Employee loggedInEmployee);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user