Added an API to allocate employee to service project
This commit is contained in:
parent
fbb8a2261b
commit
62031046a1
@ -0,0 +1,10 @@
|
||||
namespace Marco.Pms.Model.Dtos.ServiceProject
|
||||
{
|
||||
public class ServiceProjectAllocationDto
|
||||
{
|
||||
public Guid ProjectId { get; set; }
|
||||
public Guid EmployeeId { get; set; }
|
||||
public Guid TeamRoleId { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
using Marco.Pms.Model.ServiceProject;
|
||||
using Marco.Pms.Model.ViewModels.Activities;
|
||||
|
||||
namespace Marco.Pms.Model.ViewModels.ServiceProject
|
||||
{
|
||||
public class ServiceProjectAllocationVM
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public BasicServiceProjectVM? Project { get; set; }
|
||||
public BasicEmployeeVM? Employee { get; set; }
|
||||
public TeamRoleMaster? TeamRole { get; set; }
|
||||
public bool IsActive { get; set; } = true;
|
||||
public DateTime AssignedAt { get; set; }
|
||||
public BasicEmployeeVM? AssignedBy { get; set; }
|
||||
public DateTime? ReAssignedAt { get; set; }
|
||||
public BasicEmployeeVM? ReAssignedBy { get; set; }
|
||||
}
|
||||
}
|
||||
@ -327,7 +327,7 @@ namespace MarcoBMS.Services.Controllers
|
||||
}
|
||||
|
||||
[HttpGet("basic")]
|
||||
public async Task<IActionResult> GetEmployeesByProjectBasic(Guid? projectId, [FromQuery] string? searchString, [FromQuery] bool allEmployee)
|
||||
public async Task<IActionResult> GetEmployeesByProjectBasic(Guid? projectId, [FromQuery] Guid? employeeId, [FromQuery] string? searchString, [FromQuery] bool allEmployee)
|
||||
{
|
||||
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var employeeQuery = _context.Employees.Where(e => e.IsActive);
|
||||
@ -352,6 +352,11 @@ namespace MarcoBMS.Services.Controllers
|
||||
employeeQuery = employeeQuery.Where(e => (e.FirstName + " " + e.LastName).ToLower().Contains(searchStringLower));
|
||||
}
|
||||
|
||||
if (employeeId.HasValue)
|
||||
{
|
||||
employeeQuery = employeeQuery.Where(e => e.Id == employeeId.Value);
|
||||
}
|
||||
|
||||
var query = employeeQuery.OrderBy(e => e.FirstName);
|
||||
|
||||
if (!allEmployee)
|
||||
|
||||
@ -96,6 +96,29 @@ namespace Marco.Pms.Services.Controllers
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region =================================================================== Service Project Allocation Functions ===================================================================
|
||||
|
||||
[HttpPost("get/allocation/list")]
|
||||
public async Task<IActionResult> GetServiceProjectAllocationList([FromQuery] Guid? projectId, [FromQuery] Guid? employeeId)
|
||||
{
|
||||
Employee loggedInEmploee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var response = await _serviceProject.GetServiceProjectAllocationListAsync(projectId, employeeId, loggedInEmploee, tenantId);
|
||||
return StatusCode(response.StatusCode, response);
|
||||
}
|
||||
|
||||
[HttpPost("manage/allocation")]
|
||||
public async Task<IActionResult> ManageServiceProjectAllocation([FromBody] List<ServiceProjectAllocationDto> model)
|
||||
{
|
||||
Employee loggedInEmploee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var response = await _serviceProject.ManageServiceProjectAllocationAsync(model, loggedInEmploee, tenantId);
|
||||
if (response.Success)
|
||||
{
|
||||
var notification = new { LoggedInUserId = loggedInEmploee.Id, Keyword = "Service_Project_Allocation", Response = response.Data };
|
||||
await _signalR.SendNotificationAsync(notification);
|
||||
}
|
||||
return StatusCode(response.StatusCode, response);
|
||||
}
|
||||
#endregion
|
||||
#region =================================================================== Job Tickets Functions ===================================================================
|
||||
|
||||
[HttpGet("job/list")]
|
||||
@ -116,15 +139,6 @@ namespace Marco.Pms.Services.Controllers
|
||||
return StatusCode(response.StatusCode, response);
|
||||
}
|
||||
|
||||
[HttpGet("job/comment/list")]
|
||||
public async Task<IActionResult> GetCommentListByJobTicket([FromQuery] Guid? jobTicketId, [FromQuery] int pageNumber = 1, [FromQuery] int pageSize = 20)
|
||||
{
|
||||
Employee loggedInEmploee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var response = await _serviceProject.GetCommentListByJobTicketAsync(jobTicketId, pageNumber, pageSize, loggedInEmploee, tenantId);
|
||||
|
||||
return StatusCode(response.StatusCode, response);
|
||||
}
|
||||
|
||||
[HttpGet("job/tag/list")]
|
||||
public async Task<IActionResult> GetJobTagList()
|
||||
{
|
||||
@ -135,7 +149,7 @@ namespace Marco.Pms.Services.Controllers
|
||||
}
|
||||
|
||||
[HttpPost("job/create")]
|
||||
public async Task<IActionResult> CreateJobTicket(CreateJobTicketDto model)
|
||||
public async Task<IActionResult> CreateJobTicket([FromBody] CreateJobTicketDto model)
|
||||
{
|
||||
Employee loggedInEmploee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var response = await _serviceProject.CreateJobTicketAsync(model, loggedInEmploee, tenantId);
|
||||
@ -148,7 +162,7 @@ namespace Marco.Pms.Services.Controllers
|
||||
}
|
||||
|
||||
[HttpPost("job/status-change")]
|
||||
public async Task<IActionResult> ChangeJobsStatus(ChangeJobStatusDto model)
|
||||
public async Task<IActionResult> ChangeJobsStatus([FromBody] ChangeJobStatusDto model)
|
||||
{
|
||||
Employee loggedInEmploee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var response = await _serviceProject.ChangeJobsStatusAsync(model, loggedInEmploee, tenantId);
|
||||
@ -161,7 +175,7 @@ namespace Marco.Pms.Services.Controllers
|
||||
}
|
||||
|
||||
[HttpPatch("job/edit/{id}")]
|
||||
public async Task<IActionResult> UpdateJobTicket(Guid id, JsonPatchDocument<UpdateJobTicketDto> patchDoc)
|
||||
public async Task<IActionResult> UpdateJobTicket(Guid id, [FromBody] JsonPatchDocument<UpdateJobTicketDto> patchDoc)
|
||||
{
|
||||
// Validate incoming patch document
|
||||
if (patchDoc == null)
|
||||
@ -204,9 +218,21 @@ namespace Marco.Pms.Services.Controllers
|
||||
return StatusCode(response.StatusCode, response);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region =================================================================== Job Comments Functions ===================================================================
|
||||
|
||||
[HttpGet("job/comment/list")]
|
||||
public async Task<IActionResult> GetCommentListByJobTicket([FromQuery] Guid? jobTicketId, [FromQuery] int pageNumber = 1, [FromQuery] int pageSize = 20)
|
||||
{
|
||||
Employee loggedInEmploee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var response = await _serviceProject.GetCommentListByJobTicketAsync(jobTicketId, pageNumber, pageSize, loggedInEmploee, tenantId);
|
||||
|
||||
return StatusCode(response.StatusCode, response);
|
||||
}
|
||||
|
||||
[HttpPost("job/add/comment")]
|
||||
public async Task<IActionResult> AddCommentToJobTicket(JobCommentDto model)
|
||||
public async Task<IActionResult> AddCommentToJobTicket([FromBody] JobCommentDto model)
|
||||
{
|
||||
Employee loggedInEmploee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var response = await _serviceProject.AddCommentToJobTicketAsync(model, loggedInEmploee, tenantId);
|
||||
@ -217,6 +243,7 @@ namespace Marco.Pms.Services.Controllers
|
||||
}
|
||||
return StatusCode(response.StatusCode, response);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@ -198,6 +198,7 @@ namespace Marco.Pms.Services.MappingProfiles
|
||||
CreateMap<ServiceProject, ServiceProjectVM>();
|
||||
CreateMap<ServiceProject, BasicServiceProjectVM>();
|
||||
CreateMap<ServiceProject, ServiceProjectDetailsVM>();
|
||||
CreateMap<ServiceProjectAllocation, ServiceProjectAllocationVM>();
|
||||
|
||||
#region ======================================================= Job Ticket =======================================================
|
||||
CreateMap<CreateJobTicketDto, JobTicket>();
|
||||
|
||||
@ -15,6 +15,10 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces
|
||||
Task<ApiResponse<object>> DeActivateServiceProjectAsync(Guid id, bool isActive, Employee loggedInEmployee, Guid tenantId);
|
||||
#endregion
|
||||
|
||||
#region =================================================================== Service Project Allocation Functions ===================================================================
|
||||
Task<ApiResponse<object>> GetServiceProjectAllocationListAsync(Guid? projectId, Guid? employeeId, Employee loggedInEmployee, Guid tenantId);
|
||||
Task<ApiResponse<object>> ManageServiceProjectAllocationAsync(List<ServiceProjectAllocationDto> model, Employee loggedInEmployee, Guid tenantId);
|
||||
#endregion
|
||||
#region =================================================================== Job Tickets Functions ===================================================================
|
||||
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);
|
||||
@ -24,6 +28,10 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces
|
||||
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>> AddCommentToJobTicketAsync(JobCommentDto model, Employee loggedInEmployee, Guid tenantId);
|
||||
|
||||
#endregion
|
||||
#region =================================================================== Job Comments Functions ===================================================================
|
||||
|
||||
#endregion
|
||||
|
||||
#region =================================================================== Pubic Helper Functions ===================================================================
|
||||
|
||||
@ -362,7 +362,145 @@ namespace Marco.Pms.Services.Service
|
||||
|
||||
#endregion
|
||||
|
||||
#region =================================================================== Expense Functions ===================================================================
|
||||
#region =================================================================== Service Project Allocation Functions ===================================================================
|
||||
public async Task<ApiResponse<object>> GetServiceProjectAllocationListAsync(Guid? projectId, Guid? employeeId, Employee loggedInEmployee, Guid tenantId)
|
||||
{
|
||||
return ApiResponse<object>.SuccessResponse(projectId, "Service project allocation fetched successfully", 200);
|
||||
}
|
||||
/// <summary>
|
||||
/// Manages service project allocations by adding new active allocations and removing inactive ones.
|
||||
/// Validates projects, employees, and team roles exist before applying changes.
|
||||
/// </summary>
|
||||
/// <param name="model">List of allocation DTOs specifying project, employee, team role, and active status.</param>
|
||||
/// <param name="loggedInEmployee">Employee performing the allocation management (for audit).</param>
|
||||
/// <param name="tenantId">Tenant identifier for multi-tenant data isolation.</param>
|
||||
/// <returns>ApiResponse containing the updated list of active allocations or error details.</returns>
|
||||
public async Task<ApiResponse<object>> ManageServiceProjectAllocationAsync(List<ServiceProjectAllocationDto> model, Employee loggedInEmployee, Guid tenantId)
|
||||
{
|
||||
if (tenantId == Guid.Empty)
|
||||
{
|
||||
_logger.LogWarning("ManageServiceProjectAllocationAsync called with invalid tenant context by employee {EmployeeId}", loggedInEmployee.Id);
|
||||
return ApiResponse<object>.ErrorResponse("Access Denied", "Invalid tenant context.", 403);
|
||||
}
|
||||
|
||||
if (model == null || !model.Any())
|
||||
{
|
||||
_logger.LogInfo("Empty allocation model provided by employee {EmployeeId}", loggedInEmployee.Id);
|
||||
return ApiResponse<object>.ErrorResponse("Bad Request", "No allocation data provided.", 400);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Extract distinct IDs from input for efficient bulk queries
|
||||
var projectIds = model.Select(spa => spa.ProjectId).Distinct().ToList();
|
||||
var employeeIds = model.Select(spa => spa.EmployeeId).Distinct().ToList();
|
||||
var teamRoleIds = model.Select(spa => spa.TeamRoleId).Distinct().ToList();
|
||||
|
||||
// Load required reference data concurrently using separate DbContexts
|
||||
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();
|
||||
});
|
||||
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();
|
||||
});
|
||||
var teamRoleTask = Task.Run(async () =>
|
||||
{
|
||||
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
||||
return await context.TeamRoleMasters.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();
|
||||
});
|
||||
|
||||
await Task.WhenAll(projectTask, employeeTask, teamRoleTask, allocationTask);
|
||||
|
||||
var projects = projectTask.Result;
|
||||
var employees = employeeTask.Result;
|
||||
var teamRoles = teamRoleTask.Result;
|
||||
var allocations = allocationTask.Result;
|
||||
|
||||
var newAllocations = new List<ServiceProjectAllocation>();
|
||||
var allocationsToRemove = new List<ServiceProjectAllocation>();
|
||||
|
||||
// Process each input allocation DTO
|
||||
foreach (var dto in model)
|
||||
{
|
||||
// Validate referenced entities exist
|
||||
var project = projects.FirstOrDefault(sp => sp.Id == dto.ProjectId);
|
||||
var employee = employees.FirstOrDefault(e => e.Id == dto.EmployeeId);
|
||||
var teamRole = teamRoles.FirstOrDefault(tr => tr.Id == dto.TeamRoleId);
|
||||
|
||||
if (project == null || employee == null || teamRole == null)
|
||||
{
|
||||
_logger.LogWarning("Skipping allocation with invalid references: ProjectId={ProjectId}, EmployeeId={EmployeeId}, TeamRoleId={TeamRoleId}", dto.ProjectId, dto.EmployeeId, dto.TeamRoleId);
|
||||
continue;
|
||||
}
|
||||
|
||||
var existingAllocation = allocations.FirstOrDefault(spa =>
|
||||
spa.ProjectId == dto.ProjectId &&
|
||||
spa.EmployeeId == dto.EmployeeId &&
|
||||
spa.TeamRoleId == dto.TeamRoleId);
|
||||
|
||||
if (dto.IsActive && existingAllocation == null)
|
||||
{
|
||||
newAllocations.Add(new ServiceProjectAllocation
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
ProjectId = dto.ProjectId,
|
||||
EmployeeId = dto.EmployeeId,
|
||||
TeamRoleId = dto.TeamRoleId,
|
||||
AssignedAt = DateTime.UtcNow,
|
||||
AssignedById = loggedInEmployee.Id,
|
||||
IsActive = true,
|
||||
TenantId = tenantId
|
||||
});
|
||||
}
|
||||
else if (!dto.IsActive && existingAllocation != null)
|
||||
{
|
||||
allocationsToRemove.Add(existingAllocation);
|
||||
}
|
||||
}
|
||||
|
||||
// Batch changes for efficiency
|
||||
if (newAllocations.Any()) _context.ServiceProjectAllocations.AddRange(newAllocations);
|
||||
if (allocationsToRemove.Any()) _context.ServiceProjectAllocations.RemoveRange(allocationsToRemove);
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
// Reload current active allocations for response with relevant navigation properties
|
||||
var updatedAllocations = await _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)
|
||||
.Where(spa =>
|
||||
projectIds.Contains(spa.ProjectId) &&
|
||||
employeeIds.Contains(spa.EmployeeId) &&
|
||||
teamRoleIds.Contains(spa.TeamRoleId) &&
|
||||
spa.IsActive &&
|
||||
spa.TenantId == tenantId)
|
||||
.ToListAsync();
|
||||
|
||||
var response = _mapper.Map<List<ServiceProjectAllocationVM>>(updatedAllocations);
|
||||
|
||||
_logger.LogInfo("Service project allocations managed successfully by employee {EmployeeId} for tenant {TenantId}. Added {AddedCount}, Removed {RemovedCount}",
|
||||
loggedInEmployee.Id, tenantId, newAllocations.Count, allocationsToRemove.Count);
|
||||
|
||||
return ApiResponse<object>.SuccessResponse(response, "Service project allocations managed successfully", 200);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error managing service project allocations by employee {EmployeeId} in tenant {TenantId}", loggedInEmployee.Id, tenantId);
|
||||
return ApiResponse<object>.ErrorResponse("Internal Server Error", "An error occurred while managing service project allocations. Please try again later.", 500);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region =================================================================== Job Tickets Functions ===================================================================
|
||||
@ -924,12 +1062,7 @@ namespace Marco.Pms.Services.Service
|
||||
/// <param name="loggedInEmployee">Employee performing the update (used for audit and authorization).</param>
|
||||
/// <param name="tenantId">Tenant identifier for data isolation.</param>
|
||||
/// <returns>ApiResponse containing updated job ticket data or error details.</returns>
|
||||
public async Task<ApiResponse<object>> UpdateJobTicketAsync(
|
||||
Guid id,
|
||||
JobTicket jobTicket,
|
||||
UpdateJobTicketDto model,
|
||||
Employee loggedInEmployee,
|
||||
Guid tenantId)
|
||||
public async Task<ApiResponse<object>> UpdateJobTicketAsync(Guid id, JobTicket jobTicket, UpdateJobTicketDto model, Employee loggedInEmployee, Guid tenantId)
|
||||
{
|
||||
// Validate tenant context early
|
||||
if (tenantId == Guid.Empty)
|
||||
@ -1166,7 +1299,6 @@ namespace Marco.Pms.Services.Service
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
#region =================================================================== Job Comments Functions ===================================================================
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user