diff --git a/Marco.Pms.Services/Controllers/AttendanceController.cs b/Marco.Pms.Services/Controllers/AttendanceController.cs index 3181ce9..d23a007 100644 --- a/Marco.Pms.Services/Controllers/AttendanceController.cs +++ b/Marco.Pms.Services/Controllers/AttendanceController.cs @@ -597,257 +597,185 @@ namespace MarcoBMS.Services.Controllers { if (!ModelState.IsValid) { - var errors = ModelState.Values - .SelectMany(v => v.Errors) - .Select(e => e.ErrorMessage) - .ToList(); - _logger.LogError("User sent Invalid Date while marking attendance"); + var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage).ToList(); + _logger.LogError("Invalid attendance model received."); return BadRequest(ApiResponse.ErrorResponse("Invalid data", errors, 400)); } - Guid TenantId = GetTenantId(); + Guid tenantId = GetTenantId(); + var currentEmployee = await _userHelper.GetCurrentEmployeeAsync(); using var transaction = await _context.Database.BeginTransactionAsync(); try { - Attendance? attendance = await _context.Attendes.FirstOrDefaultAsync(a => a.Id == recordAttendanceDot.Id && a.TenantId == TenantId); ; - + // Validate mark time if (recordAttendanceDot.MarkTime == null) { - _logger.LogError("User sent Invalid Mark Time while marking attendance"); - return BadRequest(ApiResponse.ErrorResponse("Invalid Mark Time", "Invalid Mark Time", 400)); + _logger.LogWarning("Null mark time provided."); + return BadRequest(ApiResponse.ErrorResponse("Invalid Mark Time", "Mark time is required", 400)); } - DateTime finalDateTime = GetDateFromTimeStamp(recordAttendanceDot.Date, recordAttendanceDot.MarkTime); - if (recordAttendanceDot.Comment == null) + if (string.IsNullOrWhiteSpace(recordAttendanceDot.Comment)) { - _logger.LogError("User sent Invalid comment while marking attendance"); - return BadRequest(ApiResponse.ErrorResponse("Invalid Comment", "Invalid Comment", 400)); + _logger.LogWarning("Empty comment provided."); + return BadRequest(ApiResponse.ErrorResponse("Invalid Comment", "Comment is required", 400)); } - if (attendance != null) + var finalDateTime = GetDateFromTimeStamp(recordAttendanceDot.Date, recordAttendanceDot.MarkTime); + var attendance = await _context.Attendes + .FirstOrDefaultAsync(a => a.Id == recordAttendanceDot.Id && a.TenantId == tenantId); + + // Create or update attendance + if (attendance == null) { - attendance.Comment = recordAttendanceDot.Comment; - if (recordAttendanceDot.Action == ATTENDANCE_MARK_TYPE.CHECK_IN) + attendance = new Attendance { - attendance.InTime = finalDateTime; - attendance.OutTime = null; - attendance.Activity = ATTENDANCE_MARK_TYPE.CHECK_OUT; - } - else if (recordAttendanceDot.Action == ATTENDANCE_MARK_TYPE.CHECK_OUT) - { - attendance.IsApproved = true; - attendance.Activity = ATTENDANCE_MARK_TYPE.REGULARIZE; - - - //string timeString = "10:30 PM"; // Format: "hh:mm tt" - - attendance.OutTime = finalDateTime; - } - else if (recordAttendanceDot.Action == ATTENDANCE_MARK_TYPE.REQUEST_REGULARIZE) - { - DateTime date = attendance.AttendanceDate; - finalDateTime = GetDateFromTimeStamp(date.Date, recordAttendanceDot.MarkTime); - if (attendance.InTime < finalDateTime) - { - attendance.OutTime = finalDateTime; - attendance.Activity = ATTENDANCE_MARK_TYPE.REQUEST_REGULARIZE; - } - else - { - _logger.LogError("Employee {EmployeeId} sent regularization request but it check-out time is earlier than check-out"); - return BadRequest(ApiResponse.ErrorResponse("Check-out time must be later than check-in time", "Check-out time must be later than check-in time", 400)); - } - // do nothing - } - else if (recordAttendanceDot.Action == ATTENDANCE_MARK_TYPE.REGULARIZE) - { - attendance.IsApproved = true; - attendance.Activity = ATTENDANCE_MARK_TYPE.REGULARIZE; - // do nothing - } - else if (recordAttendanceDot.Action == ATTENDANCE_MARK_TYPE.REGULARIZE_REJECT) - { - attendance.IsApproved = false; - attendance.Activity = ATTENDANCE_MARK_TYPE.REGULARIZE_REJECT; - // do nothing - } - attendance.Date = DateTime.UtcNow; - - // update code - _context.Attendes.Update(attendance); + TenantId = tenantId, + AttendanceDate = recordAttendanceDot.Date, + Comment = recordAttendanceDot.Comment, + EmployeeID = recordAttendanceDot.EmployeeID, + ProjectID = recordAttendanceDot.ProjectID, + Date = DateTime.UtcNow, + InTime = finalDateTime, + Activity = ATTENDANCE_MARK_TYPE.CHECK_OUT + }; + _context.Attendes.Add(attendance); } else { - attendance = new Attendance(); - attendance.TenantId = TenantId; - attendance.AttendanceDate = recordAttendanceDot.Date; - // attendance.Activity = recordAttendanceDot.Action; attendance.Comment = recordAttendanceDot.Comment; - attendance.EmployeeID = recordAttendanceDot.EmployeeID; - attendance.ProjectID = recordAttendanceDot.ProjectID; attendance.Date = DateTime.UtcNow; + switch (recordAttendanceDot.Action) + { + case ATTENDANCE_MARK_TYPE.CHECK_IN: + attendance.InTime = finalDateTime; + attendance.OutTime = null; + attendance.Activity = ATTENDANCE_MARK_TYPE.CHECK_OUT; + break; + case ATTENDANCE_MARK_TYPE.CHECK_OUT: + attendance.OutTime = finalDateTime; + attendance.Activity = ATTENDANCE_MARK_TYPE.REGULARIZE; + attendance.IsApproved = true; + break; + case ATTENDANCE_MARK_TYPE.REQUEST_REGULARIZE: + DateTime date = attendance.InTime ?? recordAttendanceDot.Date; + finalDateTime = GetDateFromTimeStamp(date.Date, recordAttendanceDot.MarkTime); + if (attendance.InTime < finalDateTime) + { + attendance.OutTime = finalDateTime; + attendance.Activity = ATTENDANCE_MARK_TYPE.REQUEST_REGULARIZE; + } + else + { + _logger.LogWarning("Regularization check-out time is before check-in."); + return BadRequest(ApiResponse.ErrorResponse("Check-out time must be later than check-in time", "Invalid regularization", 400)); + } + break; + case ATTENDANCE_MARK_TYPE.REGULARIZE: + attendance.IsApproved = true; + attendance.Activity = ATTENDANCE_MARK_TYPE.REGULARIZE; + break; + case ATTENDANCE_MARK_TYPE.REGULARIZE_REJECT: + attendance.IsApproved = false; + attendance.Activity = ATTENDANCE_MARK_TYPE.REGULARIZE_REJECT; + break; + } - - attendance.InTime = finalDateTime; - attendance.OutTime = null; - attendance.Activity = ATTENDANCE_MARK_TYPE.CHECK_OUT; - - _context.Attendes.Add(attendance); - + _context.Attendes.Update(attendance); } + + // Upload image if present Document? document = null; - var Image = recordAttendanceDot.Image; - var preSignedUrl = string.Empty; + string? preSignedUrl = null; - if (Image != null && Image.ContentType != null) + if (recordAttendanceDot.Image != null && recordAttendanceDot.Image.ContentType != null) { + string base64 = recordAttendanceDot.Image.Base64Data?.Split(',').LastOrDefault() ?? ""; + if (string.IsNullOrWhiteSpace(base64)) + return BadRequest(ApiResponse.ErrorResponse("Base64 data is missing", "Image data missing", 400)); - if (string.IsNullOrEmpty(Image.Base64Data)) - return BadRequest(ApiResponse.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400)); + var fileType = _s3Service.GetContentTypeFromBase64(base64); + var fileName = _s3Service.GenerateFileName(fileType, tenantId, "attendance"); + var objectKey = $"tenant-{tenantId}/Employee/{recordAttendanceDot.EmployeeID}/Attendance/{fileName}"; - //If base64 has a data URI prefix, strip it - var base64 = Image.Base64Data.Contains(",") - ? Image.Base64Data.Substring(Image.Base64Data.IndexOf(",") + 1) - : Image.Base64Data; - - string fileType = _s3Service.GetContentTypeFromBase64(base64); - string fileName = _s3Service.GenerateFileName(fileType, TenantId, "attendance"); - - string objectKey = $"tenant-{TenantId}/Employee/{recordAttendanceDot.EmployeeID}/Attendance/{fileName}"; await _s3Service.UploadFileAsync(base64, fileType, objectKey); preSignedUrl = _s3Service.GeneratePreSignedUrlAsync(objectKey); document = new Document { - FileName = Image.FileName ?? "", - ContentType = Image.ContentType, + FileName = recordAttendanceDot.Image.FileName ?? "", + ContentType = recordAttendanceDot.Image.ContentType, S3Key = objectKey, - Base64Data = Image.Base64Data, - FileSize = Image.FileSize, + Base64Data = recordAttendanceDot.Image.Base64Data, + FileSize = recordAttendanceDot.Image.FileSize, UploadedAt = recordAttendanceDot.Date, - TenantId = TenantId + TenantId = tenantId }; + _context.Documents.Add(document); - - - await _context.SaveChangesAsync(); } - - - - // Step 3: Always insert a new log entry - if (document != null) + // Log attendance + var attendanceLog = new AttendanceLog { - var attendanceLog = new AttendanceLog - { - AttendanceId = attendance.Id, // Use existing or new AttendanceId - Activity = attendance.Activity, + AttendanceId = attendance.Id, + Activity = attendance.Activity, + ActivityTime = finalDateTime, + Comment = recordAttendanceDot.Comment, + EmployeeID = recordAttendanceDot.EmployeeID, + Latitude = recordAttendanceDot.Latitude, + Longitude = recordAttendanceDot.Longitude, + DocumentId = document?.Id, + TenantId = tenantId, + UpdatedBy = recordAttendanceDot.EmployeeID, + UpdatedOn = recordAttendanceDot.Date + }; + _context.AttendanceLogs.Add(attendanceLog); - ActivityTime = finalDateTime, - Comment = recordAttendanceDot.Comment, - EmployeeID = recordAttendanceDot.EmployeeID, - Latitude = recordAttendanceDot.Latitude, - Longitude = recordAttendanceDot.Longitude, - DocumentId = document.Id, - TenantId = TenantId, - UpdatedBy = recordAttendanceDot.EmployeeID, - UpdatedOn = recordAttendanceDot.Date - }; - _context.AttendanceLogs.Add(attendanceLog); - } - else - { - var attendanceLog = new AttendanceLog - { - AttendanceId = attendance.Id, // Use existing or new AttendanceId - Activity = attendance.Activity, - - ActivityTime = finalDateTime, - Comment = recordAttendanceDot.Comment, - EmployeeID = recordAttendanceDot.EmployeeID, - Latitude = recordAttendanceDot.Latitude, - Longitude = recordAttendanceDot.Longitude, - DocumentId = document != null ? document.Id : null, - TenantId = TenantId, - UpdatedBy = recordAttendanceDot.EmployeeID, - UpdatedOn = recordAttendanceDot.Date - }; - _context.AttendanceLogs.Add(attendanceLog); - } - - //if (recordAttendanceDot.Image != null && recordAttendanceDot.Image.Count > 0) - //{ - // attendanceLog.Photo = recordAttendanceDot.Image[0].Base64Data; - //} await _context.SaveChangesAsync(); + await transaction.CommitAsync(); - await transaction.CommitAsync(); // Commit transaction - - Employee employee = await _employeeHelper.GetEmployeeByID(recordAttendanceDot.EmployeeID); - if (employee.JobRole != null) + // Construct view model + var employee = await _employeeHelper.GetEmployeeByID(recordAttendanceDot.EmployeeID); + var vm = new EmployeeAttendanceVM { - EmployeeAttendanceVM vm = new EmployeeAttendanceVM(); - if (document != null) - { - vm = new EmployeeAttendanceVM() - { - CheckInTime = attendance.InTime, - CheckOutTime = attendance.OutTime, - EmployeeAvatar = null, - EmployeeId = recordAttendanceDot.EmployeeID, - FirstName = employee.FirstName, - LastName = employee.LastName, - Id = attendance.Id, - Activity = attendance.Activity, - JobRoleName = employee.JobRole.Name, - DocumentId = document.Id, - ThumbPreSignedUrl = preSignedUrl, - PreSignedUrl = preSignedUrl - }; - } - else - { - vm = new EmployeeAttendanceVM() - { - CheckInTime = attendance.InTime, - CheckOutTime = attendance.OutTime, - EmployeeAvatar = null, - EmployeeId = recordAttendanceDot.EmployeeID, - FirstName = employee.FirstName, - LastName = employee.LastName, - Id = attendance.Id, - Activity = attendance.Activity, - JobRoleName = employee.JobRole.Name, - DocumentId = Guid.Empty, - ThumbPreSignedUrl = string.Empty, - PreSignedUrl = string.Empty - }; - } + Id = attendance.Id, + EmployeeId = employee.Id, + FirstName = employee.FirstName, + LastName = employee.LastName, + CheckInTime = attendance.InTime, + CheckOutTime = attendance.OutTime, + Activity = attendance.Activity, + JobRoleName = employee.JobRole?.Name, + DocumentId = document?.Id ?? Guid.Empty, + ThumbPreSignedUrl = preSignedUrl ?? "", + PreSignedUrl = preSignedUrl ?? "" + }; - _logger.LogInfo("Attendance for employee {FirstName} {LastName} has been marked", employee.FirstName ?? string.Empty, employee.LastName ?? string.Empty); - return Ok(ApiResponse.SuccessResponse(vm, "Attendance marked successfully.", 200)); - } - _logger.LogInfo("Attendance for employee {FirstName} {LastName} has been marked", employee.FirstName ?? string.Empty, employee.LastName ?? string.Empty); - return Ok(ApiResponse.SuccessResponse(new EmployeeAttendanceVM(), "Attendance marked successfully.", 200)); + var notification = new + { + LoggedInUserId = currentEmployee.Id, + Keyword = "Attendance", + Activity = recordAttendanceDot.Id == Guid.Empty ? 1 : 0, + ProjectId = attendance.ProjectID, + Response = vm + }; + await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification); + + _logger.LogInfo("Attendance recorded for employee: {FullName}", $"{employee.FirstName} {employee.LastName}"); + + return Ok(ApiResponse.SuccessResponse(vm, "Attendance marked successfully.", 200)); } catch (Exception ex) { - await transaction.RollbackAsync(); // Rollback on failure - _logger.LogError("{Error} while marking attendance", ex.Message); - var response = new - { - message = ex.Message, - detail = ex.StackTrace, - statusCode = StatusCodes.Status500InternalServerError - }; - return BadRequest(ApiResponse.ErrorResponse(ex.Message, response, 400)); + await transaction.RollbackAsync(); + _logger.LogError("Error while recording attendance : {Error}", ex.Message); + return BadRequest(ApiResponse.ErrorResponse("Something went wrong", ex.Message, 500)); } - } + private static DateTime GetDateFromTimeStamp(DateTime date, string timeString) { //DateTime date = recordAttendanceDot.Date;