Added an API to get todays attendance record for current logged-in-employee
This commit is contained in:
parent
a4714d5440
commit
5d8a5e0cc8
@ -1,6 +1,6 @@
|
|||||||
namespace Marco.Pms.Model.ViewModels.DashBoard
|
namespace Marco.Pms.Model.ViewModels.DashBoard
|
||||||
{
|
{
|
||||||
public class EmployeeAttendanceVM
|
public class DashBoardEmployeeAttendanceVM
|
||||||
{
|
{
|
||||||
public string? FirstName { get; set; }
|
public string? FirstName { get; set; }
|
||||||
public string? LastName { get; set; }
|
public string? LastName { get; set; }
|
||||||
@ -2,7 +2,7 @@
|
|||||||
{
|
{
|
||||||
public class ProjectAttendanceVM
|
public class ProjectAttendanceVM
|
||||||
{
|
{
|
||||||
public List<EmployeeAttendanceVM>? AttendanceTable { get; set; }
|
public List<DashBoardEmployeeAttendanceVM>? AttendanceTable { get; set; }
|
||||||
public int CheckedInEmployee { get; set; }
|
public int CheckedInEmployee { get; set; }
|
||||||
public int AssignedEmployee { get; set; }
|
public int AssignedEmployee { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -832,6 +832,13 @@ namespace Marco.Pms.Services.Controllers
|
|||||||
Available = true,
|
Available = true,
|
||||||
MobileLink = "/dashboard/service-projects"
|
MobileLink = "/dashboard/service-projects"
|
||||||
});
|
});
|
||||||
|
response.Add(new MenuSectionApplicationVM
|
||||||
|
{
|
||||||
|
Id = Guid.Parse("5fab4b88-c9a0-417b-aca2-130980fdb0cf"),
|
||||||
|
Name = "Infra Projects",
|
||||||
|
Available = true,
|
||||||
|
MobileLink = "/dashboard/infra-projects"
|
||||||
|
});
|
||||||
|
|
||||||
// Step 3: Log success
|
// Step 3: Log success
|
||||||
response = response.Where(ms => !string.IsNullOrWhiteSpace(ms.MobileLink)).ToList();
|
response = response.Where(ms => !string.IsNullOrWhiteSpace(ms.MobileLink)).ToList();
|
||||||
|
|||||||
@ -1,8 +1,11 @@
|
|||||||
using Marco.Pms.DataAccess.Data;
|
using AutoMapper;
|
||||||
|
using Marco.Pms.DataAccess.Data;
|
||||||
using Marco.Pms.Model.Dtos.Attendance;
|
using Marco.Pms.Model.Dtos.Attendance;
|
||||||
using Marco.Pms.Model.Entitlements;
|
using Marco.Pms.Model.Entitlements;
|
||||||
using Marco.Pms.Model.Expenses;
|
using Marco.Pms.Model.Expenses;
|
||||||
using Marco.Pms.Model.Utilities;
|
using Marco.Pms.Model.Utilities;
|
||||||
|
using Marco.Pms.Model.ViewModels.Activities;
|
||||||
|
using Marco.Pms.Model.ViewModels.AttendanceVM;
|
||||||
using Marco.Pms.Model.ViewModels.DashBoard;
|
using Marco.Pms.Model.ViewModels.DashBoard;
|
||||||
using Marco.Pms.Services.Service;
|
using Marco.Pms.Services.Service;
|
||||||
using Marco.Pms.Services.Service.ServiceInterfaces;
|
using Marco.Pms.Services.Service.ServiceInterfaces;
|
||||||
@ -26,6 +29,9 @@ namespace Marco.Pms.Services.Controllers
|
|||||||
private readonly ILoggingService _logger;
|
private readonly ILoggingService _logger;
|
||||||
private readonly PermissionServices _permissionServices;
|
private readonly PermissionServices _permissionServices;
|
||||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||||
|
private readonly IMapper _mapper;
|
||||||
|
|
||||||
|
|
||||||
public static readonly Guid ActiveId = Guid.Parse("b74da4c2-d07e-46f2-9919-e75e49b12731");
|
public static readonly Guid ActiveId = Guid.Parse("b74da4c2-d07e-46f2-9919-e75e49b12731");
|
||||||
private static readonly Guid Draft = Guid.Parse("297e0d8f-f668-41b5-bfea-e03b354251c8");
|
private static readonly Guid Draft = Guid.Parse("297e0d8f-f668-41b5-bfea-e03b354251c8");
|
||||||
private static readonly Guid Review = Guid.Parse("6537018f-f4e9-4cb3-a210-6c3b2da999d7");
|
private static readonly Guid Review = Guid.Parse("6537018f-f4e9-4cb3-a210-6c3b2da999d7");
|
||||||
@ -40,7 +46,8 @@ namespace Marco.Pms.Services.Controllers
|
|||||||
IProjectServices projectServices,
|
IProjectServices projectServices,
|
||||||
IServiceScopeFactory serviceScopeFactory,
|
IServiceScopeFactory serviceScopeFactory,
|
||||||
ILoggingService logger,
|
ILoggingService logger,
|
||||||
PermissionServices permissionServices)
|
PermissionServices permissionServices,
|
||||||
|
IMapper mapper)
|
||||||
{
|
{
|
||||||
_context = context;
|
_context = context;
|
||||||
_userHelper = userHelper;
|
_userHelper = userHelper;
|
||||||
@ -48,8 +55,10 @@ namespace Marco.Pms.Services.Controllers
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
_serviceScopeFactory = serviceScopeFactory;
|
_serviceScopeFactory = serviceScopeFactory;
|
||||||
_permissionServices = permissionServices;
|
_permissionServices = permissionServices;
|
||||||
|
_mapper = mapper;
|
||||||
tenantId = userHelper.GetTenantId();
|
tenantId = userHelper.GetTenantId();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fetches project progression data (planned and completed tasks) in graph form for a tenant and specified (or all) projects over a date range.
|
/// Fetches project progression data (planned and completed tasks) in graph form for a tenant and specified (or all) projects over a date range.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -499,7 +508,7 @@ namespace Marco.Pms.Services.Controllers
|
|||||||
return Ok(ApiResponse<object>.SuccessResponse(
|
return Ok(ApiResponse<object>.SuccessResponse(
|
||||||
new ProjectAttendanceVM
|
new ProjectAttendanceVM
|
||||||
{
|
{
|
||||||
AttendanceTable = new List<EmployeeAttendanceVM>(),
|
AttendanceTable = new List<DashBoardEmployeeAttendanceVM>(),
|
||||||
CheckedInEmployee = 0,
|
CheckedInEmployee = 0,
|
||||||
AssignedEmployee = 0
|
AssignedEmployee = 0
|
||||||
},
|
},
|
||||||
@ -523,7 +532,7 @@ namespace Marco.Pms.Services.Controllers
|
|||||||
.Join(employees,
|
.Join(employees,
|
||||||
attendance => attendance.EmployeeId,
|
attendance => attendance.EmployeeId,
|
||||||
employee => employee.Id,
|
employee => employee.Id,
|
||||||
(attendance, employee) => new EmployeeAttendanceVM
|
(attendance, employee) => new DashBoardEmployeeAttendanceVM
|
||||||
{
|
{
|
||||||
FirstName = employee.FirstName,
|
FirstName = employee.FirstName,
|
||||||
LastName = employee.LastName,
|
LastName = employee.LastName,
|
||||||
@ -1074,5 +1083,126 @@ namespace Marco.Pms.Services.Controllers
|
|||||||
ApiResponse<object>.ErrorResponse("An error occurred while fetching pending expenses.", "An error occurred while fetching pending expenses.", 500)); // [Error Response]
|
ApiResponse<object>.ErrorResponse("An error occurred while fetching pending expenses.", "An error occurred while fetching pending expenses.", 500)); // [Error Response]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves today's attendance details for a specific employee on a given project,
|
||||||
|
/// defaulting to the currently logged-in employee when no employeeId is provided.
|
||||||
|
/// Includes related project and employee information for UI display.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="projectId">The project identifier whose attendance is requested.</param>
|
||||||
|
/// <param name="employeeId">
|
||||||
|
/// Optional employee identifier. When null, the currently logged-in employee is used.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// 200 OK with an <see cref="EmployeeAttendanceVM"/> payload on success, or a standardized
|
||||||
|
/// error envelope on validation or processing failure.
|
||||||
|
/// </returns>
|
||||||
|
[HttpGet("get/attendance/employee/{projectId}")]
|
||||||
|
public async Task<IActionResult> GetAttendanceByEmployeeAsync(Guid projectId, [FromQuery] Guid? employeeId)
|
||||||
|
{
|
||||||
|
// TenantId is assumed to come from a base controller, HttpContext, or similar.
|
||||||
|
if (tenantId == Guid.Empty)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("GetAttendanceByEmployeeAsync called with empty TenantId. ProjectId={ProjectId}", projectId);
|
||||||
|
|
||||||
|
return BadRequest(
|
||||||
|
ApiResponse<object>.ErrorResponse("Invalid tenant information.", "TenantId is empty in GetAttendanceByEmployeeAsync.", 400));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (projectId == Guid.Empty)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("GetAttendanceByEmployeeAsync called with empty ProjectId. TenantId={TenantId}", tenantId);
|
||||||
|
|
||||||
|
return BadRequest(
|
||||||
|
ApiResponse<object>.ErrorResponse("Project reference is required.", "ProjectId is empty in GetAttendanceByEmployeeAsync.", 400));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve the currently logged-in employee (e.g., from token or session).
|
||||||
|
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||||
|
|
||||||
|
var attendanceEmployeeId = employeeId ?? loggedInEmployee.Id;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
|
// Step 1: Ensure employee is allocated to the project for this tenant.
|
||||||
|
var projectAllocation = await _context.ProjectAllocations
|
||||||
|
.Include(pa => pa.Employee)!.ThenInclude(e => e.JobRole)
|
||||||
|
.Include(pa => pa.Employee)!.ThenInclude(e => e.Organization)
|
||||||
|
.Include(pa => pa.Project)
|
||||||
|
.FirstOrDefaultAsync(pa =>
|
||||||
|
pa.ProjectId == projectId &&
|
||||||
|
pa.EmployeeId == attendanceEmployeeId &&
|
||||||
|
pa.IsActive &&
|
||||||
|
pa.TenantId == tenantId);
|
||||||
|
|
||||||
|
if (projectAllocation == null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(
|
||||||
|
"GetAttendanceByEmployeeAsync failed: Employee not allocated to project. TenantId={TenantId}, ProjectId={ProjectId}, EmployeeId={EmployeeId}, RequestedById={RequestedById}",
|
||||||
|
tenantId, projectId, attendanceEmployeeId, loggedInEmployee.Id);
|
||||||
|
|
||||||
|
return BadRequest(ApiResponse<object>.ErrorResponse("The employee is not allocated to the selected project.", "Project allocation not found for given ProjectId, EmployeeId, and TenantId.",
|
||||||
|
400));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: Fetch today's attendance (if any) for the selected employee and project.
|
||||||
|
var today = DateTime.UtcNow.Date; // Prefer UTC for server-side comparisons.
|
||||||
|
|
||||||
|
var attendance = await _context.Attendes
|
||||||
|
.Include(a => a.Approver)
|
||||||
|
.Include(a => a.RequestedBy)
|
||||||
|
.FirstOrDefaultAsync(a =>
|
||||||
|
a.TenantId == tenantId &&
|
||||||
|
a.EmployeeId == attendanceEmployeeId &&
|
||||||
|
a.ProjectID == projectId &&
|
||||||
|
a.AttendanceDate.Date == today);
|
||||||
|
|
||||||
|
// Step 3: Map to view model with defensive null handling.
|
||||||
|
var attendanceVm = new EmployeeAttendanceVM
|
||||||
|
{
|
||||||
|
Id = attendance?.Id ?? Guid.Empty,
|
||||||
|
EmployeeAvatar = null, // Can be filled from a file service or CDN later.
|
||||||
|
EmployeeId = projectAllocation.EmployeeId,
|
||||||
|
FirstName = projectAllocation.Employee?.FirstName,
|
||||||
|
OrganizationName = projectAllocation.Employee?.Organization?.Name,
|
||||||
|
LastName = projectAllocation.Employee?.LastName,
|
||||||
|
JobRoleName = projectAllocation.Employee?.JobRole?.Name,
|
||||||
|
ProjectId = projectId,
|
||||||
|
ProjectName = projectAllocation.Project?.Name,
|
||||||
|
CheckInTime = attendance?.InTime,
|
||||||
|
CheckOutTime = attendance?.OutTime,
|
||||||
|
Activity = attendance?.Activity ?? ATTENDANCE_MARK_TYPE.CHECK_IN,
|
||||||
|
ApprovedAt = attendance?.ApprovedAt,
|
||||||
|
Approver = attendance == null
|
||||||
|
? null
|
||||||
|
: _mapper.Map<BasicEmployeeVM>(attendance.Approver),
|
||||||
|
RequestedAt = attendance?.RequestedAt,
|
||||||
|
RequestedBy = attendance == null
|
||||||
|
? null
|
||||||
|
: _mapper.Map<BasicEmployeeVM>(attendance.RequestedBy)
|
||||||
|
};
|
||||||
|
|
||||||
|
_logger.LogInfo("GetAttendanceByEmployeeAsync completed successfully. TenantId={TenantId}, ProjectId={ProjectId}, EmployeeId={EmployeeId}, HasAttendance={HasAttendance}",
|
||||||
|
tenantId, projectId, attendanceEmployeeId, attendance != null);
|
||||||
|
|
||||||
|
return Ok(ApiResponse<object>.SuccessResponse(attendanceVm, "Attendance fetched successfully.", 200));
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("GetAttendanceByEmployeeAsync was canceled. TenantId={TenantId}, ProjectId={ProjectId}, EmployeeId={EmployeeId}",
|
||||||
|
tenantId, projectId, attendanceEmployeeId);
|
||||||
|
|
||||||
|
return StatusCode(499, ApiResponse<object>.ErrorResponse("The request was canceled.", "GetAttendanceByEmployeeAsync was canceled by the client.", 499));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "GetAttendanceByEmployeeAsync failed with an unexpected error. TenantId={TenantId}, ProjectId={ProjectId}, EmployeeId={EmployeeId}",
|
||||||
|
tenantId, projectId, attendanceEmployeeId);
|
||||||
|
|
||||||
|
return StatusCode(500, ApiResponse<object>.ErrorResponse("An error occurred while fetching attendance.", "Unhandled exception in GetAttendanceByEmployeeAsync.", 500));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user