diff --git a/Marco.Pms.Model/ViewModels/ServiceProject/JobTicketDetailsVM.cs b/Marco.Pms.Model/ViewModels/ServiceProject/JobTicketDetailsVM.cs index 1c77336..36eb7d9 100644 --- a/Marco.Pms.Model/ViewModels/ServiceProject/JobTicketDetailsVM.cs +++ b/Marco.Pms.Model/ViewModels/ServiceProject/JobTicketDetailsVM.cs @@ -16,6 +16,8 @@ namespace Marco.Pms.Model.ViewModels.ServiceProject public DateTime StartDate { get; set; } public DateTime DueDate { get; set; } public bool IsActive { get; set; } + public TAGGING_MARK_TYPE? TaggingAction { get; set; } + public TAGGING_MARK_TYPE? NextTaggingAction { get; set; } public DateTime CreatedAt { get; set; } public BasicEmployeeVM? CreatedBy { get; set; } public List? Tags { get; set; } diff --git a/Marco.Pms.Services/Service/ServiceProjectService.cs b/Marco.Pms.Services/Service/ServiceProjectService.cs index 4ae2ab8..7bf793f 100644 --- a/Marco.Pms.Services/Service/ServiceProjectService.cs +++ b/Marco.Pms.Services/Service/ServiceProjectService.cs @@ -956,6 +956,9 @@ namespace Marco.Pms.Services.Service await Task.WhenAll(assigneeTask, tagTask, updateLogTask); + var assignees = assigneeTask.Result; + var isAssigned = assignees.Any(e => e.Id == loggedInEmployee.Id); + // Map update logs with status descriptions var jobUpdateLogVMs = updateLogTask.Result.Select(ul => { @@ -972,7 +975,7 @@ namespace Marco.Pms.Services.Service }).ToList(); // Map assignees, and tags to their respective viewmodels - var assigneeVMs = _mapper.Map>(assigneeTask.Result); + var assigneeVMs = _mapper.Map>(assignees); var tagVMs = _mapper.Map>(tagTask.Result); // Map main job ticket DTO and attach related data @@ -981,6 +984,31 @@ namespace Marco.Pms.Services.Service response.Tags = tagVMs; response.UpdateLogs = jobUpdateLogVMs; + if (isAssigned) + { + // Fetch the most recent attendance record for the logged-in employee for the specified job + var jobAttendance = await _context.JobAttendance + .AsNoTracking() + .Where(ja => ja.JobTcketId == jobTicket.Id && ja.EmployeeId == loggedInEmployee.Id && ja.TenantId == tenantId) + .OrderByDescending(ja => ja.TaggedInTime) + .FirstOrDefaultAsync(); + + + // If no attendance record exists or last record is tagged out or for a different day, prepare a default response with next action TAG_IN + if (jobAttendance == null || (jobAttendance.TaggedOutTime.HasValue && jobAttendance.TaggedInTime.Date != DateTime.UtcNow.Date)) + { + response.NextTaggingAction = TAGGING_MARK_TYPE.TAG_IN; + _logger.LogInfo("No current active attendance found for EmployeeId: {EmployeeId}. Prompting to TAG_IN.", loggedInEmployee.Id); + } + else + { + // Active attendance exists + response.TaggingAction = jobAttendance.Action; + response.NextTaggingAction = jobAttendance.Action == TAGGING_MARK_TYPE.TAG_IN ? TAGGING_MARK_TYPE.TAG_OUT : TAGGING_MARK_TYPE.TAG_IN; + _logger.LogInfo("Latest attendance fetched for EmployeeId: {EmployeeId} on JobTicketId: {JobTicketId}", loggedInEmployee.Id, jobTicket.Id); + } + } + _logger.LogInfo("Job ticket details assembled successfully for JobTicketId: {JobTicketId}", id); return ApiResponse.SuccessResponse(response, "Job details fetched successfully", 200); @@ -1937,7 +1965,7 @@ namespace Marco.Pms.Services.Service var documentIds = deleteBillAttachments.Select(d => d.DocumentId!.Value).ToList(); try { - await DeleteAttachemnts(documentIds); + await DeleteJobAttachemnts(documentIds); _logger.LogInfo("{Count} attachments deleted for comment {CommentId} by employee {EmployeeId}", deleteBillAttachments.Count, id, loggedInEmployee.Id); } catch (DbUpdateException dbEx) @@ -2043,6 +2071,24 @@ namespace Marco.Pms.Services.Service return ApiResponse.ErrorResponse("Job not found", "Job is not found", 404); } + var jobEmployeeMapping = await _context.JobEmployeeMappings + .Where(jem => jem.AssigneeId == loggedInEmployee.Id && jem.JobTicketId == jobTicketId && jem.TenantId == tenantId) + .FirstOrDefaultAsync(); + + // Check if the job-to-employee mapping is null, indicating no assignment + if (jobEmployeeMapping == null) + { + // Log the error with relevant context for diagnostics + _logger.LogWarning("Tagging failed: Employee is not assigned to the job. JobId: {JobId}, EmployeeId: {EmployeeId}", jobTicketId, loggedInEmployee.Id); + + // Return a structured error response with meaningful message and HTTP 400 Bad Request code + return ApiResponse.ErrorResponse( + "Tagging operation failed because the employee is not assigned to this job.", + $"No job-employee mapping found for JobId: {jobTicketId} and EmployeeId: {loggedInEmployee.Id}.", + statusCode: 400); + } + + // Fetch the most recent attendance record for the logged-in employee for the specified job var jobAttendance = await _context.JobAttendance .AsNoTracking() @@ -2395,7 +2441,52 @@ namespace Marco.Pms.Services.Service #region =================================================================== Helper Functions =================================================================== - private async Task DeleteAttachemnts(List documentIds) + //private async Task DeleteTalkingPointAttachments(List documentIds) + //{ + // using var scope = _serviceScopeFactory.CreateScope(); + // var _updateLogHelper = scope.ServiceProvider.GetRequiredService(); + + // var attachmentTask = Task.Run(async () => + // { + // await using var dbContext = await _dbContextFactory.CreateDbContextAsync(); + // var attachments = await dbContext.TalkingPointAttachments.AsNoTracking().Where(ba => documentIds.Contains(ba.DocumentId)).ToListAsync(); + + // dbContext.TalkingPointAttachments.RemoveRange(attachments); + // await dbContext.SaveChangesAsync(); + // }); + // var documentsTask = Task.Run(async () => + // { + // await using var dbContext = await _dbContextFactory.CreateDbContextAsync(); + // var documents = await dbContext.Documents.AsNoTracking().Where(ba => documentIds.Contains(ba.Id)).ToListAsync(); + + // if (documents.Any()) + // { + // dbContext.Documents.RemoveRange(documents); + // await dbContext.SaveChangesAsync(); + + // List deletionObject = new List(); + // foreach (var document in documents) + // { + // deletionObject.Add(new S3DeletionObject + // { + // Key = document.S3Key + // }); + // if (!string.IsNullOrWhiteSpace(document.ThumbS3Key) && document.ThumbS3Key != document.S3Key) + // { + // deletionObject.Add(new S3DeletionObject + // { + // Key = document.ThumbS3Key + // }); + // } + // } + // await _updateLogHelper.PushToS3DeletionAsync(deletionObject); + // } + // }); + + // await Task.WhenAll(attachmentTask, documentsTask); + //} + + private async Task DeleteJobAttachemnts(List documentIds) { using var scope = _serviceScopeFactory.CreateScope(); var _updateLogHelper = scope.ServiceProvider.GetRequiredService(); @@ -2514,6 +2605,7 @@ namespace Marco.Pms.Services.Service var jobStatusMapping = await statusMappingQuery.FirstOrDefaultAsync(); return jobStatusMapping; } + #endregion } }