Implemented signalR in record-mobile API

This commit is contained in:
ashutosh.nehete 2025-06-19 18:28:57 +05:30
parent a6a842bf10
commit 64a7cde69c

View File

@ -597,56 +597,69 @@ namespace MarcoBMS.Services.Controllers
{ {
if (!ModelState.IsValid) if (!ModelState.IsValid)
{ {
var errors = ModelState.Values var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage).ToList();
.SelectMany(v => v.Errors) _logger.LogError("Invalid attendance model received.");
.Select(e => e.ErrorMessage)
.ToList();
_logger.LogError("User sent Invalid Date while marking attendance");
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", errors, 400)); return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", errors, 400));
} }
Guid TenantId = GetTenantId(); Guid tenantId = GetTenantId();
var currentEmployee = await _userHelper.GetCurrentEmployeeAsync();
using var transaction = await _context.Database.BeginTransactionAsync(); using var transaction = await _context.Database.BeginTransactionAsync();
try try
{ {
Attendance? attendance = await _context.Attendes.FirstOrDefaultAsync(a => a.Id == recordAttendanceDot.Id && a.TenantId == TenantId); ; // Validate mark time
if (recordAttendanceDot.MarkTime == null) if (recordAttendanceDot.MarkTime == null)
{ {
_logger.LogError("User sent Invalid Mark Time while marking attendance"); _logger.LogWarning("Null mark time provided.");
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid Mark Time", "Invalid Mark Time", 400)); return BadRequest(ApiResponse<object>.ErrorResponse("Invalid Mark Time", "Mark time is required", 400));
} }
DateTime finalDateTime = GetDateFromTimeStamp(recordAttendanceDot.Date, recordAttendanceDot.MarkTime); if (string.IsNullOrWhiteSpace(recordAttendanceDot.Comment))
if (recordAttendanceDot.Comment == null)
{ {
_logger.LogError("User sent Invalid comment while marking attendance"); _logger.LogWarning("Empty comment provided.");
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid Comment", "Invalid Comment", 400)); return BadRequest(ApiResponse<object>.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 = new 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.Comment = recordAttendanceDot.Comment; attendance.Comment = recordAttendanceDot.Comment;
if (recordAttendanceDot.Action == ATTENDANCE_MARK_TYPE.CHECK_IN) attendance.Date = DateTime.UtcNow;
switch (recordAttendanceDot.Action)
{ {
case ATTENDANCE_MARK_TYPE.CHECK_IN:
attendance.InTime = finalDateTime; attendance.InTime = finalDateTime;
attendance.OutTime = null; attendance.OutTime = null;
attendance.Activity = ATTENDANCE_MARK_TYPE.CHECK_OUT; attendance.Activity = ATTENDANCE_MARK_TYPE.CHECK_OUT;
} break;
else if (recordAttendanceDot.Action == ATTENDANCE_MARK_TYPE.CHECK_OUT) case 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; attendance.OutTime = finalDateTime;
} attendance.Activity = ATTENDANCE_MARK_TYPE.REGULARIZE;
else if (recordAttendanceDot.Action == ATTENDANCE_MARK_TYPE.REQUEST_REGULARIZE) attendance.IsApproved = true;
{ break;
DateTime date = attendance.AttendanceDate; case ATTENDANCE_MARK_TYPE.REQUEST_REGULARIZE:
DateTime date = attendance.InTime ?? recordAttendanceDot.Date;
finalDateTime = GetDateFromTimeStamp(date.Date, recordAttendanceDot.MarkTime); finalDateTime = GetDateFromTimeStamp(date.Date, recordAttendanceDot.MarkTime);
if (attendance.InTime < finalDateTime) if (attendance.InTime < finalDateTime)
{ {
@ -655,199 +668,114 @@ namespace MarcoBMS.Services.Controllers
} }
else else
{ {
_logger.LogError("Employee {EmployeeId} sent regularization request but it check-out time is earlier than check-out"); _logger.LogWarning("Regularization check-out time is before check-in.");
return BadRequest(ApiResponse<object>.ErrorResponse("Check-out time must be later than check-in time", "Check-out time must be later than check-in time", 400)); return BadRequest(ApiResponse<object>.ErrorResponse("Check-out time must be later than check-in time", "Invalid regularization", 400));
} }
// do nothing break;
} case ATTENDANCE_MARK_TYPE.REGULARIZE:
else if (recordAttendanceDot.Action == ATTENDANCE_MARK_TYPE.REGULARIZE)
{
attendance.IsApproved = true; attendance.IsApproved = true;
attendance.Activity = ATTENDANCE_MARK_TYPE.REGULARIZE; attendance.Activity = ATTENDANCE_MARK_TYPE.REGULARIZE;
// do nothing break;
} case ATTENDANCE_MARK_TYPE.REGULARIZE_REJECT:
else if (recordAttendanceDot.Action == ATTENDANCE_MARK_TYPE.REGULARIZE_REJECT)
{
attendance.IsApproved = false; attendance.IsApproved = false;
attendance.Activity = ATTENDANCE_MARK_TYPE.REGULARIZE_REJECT; attendance.Activity = ATTENDANCE_MARK_TYPE.REGULARIZE_REJECT;
// do nothing break;
} }
attendance.Date = DateTime.UtcNow;
// update code
_context.Attendes.Update(attendance); _context.Attendes.Update(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;
// Upload image if present
attendance.InTime = finalDateTime;
attendance.OutTime = null;
attendance.Activity = ATTENDANCE_MARK_TYPE.CHECK_OUT;
_context.Attendes.Add(attendance);
}
Document? document = null; Document? document = null;
var Image = recordAttendanceDot.Image; string? preSignedUrl = null;
var preSignedUrl = string.Empty;
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<object>.ErrorResponse("Base64 data is missing", "Image data missing", 400));
if (string.IsNullOrEmpty(Image.Base64Data)) var fileType = _s3Service.GetContentTypeFromBase64(base64);
return BadRequest(ApiResponse<object>.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400)); 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); await _s3Service.UploadFileAsync(base64, fileType, objectKey);
preSignedUrl = _s3Service.GeneratePreSignedUrlAsync(objectKey); preSignedUrl = _s3Service.GeneratePreSignedUrlAsync(objectKey);
document = new Document document = new Document
{ {
FileName = Image.FileName ?? "", FileName = recordAttendanceDot.Image.FileName ?? "",
ContentType = Image.ContentType, ContentType = recordAttendanceDot.Image.ContentType,
S3Key = objectKey, S3Key = objectKey,
Base64Data = Image.Base64Data, Base64Data = recordAttendanceDot.Image.Base64Data,
FileSize = Image.FileSize, FileSize = recordAttendanceDot.Image.FileSize,
UploadedAt = recordAttendanceDot.Date, UploadedAt = recordAttendanceDot.Date,
TenantId = TenantId TenantId = tenantId
}; };
_context.Documents.Add(document); _context.Documents.Add(document);
await _context.SaveChangesAsync();
} }
// Log attendance
// Step 3: Always insert a new log entry
if (document != null)
{
var attendanceLog = new AttendanceLog var attendanceLog = new AttendanceLog
{ {
AttendanceId = attendance.Id, // Use existing or new AttendanceId AttendanceId = attendance.Id,
Activity = attendance.Activity, Activity = attendance.Activity,
ActivityTime = finalDateTime, ActivityTime = finalDateTime,
Comment = recordAttendanceDot.Comment, Comment = recordAttendanceDot.Comment,
EmployeeID = recordAttendanceDot.EmployeeID, EmployeeID = recordAttendanceDot.EmployeeID,
Latitude = recordAttendanceDot.Latitude, Latitude = recordAttendanceDot.Latitude,
Longitude = recordAttendanceDot.Longitude, Longitude = recordAttendanceDot.Longitude,
DocumentId = document.Id, DocumentId = document?.Id,
TenantId = TenantId, TenantId = tenantId,
UpdatedBy = recordAttendanceDot.EmployeeID, UpdatedBy = recordAttendanceDot.EmployeeID,
UpdatedOn = recordAttendanceDot.Date UpdatedOn = recordAttendanceDot.Date
}; };
_context.AttendanceLogs.Add(attendanceLog); _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 _context.SaveChangesAsync();
await transaction.CommitAsync();
await transaction.CommitAsync(); // Commit transaction // Construct view model
var employee = await _employeeHelper.GetEmployeeByID(recordAttendanceDot.EmployeeID);
Employee employee = await _employeeHelper.GetEmployeeByID(recordAttendanceDot.EmployeeID); var vm = new EmployeeAttendanceVM
if (employee.JobRole != null)
{ {
EmployeeAttendanceVM vm = new EmployeeAttendanceVM(); Id = attendance.Id,
if (document != null) EmployeeId = employee.Id,
{
vm = new EmployeeAttendanceVM()
{
CheckInTime = attendance.InTime,
CheckOutTime = attendance.OutTime,
EmployeeAvatar = null,
EmployeeId = recordAttendanceDot.EmployeeID,
FirstName = employee.FirstName, FirstName = employee.FirstName,
LastName = employee.LastName, 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, CheckInTime = attendance.InTime,
CheckOutTime = attendance.OutTime, CheckOutTime = attendance.OutTime,
EmployeeAvatar = null,
EmployeeId = recordAttendanceDot.EmployeeID,
FirstName = employee.FirstName,
LastName = employee.LastName,
Id = attendance.Id,
Activity = attendance.Activity, Activity = attendance.Activity,
JobRoleName = employee.JobRole.Name, JobRoleName = employee.JobRole?.Name,
DocumentId = Guid.Empty, DocumentId = document?.Id ?? Guid.Empty,
ThumbPreSignedUrl = string.Empty, ThumbPreSignedUrl = preSignedUrl ?? "",
PreSignedUrl = string.Empty PreSignedUrl = preSignedUrl ?? ""
}; };
}
_logger.LogInfo("Attendance for employee {FirstName} {LastName} has been marked", employee.FirstName ?? string.Empty, employee.LastName ?? string.Empty); 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<object>.SuccessResponse(vm, "Attendance marked successfully.", 200)); return Ok(ApiResponse<object>.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<object>.SuccessResponse(new EmployeeAttendanceVM(), "Attendance marked successfully.", 200));
}
catch (Exception ex) catch (Exception ex)
{ {
await transaction.RollbackAsync(); // Rollback on failure await transaction.RollbackAsync();
_logger.LogError("{Error} while marking attendance", ex.Message); _logger.LogError("Error while recording attendance : {Error}", ex.Message);
var response = new return BadRequest(ApiResponse<object>.ErrorResponse("Something went wrong", ex.Message, 500));
{ }
message = ex.Message,
detail = ex.StackTrace,
statusCode = StatusCodes.Status500InternalServerError
};
return BadRequest(ApiResponse<object>.ErrorResponse(ex.Message, response, 400));
} }
}
private static DateTime GetDateFromTimeStamp(DateTime date, string timeString) private static DateTime GetDateFromTimeStamp(DateTime date, string timeString)
{ {
//DateTime date = recordAttendanceDot.Date; //DateTime date = recordAttendanceDot.Date;