From bd14424062103017d9ba7493c9d3ad1a6805e21f Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Mon, 17 Nov 2025 13:06:24 +0530 Subject: [PATCH] Added an API to get logs of certain attendance --- .../ServiceProject/JobAttendanceLogVM.cs | 20 +++++ .../Controllers/ServiceProjectController.cs | 9 +++ .../MappingProfiles/MappingProfile.cs | 5 ++ .../ServiceInterfaces/IServiceProject.cs | 1 + .../Service/ServiceProjectService.cs | 80 +++++++++++++++++++ 5 files changed, 115 insertions(+) create mode 100644 Marco.Pms.Model/ViewModels/ServiceProject/JobAttendanceLogVM.cs diff --git a/Marco.Pms.Model/ViewModels/ServiceProject/JobAttendanceLogVM.cs b/Marco.Pms.Model/ViewModels/ServiceProject/JobAttendanceLogVM.cs new file mode 100644 index 0000000..7a0f32d --- /dev/null +++ b/Marco.Pms.Model/ViewModels/ServiceProject/JobAttendanceLogVM.cs @@ -0,0 +1,20 @@ +using Marco.Pms.Model.ServiceProject; +using Marco.Pms.Model.ViewModels.Activities; +using Marco.Pms.Model.ViewModels.DocumentManager; + +namespace Marco.Pms.Model.ViewModels.ServiceProject +{ + public class JobAttendanceLogVM + { + public Guid Id { get; set; } + public BasicJobTicketVM? JobTicket { get; set; } + public BasicDocumentVM? Document { get; set; } + public string? Latitude { get; set; } + public string? Longitude { get; set; } + public TAGGING_MARK_TYPE Action { get; set; } + public string? Comment { get; set; } + public BasicEmployeeVM? Employee { get; set; } + public DateTime MarkedTIme { get; set; } + public DateTime MarkedAt { get; set; } + } +} diff --git a/Marco.Pms.Services/Controllers/ServiceProjectController.cs b/Marco.Pms.Services/Controllers/ServiceProjectController.cs index 7dda789..2c538b5 100644 --- a/Marco.Pms.Services/Controllers/ServiceProjectController.cs +++ b/Marco.Pms.Services/Controllers/ServiceProjectController.cs @@ -271,6 +271,15 @@ namespace Marco.Pms.Services.Controllers return StatusCode(response.StatusCode, response); } + [HttpGet("job/attendance/log/{attendanceId}")] + public async Task GetAttendanceLogForAttendance(Guid attendanceId) + { + Employee loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + var response = await _serviceProject.GetAttendanceLogForAttendanceAsync(attendanceId, 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) { diff --git a/Marco.Pms.Services/MappingProfiles/MappingProfile.cs b/Marco.Pms.Services/MappingProfiles/MappingProfile.cs index a6f7ea2..d2a0f6d 100644 --- a/Marco.Pms.Services/MappingProfiles/MappingProfile.cs +++ b/Marco.Pms.Services/MappingProfiles/MappingProfile.cs @@ -212,6 +212,7 @@ namespace Marco.Pms.Services.MappingProfiles CreateMap(); + CreateMap(); CreateMap() .ForMember( dest => dest.NextAction, @@ -509,6 +510,10 @@ namespace Marco.Pms.Services.MappingProfiles #region ======================================================= Document ======================================================= CreateMap(); + CreateMap() + .ForMember( + dest => dest.DocumentId, + opt => opt.MapFrom(src => src.Id)); CreateMap() .ForMember( dest => dest.DocumentId, diff --git a/Marco.Pms.Services/Service/ServiceInterfaces/IServiceProject.cs b/Marco.Pms.Services/Service/ServiceInterfaces/IServiceProject.cs index ebd22c7..6e001d4 100644 --- a/Marco.Pms.Services/Service/ServiceInterfaces/IServiceProject.cs +++ b/Marco.Pms.Services/Service/ServiceInterfaces/IServiceProject.cs @@ -38,6 +38,7 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces #region =================================================================== Job Tagging Functions =================================================================== Task> GetAttendanceForSelfAsync(Guid jobTicketId, Employee loggedInEmployee, Guid tenantId); + Task> GetAttendanceLogForAttendanceAsync(Guid jobAttendanceId, 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 86945c4..aad3a8f 100644 --- a/Marco.Pms.Services/Service/ServiceProjectService.cs +++ b/Marco.Pms.Services/Service/ServiceProjectService.cs @@ -9,6 +9,7 @@ using Marco.Pms.Model.MongoDBModels.Utility; using Marco.Pms.Model.ServiceProject; using Marco.Pms.Model.Utilities; using Marco.Pms.Model.ViewModels.Activities; +using Marco.Pms.Model.ViewModels.AttendanceVM; using Marco.Pms.Model.ViewModels.DocumentManager; using Marco.Pms.Model.ViewModels.Master; using Marco.Pms.Model.ViewModels.Organization; @@ -2070,6 +2071,84 @@ namespace Marco.Pms.Services.Service return ApiResponse.ErrorResponse("An unexpected error occurred.", ex.Message, 500); } } + public async Task> GetAttendanceLogForAttendanceAsync(Guid jobAttendanceId, Employee loggedInEmployee, Guid tenantId) + { + _logger.LogInfo("GetAttendanceLogForAttendanceAsync called for JobAttendanceId: {JobAttendanceId}, TenantId: {TenantId}, EmployeeId: {EmployeeId}", jobAttendanceId, tenantId, loggedInEmployee.Id); + + try + { + // Validate existence of the JobAttendance record for the tenant + var jobAttendance = await _context.JobAttendance + .AsNoTracking() + .FirstOrDefaultAsync(ja => ja.Id == jobAttendanceId && ja.TenantId == tenantId); + + if (jobAttendance == null) + { + _logger.LogWarning("JobAttendance not found. JobAttendanceId: {JobAttendanceId}, TenantId: {TenantId}", jobAttendanceId, tenantId); + return ApiResponse.ErrorResponse("Job attendance not found", "Job attendance not found", 404); + } + + // Fetch related attendance logs including JobTicket status and Employee role details + var attendanceLogs = await _context.JobAttendanceLogs + .AsNoTracking() + .Include(jal => jal.JobTicket).ThenInclude(jt => jt!.Status) + .Include(jal => jal.Employee).ThenInclude(e => e!.JobRole) + .Where(jal => jal.JobAttendanceId == jobAttendanceId && jal.TenantId == tenantId) + .ToListAsync(); + + // If no logs found, return empty list with success message + if (!attendanceLogs.Any()) + { + _logger.LogInfo("No attendance logs found for JobAttendanceId: {JobAttendanceId}", jobAttendanceId); + return ApiResponse.SuccessResponse(new List(), "Job attendance log fetched successfully", 200); + } + + // Extract document IDs from logs that have attached documents + var documentIds = attendanceLogs.Where(log => log.DocumentId.HasValue) + .Select(log => log.DocumentId!.Value) + .Distinct() + .ToList(); + + // Fetch documents related to the extracted document IDs and tenant + var documents = await _context.Documents + .AsNoTracking() + .Where(d => documentIds.Contains(d.Id) && d.TenantId == tenantId) + .ToListAsync(); + + // Map each attendance log and enrich with document info including pre-signed URLs + var response = attendanceLogs.Select(log => + { + var logVm = _mapper.Map(log); + + if (log.DocumentId.HasValue) + { + var document = documents.FirstOrDefault(d => d.Id == log.DocumentId.Value); + if (document != null) + { + var preSignedUrl = _s3Service.GeneratePreSignedUrl(document.S3Key); + var thumbPreSignedUrl = !string.IsNullOrWhiteSpace(document.ThumbS3Key) + ? _s3Service.GeneratePreSignedUrl(document.ThumbS3Key) + : preSignedUrl; + + logVm.Document = _mapper.Map(document); + logVm.Document.PreSignedUrl = preSignedUrl; + logVm.Document.ThumbPreSignedUrl = thumbPreSignedUrl; + } + } + + return logVm; + }).ToList(); + + _logger.LogInfo("Job attendance log fetched successfully. JobAttendanceId: {JobAttendanceId}, RecordsCount: {Count}", jobAttendanceId, response.Count); + + return ApiResponse.SuccessResponse(response, "Job attendance log fetched successfully", 200); + } + catch (Exception ex) + { + _logger.LogError(ex, "Unhandled exception in GetAttendanceLogForAttendanceAsync for JobAttendanceId: {JobAttendanceId}, TenantId: {TenantId}", jobAttendanceId, tenantId); + return ApiResponse.ErrorResponse("An unexpected error occurred.", ex.Message, 500); + } + } public async Task> GetAttendanceForJobTeamAsync(Guid jobTicketId, DateTime? startDate, DateTime? endDate, Employee loggedInEmployee, Guid tenantId) { @@ -2137,6 +2216,7 @@ namespace Marco.Pms.Services.Service // Validate the job ticket existence and status var jobTicket = await _context.JobTickets .AsNoTracking() + .Include(jt => jt.Status) .FirstOrDefaultAsync(jt => jt.Id == model.JobTcketId && jt.TenantId == tenantId && jt.IsActive); if (jobTicket == null) {