diff --git a/Marco.Pms.Model/ViewModels/ServiceProject/JobAttendanceVM.cs b/Marco.Pms.Model/ViewModels/ServiceProject/JobAttendanceVM.cs index b22e861..2e555d7 100644 --- a/Marco.Pms.Model/ViewModels/ServiceProject/JobAttendanceVM.cs +++ b/Marco.Pms.Model/ViewModels/ServiceProject/JobAttendanceVM.cs @@ -6,14 +6,11 @@ namespace Marco.Pms.Model.ViewModels.ServiceProject public class JobAttendanceVM { public Guid Id { get; set; } - public Guid JobTcketId { get; set; } public BasicJobTicketVM? JobTicket { get; set; } - public TAGGING_MARK_TYPE Action { get; set; } + public TAGGING_MARK_TYPE? Action { get; set; } public TAGGING_MARK_TYPE? NextAction { get; set; } public BasicEmployeeVM? Employee { get; set; } - public DateTime TaggedInTime { get; set; } + public DateTime? TaggedInTime { get; set; } public DateTime? TaggedOutTime { get; set; } - public DateTime TaggedInAt { get; set; } - public DateTime? TaggedOutAt { get; set; } } } diff --git a/Marco.Pms.Services/Controllers/ServiceProjectController.cs b/Marco.Pms.Services/Controllers/ServiceProjectController.cs index 7f60839..7dda789 100644 --- a/Marco.Pms.Services/Controllers/ServiceProjectController.cs +++ b/Marco.Pms.Services/Controllers/ServiceProjectController.cs @@ -262,7 +262,16 @@ namespace Marco.Pms.Services.Controllers #region =================================================================== Job Tagging Functions =================================================================== - [HttpGet("job/attendance/team")] + [HttpGet("job/attendance/self/{jobTicketId}")] + public async Task GetAttendanceForSelf(Guid jobTicketId) + { + Employee loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + var response = await _serviceProject.GetAttendanceForSelfAsync(jobTicketId, loggedInEmployee, tenantId); + + return StatusCode(response.StatusCode, response); + } + + [HttpGet("job/attendance/team/history")] public async Task GetAttendanceForJobTeam([FromQuery] Guid jobTicketId, [FromQuery] DateTime? fromDate, [FromQuery] DateTime? toDate) { Employee loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); diff --git a/Marco.Pms.Services/Service/ServiceInterfaces/IServiceProject.cs b/Marco.Pms.Services/Service/ServiceInterfaces/IServiceProject.cs index 7d5a98e..ebd22c7 100644 --- a/Marco.Pms.Services/Service/ServiceInterfaces/IServiceProject.cs +++ b/Marco.Pms.Services/Service/ServiceInterfaces/IServiceProject.cs @@ -37,6 +37,7 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces #endregion #region =================================================================== Job Tagging Functions =================================================================== + Task> GetAttendanceForSelfAsync(Guid jobTicketId, Employee loggedInEmployee, Guid tenantId); Task> GetAttendanceForJobTeamAsync(Guid jobTicketId, DateTime? startDate, DateTime? endDate, Employee loggedInEmployee, Guid tenantId); Task> ManageJobTaggingAsync(JobAttendanceDto model, Employee loggedInEmployee, Guid tenantId); #endregion diff --git a/Marco.Pms.Services/Service/ServiceProjectService.cs b/Marco.Pms.Services/Service/ServiceProjectService.cs index 1faaad2..86945c4 100644 --- a/Marco.Pms.Services/Service/ServiceProjectService.cs +++ b/Marco.Pms.Services/Service/ServiceProjectService.cs @@ -2014,6 +2014,63 @@ namespace Marco.Pms.Services.Service #endregion #region =================================================================== Job Tagging Functions =================================================================== + public async Task> GetAttendanceForSelfAsync(Guid jobTicketId, Employee loggedInEmployee, Guid tenantId) + { + _logger.LogInfo("GetAttendanceForSelfAsync initiated for EmployeeId: {EmployeeId}, JobTicketId: {JobTicketId}", loggedInEmployee.Id, jobTicketId); + + try + { + // Validate existence of the Job Ticket with related Status + var jobTicket = await _context.JobTickets + .AsNoTracking() + .Include(jt => jt.Status) + .FirstOrDefaultAsync(jt => jt.Id == jobTicketId && jt.TenantId == tenantId && jt.IsActive); + + if (jobTicket == null) + { + _logger.LogWarning("JobTicket not found. JobTicketId: {JobTicketId}, TenantId: {TenantId}", jobTicketId, tenantId); + return ApiResponse.ErrorResponse("Job not found", "Job is not found", 404); + } + + // Fetch the most recent attendance record for the logged-in employee for the specified job + var jobAttendance = await _context.JobAttendance + .AsNoTracking() + .Include(ja => ja.JobTicket).ThenInclude(jt => jt!.Status) + .Include(ja => ja.Employee).ThenInclude(e => e!.JobRole) + .Where(ja => ja.JobTcketId == jobTicketId && ja.EmployeeId == loggedInEmployee.Id && ja.TenantId == tenantId) + .OrderByDescending(ja => ja.TaggedInTime) + .FirstOrDefaultAsync(); + + JobAttendanceVM response; + + // 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 = new JobAttendanceVM + { + JobTicket = _mapper.Map(jobTicket), + Employee = _mapper.Map(loggedInEmployee), + NextAction = 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, returning last attendance with details + response = _mapper.Map(jobAttendance); + _logger.LogInfo("Latest attendance fetched for EmployeeId: {EmployeeId} on JobTicketId: {JobTicketId}", loggedInEmployee.Id, jobTicketId); + } + + // Return success with the constructed response + return ApiResponse.SuccessResponse(response, "Latest job tagging for current employee fetched successfully", 200); + } + catch (Exception ex) + { + _logger.LogError(ex, "Unhandled exception in GetAttendanceForSelfAsync for EmployeeId: {EmployeeId}, JobTicketId: {JobTicketId}", loggedInEmployee.Id, jobTicketId); + return ApiResponse.ErrorResponse("An unexpected error occurred.", ex.Message, 500); + } + } + public async Task> GetAttendanceForJobTeamAsync(Guid jobTicketId, DateTime? startDate, DateTime? endDate, Employee loggedInEmployee, Guid tenantId) { _logger.LogInfo("GetAttendanceForJobTeamAsync called for JobTicketId: {JobTicketId}, TenantId: {TenantId}, EmployeeId: {EmployeeId}", jobTicketId, tenantId, loggedInEmployee.Id); @@ -2023,7 +2080,6 @@ namespace Marco.Pms.Services.Service // Validate the existence and active status of the job ticket including its status related data var jobTicket = await _context.JobTickets .AsNoTracking() - .Include(jt => jt.Status) .FirstOrDefaultAsync(jt => jt.Id == jobTicketId && jt.TenantId == tenantId && jt.IsActive); if (jobTicket == null) @@ -2039,8 +2095,8 @@ namespace Marco.Pms.Services.Service // Fetch attendance records within the date range for the specified job ticket and tenant var attendances = await _context.JobAttendance .AsNoTracking() - .Include(ja => ja.JobTicket).ThenInclude(jt => jt.Status) - .Include(ja => ja.Employee).ThenInclude(e => e.JobRole) + .Include(ja => ja.JobTicket).ThenInclude(jt => jt!.Status) + .Include(ja => ja.Employee).ThenInclude(e => e!.JobRole) .Where(ja => ja.JobTcketId == jobTicketId && ja.TaggedInTime.Date >= fromDate && ja.TaggedInTime.Date <= toDate @@ -2054,7 +2110,7 @@ namespace Marco.Pms.Services.Service // Determine if current attendance record is not the latest, if so clear NextAction var isNotLast = attendances.Any(attendance => attendance.TaggedInTime.Date > ja.TaggedInTime.Date); - if (isNotLast || (ja.TaggedOutTime.HasValue && ja.TaggedInTime.Date != DateTime.UtcNow.Date)) + if (isNotLast || (ja.TaggedOutTime.HasValue && ja.TaggedInTime.Date != DateTime.UtcNow.Date) || ja.EmployeeId != loggedInEmployee.Id) { result.NextAction = null; }