Added an API to change the status of the job ticket

This commit is contained in:
ashutosh.nehete 2025-11-14 11:16:57 +05:30
parent 2f04339b4d
commit ed16c0f102
4 changed files with 141 additions and 0 deletions

View File

@ -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; }
}
}

View File

@ -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)
{

View File

@ -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
}

View File

@ -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 ===================================================================