573 lines
27 KiB
C#

using System.Data;
using System.Net;
using Marco.Pms.DataAccess.Data;
using Marco.Pms.Model.Dtos.Attendance;
using Marco.Pms.Model.Dtos.Employees;
using Marco.Pms.Model.Employees;
using Marco.Pms.Model.Entitlements;
using Marco.Pms.Model.Mapper;
using Marco.Pms.Model.Projects;
using Marco.Pms.Model.Utilities;
using Marco.Pms.Model.ViewModels.Employee;
using Marco.Pms.Services.Hubs;
using Marco.Pms.Services.Service;
using MarcoBMS.Services.Helpers;
using MarcoBMS.Services.Service;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;
namespace MarcoBMS.Services.Controllers
{
[Route("api/[controller]")]
[ApiController]
[Authorize]
public class EmployeeController : ControllerBase
{
private readonly ApplicationDbContext _context;
private readonly UserManager<ApplicationUser> _userManager;
private readonly IEmailSender _emailSender;
private readonly EmployeeHelper _employeeHelper;
private readonly UserHelper _userHelper;
private readonly IConfiguration _configuration;
private readonly ILoggingService _logger;
private readonly IHubContext<MarcoHub> _signalR;
private readonly PermissionServices _permission;
private readonly ProjectsHelper _projectsHelper;
private readonly Guid ViewAllEmployees;
private readonly Guid ViewTeamMembers;
private readonly Guid tenantId;
public EmployeeController(UserManager<ApplicationUser> userManager, IEmailSender emailSender,
ApplicationDbContext context, EmployeeHelper employeeHelper, UserHelper userHelper, IConfiguration configuration, ILoggingService logger,
IHubContext<MarcoHub> signalR, PermissionServices permission, ProjectsHelper projectsHelper)
{
_context = context;
_userManager = userManager;
_emailSender = emailSender;
_employeeHelper = employeeHelper;
_userHelper = userHelper;
_configuration = configuration;
_logger = logger;
_signalR = signalR;
_permission = permission;
ViewAllEmployees = Guid.Parse("60611762-7f8a-4fb5-b53f-b1139918796b");
ViewTeamMembers = Guid.Parse("b82d2b7e-0d52-45f3-997b-c008ea460e7f");
_projectsHelper = projectsHelper;
tenantId = _userHelper.GetTenantId();
}
[HttpGet]
[Route("roles/{employeeId?}")]
public async Task<IActionResult> GetRoles(Guid employeeId)
{
if (!ModelState.IsValid)
{
var errors = ModelState.Values
.SelectMany(v => v.Errors)
.Select(e => e.ErrorMessage)
.ToList();
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", errors, 400));
}
Guid tenantId = GetTenantId();
var empRoles = await _context.EmployeeRoleMappings.Where(c => c.EmployeeId == employeeId).Include(c => c.Role).Include(c => c.Employee).ToListAsync();
if (empRoles.Any())
{
List<EmployeeRolesVM> roles = new List<EmployeeRolesVM>();
foreach (EmployeeRoleMapping mapping in empRoles)
{
roles.Add(new EmployeeRolesVM()
{
Id = mapping.Id,
EmployeeId = mapping.EmployeeId,
Name = mapping.Role != null ? mapping.Role.Role : null,
Description = mapping.Role != null ? mapping.Role.Description : null,
IsEnabled = mapping.IsEnabled,
RoleId = mapping.RoleId,
});
}
return Ok(ApiResponse<object>.SuccessResponse(roles, "Success.", 200));
}
else
{
return BadRequest(ApiResponse<object>.ErrorResponse("This employee has no assigned permissions.", "This employee has no assigned permissions.", 400));
}
}
[HttpGet]
[Route("list/{projectid?}")]
public async Task<IActionResult> GetEmployeesByProject(Guid? projectid, [FromQuery] bool ShowInactive)
{
// Step 1: Validate incoming request model state
if (!ModelState.IsValid)
{
var errors = ModelState.Values
.SelectMany(v => v.Errors)
.Select(e => e.ErrorMessage)
.ToList();
_logger.LogWarning("Invalid model state in GetEmployeesByProject. Errors: {@Errors}", errors);
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", errors, 400));
}
// Step 2: Get logged-in employee
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
_logger.LogInfo("GetEmployeesByProject called by EmployeeId: {EmployeeId}, ProjectId: {ProjectId}, ShowInactive: {ShowInactive}",
loggedInEmployee.Id, projectid ?? Guid.Empty, ShowInactive);
// Step 3: Fetch project access and permissions
List<Project> projects = await _projectsHelper.GetMyProjects(tenantId, loggedInEmployee);
var projectIds = projects.Select(p => p.Id).ToList();
var hasViewAllEmployeesPermission = await _permission.HasPermission(ViewAllEmployees, loggedInEmployee.Id);
var hasViewTeamMembersPermission = await _permission.HasPermission(ViewTeamMembers, loggedInEmployee.Id);
List<EmployeeVM> result = new();
// Step 4: Determine access level and fetch employees accordingly
if (hasViewAllEmployeesPermission || projectid != null)
{
result = await _employeeHelper.GetEmployeeByProjectId(tenantId, projectid, ShowInactive);
_logger.LogInfo("Employee list fetched using full access or specific project.");
}
else if (hasViewTeamMembersPermission && !ShowInactive)
{
var employeeIds = await _context.ProjectAllocations
.Where(pa => projectIds.Contains(pa.ProjectId) && pa.IsActive)
.Select(pa => pa.EmployeeId)
.Distinct()
.ToListAsync();
result = await _context.Employees
.Include(fp => fp.JobRole)
.Where(e => employeeIds.Contains(e.Id) && e.IsActive)
.Select(e => e.ToEmployeeVMFromEmployee())
.ToListAsync();
_logger.LogInfo("Employee list fetched using limited access (active only).");
}
else
{
_logger.LogWarning("Access denied for EmployeeId: {EmployeeId} - insufficient permissions.", loggedInEmployee.Id);
return Ok(ApiResponse<object>.SuccessResponse(result, "Filter applied.", 200));
}
// Step 5: Log and return results
_logger.LogInfo("Employees fetched successfully by EmployeeId: {EmployeeId} for ProjectId: {ProjectId}. Count: {Count}",
loggedInEmployee.Id, projectid ?? Guid.Empty, result.Count);
return Ok(ApiResponse<object>.SuccessResponse(result, "Filter applied.", 200));
}
[HttpGet]
[Route("search/{name}/{projectid?}")]
public async Task<IActionResult> SearchEmployee(string name, Guid? projectid)
{
if (!ModelState.IsValid)
{
var errors = ModelState.Values
.SelectMany(v => v.Errors)
.Select(e => e.ErrorMessage)
.ToList();
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", errors, 400));
}
var result = await _employeeHelper.SearchEmployeeByProjectId(GetTenantId(), name.ToLower(), projectid);
return Ok(ApiResponse<object>.SuccessResponse(result, "Filter applied.", 200));
}
[HttpGet]
[Route("profile/get/{employeeId}")]
public async Task<IActionResult> GetEmployeeProfileById(Guid employeeId)
{
if (!ModelState.IsValid)
{
var errors = ModelState.Values
.SelectMany(v => v.Errors)
.Select(e => e.ErrorMessage)
.ToList();
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", errors, 400));
}
Employee emp = await _employeeHelper.GetEmployeeByID(employeeId);
EmployeeVM employeeVM = EmployeeMapper.ToEmployeeVMFromEmployee(emp);
return Ok(ApiResponse<object>.SuccessResponse(employeeVM, "Employee Profile.", 200));
}
private Guid GetTenantId()
{
return _userHelper.GetTenantId();
}
//[HttpPost("manage/quick")]
//public async Task<IActionResult> CreateQuickUser([FromBody] CreateQuickUserDto model)
//{
// return Ok("Pending implementation");
//}
[HttpPost("manage")]
public async Task<IActionResult> CreateUser([FromBody] CreateUserDto model)
{
Guid tenantId = _userHelper.GetTenantId();
var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
Guid employeeId = Guid.Empty;
if (model == null)
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", "Invaild Data", 400));
if (model.FirstName == null && model.PhoneNumber == null)
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", "Invaild Data", 400));
string responsemessage = "";
if (model.Email != null)
{
// Check if user already exists by email
IdentityUser? existingUser = await _userHelper.GetRegisteredUser(model.Email);
var existingEmployee = await _context.Employees.FirstOrDefaultAsync(e => e.Id == model.Id && e.IsActive == true);
if (existingUser != null)
{
/* Identity user Exists - Create/update employee Employee */
// Update Employee record
existingEmployee = await _context.Employees.FirstOrDefaultAsync(e => e.Email == model.Email && e.Id == model.Id && e.IsActive == true);
if (existingEmployee != null)
{
existingEmployee = GetUpdateEmployeeModel(model, existingEmployee, existingUser);
_context.Employees.Update(existingEmployee);
await _context.SaveChangesAsync();
employeeId = existingEmployee.Id;
responsemessage = "User updated successfully.";
}
else
{
// Create Employee record if missing
//Employee newEmployee = GetNewEmployeeModel(model, TenantId, existingUser.Id);
//_context.Employees.Add(newEmployee);
return Conflict(ApiResponse<object>.ErrorResponse("Email already exist", "Email already exist", 409));
}
}
else
{
var user = new ApplicationUser
{
UserName = model.Email,
Email = model.Email,
EmailConfirmed = true,
TenantId = tenantId
};
// Create Identity User
var result = await _userManager.CreateAsync(user, "User@123");
if (!result.Succeeded)
return BadRequest(ApiResponse<object>.ErrorResponse("Failed to create user", result.Errors, 400));
if (existingEmployee == null)
{
Employee newEmployee = GetNewEmployeeModel(model, tenantId, user.Id);
_context.Employees.Add(newEmployee);
await _context.SaveChangesAsync();
employeeId = newEmployee.Id;
/* SEND USER REGISTRATION MAIL*/
var token = await _userManager.GeneratePasswordResetTokenAsync(user);
var resetLink = $"{_configuration["AppSettings:WebFrontendUrl"]}/reset-password?token={WebUtility.UrlEncode(token)}";
if (newEmployee.FirstName != null)
{
await _emailSender.SendResetPasswordEmailOnRegister(user.Email, newEmployee.FirstName, resetLink);
}
}
else
{
existingEmployee.Email = model.Email;
existingEmployee = GetUpdateEmployeeModel(model, existingEmployee, user);
_context.Employees.Update(existingEmployee);
await _context.SaveChangesAsync();
employeeId = existingEmployee.Id;
/* SEND USER REGISTRATION MAIL*/
var token = await _userManager.GeneratePasswordResetTokenAsync(user);
var resetLink = $"{_configuration["AppSettings:WebFrontendUrl"]}/reset-password?token={WebUtility.UrlEncode(token)}";
if (existingEmployee.FirstName != null)
{
await _emailSender.SendResetPasswordEmailOnRegister(user.Email, existingEmployee.FirstName, resetLink);
}
}
responsemessage = "User created successfully. Password reset link is sent to registered email";
}
}
else
{
var existingEmployee = await _context.Employees.FirstOrDefaultAsync(e => e.Id == model.Id && e.IsActive == true);
if (existingEmployee != null)
{
existingEmployee = GetUpdateEmployeeModel(model, existingEmployee);
_context.Employees.Update(existingEmployee);
responsemessage = "User updated successfully.";
employeeId = existingEmployee.Id;
}
else
{
// Create Employee record if missing
Employee newEmployee = GetNewEmployeeModel(model, tenantId, string.Empty);
_context.Employees.Add(newEmployee);
employeeId = newEmployee.Id;
}
await _context.SaveChangesAsync();
responsemessage = "User created successfully.";
}
var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "Employee", EmployeeId = employeeId };
await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification);
return Ok(ApiResponse<object>.SuccessResponse("Success.", responsemessage, 200));
}
[HttpPost("manage-mobile")]
public async Task<IActionResult> CreateUserMoblie([FromBody] MobileUserManageDto model)
{
Guid tenantId = _userHelper.GetTenantId();
if (model == null)
{
_logger.LogWarning("User submitted empty or null employee information during employee creation or update in tenant {TenantId}", tenantId);
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", "Invaild Data", 400));
}
if (string.IsNullOrWhiteSpace(model.FirstName) || string.IsNullOrWhiteSpace(model.PhoneNumber))
{
_logger.LogWarning("User submitted empty or null first name or phone number during employee creation or update in tenant {TenantId}", tenantId);
return BadRequest(ApiResponse<object>.ErrorResponse("First name and phone number are required fields.", "First name and phone number are required fields.", 400));
}
if (model.Id == null)
{
byte[]? imageBytes = null;
if (!string.IsNullOrWhiteSpace(model.ProfileImage))
{
try
{
imageBytes = Convert.FromBase64String(model.ProfileImage);
}
catch (FormatException)
{
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid image format.", "Invalid image format", 400));
}
}
Employee employee = model.ToEmployeeFromMobileUserManageDto(tenantId, imageBytes);
_context.Employees.Add(employee);
await _context.SaveChangesAsync();
EmployeeVM employeeVM = employee.ToEmployeeVMFromEmployee();
_logger.LogInfo($"Employee {employee.FirstName} {employee.LastName} created in tenant {tenantId}");
return Ok(ApiResponse<object>.SuccessResponse(employeeVM, "Employee created successfully", 200));
}
else
{
Employee? existingEmployee = await _context.Employees.FirstOrDefaultAsync(e => e.Id == model.Id.Value);
if (existingEmployee == null)
{
_logger.LogError("User tries to update employee {EmployeeId} but not found in database", model.Id);
return NotFound(ApiResponse<object>.ErrorResponse("Employee not found", "Employee not found", 404));
}
byte[]? imageBytes = null;
if (!string.IsNullOrWhiteSpace(model.ProfileImage))
{
try
{
imageBytes = Convert.FromBase64String(model.ProfileImage);
}
catch (FormatException)
{
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid image format.", "Invalid image format", 400));
}
}
imageBytes ??= existingEmployee.Photo;
existingEmployee.FirstName = model.FirstName;
existingEmployee.LastName = model.LastName;
existingEmployee.Gender = model.Gender;
existingEmployee.PhoneNumber = model.PhoneNumber;
existingEmployee.JobRoleId = model.JobRoleId;
existingEmployee.Photo = imageBytes;
await _context.SaveChangesAsync();
EmployeeVM employeeVM = existingEmployee.ToEmployeeVMFromEmployee();
_logger.LogInfo($"Employee {existingEmployee.FirstName} {existingEmployee.LastName} updated in tenant {tenantId}");
return Ok(ApiResponse<object>.SuccessResponse(employeeVM, "Employee updated successfully", 200));
}
}
[HttpDelete("{id}")]
public async Task<IActionResult> SuspendEmployee(Guid id)
{
Guid tenantId = _userHelper.GetTenantId();
var LoggedEmployee = await _userHelper.GetCurrentEmployeeAsync();
Employee? employee = await _context.Employees.FirstOrDefaultAsync(e => e.Id == id && e.IsActive && e.TenantId == tenantId);
if (employee != null)
{
if (employee.IsSystem)
{
_logger.LogWarning("Employee with ID {LoggedEmployeeId} tries to suspend system-defined employee with ID {EmployeeId}", LoggedEmployee.Id, employee.Id);
return BadRequest(ApiResponse<object>.ErrorResponse("System-defined employees cannot be suspended.", "System-defined employees cannot be suspended.", 400));
}
else
{
var assignedToTasks = await _context.TaskMembers.Where(t => t.EmployeeId == employee.Id).ToListAsync();
if (assignedToTasks.Count != 0)
{
List<Guid> taskIds = assignedToTasks.Select(t => t.TaskAllocationId).ToList();
var tasks = await _context.TaskAllocations.Where(t => taskIds.Contains(t.Id)).ToListAsync();
foreach (var assignedToTask in assignedToTasks)
{
var task = tasks.Find(t => t.Id == assignedToTask.TaskAllocationId);
if (task != null && task.CompletedTask == 0)
{
_logger.LogWarning("Employee with ID {EmployeeId} is currently assigned to any incomplete task", employee.Id);
return BadRequest(ApiResponse<object>.ErrorResponse("Employee is currently assigned to any incomplete task", "Employee is currently assigned to any incomplete task", 400));
}
}
}
var attendance = await _context.Attendes.Where(a => a.EmployeeID == employee.Id && (a.OutTime == null || a.Activity == ATTENDANCE_MARK_TYPE.REQUEST_REGULARIZE)).ToListAsync();
if (attendance.Count != 0)
{
_logger.LogWarning("Employee with ID {EmployeeId} have any pending check-out or regularization requests", employee.Id);
return BadRequest(ApiResponse<object>.ErrorResponse("Employee have any pending check-out or regularization requests", "Employee have any pending check-out or regularization requests", 400));
}
employee.IsActive = false;
var projectAllocations = await _context.ProjectAllocations.Where(a => a.EmployeeId == employee.Id).ToListAsync();
if (projectAllocations.Count != 0)
{
List<ProjectAllocation> allocations = new List<ProjectAllocation>();
foreach (var projectAllocation in projectAllocations)
{
projectAllocation.ReAllocationDate = DateTime.UtcNow;
projectAllocation.IsActive = false;
allocations.Add(projectAllocation);
}
_logger.LogInfo("Employee with ID {EmployeeId} has been removed from all assigned projects.", employee.Id);
}
var user = await _context.ApplicationUsers.FirstOrDefaultAsync(u => u.Id == employee.ApplicationUserId);
if (user != null)
{
user.IsActive = false;
_logger.LogInfo("The application user associated with employee ID {EmployeeId} has been suspended.", employee.Id);
var refreshTokens = await _context.RefreshTokens.AsNoTracking().Where(t => t.UserId == user.Id).ToListAsync();
if (refreshTokens.Count != 0)
{
_context.RefreshTokens.RemoveRange(refreshTokens);
_logger.LogInfo("Refresh tokens associated with employee ID {EmployeeId} has been removed.", employee.Id);
}
}
var roleMapping = await _context.EmployeeRoleMappings.AsNoTracking().Where(r => r.EmployeeId == employee.Id).ToListAsync();
if (roleMapping.Count != 0)
{
_context.EmployeeRoleMappings.RemoveRange(roleMapping);
_logger.LogInfo("Application role mapping associated with employee ID {EmployeeId} has been removed.", employee.Id);
}
await _context.SaveChangesAsync();
_logger.LogInfo("Employee with ID {EmployeId} Deleted successfully", employee.Id);
var notification = new { LoggedInUserId = LoggedEmployee.Id, Keyword = "Employee", EmployeeId = employee.Id };
await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification);
}
}
else
{
_logger.LogError("Employee with ID {EmploueeId} not found in database", id);
}
return Ok(ApiResponse<object>.SuccessResponse(new { }, "Employee Suspended successfully", 200));
}
private static Employee GetNewEmployeeModel(CreateUserDto model, Guid TenantId, string ApplicationUserId)
{
var newEmployee = new Employee
{
ApplicationUserId = String.IsNullOrEmpty(ApplicationUserId) ? null : ApplicationUserId,
FirstName = model.FirstName,
LastName = model.LastName,
Email = model.Email,
TenantId = TenantId,
CurrentAddress = model.CurrentAddress,
BirthDate = Convert.ToDateTime(model.BirthDate),
EmergencyPhoneNumber = model.EmergencyPhoneNumber,
EmergencyContactPerson = model.EmergencyContactPerson,
AadharNumber = model.AadharNumber,
Gender = model.Gender,
MiddleName = model.MiddleName,
PanNumber = model.PanNumber,
PermanentAddress = model.PermanentAddress,
PhoneNumber = model.PhoneNumber,
Photo = null, // GetFileDetails(model.Photo).Result.FileData,
JobRoleId = model.JobRoleId,
JoiningDate = Convert.ToDateTime(model.JoiningDate),
};
return newEmployee;
}
private static Employee GetUpdateEmployeeModel(CreateUserDto model, Employee existingEmployee, IdentityUser? existingIdentityUser = null)
{
if (existingEmployee.ApplicationUserId == null && existingIdentityUser != null)
{
existingEmployee.ApplicationUserId = existingIdentityUser.Id;
}
existingEmployee.FirstName = model.FirstName;
existingEmployee.LastName = model.LastName;
existingEmployee.CurrentAddress = model.CurrentAddress;
existingEmployee.BirthDate = Convert.ToDateTime(model.BirthDate);
existingEmployee.JoiningDate = Convert.ToDateTime(model.JoiningDate);
existingEmployee.EmergencyPhoneNumber = model.EmergencyPhoneNumber;
existingEmployee.EmergencyContactPerson = model.EmergencyContactPerson;
existingEmployee.AadharNumber = model.AadharNumber;
existingEmployee.Gender = model.Gender;
existingEmployee.MiddleName = model.MiddleName;
existingEmployee.PanNumber = model.PanNumber;
existingEmployee.PermanentAddress = model.PermanentAddress;
existingEmployee.PhoneNumber = model.PhoneNumber;
existingEmployee.Photo = existingEmployee.Photo; // GetFileDetails(model.Photo).Result.FileData,
existingEmployee.JobRoleId = model.JobRoleId;
return existingEmployee;
}
private static async Task<FileDetails> GetFileDetails(IFormFile file)
{
FileDetails info = new FileDetails();
info.ContentType = file.ContentType;
info.FileName = file.FileName;
using (var memoryStream = new MemoryStream())
{
await file.CopyToAsync(memoryStream);
info.FileData = memoryStream.ToArray();
}
return info;
}
}
}