Added the Requested by in attendance table

This commit is contained in:
ashutosh.nehete 2025-10-08 17:54:37 +05:30
parent c47b0da7b8
commit 4acc61f03a
6 changed files with 6412 additions and 57 deletions

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,70 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Marco.Pms.DataAccess.Migrations
{
/// <inheritdoc />
public partial class Added_Requested_In_Attendance_Table : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<DateTime>(
name: "ApprovedAt",
table: "Attendes",
type: "datetime(6)",
nullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "RequestedAt",
table: "Attendes",
type: "datetime(6)",
nullable: true);
migrationBuilder.AddColumn<Guid>(
name: "RequestedById",
table: "Attendes",
type: "char(36)",
nullable: true,
collation: "ascii_general_ci");
migrationBuilder.CreateIndex(
name: "IX_Attendes_RequestedById",
table: "Attendes",
column: "RequestedById");
migrationBuilder.AddForeignKey(
name: "FK_Attendes_Employees_RequestedById",
table: "Attendes",
column: "RequestedById",
principalTable: "Employees",
principalColumn: "Id");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Attendes_Employees_RequestedById",
table: "Attendes");
migrationBuilder.DropIndex(
name: "IX_Attendes_RequestedById",
table: "Attendes");
migrationBuilder.DropColumn(
name: "ApprovedAt",
table: "Attendes");
migrationBuilder.DropColumn(
name: "RequestedAt",
table: "Attendes");
migrationBuilder.DropColumn(
name: "RequestedById",
table: "Attendes");
}
}
}

View File

@ -172,6 +172,9 @@ namespace Marco.Pms.DataAccess.Migrations
b.Property<int>("Activity")
.HasColumnType("int");
b.Property<DateTime?>("ApprovedAt")
.HasColumnType("datetime(6)");
b.Property<Guid?>("ApprovedById")
.HasColumnType("char(36)");
@ -200,6 +203,12 @@ namespace Marco.Pms.DataAccess.Migrations
b.Property<Guid>("ProjectID")
.HasColumnType("char(36)");
b.Property<DateTime?>("RequestedAt")
.HasColumnType("datetime(6)");
b.Property<Guid?>("RequestedById")
.HasColumnType("char(36)");
b.Property<Guid>("TenantId")
.HasColumnType("char(36)");
@ -209,6 +218,8 @@ namespace Marco.Pms.DataAccess.Migrations
b.HasIndex("EmployeeId");
b.HasIndex("RequestedById");
b.HasIndex("TenantId");
b.ToTable("Attendes");
@ -4715,6 +4726,10 @@ namespace Marco.Pms.DataAccess.Migrations
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Marco.Pms.Model.Employees.Employee", "RequestedBy")
.WithMany()
.HasForeignKey("RequestedById");
b.HasOne("Marco.Pms.Model.TenantModels.Tenant", "Tenant")
.WithMany()
.HasForeignKey("TenantId")
@ -4725,6 +4740,8 @@ namespace Marco.Pms.DataAccess.Migrations
b.Navigation("Employee");
b.Navigation("RequestedBy");
b.Navigation("Tenant");
});

View File

@ -19,6 +19,8 @@ namespace Marco.Pms.Model.AttendanceModule
public Guid ProjectID { get; set; }
public DateTime AttendanceDate { get; set; }
public DateTime? RequestedAt { get; set; }
public DateTime? ApprovedAt { get; set; }
public DateTime? InTime { get; set; }
public DateTime? OutTime { get; set; }
public bool IsApproved { get; set; }
@ -29,5 +31,10 @@ namespace Marco.Pms.Model.AttendanceModule
[ForeignKey("ApprovedById")]
[ValidateNever]
public Employee? Approver { get; set; }
public Guid? RequestedById { get; set; }
[ForeignKey("RequestedById")]
[ValidateNever]
public Employee? RequestedBy { get; set; }
}
}

View File

