Added an API to change the status of the job ticket
This commit is contained in:
parent
2f04339b4d
commit
ed16c0f102
@ -0,0 +1,9 @@
|
||||
namespace Marco.Pms.Model.Dtos.ServiceProject
|
||||
{
|
||||
public class ChangeJobStatusDto
|
||||
{
|
||||
public required Guid JobTicketId { get; set; }
|
||||
public required Guid StatusId { get; set; }
|
||||
public required string Comment { get; set; }
|
||||
}
|
||||
}
|
||||
@ -143,6 +143,19 @@ namespace Marco.Pms.Services.Controllers
|
||||
return StatusCode(response.StatusCode, response);
|
||||
}
|
||||
|
||||
[HttpPost("job/status-change")]
|
||||
public async Task<IActionResult> ChangeJobsStatus(ChangeJobStatusDto model)
|
||||
{
|
||||
Employee loggedInEmploee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var response = await _serviceProject.ChangeJobsStatusAsync(model, loggedInEmploee, tenantId);
|
||||
if (response.Success)
|
||||
{
|
||||
var notification = new { LoggedInUserId = loggedInEmploee.Id, Keyword = "Job_Ticket", Response = response.Data };
|
||||
await _signalR.SendNotificationAsync(notification);
|
||||
}
|
||||
return StatusCode(response.StatusCode, response);
|
||||
}
|
||||
|
||||
[HttpPost("job/add/comment")]
|
||||
public async Task<IActionResult> AddCommentToJobTicket(JobCommentDto model)
|
||||
{
|
||||
|
||||
@ -21,6 +21,7 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces
|
||||
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>> CreateJobTicketAsync(CreateJobTicketDto model, Employee loggedInEmployee, Guid tenantId);
|
||||
Task<ApiResponse<object>> ChangeJobsStatusAsync(ChangeJobStatusDto model, Employee loggedInEmployee, Guid tenantId);
|
||||
Task<ApiResponse<object>> AddCommentToJobTicketAsync(JobCommentDto model, Employee loggedInEmployee, Guid tenantId);
|
||||
#endregion
|
||||
}
|
||||
|
||||
@ -828,6 +828,124 @@ namespace Marco.Pms.Services.Service
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the status of a specified job ticket, recording the change in a status update log.
|
||||
/// Ensures team-role-aware status transitions if applicable.
|
||||
/// </summary>
|
||||
/// <param name="model">DTO containing target status ID, job ticket ID, and optional comment.</param>
|
||||
/// <param name="loggedInEmployee">Employee performing the status change (for audit and permissions).</param>
|
||||
/// <param name="tenantId">Tenant context for multi-tenancy.</param>
|
||||
/// <returns>ApiResponse with updated job ticket or error info.</returns>
|
||||
public async Task<ApiResponse<object>> ChangeJobsStatusAsync(ChangeJobStatusDto model, Employee loggedInEmployee, Guid tenantId)
|
||||
{
|
||||
if (tenantId == Guid.Empty)
|
||||
{
|
||||
_logger.LogWarning("ChangeJobsStatusAsync: Invalid (empty) tenantId for employee {EmployeeId}", loggedInEmployee.Id);
|
||||
return ApiResponse<object>.ErrorResponse("Access Denied", "Invalid tenant context.", 403);
|
||||
}
|
||||
|
||||
if (model == null || model.JobTicketId == Guid.Empty || model.StatusId == Guid.Empty)
|
||||
{
|
||||
_logger.LogInfo("ChangeJobsStatusAsync: Invalid parameters submitted by employee {EmployeeId}", loggedInEmployee.Id);
|
||||
return ApiResponse<object>.ErrorResponse("Bad Request", "Job or status ID is missing.", 400);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_logger.LogInfo("Attempting to change status for job {JobTicketId} to {NewStatusId} by employee {EmployeeId}",
|
||||
model.JobTicketId, model.StatusId, loggedInEmployee.Id);
|
||||
|
||||
// Load the job ticket and key navigation properties
|
||||
var jobTicket = await _context.JobTickets
|
||||
.Include(jt => jt.Project)
|
||||
.Include(jt => jt.CreatedBy).ThenInclude(e => e!.JobRole)
|
||||
.FirstOrDefaultAsync(jt => jt.Id == model.JobTicketId && jt.TenantId == tenantId);
|
||||
|
||||
if (jobTicket == null)
|
||||
{
|
||||
_logger.LogWarning("Job ticket {JobTicketId} not found for status change in tenant {TenantId}", model.JobTicketId, tenantId);
|
||||
return ApiResponse<object>.ErrorResponse("Job Not Found", "Job ticket not found.", 404);
|
||||
}
|
||||
|
||||
// Find transition mappings for the current status and desired status, considering team role if allocation exists
|
||||
var statusMappingQuery = _context.JobStatusMappings
|
||||
.Include(jsm => jsm.Status)
|
||||
.Include(jsm => jsm.NextStatus)
|
||||
.Where(jsm =>
|
||||
jsm.StatusId == jobTicket.StatusId &&
|
||||
jsm.NextStatusId == model.StatusId &&
|
||||
jsm.Status != null &&
|
||||
jsm.NextStatus != null &&
|
||||
jsm.TenantId == tenantId);
|
||||
|
||||
// Find allocation for current employee (to determine team role for advanced mapping)
|
||||
var projectAllocation = await _context.ServiceProjectAllocations
|
||||
.Where(spa => spa.EmployeeId == loggedInEmployee.Id &&
|
||||
spa.ProjectId == jobTicket.ProjectId &&
|
||||
spa.TenantId == tenantId &&
|
||||
spa.IsActive)
|
||||
.OrderByDescending(spa => spa.AssignedAt)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
var teamRoleId = projectAllocation?.TeamRoleId;
|
||||
var hasTeamRoleMapping = projectAllocation != null
|
||||
&& await statusMappingQuery.AnyAsync(jsm => jsm.TeamRoleId == teamRoleId);
|
||||
|
||||
// Filter by team role or fallback to global (null team role)
|
||||
if (hasTeamRoleMapping)
|
||||
{
|
||||
statusMappingQuery = statusMappingQuery.Where(jsm => jsm.TeamRoleId == teamRoleId);
|
||||
}
|
||||
else
|
||||
{
|
||||
statusMappingQuery = statusMappingQuery.Where(jsm => jsm.TeamRoleId == null);
|
||||
}
|
||||
|
||||
var jobStatusMapping = await statusMappingQuery.FirstOrDefaultAsync();
|
||||
|
||||
if (jobStatusMapping == null)
|
||||
{
|
||||
_logger.LogWarning("Invalid status transition requested: current={CurrentStatusId}, desired={DesiredStatusId}, tenant={TenantId}",
|
||||
jobTicket.StatusId, model.StatusId, tenantId);
|
||||
return ApiResponse<object>.ErrorResponse("Invalid Status", "Selected status transition is not allowed.", 400);
|
||||
}
|
||||
|
||||
// Apply the new status and metadata
|
||||
jobTicket.StatusId = model.StatusId;
|
||||
jobTicket.UpdatedAt = DateTime.UtcNow;
|
||||
jobTicket.UpdatedById = loggedInEmployee.Id;
|
||||
|
||||
// Write status change log
|
||||
var updateLog = new StatusUpdateLog
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
EntityId = jobTicket.Id,
|
||||
StatusId = jobStatusMapping.StatusId,
|
||||
NextStatusId = jobStatusMapping.NextStatusId,
|
||||
Comment = model.Comment,
|
||||
UpdatedAt = DateTime.UtcNow,
|
||||
UpdatedById = loggedInEmployee.Id,
|
||||
TenantId = tenantId
|
||||
};
|
||||
_context.StatusUpdateLogs.Add(updateLog);
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
// Prepare response VM
|
||||
var responseVm = _mapper.Map<JobTicketVM>(jobTicket);
|
||||
responseVm.Status = jobStatusMapping.NextStatus;
|
||||
|
||||
_logger.LogInfo("Job {JobTicketId} status changed to {NewStatusId} by employee {EmployeeId}", jobTicket.Id, model.StatusId, loggedInEmployee.Id);
|
||||
|
||||
return ApiResponse<object>.SuccessResponse(responseVm, "Job status changed successfully.", 200);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Exception during ChangeJobsStatusAsync for job {JobTicketId} by employee {EmployeeId} in tenant {TenantId}", model.JobTicketId, loggedInEmployee.Id, tenantId);
|
||||
return ApiResponse<object>.ErrorResponse("Internal Server Error", "Failed to change job status. Please try again later.", 500);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
#region =================================================================== Job Comments Functions ===================================================================
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user