Implemented signalR in record-mobile API
This commit is contained in:
parent
a6a842bf10
commit
64a7cde69c
@ -597,257 +597,185 @@ 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.Comment = recordAttendanceDot.Comment;
|
attendance = new Attendance
|
||||||
if (recordAttendanceDot.Action == ATTENDANCE_MARK_TYPE.CHECK_IN)
|
|
||||||
{
|
{
|
||||||
attendance.InTime = finalDateTime;
|
TenantId = tenantId,
|
||||||
attendance.OutTime = null;
|
AttendanceDate = recordAttendanceDot.Date,
|
||||||
attendance.Activity = ATTENDANCE_MARK_TYPE.CHECK_OUT;
|
Comment = recordAttendanceDot.Comment,
|
||||||
}
|
EmployeeID = recordAttendanceDot.EmployeeID,
|
||||||
else if (recordAttendanceDot.Action == ATTENDANCE_MARK_TYPE.CHECK_OUT)
|
ProjectID = recordAttendanceDot.ProjectID,
|
||||||
{
|
Date = DateTime.UtcNow,
|
||||||
attendance.IsApproved = true;
|
InTime = finalDateTime,
|
||||||
attendance.Activity = ATTENDANCE_MARK_TYPE.REGULARIZE;
|
Activity = ATTENDANCE_MARK_TYPE.CHECK_OUT
|
||||||
|
};
|
||||||
|
_context.Attendes.Add(attendance);
|
||||||
//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<object>.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);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
attendance = new Attendance();
|
|
||||||
attendance.TenantId = TenantId;
|
|
||||||
attendance.AttendanceDate = recordAttendanceDot.Date;
|
|
||||||
// attendance.Activity = recordAttendanceDot.Action;
|
|
||||||
attendance.Comment = recordAttendanceDot.Comment;
|
attendance.Comment = recordAttendanceDot.Comment;
|
||||||
attendance.EmployeeID = recordAttendanceDot.EmployeeID;
|
|
||||||
attendance.ProjectID = recordAttendanceDot.ProjectID;
|
|
||||||
attendance.Date = DateTime.UtcNow;
|
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<object>.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
_context.Attendes.Update(attendance);
|
||||||
attendance.InTime = finalDateTime;
|
|
||||||
attendance.OutTime = null;
|
|
||||||
attendance.Activity = ATTENDANCE_MARK_TYPE.CHECK_OUT;
|
|
||||||
|
|
||||||
_context.Attendes.Add(attendance);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Upload image if present
|
||||||
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
|
||||||
|
var attendanceLog = new AttendanceLog
|
||||||
|
|
||||||
// Step 3: Always insert a new log entry
|
|
||||||
if (document != null)
|
|
||||||
{
|
{
|
||||||
var attendanceLog = new AttendanceLog
|
AttendanceId = attendance.Id,
|
||||||
{
|
Activity = attendance.Activity,
|
||||||
AttendanceId = attendance.Id, // Use existing or new AttendanceId
|
ActivityTime = finalDateTime,
|
||||||
Activity = attendance.Activity,
|
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 _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,
|
||||||
{
|
FirstName = employee.FirstName,
|
||||||
vm = new EmployeeAttendanceVM()
|
LastName = employee.LastName,
|
||||||
{
|
CheckInTime = attendance.InTime,
|
||||||
CheckInTime = attendance.InTime,
|
CheckOutTime = attendance.OutTime,
|
||||||
CheckOutTime = attendance.OutTime,
|
Activity = attendance.Activity,
|
||||||
EmployeeAvatar = null,
|
JobRoleName = employee.JobRole?.Name,
|
||||||
EmployeeId = recordAttendanceDot.EmployeeID,
|
DocumentId = document?.Id ?? Guid.Empty,
|
||||||
FirstName = employee.FirstName,
|
ThumbPreSignedUrl = preSignedUrl ?? "",
|
||||||
LastName = employee.LastName,
|
PreSignedUrl = preSignedUrl ?? ""
|
||||||
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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogInfo("Attendance for employee {FirstName} {LastName} has been marked", employee.FirstName ?? string.Empty, employee.LastName ?? string.Empty);
|
var notification = new
|
||||||
return Ok(ApiResponse<object>.SuccessResponse(vm, "Attendance marked successfully.", 200));
|
{
|
||||||
}
|
LoggedInUserId = currentEmployee.Id,
|
||||||
_logger.LogInfo("Attendance for employee {FirstName} {LastName} has been marked", employee.FirstName ?? string.Empty, employee.LastName ?? string.Empty);
|
Keyword = "Attendance",
|
||||||
return Ok(ApiResponse<object>.SuccessResponse(new EmployeeAttendanceVM(), "Attendance marked successfully.", 200));
|
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));
|
||||||
}
|
}
|
||||||
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;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user