@ -1,4 +1,5 @@
using Marco.Pms.Model.Dtos.Attendance;
using Marco.Pms.Model.ViewModels.Activities;
namespace Marco.Pms.Model.ViewModels.AttendanceVM
{
@ -13,9 +14,12 @@ namespace Marco.Pms.Model.ViewModels.AttendanceVM
public string? ProjectName { get; set; }
public DateTime? CheckInTime { get; set; }
public DateTime? CheckOutTime { get; set; }
public DateTime? RequestedAt { get; set; }
public DateTime? ApprovedAt { get; set; }
public string? JobRoleName { get; set; }
public ATTENDANCE_MARK_TYPE Activity { get; set; }
public BasicEmployeeVM? Approver { get; set; }
public BasicEmployeeVM? RequestedBy { get; set; }
public Guid? DocumentId { get; set; }
public string? ThumbPreSignedUrl { get; set; }
public string? PreSignedUrl { get; set; }

View File

@ -1,10 +1,12 @@
using Marco.Pms.DataAccess.Data;
using AutoMapper;
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.Entitlements;
using Marco.Pms.Model.Mapper;
using Marco.Pms.Model.Utilities;
using Marco.Pms.Model.ViewModels.Activities;
using Marco.Pms.Model.ViewModels.AttendanceVM;
using Marco.Pms.Services.Hubs;
using Marco.Pms.Services.Service;
@ -27,48 +29,42 @@ namespace MarcoBMS.Services.Controllers
public class AttendanceController : ControllerBase
{
private readonly ApplicationDbContext _context;
private readonly EmployeeHelper _employeeHelper;
private readonly IProjectServices _projectServices;
private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly UserHelper _userHelper;
private readonly S3UploadService _s3Service;
private readonly PermissionServices _permission;
private readonly ILoggingService _logger;
private readonly IHubContext<MarcoHub> _signalR;
private readonly IFirebaseService _firebase;
private readonly Guid tenantId;
private readonly IMapper _mapper;
public AttendanceController(
ApplicationDbContext context, EmployeeHelper employeeHelper, IProjectServices projectServices, UserHelper userHelper,
S3UploadService s3Service, ILoggingService logger, PermissionServices permission, IHubContext<MarcoHub> signalR, IFirebaseService firebase)
ApplicationDbContext context,
UserHelper userHelper,
IServiceScopeFactory serviceScopeFactory,
ILoggingService logger,
PermissionServices permission,
IMapper mapper)
{
_context = context;
_employeeHelper = employeeHelper;
_projectServices = projectServices;
_serviceScopeFactory = serviceScopeFactory;
_userHelper = userHelper;
_s3Service = s3Service;
_logger = logger;
_permission = permission;
_signalR = signalR;
_firebase = firebase;
}
private Guid GetTenantId()
{
return _userHelper.GetTenantId();
//var tenant = User.FindFirst("TenantId")?.Value;
//return (tenant != null ? Convert.ToInt32(tenant) : 1);
_mapper = mapper;
tenantId = userHelper.GetTenantId();
}
[HttpGet("log/attendance/{attendanceid}")]
public async Task<IActionResult> GetAttendanceLogById(Guid attendanceid)
{
Guid TenantId = GetTenantId();
using var scope = _serviceScopeFactory.CreateScope();
var _s3Service = scope.ServiceProvider.GetRequiredService<S3UploadService>();
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)
.Where(c => c.AttendanceId == attendanceid && c.TenantId == tenantId)
.ToListAsync();
List<AttendanceLogVM> attendanceLogVMs = new List<AttendanceLogVM>();
@ -86,7 +82,6 @@ namespace MarcoBMS.Services.Controllers
[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();
@ -106,8 +101,11 @@ namespace MarcoBMS.Services.Controllers
_logger.LogWarning("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<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)
@ -147,7 +145,6 @@ namespace MarcoBMS.Services.Controllers
public async Task<IActionResult> EmployeeAttendanceByDateRange([FromQuery] Guid? projectId, [FromQuery] Guid? organizationId, [FromQuery] string? dateFrom = null, [FromQuery] string? dateTo = null)
{
Guid tenantId = GetTenantId();
var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
var hasTeamAttendancePermission = await _permission.HasPermission(PermissionsMaster.TeamAttendance, LoggedInEmployee.Id);
@ -174,6 +171,10 @@ namespace MarcoBMS.Services.Controllers
if (dateTo == null && dateFrom != null) toDate = fromDate.AddDays(-1);
var lstAttendanceQuery = _context.Attendes
.Include(a => a.Employee)
.ThenInclude(e => e!.JobRole)
.Include(a => a.Employee)
.ThenInclude(e => e!.JobRole)
.Include(a => a.Employee)
.ThenInclude(e => e!.Organization)
.Include(a => a.Employee)
@ -185,6 +186,7 @@ namespace MarcoBMS.Services.Controllers
a.Employee != null &&
a.Employee.Organization != null &&
a.Employee.JobRole != null);
if (organizationId.HasValue)
{
lstAttendanceQuery = lstAttendanceQuery.Where(a => a.Employee != null && a.Employee.OrganizationId == organizationId);
@ -215,8 +217,11 @@ namespace MarcoBMS.Services.Controllers
LastName = attendance.Employee?.LastName,
JobRoleName = attendance.Employee?.JobRole?.Name,
ProjectName = projects.Where(p => p.Id == attendance.ProjectID).Select(p => p.Name).FirstOrDefault(),
OrganizationName = attendance.Employee?.Organization?.Name
OrganizationName = attendance.Employee?.Organization?.Name,
RequestedAt = attendance.RequestedAt,
RequestedBy = _mapper.Map<BasicEmployeeVM>(attendance.RequestedBy),
ApprovedAt = attendance.ApprovedAt,
Approver = _mapper.Map<BasicEmployeeVM>(attendance.Approver)
};
result.Add(result1);
}
@ -244,7 +249,11 @@ namespace MarcoBMS.Services.Controllers
OrganizationName = attendance.Employee?.Organization?.Name,
CheckInTime = attendance.InTime,
CheckOutTime = attendance.OutTime,
Activity = attendance.Activity
Activity = attendance.Activity,
RequestedAt = attendance.RequestedAt,
RequestedBy = _mapper.Map<BasicEmployeeVM>(attendance.RequestedBy),
ApprovedAt = attendance.ApprovedAt,
Approver = _mapper.Map<BasicEmployeeVM>(attendance.Approver)
};
result.Add(result1);
}
@ -266,7 +275,6 @@ namespace MarcoBMS.Services.Controllers
/// <returns>An IActionResult containing a list of employee attendance records or an error response.</returns>
public async Task<IActionResult> EmployeeAttendanceByProjectAsync([FromQuery] Guid? projectId, [FromQuery] Guid? organizationId, [FromQuery] bool includeInactive, [FromQuery] string? date = null)
{
var tenantId = GetTenantId();
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
// --- 1. Initial Validation and Permission Checks ---
@ -318,16 +326,18 @@ namespace MarcoBMS.Services.Controllers
[HttpGet("regularize")]
public async Task<IActionResult> GetRequestRegularizeAttendance([FromQuery] Guid? projectId, [FromQuery] Guid? organizationId, [FromQuery] bool IncludeInActive)
{
Guid TenantId = GetTenantId();
Employee LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
var result = new List<EmployeeAttendanceVM>();
var lstAttendanceQuery = _context.Attendes
.Include(a => a.RequestedBy)
.ThenInclude(e => e!.JobRole)
.Include(a => a.Employee)
.ThenInclude(e => e!.Organization)
.Include(a => a.Employee)
.ThenInclude(e => e!.JobRole)
.Where(c => c.Activity == ATTENDANCE_MARK_TYPE.REQUEST_REGULARIZE && c.Employee != null && c.Employee.JobRole != null && c.TenantId == TenantId);
.Where(c => c.Activity == ATTENDANCE_MARK_TYPE.REQUEST_REGULARIZE && c.Employee != null && c.Employee.JobRole != null && c.TenantId == tenantId);
if (organizationId.HasValue)
{
@ -342,7 +352,7 @@ namespace MarcoBMS.Services.Controllers
List<Attendance> lstAttendance = await lstAttendanceQuery.ToListAsync();
var projectIds = lstAttendance.Select(a => a.ProjectID).ToList();
var projects = await _context.Projects.Where(p => projectIds.Contains(p.Id) && p.TenantId == TenantId).ToListAsync();
var projects = await _context.Projects.Where(p => projectIds.Contains(p.Id) && p.TenantId == tenantId).ToListAsync();
foreach (Attendance attende in lstAttendance)
{
@ -358,7 +368,9 @@ namespace MarcoBMS.Services.Controllers
ProjectName = projects.Where(p => p.Id == attende.ProjectID).Select(p => p.Name).FirstOrDefault(),
LastName = attende.Employee?.LastName,
JobRoleName = attende.Employee?.JobRole?.Name,
OrganizationName = attende.Employee?.Organization?.Name
OrganizationName = attende.Employee?.Organization?.Name,
RequestedAt = attende.RequestedAt,
RequestedBy = _mapper.Map<BasicEmployeeVM>(attende.RequestedBy)
};
result.Add(result1);
@ -387,13 +399,16 @@ namespace MarcoBMS.Services.Controllers
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", errors, 400));
}
Guid TenantId = GetTenantId();
using var scope = _serviceScopeFactory.CreateScope();
var _signalR = scope.ServiceProvider.GetRequiredService<IHubContext<MarcoHub>>();
var _employeeHelper = scope.ServiceProvider.GetRequiredService<EmployeeHelper>();
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); ;
Attendance? attendance = await _context.Attendes.FirstOrDefaultAsync(a => a.Id == recordAttendanceDot.Id && a.TenantId == tenantId); ;
if (recordAttendanceDot.MarkTime == null)
{
@ -431,10 +446,12 @@ namespace MarcoBMS.Services.Controllers
{
DateTime date = attendance.AttendanceDate;
finalDateTime = GetDateFromTimeStamp(date.Date, recordAttendanceDot.MarkTime);
if (attendance.InTime < finalDateTime)
if (attendance.InTime <= finalDateTime)
{
attendance.OutTime = finalDateTime;
attendance.Activity = ATTENDANCE_MARK_TYPE.REQUEST_REGULARIZE;
attendance.RequestedAt = DateTime.UtcNow;
attendance.RequestedById = currentEmployee.Id;
}
else
{
@ -448,12 +465,15 @@ namespace MarcoBMS.Services.Controllers
attendance.IsApproved = true;
attendance.Activity = ATTENDANCE_MARK_TYPE.REGULARIZE;
attendance.ApprovedById = currentEmployee.Id;
attendance.ApprovedAt = DateTime.UtcNow;
// do nothing
}
else if (recordAttendanceDot.Action == ATTENDANCE_MARK_TYPE.REGULARIZE_REJECT)
{
attendance.IsApproved = false;
attendance.Activity = ATTENDANCE_MARK_TYPE.REGULARIZE_REJECT;
attendance.ApprovedById = currentEmployee.Id;
attendance.ApprovedAt = DateTime.UtcNow;
// do nothing
}
attendance.Date = DateTime.UtcNow;
@ -464,7 +484,7 @@ namespace MarcoBMS.Services.Controllers
else
{
attendance = new Attendance();
attendance.TenantId = TenantId;
attendance.TenantId = tenantId;
attendance.AttendanceDate = recordAttendanceDot.Date;
// attendance.Activity = recordAttendanceDot.Action;
attendance.Comment = recordAttendanceDot.Comment;
@ -496,7 +516,7 @@ namespace MarcoBMS.Services.Controllers
Latitude = recordAttendanceDot.Latitude,
Longitude = recordAttendanceDot.Longitude,
TenantId = TenantId,
TenantId = tenantId,
UpdatedBy = currentEmployee.Id,
UpdatedOn = recordAttendanceDot.Date
};
@ -540,10 +560,10 @@ namespace MarcoBMS.Services.Controllers
// --- Push Notification Section ---
// This section attempts to send a test push notification to the user's device.
// It's designed to fail gracefully and handle invalid Firebase Cloud Messaging (FCM) tokens.
var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();
var name = $"{vm.FirstName} {vm.LastName}";
await _firebase.SendAttendanceMessageAsync(attendance.ProjectID, name, recordAttendanceDot.Action, attendance.EmployeeId, TenantId);
await _firebase.SendAttendanceMessageAsync(attendance.ProjectID, name, recordAttendanceDot.Action, attendance.EmployeeId, tenantId);
});
@ -579,7 +599,12 @@ namespace MarcoBMS.Services.Controllers
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", errors, 400));
}
Guid tenantId = GetTenantId();
using var scope = _serviceScopeFactory.CreateScope();
var _s3Service = scope.ServiceProvider.GetRequiredService<S3UploadService>();
var _employeeHelper = scope.ServiceProvider.GetRequiredService<EmployeeHelper>();
var _firebase = scope.ServiceProvider.GetRequiredService<IFirebaseService>();
var _signalR = scope.ServiceProvider.GetRequiredService<IHubContext<MarcoHub>>();
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
var batchId = Guid.NewGuid();
@ -643,6 +668,8 @@ namespace MarcoBMS.Services.Controllers
{
attendance.OutTime = finalDateTime;
attendance.Activity = ATTENDANCE_MARK_TYPE.REQUEST_REGULARIZE;
attendance.RequestedAt = DateTime.UtcNow;
attendance.RequestedById = loggedInEmployee.Id;
}
else
{
@ -653,10 +680,14 @@ namespace MarcoBMS.Services.Controllers
case ATTENDANCE_MARK_TYPE.REGULARIZE:
attendance.IsApproved = true;
attendance.Activity = ATTENDANCE_MARK_TYPE.REGULARIZE;
attendance.ApprovedAt = DateTime.UtcNow;
attendance.ApprovedById = loggedInEmployee.Id;
break;
case ATTENDANCE_MARK_TYPE.REGULARIZE_REJECT:
attendance.IsApproved = false;
attendance.Activity = ATTENDANCE_MARK_TYPE.REGULARIZE_REJECT;
attendance.ApprovedAt = DateTime.UtcNow;
attendance.ApprovedById = loggedInEmployee.Id;
break;
}