marco.pms.api/Marco.Pms.Services/Controllers/AttendanceController.cs

854 lines
42 KiB
C#

using System.Globalization;
using Marco.Pms.DataAccess.Data;
using Marco.Pms.Model.AttendanceModule;
using Marco.Pms.Model.Dtos.Attendance;
using Marco.Pms.Model.Employees;
using Marco.Pms.Model.Mapper;
using Marco.Pms.Model.Projects;
using Marco.Pms.Model.Utilities;
using Marco.Pms.Model.ViewModels.AttendanceVM;
using Marco.Pms.Services.Service;
using MarcoBMS.Services.Helpers;
using MarcoBMS.Services.Service;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.CodeAnalysis;
using Microsoft.EntityFrameworkCore;
using Document = Marco.Pms.Model.DocumentManager.Document;
namespace MarcoBMS.Services.Controllers
{
[Authorize]
[ApiController]
[Route("api/[controller]")]
public class AttendanceController : ControllerBase
{
private readonly ApplicationDbContext _context;
private readonly EmployeeHelper _employeeHelper;
private readonly ProjectsHelper _projectsHelper;
private readonly UserHelper _userHelper;
private readonly S3UploadService _s3Service;
private readonly PermissionServices _permission;
private readonly ILoggingService _logger;
public AttendanceController(
ApplicationDbContext context, EmployeeHelper employeeHelper, ProjectsHelper projectsHelper, UserHelper userHelper, S3UploadService s3Service, ILoggingService logger, PermissionServices permission)
{
_context = context;
_employeeHelper = employeeHelper;
_projectsHelper = projectsHelper;
_userHelper = userHelper;
_s3Service = s3Service;
_logger = logger;
_permission = permission;
}
private Guid GetTenantId()
{
return _userHelper.GetTenantId();
//var tenant = User.FindFirst("TenantId")?.Value;
//return (tenant != null ? Convert.ToInt32(tenant) : 1);
}
[HttpGet("log/attendance/{attendanceid}")]
public async Task<IActionResult> GetAttendanceLogById(Guid attendanceid)
{
Guid TenantId = GetTenantId();
List<AttendanceLog> lstAttendance = await _context.AttendanceLogs.Include(a => a.Document).Include(a => a.Employee).Include(a => a.UpdatedByEmployee).Where(c => c.AttendanceId == attendanceid && c.TenantId == TenantId).ToListAsync();
List<AttendanceLogVM> attendanceLogVMs = new List<AttendanceLogVM>();
foreach (var attendanceLog in lstAttendance)
{
string objectKey = attendanceLog.Document != null ? attendanceLog.Document.S3Key : string.Empty;
string preSignedUrl = string.IsNullOrEmpty(objectKey) ? string.Empty : _s3Service.GeneratePreSignedUrlAsync(objectKey);
attendanceLogVMs.Add(attendanceLog.ToAttendanceLogVMFromAttendanceLog(preSignedUrl, preSignedUrl));
}
_logger.LogInfo("{count} Attendance records fetched successfully", lstAttendance.Count);
return Ok(ApiResponse<object>.SuccessResponse(attendanceLogVMs, System.String.Format("{0} Attendance records fetched successfully", lstAttendance.Count), 200));
}
[HttpGet("log/employee/{employeeId}")]
public async Task<IActionResult> GetAttendanceLogByEmployeeId(Guid employeeId, [FromQuery] string? dateFrom = null, [FromQuery] string? dateTo = null)
{
Guid TenantId = GetTenantId();
DateTime fromDate = new DateTime();
DateTime toDate = new DateTime();
if (dateFrom != null && DateTime.TryParse(dateFrom, out fromDate) == false)
{
_logger.LogError("User sent Invalid from Date while featching attendance logs");
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid Date", "Invalid Date", 400));
}
if (dateTo != null && DateTime.TryParse(dateTo, out toDate) == false)
{
_logger.LogError("User sent Invalid to Date while featching attendance logs");
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid Date", "Invalid Date", 400));
}
if (employeeId == Guid.Empty)
{
_logger.LogError("The employee Id sent by user is empty");
return BadRequest(ApiResponse<object>.ErrorResponse("Employee ID is required and must not be Empty.", "Employee ID is required and must not be empty.", 400));
}
List<Attendance> attendances = await _context.Attendes.Where(c => c.EmployeeID == employeeId && c.TenantId == TenantId && c.AttendanceDate.Date >= fromDate && c.AttendanceDate.Date <= toDate).ToListAsync();
Employee? employee = await _context.Employees.Include(e => e.JobRole).FirstOrDefaultAsync(e => e.Id == employeeId && e.TenantId == TenantId && e.IsActive);
List<EmployeeAttendanceVM> results = new List<EmployeeAttendanceVM>();
if (employee != null)
{
foreach (var attendance in attendances)
{
EmployeeAttendanceVM result = new EmployeeAttendanceVM
{
Id = attendance.Id,
EmployeeId = employee.Id,
FirstName = employee.FirstName,
LastName = employee.LastName,
CheckInTime = attendance.InTime,
CheckOutTime = attendance.OutTime,
JobRoleName = employee.JobRole != null ? employee.JobRole.Name : "",
Activity = attendance.Activity,
EmployeeAvatar = null
};
results.Add(result);
}
}
_logger.LogInfo("{count} Attendance records fetched successfully", results.Count);
return Ok(ApiResponse<object>.SuccessResponse(results, System.String.Format("{0} Attendance records fetched successfully", results.Count), 200));
}
/// <summary>
///
/// </summary>
/// <param name="projectId">ProjectID</param>
/// <param name="date">YYYY-MM-dd</param>
/// <returns></returns>
[HttpGet("project/log")]
public async Task<IActionResult> EmployeeAttendanceByDateRange([FromQuery] Guid projectId, [FromQuery] string? dateFrom = null, [FromQuery] string? dateTo = null)
{
Guid TenantId = GetTenantId();
var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
var hasTeamAttendancePermission = await _permission.HasPermission(new Guid("915e6bff-65f6-4e3f-aea8-3fd217d3ea9e"), LoggedInEmployee.Id);
var hasSelfAttendancePermission = await _permission.HasPermission(new Guid("ccb0589f-712b-43de-92ed-5b6088e7dc4e"), LoggedInEmployee.Id);
var hasProjectPermission = await _permission.HasProjectPermission(LoggedInEmployee, projectId.ToString());
if (!hasProjectPermission)
{
_logger.LogWarning("Employee {EmployeeId} tries to access attendance of project {ProjectId}, but don't have access", LoggedInEmployee.Id, projectId);
return Unauthorized(ApiResponse<object>.ErrorResponse("Unauthorized access", "Unauthorized access", 404));
}
DateTime fromDate = new DateTime();
DateTime toDate = new DateTime();
if (dateFrom != null && DateTime.TryParse(dateFrom, out fromDate) == false)
{
_logger.LogError("User sent Invalid fromDate while featching attendance logs");
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid Date", "Invalid Date", 400));
}
if (dateTo != null && DateTime.TryParse(dateTo, out toDate) == false)
{
_logger.LogError("User sent Invalid toDate while featching attendance logs");
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid Date", "Invalid Date", 400));
}
if (projectId == Guid.Empty)
{
_logger.LogError("The project Id sent by user is less than or equal to zero");
return BadRequest(ApiResponse<object>.ErrorResponse("Project ID is required and must be greater than zero.", "Project ID is required and must be greater than zero.", 400));
}
var result = new List<EmployeeAttendanceVM>();
//Attendance? attendance = null;
ProjectAllocation? teamMember = null;
if (dateFrom == null) fromDate = DateTime.UtcNow.Date;
if (dateTo == null && dateFrom != null) toDate = fromDate.AddDays(-1);
if (hasTeamAttendancePermission)
{
List<Attendance> lstAttendance = await _context.Attendes.Where(c => c.ProjectID == projectId && c.AttendanceDate.Date >= fromDate.Date && c.AttendanceDate.Date <= toDate.Date && c.TenantId == TenantId).ToListAsync();
List<ProjectAllocation> projectteam = await _projectsHelper.GetTeamByProject(TenantId, projectId, true);
var jobRole = await _context.JobRoles.ToListAsync();
foreach (Attendance? attendance in lstAttendance)
{
var result1 = new EmployeeAttendanceVM()
{
Id = attendance.Id,
CheckInTime = attendance.InTime,
CheckOutTime = attendance.OutTime,
Activity = attendance.Activity
};
teamMember = projectteam.Find(x => x.EmployeeId == attendance.EmployeeID);
if (teamMember != null)
{
result1.EmployeeAvatar = null;
result1.EmployeeId = teamMember.EmployeeId;
if (teamMember.Employee != null)
{
result1.FirstName = teamMember.Employee.FirstName;
result1.LastName = teamMember.Employee.LastName;
result1.JobRoleName = teamMember.Employee.JobRole != null ? teamMember.Employee.JobRole.Name : null;
}
else
{
result1.FirstName = null;
result1.LastName = null;
result1.JobRoleName = null;
}
result.Add(result1);
}
}
}
else if (hasSelfAttendancePermission)
{
List<Attendance> lstAttendances = await _context.Attendes.Where(c => c.ProjectID == projectId && c.EmployeeID == LoggedInEmployee.Id && c.AttendanceDate.Date >= fromDate.Date && c.AttendanceDate.Date <= toDate.Date && c.TenantId == TenantId).ToListAsync();
ProjectAllocation? projectAllocation = await _context.ProjectAllocations.Include(pa => pa.Employee).FirstOrDefaultAsync(pa => pa.ProjectId == projectId && pa.EmployeeId == LoggedInEmployee.Id && pa.TenantId == TenantId && pa.IsActive);
foreach (var attendance in lstAttendances)
{
if (projectAllocation != null)
{
EmployeeAttendanceVM result1 = new EmployeeAttendanceVM
{
Id = attendance.Id,
EmployeeAvatar = null,
EmployeeId = projectAllocation.EmployeeId,
FirstName = projectAllocation.Employee?.FirstName,
LastName = projectAllocation.Employee?.LastName,
JobRoleName = projectAllocation.Employee?.JobRole?.Name,
CheckInTime = attendance.InTime,
CheckOutTime = attendance.OutTime,
Activity = attendance.Activity
};
result.Add(result1);
}
}
}
_logger.LogInfo("{count} Attendance records fetched successfully", result.Count);
return Ok(ApiResponse<object>.SuccessResponse(result, System.String.Format("{0} Attendance records fetched successfully", result.Count), 200));
}
/// <summary>
///
/// </summary>
/// <param name="projectId">ProjectID</param>
/// <param name="date">YYYY-MM-dd</param>
/// <returns></returns>
[HttpGet("project/team")]
public async Task<IActionResult> EmployeeAttendanceByProject([FromQuery] Guid projectId, [FromQuery] bool IncludeInActive, [FromQuery] string? date = null)
{
Guid TenantId = GetTenantId();
var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
var hasTeamAttendancePermission = await _permission.HasPermission(new Guid("915e6bff-65f6-4e3f-aea8-3fd217d3ea9e"), LoggedInEmployee.Id);
var hasSelfAttendancePermission = await _permission.HasPermission(new Guid("ccb0589f-712b-43de-92ed-5b6088e7dc4e"), LoggedInEmployee.Id);
var hasProjectPermission = await _permission.HasProjectPermission(LoggedInEmployee, projectId.ToString());
if (!hasProjectPermission)
{
_logger.LogWarning("Employee {EmployeeId} tries to access attendance of project {ProjectId}, but don't have access", LoggedInEmployee.Id, projectId);
return Unauthorized(ApiResponse<object>.ErrorResponse("Unauthorized access", "Unauthorized access", 404));
}
DateTime forDate = new DateTime();
if (date != null && DateTime.TryParse(date, out forDate) == false)
{
_logger.LogError("User sent Invalid Date while featching attendance logs");
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid Date", "Invalid Date", 400));
}
if (projectId == Guid.Empty)
{
_logger.LogError("The project Id sent by user is less than or equal to zero");
return BadRequest(ApiResponse<object>.ErrorResponse("Project ID is required and must be greater than zero.", "Project ID is required and must be greater than zero.", 400));
}
var result = new List<EmployeeAttendanceVM>();
Attendance? attendance = null;
if (date == null) forDate = DateTime.UtcNow.Date;
if (hasTeamAttendancePermission)
{
List<Attendance> lstAttendance = await _context.Attendes.Where(c => c.ProjectID == projectId && c.AttendanceDate.Date == forDate && c.TenantId == TenantId).ToListAsync();
List<ProjectAllocation> projectteam = await _projectsHelper.GetTeamByProject(TenantId, projectId, IncludeInActive);
var idList = projectteam.Select(p => p.EmployeeId).ToList();
//var emp = await _context.Employees.Where(e => idList.Contains(e.Id)).Include(e => e.JobRole).ToListAsync();
var jobRole = await _context.JobRoles.ToListAsync();
foreach (ProjectAllocation teamMember in projectteam)
{
if (teamMember.Employee != null && teamMember.Employee.JobRole != null)
{
var result1 = new EmployeeAttendanceVM()
{
EmployeeAvatar = null,
EmployeeId = teamMember.EmployeeId,
FirstName = teamMember.Employee.FirstName,
LastName = teamMember.Employee.LastName,
JobRoleName = teamMember.Employee.JobRole.Name,
};
//var member = emp.Where(e => e.Id == teamMember.EmployeeId);
attendance = lstAttendance.Find(x => x.EmployeeID == teamMember.EmployeeId) ?? new Attendance();
if (attendance != null)
{
result1.Id = attendance.Id;
result1.CheckInTime = attendance.InTime;
result1.CheckOutTime = attendance.OutTime;
result1.Activity = attendance.Activity;
}
result.Add(result1);
}
}
result.Sort(delegate (EmployeeAttendanceVM x, EmployeeAttendanceVM y)
{
//return x.FirstName.CompareTo(y.FirstName);
return string.Compare(x.FirstName, y.FirstName, StringComparison.Ordinal);
});
}
else if (hasSelfAttendancePermission)
{
Attendance lstAttendance = await _context.Attendes.FirstOrDefaultAsync(c => c.ProjectID == projectId && c.EmployeeID == LoggedInEmployee.Id && c.AttendanceDate.Date == forDate && c.TenantId == TenantId) ?? new Attendance();
ProjectAllocation? projectAllocation = await _context.ProjectAllocations.Include(pa => pa.Employee).FirstOrDefaultAsync(pa => pa.ProjectId == projectId && pa.EmployeeId == LoggedInEmployee.Id && pa.TenantId == TenantId && pa.IsActive);
if (projectAllocation != null)
{
EmployeeAttendanceVM result1 = new EmployeeAttendanceVM
{
Id = lstAttendance.Id,
EmployeeAvatar = null,
EmployeeId = projectAllocation.EmployeeId,
FirstName = projectAllocation.Employee?.FirstName,
LastName = projectAllocation.Employee?.LastName,
JobRoleName = projectAllocation.Employee?.JobRole?.Name,
CheckInTime = lstAttendance.InTime,
CheckOutTime = lstAttendance.OutTime,
Activity = lstAttendance.Activity
};
result.Add(result1);
}
}
_logger.LogInfo("{count} Attendance records fetched successfully", result.Count);
return Ok(ApiResponse<object>.SuccessResponse(result, System.String.Format("{0} Attendance records fetched successfully", result.Count), 200));
}
[HttpGet("regularize")]
public async Task<IActionResult> GetRequestRegularizeAttendance([FromQuery] Guid projectId, [FromQuery] bool IncludeInActive)
{
Guid TenantId = GetTenantId();
Employee LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
var result = new List<EmployeeAttendanceVM>();
var hasProjectPermission = await _permission.HasProjectPermission(LoggedInEmployee, projectId.ToString());
if (!hasProjectPermission)
{
_logger.LogWarning("Employee {EmployeeId} tries to access attendance of project {ProjectId}, but don't have access", LoggedInEmployee.Id, projectId);
return Unauthorized(ApiResponse<object>.ErrorResponse("Unauthorized access", "Unauthorized access", 404));
}
List<Attendance> lstAttendance = await _context.Attendes.Where(c => c.ProjectID == projectId && c.Activity == ATTENDANCE_MARK_TYPE.REQUEST_REGULARIZE && c.TenantId == TenantId).ToListAsync();
List<ProjectAllocation> projectteam = await _projectsHelper.GetTeamByProject(TenantId, projectId, true);
var idList = projectteam.Select(p => p.EmployeeId).ToList();
var jobRole = await _context.JobRoles.ToListAsync();
foreach (Attendance attende in lstAttendance)
{
var result1 = new EmployeeAttendanceVM()
{
Id = attende.Id,
CheckInTime = attende.InTime,
CheckOutTime = attende.OutTime,
Activity = attende.Activity,
EmployeeAvatar = null,
EmployeeId = attende.EmployeeID,
};
var teamMember = projectteam.Find(m => m.EmployeeId == attende.EmployeeID);
if (teamMember != null && teamMember.Employee != null && teamMember.Employee.JobRole != null)
{
result1.FirstName = teamMember.Employee.FirstName;
result1.LastName = teamMember.Employee.LastName;
result1.JobRoleName = teamMember.Employee.JobRole.Name;
}
result.Add(result1);
}
result.Sort(delegate (EmployeeAttendanceVM x, EmployeeAttendanceVM y)
{
return string.Compare(x.FirstName, y.FirstName, StringComparison.Ordinal);
});
_logger.LogInfo("{count} Attendance records fetched successfully", result.Count);
return Ok(ApiResponse<object>.SuccessResponse(result, System.String.Format("{0} Attendance records fetched successfully", result.Count), 200));
}
[HttpPost]
[Route("record")]
public async Task<IActionResult> RecordAttendance([FromBody] RecordAttendanceDot recordAttendanceDot)
{
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");
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", errors, 400));
}
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); ;
if (recordAttendanceDot.MarkTime == null)
{
_logger.LogError("User sent Invalid Mark Time while marking attendance");
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid Mark Time", "Invalid Mark Time", 400));
}
DateTime finalDateTime = GetDateFromTimeStamp(recordAttendanceDot.Date, recordAttendanceDot.MarkTime);
if (recordAttendanceDot.Comment == null)
{
_logger.LogError("User sent Invalid comment while marking attendance");
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid Comment", "Invalid Comment", 400));
}
if (attendance != null)
{
attendance.Comment = recordAttendanceDot.Comment;
if (recordAttendanceDot.Action == ATTENDANCE_MARK_TYPE.CHECK_IN)
{
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<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;
attendance.ApprovedBy = currentEmployee.Id;
// 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
{
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;
attendance.InTime = finalDateTime;
attendance.OutTime = null;
attendance.Activity = ATTENDANCE_MARK_TYPE.CHECK_OUT;
_context.Attendes.Add(attendance);
}
await _context.SaveChangesAsync();
// Step 3: Always insert a new log entry
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,
TenantId = TenantId,
UpdatedBy = currentEmployee.Id,
UpdatedOn = recordAttendanceDot.Date
};
//if (recordAttendanceDot.Image != null && recordAttendanceDot.Image.Count > 0)
//{
// attendanceLog.Photo = recordAttendanceDot.Image[0].Base64Data;
//}
_context.AttendanceLogs.Add(attendanceLog);
await _context.SaveChangesAsync();
await transaction.CommitAsync(); // Commit transaction
Employee employee = await _employeeHelper.GetEmployeeByID(recordAttendanceDot.EmployeeID);
if (employee.JobRole != null)
{
EmployeeAttendanceVM 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
};
_logger.LogInfo("Attendance for employee {FirstName} {LastName} has been marked", employee.FirstName ?? string.Empty, employee.LastName ?? string.Empty);
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)
{
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<object>.ErrorResponse(ex.Message, response, 400));
}
}
[HttpPost]
[Route("record-image")]
public async Task<IActionResult> RecordAttendanceWithImage([FromBody] RecordAttendanceDot recordAttendanceDot)
{
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");
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", errors, 400));
}
Guid TenantId = GetTenantId();
using var transaction = await _context.Database.BeginTransactionAsync();
try
{
Attendance? attendance = await _context.Attendes.FirstOrDefaultAsync(a => a.Id == recordAttendanceDot.Id && a.TenantId == TenantId); ;
if (recordAttendanceDot.MarkTime == null)
{
_logger.LogError("User sent Invalid Mark Time while marking attendance");
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid Mark Time", "Invalid Mark Time", 400));
}
DateTime finalDateTime = GetDateFromTimeStamp(recordAttendanceDot.Date, recordAttendanceDot.MarkTime);
if (recordAttendanceDot.Comment == null)
{
_logger.LogError("User sent Invalid comment while marking attendance");
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid Comment", "Invalid Comment", 400));
}
if (attendance != null)
{
attendance.Comment = recordAttendanceDot.Comment;
if (recordAttendanceDot.Action == ATTENDANCE_MARK_TYPE.CHECK_IN)
{
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<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
{
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;
attendance.InTime = finalDateTime;
attendance.OutTime = null;
attendance.Activity = ATTENDANCE_MARK_TYPE.CHECK_OUT;
_context.Attendes.Add(attendance);
}
Document? document = null;
var Image = recordAttendanceDot.Image;
var preSignedUrl = string.Empty;
if (Image != null && Image.ContentType != null)
{
if (string.IsNullOrEmpty(Image.Base64Data))
return BadRequest(ApiResponse<object>.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400));
//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,
S3Key = objectKey,
Base64Data = Image.Base64Data,
FileSize = Image.FileSize,
UploadedAt = recordAttendanceDot.Date,
TenantId = TenantId
};
_context.Documents.Add(document);
await _context.SaveChangesAsync();
}
// Step 3: Always insert a new log entry
if (document != null)
{
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.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(); // Commit transaction
Employee employee = await _employeeHelper.GetEmployeeByID(recordAttendanceDot.EmployeeID);
if (employee.JobRole != null)
{
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
};
}
_logger.LogInfo("Attendance for employee {FirstName} {LastName} has been marked", employee.FirstName ?? string.Empty, employee.LastName ?? string.Empty);
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)
{
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<object>.ErrorResponse(ex.Message, response, 400));
}
}
private static DateTime GetDateFromTimeStamp(DateTime date, string timeString)
{
//DateTime date = recordAttendanceDot.Date;
// Parse time string to TimeSpan
DateTime parsedTime = DateTime.ParseExact(timeString, "hh:mm tt", CultureInfo.InvariantCulture);
// Combine date with time
DateTime finalDateTime = new DateTime(date.Year, date.Month, date.Day, parsedTime.Hour, parsedTime.Minute, 0);
return finalDateTime;
}
}
}